Technical recipe
Assemble video shots on an exact integer-frame timeline
Map decoded CFR/VFR sources to per-shot integer-frame budgets, then inspect an actual 22-frame example at 24 fps, its cut images and terminal duration.
v24.18.0; ffmpeg version 6.0 Copyright (c) 2000-2023 the FFmpeg developers; ffprobe version n4.4.1 Copyright (c) 2007-2021 the FFmpeg developers · acacbe00027ce51deb248c719def42893a97abd9 · See the article for environment and execution scope.
Practice illustration, not evidence of model-generated results.
Assemble video shots on an exact integer-frame timeline
Use this offline recipe when several already prepared video shots must occupy an exact number of output frames. It distinguishes source timestamps from the output frame grid and checks the images on both sides of every cut.
The example uses original geometric scenes and a real 22-frame MP4. On 17 October 2026, it was encoded, decoded and checked with Node v24.18.0, FFmpeg 6.0 and ffprobe n4.4.1, alongside three controlled negative artifacts. These are local technical results, not MiniMax H3 outputs; no model generation or provider call was made. Human playback and perceptual review have not been performed.
Define the delivery in frames
Write the output frame rate as a rational number, for example 24/1, and assign an integer outputFrames to every shot. The sum must equal expectedTotalFrames. At 24 fps, the recorded 22-frame fixture lasts exactly 11/12 seconds; player rounding is not the acceptance rule. Its three shot budgets are 7, 7 and 8 frames. The two cut boundaries fall between output frames 6/7 and 13/14, using zero-based frame numbers.
For a longer delivery example, 3:24 at 24 fps means 204 seconds and 4,896 output frames. Twenty-five complete eight-second segments consume 4,800 frames, leaving a 96-frame final segment. This is a planning calculation: the supplied test encodes only 22 frames, not 204 seconds, and does not show that the longer source clips exist or that a model can generate the full duration in one request.
Inspect actual source timestamps
The preparation command creates 25 original PNG inputs and three SVG design sketches. Each raster contains a source-and-frame marker plus a moving geometric element. These original inputs are 8 frames for shot A, 10 for B and 7 for C. Their actual PNG hashes matched before and after the recorded media run.
Encoding and decoding produced inventories of 8, 10 and 8 frames. The variable-rate C source includes the final image repeated by the concat input, so its eighth decoded frame is not a newly drawn 26th input. A was observed at 24000/1001 fps with time base 1/48000; B has 40 ms spacing with time base 1/51200. C starts at 2 seconds and its observed PTS are 2.00, 2.04, 2.12, 2.24, 2.28, 2.36, 2.40, 2.44 seconds.
Keep the raw measurement separate from the presentation rule. In C, ffprobe reported pkt_duration=2048 at time base 1/51200, or 40 ms, for every frame. Some adjacent PTS are 80 or 120 ms apart. The package preserves those raw packet durations; it does not rewrite them to pretend ffprobe reported the longer intervals. The recorded shot-a.probe.raw.json, shot-b.probe.raw.json and shot-c.probe.raw.json retain the actual stdout bytes and are hash-bound in the decoded inventory.
Bind each shot to the source
Each EDL row names a shot ID, source ID, exact source-file SHA-256, a source-relative starting offset and an output-frame budget. The source's first decoded timestamp supplies its origin, so a source starting at two seconds does not add two seconds of blank output.
The declared policy is active-at-or-before-no-interpolation: at each target time, choose the latest source frame whose PTS is no later than that time. For a nonterminal source frame, the effective presentation window is explicitly derived as next observed PTS - current observed PTS. This is a protocol-derived hold window, not an observed packet duration. The terminal frame has no next PTS, so its duration must come from a real, positive duration or pkt_duration observation. Missing or zero duration, non-increasing PTS, an out-of-range interval or a sampled coverage gap fails; average FPS is not a substitute.
Intervals include their start and exclude their end. The rule may repeat or drop images; it does not synthesize motion. Do not silently change speed to make a shot fit. Revise its source interval or output budget deliberately, record the decision, then rebuild the map. Use shot identity and file binding when a file is attached to the wrong shot.
Prepare inputs without encoding video
From the extracted kit directory containing run-recipe.mjs:
node --test frame-budget.test.mjs encoded-negative-cases.test.mjs
node run-recipe.mjs --out ./new-original-inputsThe output directory must not already exist. The preparation command draws the original raster inputs and writes a status with mediaExecuted:false. That remains the correct default behavior even though the separately recorded run below executed media. It makes no network request, submits no model task and creates no encoded video. The bundled logic and preparation tests are useful checks, but a passing pure test is not itself an encoder receipt.
Read the recorded run or execute a fresh one
The successful run is recorded in media-runs/actual-small-root-20261017-02/. Its media-execution-receipt.json is dated 2026-10-17T01:33:47.353Z. Its command-log.json records 48 successful tool events. The run produced a 22-frame positive output, checked both cuts and all 22 source/frame markers, and exercised the three actual negative cases below. The output is assembled-review.mp4, with SHA-256:
3b624e031f6a082e2ac2ae89b8c36cb91873bd67c5d64f8c5fa1427059db96f1To inspect the recorded result without rerunning tools, open the raw source probes, decoded inventory, EDL, frame map, marker proof and output probe beside the MP4. The command log records the actual tool calls, while runtime-source-binding.json identifies the execution source files. Keep these records with the result when comparing a reproduction.
For a fresh run, use a different, nonexistent output directory and existing local tools:
ALLOW_LOCAL_MEDIA_EXECUTION=1 node run-recipe.mjs --out ./new-media-run --execute-media --ffmpeg /absolute/path/to/ffmpeg --ffprobe /absolute/path/to/ffprobeThe explicit switch enables local media execution. The script uses 96×64 inputs, serial tool calls, single-thread settings, process timeouts and bounded buffers. It encodes the sources, reads their decoded timestamps and real hashes, checks selected markers against the original input definition, then assembles the 22-frame MP4 and tests three small negative artifacts. The observed tool versions are one tested configuration, not a promise that every FFmpeg build supports it. Missing filters or FFV1/libx264 capabilities fail; the command does not install tools or call an external service.
This command never encodes a 204-second fixture. A full-length benchmark needs a separate actual run and receipt.
Check the decoded artifact and its negative controls
Acceptance of this small technical fixture requires the decoded frame count to equal the budget, all PTS to lie on the declared CFR grid, and every source/frame marker to match its planned output position. The recorded positive output met those checks at 24 fps with duration 11/12 seconds. A correct duration alone would not establish that a cut uses the correct image.
Keep the source hashes, raw probes, decoded inventory, EDL, full frame map, original-marker proof and complete encoder/probe commands alongside the output. Raw probes and the inventory/EDL are saved before planning, so a failed plan can retain diagnostic facts without receiving a success receipt. Positive and negative observations use the same validator, including actual packet PTS, DTS and duration.
Frame indices are zero-based: index 7 is the eighth image and index 21 is the last. All three negative artifacts were actually encoded or mutated, decoded and probed in the recorded run. Each produced exactly its expected issue and no second validator defect:
| Recorded artifact | Controlled difference | Observed sole rejection |
|---|---|---|
negative-media/short-21-of-22.mp4 | 21 decoded images, frame observations and packets instead of 22 | OUTPUT_FRAME_COUNT |
negative-media/wrong-cut-image.mp4 | Still 22 frames on the same grid; image at zero-based frame 7 is wrong | OUTPUT_FRAME_ID_MISMATCH at frame 7 |
negative-media/bad-terminal-duration.mp4 | Same 22 images, PTS and encoded sample bytes; terminal duration halves | OUTPUT_TERMINAL_DURATION at frame 21 |
For the last case, the code changes actual MP4 sample timing bytes rather than probe JSON. It accepts only the fixture's one-video-track, nonfragmented, no-edit-list layout, with final moov after mdat, no composition reordering, a 24,000 media time scale and 1,000 ticks per normal frame. It keeps 21 normal samples and changes the terminal duration to 500 ticks, updates container durations and preserves media data. The recorded observations contain 22 frames and packets on each side. All positive durations and the first 21 negative durations are 1,000 ticks; the negative terminal frame and packet each report 500 ticks. Packet locations, all PTS/DTS and all 22 encoded sample payload hashes remain equal. Missing, invalid or zero durations would fail observation rather than pass the negative.
Encoder failure, failed probing, an unsupported container or an unexpected validator issue cannot count as a successful negative. The default preparation-only command lists these cases as planned; the separate recorded media run contains their executed results. Timing and marker checks do not establish whether a creative cut looks right: inspect your own footage for motion, lighting and narrative continuity.
If a source has an abrupt pose or lighting jump, exact frame assembly preserves that jump. To inspect a particular frame, use frame selection by timestamp. For still-image inputs, use the existing PNG sequence recipe.
Keep the task boundary clear
Use this recipe for timing and cut identity after clips exist. For planning separate shots, browse the workflow library. Record creative decisions and accepted clip versions in a shot manifest before assembling the frame timeline. Correct frame assembly preserves the images you supply, including any unwanted visual jump.
The original example code and fixtures include LICENSE.txt. FFmpeg and ffprobe executables are not bundled. Technical references: FFmpeg concat, fps and setpts. Use the supplied observations to check the example; a command documented by FFmpeg is not proof of your own export.