Turns a demo's raw recording into shareable files with ffmpeg.
Input: a demo directory containing Playwright's page@<hash>.webm (or a
previously renamed raw.webm) and timeline.json.
Output, named after the directory (the demo slug):
<slug>.mp4: H.264, trimmed and auto-zoomed (seePhxDemo.PlanandPhxDemo.Zoom), with a soft caption track (mov_text) and chapters from scenes. Narration audio is mixed in whensayevents carry anaudiofile (seePhxDemo.TTS), and so is background music when the:musicsetting is a path or a generation prompt (seemusic_filter/3andPhxDemo.MusicGen). Input audio (upload_audioanduse_microphonewithaudible: true, seePhxDemo.Audio) is mixed in at its timeline offset, and the music ducks under it like under narration.<slug>.srt,<slug>.vtt: sidecar captions fromsayevents<slug>.gif: when:gifis in:formats
Requires ffmpeg and ffprobe on PATH.
Summary
Functions
Narration clips as {path, at_ms} from say events that have audio.
Second pass: copy all streams and add chapters/title from FFMETADATA.
The magenta sync flash in the raw video as {start_ms, clear_ms}: its first
frame, and the first frame after it that isn't magenta (nil if the flash
outlasts the first 5 seconds). nil when there's no flash.
Where the magenta sync flash first appears in the raw video, in ms, or nil. Looks at the center of the first 5 seconds.
Audible input audio (audio_input events with audible: true) as
{path, at_ms, duration_ms, loop?}, for a video of total_ms.
ffmpeg arguments for the MP4 encode pass. Public for testing.
Filter chain for the background music input, ending in [out].
Pairs of {input_file, say_text} where an audible input overlaps a narration
line. Narration and input audio would talk over each other.
Finds the raw recording and renames it to raw.webm. Playwright names it
page@<hash>.webm.
Renders one demo directory.
Narration windows as {at_ms, duration_ms} for clips that carry audio.
Types
Functions
Narration clips as {path, at_ms} from say events that have audio.
Second pass: copy all streams and add chapters/title from FFMETADATA.
The magenta sync flash in the raw video as {start_ms, clear_ms}: its first
frame, and the first frame after it that isn't magenta (nil if the flash
outlasts the first 5 seconds). nil when there's no flash.
The page paints a frame or two after the step that follows the flash is
recorded, so cutting at a timeline time alone can leave magenta frames at
the start; PhxDemo.Plan never starts the cut before clear_ms.
Where the magenta sync flash first appears in the raw video, in ms, or nil. Looks at the center of the first 5 seconds.
Audible input audio (audio_input events with audible: true) as
{path, at_ms, duration_ms, loop?}, for a video of total_ms.
An upload plays for the file's length. A microphone plays from its play
event until the file ends (never, if it loops), the next play or stop
event, or the end of the video. loop? says the input needs -stream_loop.
ffmpeg arguments for the MP4 encode pass. Public for testing.
Inputs, in order: the raw video (trimmed to the plan), one input per narration clip, then captions (if any).
Filter chain for the background music input, ending in [out].
Input index is opened with -stream_loop -1, so it loops; the chain
trims it to duration_ms, sets the level, ducks 10 dB (with short ramps)
around each {at_ms, duration_ms} in :windows, then fades in (1s) and
out (2s). Fades shrink on very short videos.
Options: :windows, :volume_db (default -18), :out (default "aout").
Pairs of {input_file, say_text} where an audible input overlaps a narration
line. Narration and input audio would talk over each other.
Finds the raw recording and renames it to raw.webm. Playwright names it
page@<hash>.webm.
Renders one demo directory.
Options:
:formats: list of:mp4and/or:gif(default[:mp4]). A GIF is made from the finished MP4.:gif_width: default 800:gif_fps: default 12:crf: H.264 quality, lower is better (default 20)
Narration windows as {at_ms, duration_ms} for clips that carry audio.