When you post a Hit the Road Dude clip, the State Property meme where a driver at a pickup window repeats "State?" and sends someone off, the cover frame decides whether anyone taps it. Most platforms pick a frame for you, and they often pick a mid-blink or a motion-blurred one. This is a small tool that picks a better one automatically.
I build hittheroaddude.app, an independent studio that turns one photo of an adult or a pet into the 15-second, vertical 9:16 pickup-window clip with a newly generated voice. Which frame to use as a thumbnail is a fair question for anyone posting a remake, so here is a rule of thumb turned into a script. I tested it on a synthetic clip, not on generated customer clips, so the numbers below only show that the picker works, not how it scores any real remake.
What makes a good cover frame
Three things I can measure without a model:
- Sharpness. A blurred frame (motion, a transition) has little fine detail. The variance of a Laplacian is the classic cheap measure: the more edges, the higher the variance.
- Exposure. Skip frames that are mostly black or blown out. I check the mean brightness and the share of crushed or clipped pixels.
- Distance from the edges. The first and last half-second are often a fade, a hard cut or a frozen end frame. If you want to check those separately, see the black-and-frozen-edges check in my guides repo.
What this does not measure is whether the face looks right or whether the driver's expression is the funny one. For a meme with a deadpan pause, the best frame is often the "State?" beat, and no sharpness metric knows that. So the script prints the top few timestamps and you make the final call.
The script
It decodes the clip once with ffmpeg to small grayscale frames (135 by 240, which is plenty for scoring), scores each, and then asks ffmpeg for the winning frame at full resolution:
#!/usr/bin/env python3 """Pick the best cover frame from a short clip: sharp, well exposed, away from the edges. usage: python3 pick_cover.py clip.mp4 [--fps 4] [--top 3] [--out cover.jpg] Needs ffmpeg/ffprobe on PATH and numpy. """ import argparse, subprocess, json, sys import numpy as np W, H = 135, 240 # analysis size (9:16); scoring does not need full resolution def duration(path): out = subprocess.check_output(["ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "json", path]) return float(json.loads(out)["format"]["duration"]) def frames(path, fps): cmd = ["ffmpeg", "-v", "error", "-i", path, "-vf", f"fps={fps},scale={W}:{H},format=gray", "-f", "rawvideo", "-"] raw = subprocess.run(cmd, capture_output=True, check=True).stdout n = len(raw) // (W * H) return np.frombuffer(raw[: n * W * H], np.uint8).reshape(n, H, W).astype(np.float32) def sharpness(f): # variance of a 4-neighbour Laplacian: higher = more fine detail lap = (-4 * f[1:-1, 1:-1] + f[:-2, 1:-1] + f[2:, 1:-1] + f[1:-1, :-2] + f[1:-1, 2:]) return float(lap.var()) def score(f): mean = float(f.mean()) clipped = float(((f < 8) | (f > 247)).mean()) # share of crushed/blown pixels exposure_ok = 1.0 if 45 <= mean <= 210 else 0.3 return sharpness(f) * exposure_ok * (1 - clipped) def main(): ap = argparse.ArgumentParser() ap.add_argument("clip"); ap.add_argument("--fps", type=float, default=4) ap.add_argument("--top", type=int, default=3); ap.add_argument("--out", default="cover.jpg") ap.add_argument("--margin", type=float, default=0.5, help="seconds to skip at both ends") a = ap.parse_args() dur = duration(a.clip); fr = frames(a.clip, a.fps) cand = [(score(f), i / a.fps) for i, f in enumerate(fr) if a.margin <= i / a.fps <= dur - a.margin] if not cand: sys.exit("clip too short for that margin") cand.sort(reverse=True) for s, t in cand[: a.top]: print(f"t={t:5.2f}s score={s:8.1f}") best_t = cand[0][1] subprocess.run(["ffmpeg", "-v", "error", "-y", "-ss", f"{best_t:.3f}", "-i", a.clip, "-frames:v", "1", "-q:v", "2", a.out], check=True) print(f"wrote {a.out} from t={best_t:.2f}s") if __name__ == "__main__": main() A test clip with a known answer
Real clips would make a poor test because I would not know the right answer. So I generate a 10-second 9:16 clip made of three parts: seconds 0 to 3 blurred, 3 to 7 sharp, 7 to 10 dark. A correct picker must choose from the middle.
#!/usr/bin/env bash # 10 s synthetic 9:16 clip: blurry 0-3 s, sharp 3-7 s (detail), dark 7-10 s set -e ffmpeg -v error -y -f lavfi -i "testsrc2=s=540x960:r=24:d=10" -f lavfi -i "sine=f=300:d=10" \ -vf "split=3[a][b][c];[a]trim=0:3,boxblur=12,setpts=PTS-STARTPTS[x];[b]trim=3:7,setpts=PTS-STARTPTS[y];[c]trim=7:10,eq=brightness=-0.7,setpts=PTS-STARTPTS[z];[x][y][z]concat=n=3:v=1:a=0" \ -c:v libx264 -pix_fmt yuv420p -shortest -an test_cover.mp4 echo wrote test_cover.mp4 Run:
$ ./make_cover_test.sh && python3 pick_cover.py test_cover.mp4 --out cover.jpg t= 6.75s score= 695.0 t= 6.50s score= 679.2 t= 3.25s score= 678.2 wrote cover.jpg from t=6.75s The top three all sit in the sharp section (3.25 s is just after the blur ends, 6.50 and 6.75 s are just before the dark part starts). Nothing from the blurred or dark parts made the list. The scores are close because the test pattern is uniformly detailed; I have not measured the gap on real clips.
Why the exposure term is a multiplier
A plain sharpness score can reward a mostly black frame, because hard edges between black and bright areas score high on a Laplacian. Multiplying by an exposure factor (1.0 if the mean brightness is between 45 and 210, otherwise 0.3) and by the share of non-clipped pixels guards against that. It is a hack, and the thresholds are my choice, not a standard. If your clips are intentionally dark, change them.
Tips for using it
- Raise
--fps(to 8 or 12) if the clip has fast movement. The default of 4 frames per second is enough for a mostly static camera like the pickup window. - Use
--marginto skip more of the start. I use 0.5 s by default. - Always look at the top three. The score finds sharp frames, not funny ones. If the "State?" beat is in the top three, take it.
- Crop for the platform. A 9:16 cover is cropped to a square on some profile grids, so keep the face in the middle third. My posting check covers the rest of what to look at before you share a remake.
- Label the post. Say it is an AI remake. The studio's clips use a new voice, not the original recording (original audio or new voice).
If you want the clip itself
The studio makes one 15-second clip from one photo. It takes no prompt: the pickup-window scene and lines are one saved preset, and you choose 480p or 720p. As of November 10, 2026 the pricing page lists $9.99 at 480p and $12.99 at 720p per clip, paid once. Signed-in accounts can also buy credit packs: $27.99 for 30 credits, $71.99 for 100 credits and $129.99 for 200 credits, where a 480p clip spends 6 credits and a 720p clip 14. There are no free clips, and there is no watermark (FAQ).
The picker works on any short clip, so you can use it on whatever you make or download, as long as you have the right to use it.