Export (Final Output)
This is the last segment of the chain, and the only place in the entire product where actual pixels are drawn.
Everything before this is annotation: the ledger records "which words to speak", the subtitles record "what to write on screen", the visuals record "what layers to overlay", and the preview stitches these three together in real-time for the user to see—without writing to disk. Export burns them into a single file.
Input: edit-list.json (required), subtitles.json, visuals.json + modules/
Output: final.mp4
Prerequisite tool: Google Chrome on the machine (used to rasterize subtitles and animations as images).
FFmpeg / FFprobe installed from the desktop app have already been placed in the Product-managed directory;
pure CLI installation still requires system ffmpeg ≥ 6. If Chrome is missing, an explicit error will be reported—do not try to bypass it: without Chrome, there are no subtitle or animation layers.
First read and execute the "Conclusion Level" section of the business skill phase contract. Export does not enter the editing state machine: it does not modify any project files, does not write to CAS, and does not advance the stage; the output is a new file, and re-running overwrites it. Therefore, it does not require a confirmation card.
0. Readiness
First execute Check Updates's "Readiness Check"—whether skills are up-to-date and the Runtime is compatible;
the plugin root is also located there (all <plugin root> in this document refer to that literal path). Only proceed if "Ready"; for "New Session Required" or "Stop", follow its disposition (including the "No custom alternative interface" prohibition); the business skill does not include its own environment logic.
If the readiness result is runtime.kind=desktop-managed, directly reuse the stable CLI, media tools, and the same launchd/windows-task service already installed by the desktop App;
do not parse Electron paths, install additional FFmpeg/Bun, or start a second Runtime.
Commands
node "<plugin root>/scripts/ensure-running.cjs" --json
node "<plugin root>/scripts/videocut-cli.cjs" export <project> --dry-run --json # Preview plan, no encoding
node "<plugin root>/scripts/videocut-cli.cjs" export <project> --json # Final output (default 2x, source framerate)
node "<plugin root>/scripts/videocut-cli.cjs" export <project> --out /path/final.mp4 --json
node "<plugin root>/scripts/videocut-cli.cjs" export <project> --scale 1 --json # Source size only
node "<plugin root>/scripts/videocut-cli.cjs" export <project> --keep-work --json # Keep intermediate files for debugging
If ensure-running reports identity mismatch, port conflict, or unhealthy service, stop immediately; do not temporarily substitute with foreground mode and continue exporting.
Two Steps, Don't Run Only the Second Step
① --dry-run first reports the plan: duration, frame count, subtitle screens, visual layers, zoom segments, output resolution
Read it to the user. If the numbers are wrong, the upstream is wrong; ten minutes of encoding won't fix it
② Actual run: assemble → overlay → compose → verify four-stage progress
Any warnings in --dry-run must be relayed verbatim. They only report one thing: some subtitle screens or visual layer words have already been cut, so they will not appear in the final output. This is an upstream decision, not something export should silently swallow.
Resolution: Source is the Ceiling—Check Source Before Upscaling
Check the source resolution before exporting (the source field in --dry-run already has it):
Source width ≥2560 (Retina native screen recording) → --scale 1, output is native pixels—this is the best case
Source width <1920 (e.g., 960×720) → Pause first: ask the user if there is a high-definition export of the same recording.
Screen recording tools can often re-export the same recording at 3x resolution;
replacing the source is far more effective than any post-processing (see next section).
If truly not available → --scale 2
Why --scale 2 helps with low-res sources: enlarging the original won't make it clearer, but subtitles and animations are redrawn at the output resolution—those are what viewers actually read; and when the platform compresses again, the larger image gets a higher bitrate.
Do not adjust --fps to try to make it "clearer." Frame rate follows the source; changing it will desynchronize animation sampling with the screen recording.
Source Replacement: When the User Provides a High-Def Version of the Same Recording
This is not part of the export flow—export itself is always read-only. Source replacement is an independent material operation after the user explicitly agrees; then return to normal export.
First determine if it's the same take (the first two criteria are sufficient):
ffprobe -v error -show_entries format=duration -of csv=p=0 <two files> # Duration must match to the millisecond
ffmpeg -v error -i <file> -map 0:a:0 -c copy -f md5 - # Audio streams must be bit-identical
# Then sample two or three frames to visually compare content
Bit-identical audio = identical timeline: word scripts, ledgers, subtitles, visual layers are all bound to word IDs—none need to change. This is the payoff of "binding to words, not to seconds" for source replacement.
Source replacement steps (actually performed once on 2026-07-29; step ③ was missed back then, and the clip preview immediately reported "generation failure"—fingerprints are recorded in more than one place; grep the old fingerprint to find all occurrences before proceeding):
① Replace the file input/source.mp4 and uploads/source.mp4 are a hardlink pair —
delete both, copy the new file to input/, then recreate the hardlink to uploads/
(macOS: use ln; Windows: use New-Item -ItemType HardLink)
② Update the fingerprint project.json's source.sha256 to the new file's
③ Check another location workbench.json's sourceSha256 also records the source file fingerprint;
the clip preview pipeline validates against this very one—miss it and preview will refuse to generate
(This is the correct behavior of the product: fingerprint mismatch fails safely, not serving an old preview)
④ Re-export --scale 1 — Source replacement is for native pixels; do not upscale again
Safest approach: use your search tool on all *.json files in for the first 8 characters of the old fingerprint; every occurrence found must be handled (cache files like preview-edited/current.json will auto-recalculate, no manual change needed).
If the audio is different (re-recorded, trimmed, duration changed), it is not source replacement—it's a new project: the word script must be re-transcribed, and all annotations are invalid. Do not force it.
Verification: Three Things—Missing Any Means Export Is Not Complete
① Command succeeded The product measures the final output's size, frame count, and audio tracks, and compares each against the plan.
If they don't match, it reports readback_mismatch, keeps the file on disk as evidence,
and does not consider the export complete.
② Extract frames and inspect pixels Extract frames from the final output and check visually. Must cover at least: one zoom segment,
one full-screen animation segment, one subtitle-only segment, and one layer boundary.
③ Human listening No one has ever actually listened; always record as human listening UNVERIFIED
② Must not be replaced by preview screenshots. Preview and final output are two different render paths; the verification aims to see if they match—using preview images as evidence for the final output assumes the very thing being verified.
To determine if zoom is applied, look for discriminative landmarks; overall impression can be deceiving (real experience from 2026-07-29): after a 1.6x zoom, the screen page still looks like "one whole page"—in the thumbnail it's nearly identical to the full view—causing a correct zoom frame to be mistakenly dismissed as "zoom missing", wasting 30 minutes and two re-exports. The correct method is to look for evidence that only cropping can cause: elements that are half-cropped (a bubble cut off in the middle), disappearing edge elements (sidebar, title bar). Compare with the same frame from the source to settle immediately.
Extract frames using ffmpeg:
ffmpeg -v error -ss 8.84 -i final.mp4 -frames:v 1 -y frame.png
Troubleshooting Guide
--keep-work leaves three things under the project's .chengfeng-videocut/export/ directory,
each isolating which half of the pipeline went wrong:
assembled.mkv Raw edit only, no overlays. If wrong → ledger or clip segment error
overlay/*.png Only overlaid content, transparent background. If wrong → subtitle style or module error
spans/*.mp4 Post-composition segments. If wrong → zoom or alignment error
Reference table:
No subtitles/animations in final output Are overlay PNGs fully transparent? Did the module fail to acknowledge seek?
Animation stuck on first frame Module didn't implement seek, or GSAP timeline wasn't paused
Entire frame white Module missing `:root { color-scheme: dark }`
Zoom box misaligned Module's viewBox and layer's zoom are not using the same coordinate system
Final output shorter than planned Some span had insufficient frames; check compose phase errors
Layer boundary shows a flash of original Overlay snapshot is stale (frame marker validation failed). The product uses
a frame marker bar at the top of the page to prove each snapshot belongs
to which frame; if it recurs, first verify the overlay PNG has the marker bar
and that compose crops it out
Chrome not found Install Google Chrome; do not switch to a different render path
What Not To Do
- Do not say "export succeeded" as "verification passed"—command success only means the product can self-match; it's not frame-level verification
- Do not use preview screenshots, DOM, or logs as a substitute for extracting frames from the final output
- Do not mark human listening as PASS without actually listening
- Do not modify project files (change subtitles, delete layers, alter ledger) just to make export succeed. Export is read-only; it does not write
- Do not perform upstream tasks in export: if subtitles are missing, go write subtitles; if visuals are missing, go create visuals; don't patch a temporary fix here