Hypernatural

TechniqueBy HypernaturalVersion 2, Oct 26, 2026

ffmpeg-splice-into-source-cut

Rebuild a source video frame for frame on its own timeline and soundtrack with picked clips replacing chosen frame ranges, using one ffmpeg filter graph; every frame without a replacement stays the original footage. Use to assemble a shot-for-shot remake, a re-cut with a few shots redone, or any delivery cut over the original audio, plus its small and no-audio copies.

SKILL.md

Splice into the source cut

Give it the source video, its shot table and the picked clip for each shot you redid; get back the source rebuilt segment by segment: replaced frame ranges come from the clips (trimmed to exactly the range, their own audio dropped), every other frame is the original footage, and the source's soundtrack plays under all of it. Shots you left out fall back to the original, which is also how "use the original footage" is answered for a shot. For a 9:16 version use ffmpeg-crop-to-vertical, which crops the source shots and splices restaged renders the same way. When a clip's own sound must play (a re-voiced dialogue line), this is the wrong leaf: see python-assemble-dialogue-track.

Inputs

  • The source: frame rate, size and frame count (ffprobe-inspect-media).
  • The shot table: first and last frame of each shot, inclusive (ffmpeg-scene-cuts). Use the same table the shots were cut from.
  • Per replaced shot, the picked clip, already at the source's frame rate and at least as many frames as its range. A 720p take is upscaled first (topaz-upscale), though a full-frame shot that ships at 1080p quality should be rendered at 1080p instead. A clip at another rate (a lip-sync service returning 25 fps) is converted back before it comes here.

Run it

Build the segment list first: sort the replacements, check none overlap, and fill every gap with the original. Example: a 484-frame, 24 fps, 1920×1080 source with shots 03 (frames 41–73) and 09 (frames 286–371) replaced gives five segments:

SegmentFramesFrom
00–40source
141–73 (33)rev2/shot03.mp4, its first 33 frames
274–285source
3286–371 (86)rev2/shot09.mp4, its first 86 frames
4372–483source

Then one ffmpeg call. Input 0 is the source; each replacement is its own input:

exec ffmpeg -hide_banner -loglevel error -y
  -i <job>/source/acme_office_spot.mp4
  -i <job>/rev2/shot03.mp4
  -i <job>/rev2/shot09.mp4
  -filter_complex "
    [0:v]trim=start_frame=0:end_frame=41,setpts=PTS-STARTPTS,scale=1920:1080,setsar=1,format=yuv420p[v0];
    [1:v]trim=end_frame=33,setpts=PTS-STARTPTS,scale=1920:1080,setsar=1,format=yuv420p[v1];
    [0:v]trim=start_frame=74:end_frame=286,setpts=PTS-STARTPTS,scale=1920:1080,setsar=1,format=yuv420p[v2];
    [2:v]trim=end_frame=86,setpts=PTS-STARTPTS,scale=1920:1080,setsar=1,format=yuv420p[v3];
    [0:v]trim=start_frame=372:end_frame=484,setpts=PTS-STARTPTS,scale=1920:1080,setsar=1,format=yuv420p[v4];
    [v0][v1][v2][v3][v4]concat=n=5:v=1:a=0[v]"
  -map "[v]" -map 0:a
  -r 24 -t 20.166667
  -c:v libx264 -crf 14 -preset slow -pix_fmt yuv420p -c:a aac -b:a 192k
  -movflags +faststart
  <job>/rev2/acme_spot_B_cut_rev2.mp4

(Pass the filter graph as one argument; the line breaks are for reading.)

  • end_frame is exclusive: a source segment is start_frame=FIRST:end_frame=LAST+1; a clip segment is end_frame=LAST-FIRST+1 from the clip's first frame.
  • Every segment is normalised (size, square pixels, yuv420p) so concat sees identical streams. Use the source's own size in scale=.
  • -map 0:a lays the source's soundtrack under the whole cut; the clips' audio is dropped.
  • -r <fps> on the output, and no fps= in the segment chains (Tips and traps).
  • -t is the source's length, frames / fps (484 / 24 = 20.166667), so a soundtrack that runs past the picture is cut there.

Copies for delivery

Make these from the finished cut or any shot file, as the job's deliverable layout asks (AGENTS.md):

  • small review copy: -vf scale=960:540 -c:v libx264 -preset veryslow -crf 28 -pix_fmt yuv420p -c:a aac -b:a 96k
  • no-audio copy: -an -c:v copy

What you get

The cut at the source's length, size and rate, over its own audio. List which segments came from which clip in the round's notes (41-73: shot03.mp4, 0-40: original).

Tips and traps

  • ffmpeg 7 drops frames without an explicit output rate. -r <fps> on the output keeps every frame; an fps= filter inside each segment chain before the concat lost one frame per shot (472 of 484).
  • Not -shortest. A source's AAC track often ends a few milliseconds before its picture, and -shortest then drops the last video frame (483 of 484). Bound the output with -t instead.
  • A short clip shifts everything after it. trim=end_frame=N on a clip with fewer than N frames gives fewer frames, and the concat closes the gap. Count each clip's frames before splicing.
  • Splice the exact shot length; the 0.5 s tails on per-shot files are for the editor, not for the cut.
  • Keep the folder you splice from apart from what goes to the client when the client's layout differs, so a shot left out on purpose still falls back to the original.

Check it

  • The cut's frame count equals the source's (exec ffmpeg -v error -i <cut> -map 0:v -f framemd5 -, counting lines that don't start with #), and its duration matches.
  • A contact sheet of the frames either side of every join (FIRST−1, FIRST, LAST, LAST+1 of each replaced range), looked at, so no join shows a stray frame of the wrong shot:
exec ffmpeg -v error -y -i <cut>
  -vf "select='eq(n,40)+eq(n,41)+eq(n,73)+eq(n,74)+eq(n,285)+eq(n,286)+eq(n,371)+eq(n,372)',scale=480:-2,tile=4x2:padding=4"
  -frames:v 1 -update 1 <job>/rev2/joins_sheet.jpg
  • Play the cut through once with sound before it goes on a review page.

Go make something cool.

Install the app, open your agent, describe the video. That's the whole onboarding.

Sticker HypernaturalSticker BannerSticker TVBoy