Navigation: packages/AGENTS.md → video-nodes
Read packages/AGENTS.md first (media-ref rules apply). This overlay covers video-specific correctness.
- A terminal ffmpeg/ffprobe failure throws, it never returns the unchanged
input.
ffmpegTransformrejects withffmpeg failed: <stderr>(the execFile error carries.stderr); a no-op effect that quietly returns its input is indistinguishable from a real one and hides the bug. The only legitimate fallbacks are explicit multi-attempt chains — SetSpeed (audio+video → video), Overlay/Transition (with-audio → video-only), Reverse (audio+video → video), AddAudio (mix → replace). Pass{ allowFallback: true }to the non-terminal attempt so it returnsnull; the LAST attempt omits it and throws. - A missing binary is
MissingBinaryError, carrying the binary name, raised byexecFfmpeg/execFfprobeon spawnENOENT— for ffprobe as well as ffmpeg. Every probe site surfaces it (Fps, GetVideoInfo, ForEachFrame, Concat, Transition, ExtractFrame). Info-style nodes (Fps, GetVideoInfo) may still degrade a genuine probe failure on corrupt input to a 0/empty result, but must re-throwMissingBinaryError. - Empty input bytes short-circuit to an empty/passthrough ref before any spawn — that is not a failure and stays.
Internal module (not re-exported from the package index) holding the exec
helpers + MissingBinaryError, videoRef/defaultVideoRef, parseFrameRate,
ffprobeDuration, withTempFile, filePath/folderPath, dateName,
uniqueTargetPath, and coerceProviderBytes. Helpers take no node-specific
state so timeline.ts can import them too. defaultVideoRef() is called per
@prop default so each prop gets its own object — never share one mutable
default across props.
- Read input audio/video bytes with the async, context-aware resolver
(
await audioBytesAsync(this.audio, context)/videoBytesAsync), never the syncaudioBytes.AddAudioused the sync inline-only reader, so audio supplied as an asset/file:///httpURI yielded empty bytes and the node silently returned the video with no audio.
- A node that muxes an audio stream onto a video must decide the output
length itself. Left to the muxer, the file is as long as the longer
stream: a music model that ignores the requested duration returns a whole
song, and a 25-second clip came out four minutes long, with three and a half
minutes of nothing after the picture ended.
AddAudiopads the audio withapadand ends the file with-shortest, which bounds a long track to the picture and stops a short one from cutting the picture off; itsoutput_lengthproperty opts back into the unbounded behavior. The other mux sites are already bounded —Overlaymixes withamix=duration=firstover the main video's own audio,Transitionusesxfade/acrossfade, and bothnormalizeConcatSegmentandmixAudioIntopass-shortest.
- Number frame files with a contiguous counter incremented only on a successful
write — never the loop index. When a frame is skipped, a filename based on
i + 1leaves a gap, and ffmpeg'simage2demuxer stops at the first missing number, dropping every frame after the gap. Keep a separatewrittencounter (frame_000001.png,frame_000002.png, …). - Handle the zero-frames case explicitly — emit an empty
videoRef(new Uint8Array())rather than invoking ffmpeg on nothing.
Construct outputs with the videoRef helper (raw base64 data, type: "video")
— see packages/AGENTS.md § Media refs.