Skip to main content
The Ember video pipeline turns a documentation page into a narrated screen-recording video. It chains four stages — script generation, HeyGen avatar rendering, Playwright screen recording, and FFmpeg compositing — and exposes them through two modules:

Compositor

CompositeOptions

Passed to VideoCompositor.composite() to define a single compositing job. Example:

CompositorConfig

Passed to the VideoCompositor constructor to configure FFmpeg options and output location. Use createVideoCompositor() as a convenience factory; it reads FFMPEG_PATH and FFPROBE_PATH from the environment and defaults outputDir to '/tmp/ezforge-compositor' when omitted.

Orchestrator

PipelineInput

Passed to PipelineOrchestrator.run() to kick off a full pipeline run.

PipelineResult

Returned by PipelineOrchestrator.run() on success.

stateListener callback

The stateListener field in OrchestratorConfig is an optional callback that fires on every stage transitionpending → running → completed/failed. Use it to stream progress updates to a client, log pipeline state, or persist the PipelineState for crash-recovery.
Stage lifecycle:
Each StageState object carries:

Resuming a failed run

PipelineState is deliberately plain JSON — serialise it to disk or a database, then pass it back as the initial argument to skip already-completed stages:

Generating intro/outro clips

The assets/video-templates/ directory ships two FFmpeg filter templates for building standardised intro and outro clips from static image assets.

Generating the intro clip

Requires a title_card.png at 1280×720 resolution.

Generating the outro clip

Requires an end_card.png at 1280×720 resolution.
Once generated, pass the output paths to PipelineInput.introPath / PipelineInput.outroPath (or directly to CompositeOptions.introPath / CompositeOptions.outroPath).
The filter template files are reference assets — they do not ship pre-built MP4 clips. You must run the FFmpeg commands above (substituting your own image assets) before the intro/outro feature can be used in a pipeline run.

Scene Timeline Protocol

The Scene Timeline Protocol (Phase 2) lets the script generator produce a multi-scene narration that drives both the audio synthesis and screen-recording stages. Instead of a hardcoded sequence of recording steps, the generator embeds [[scene ...]] markers directly in the narration text. For the full specification, see executive/specifications/video-pipeline-scene-markers.md.

Marker grammar

Markers are inline in the narration. Canonical form:

Required attributes

Optional attributes

Each marker begins a new scene. The narration that follows belongs to that scene until the next marker or end of script. At least one of duration or wordBudget must be present on every scene.

Adapters

The orchestrator converts web: scenes to [navigate, wait] recording steps for the existing ScreenRecorder. image: scenes produce wait-only recording steps — the recorder holds the last displayed frame for the scene duration. ImageSceneSource.render() (ffmpeg) is Phase 3 infrastructure not yet wired into the orchestrator. Both adapters pass the shared contract test.

Timeline lifecycle

Stripping always happens before the narration reaches HeyGen. If any [[scene substring remains after stripping, the orchestrator throws and refuses to send the narration (§1.7 hard gate).

Back-compatibility

If the LLM emits no markers (or parsing fails), GeneratedScript.timeline is undefined and the orchestrator falls back to PipelineInput.recordingSteps — byte-identical to pre-Phase-2 behaviour. MARKETING_INTRO_RECORDING_STEPS and the isIntroDoc branch in run-pipeline.ts are retained as a known-good fallback for the introduction video specifically.

Duration resolver

Each scene’s duration in milliseconds is computed as follows:
The resolver is pure (no I/O) and is the single source of truth for scene durations.

Adapter contract

Every SceneSource implementation must satisfy the following interface:
The shared contract test in tests/video-pipeline/scene-sources/contract.test.ts is instantiated for each adapter and verifies the prepare → render → dispose lifecycle.