Ffmpeg Synth
Context: $ARGUMENTS
Quick start
- SMPTE HD bars + 1 kHz tone (10 s 1080p30):
-f lavfi -i smptehdbars=... -f lavfi -i sine=...→ Step 3, recipe A - testsrc2 moving pattern + counter:
-f lavfi -i testsrc2=size=1280x720:rate=30→ Step 3, recipe B - Solid color fill:
-f lavfi -i color=c=red:size=1280x720:rate=30:duration=5→ Step 3, recipe C - Gradient background:
-f lavfi -i "gradients=s=1920x1080:c0=red:c1=blue:speed=0.01"→ Step 3, recipe D - Mandelbrot zoom:
-f lavfi -i mandelbrot=size=1920x1080:rate=30 -t 10→ Step 3, recipe E - Legacy PAL 75 % bars:
-f lavfi -i pal75bars=size=720x576:rate=25 -t 10→ Step 3, recipe F - Silent stereo audio track:
-f lavfi -i anullsrc=channel_layout=stereo:sample_rate=48000 -t 10→ Step 3, recipe G - White / pink / brown noise:
-f lavfi -i anoisesrc=color=pink:duration=10:amplitude=0.3→ Step 3, recipe H - Sine sweep tone:
-f lavfi -i "sine=frequency=440:beep_factor=4:duration=10"→ Step 3, recipe I - Identity Hald CLUT PNG (for LUT authoring):
-f lavfi -i haldclutsrc=level=8 -frames:v 1 hald.png→ Step 3, recipe J - colorchecker 24-patch PNG:
-f lavfi -i colorchecker -frames:v 1 colorchecker.png→ Step 3, recipe K
When to use
- Pipeline integration tests that must not depend on external media files.
- Broadcast calibration — SMPTE bars + -20 dBFS 1 kHz tone for line-up.
- ABX audio listening tests (white / pink / brown noise as reference).
- Encoder stress testing — high-entropy
rgbtestsrc/mandelbrot/testsrc2. - Overlay backgrounds — solid
colororgradientsas a plate fordrawtext/overlay. - LUT authoring — identity
haldclutsrcround-tripped through a grading tool. - Placeholder silence track to marry onto a video-only source.
- Monitor calibration / gamut checks —
pal100bars,smptehdbars,rgbtestsrc. - Not a playback preview tool — for interactive filter tweaks use
ffmpeg-playback. - For writing the synthetic video through a filter chain (drawtext overlay, LUT), see
ffmpeg-video-filterandffmpeg-lut-grade.
Step 1 — Pick a source
All synthetic sources live inside libavfilter and are addressed via the lavfi input format. Choose by category:
| Category | Source | What it gives you |
|---|---|---|
| Engineering test pattern | testsrc |
Colored rects + frame number + test tone cue |
| Engineering test pattern | testsrc2 |
Like testsrc, plus a moving element and timecode |
| Broadcast bars | smptebars |
SMPTE 75 % bars, pre-HD aspect (4:3-ish, 320×240 default) |
| Broadcast bars | smptehdbars |
SMPTE HD 100 % bars (ARIB STD-B28 style) |
| Broadcast bars | pal75bars / pal100bars |
PAL legacy 75 %/100 % bars |
| Gamut / primaries | rgbtestsrc |
Pure RGB color field (tests full-range RGB path) |
| Gamut / primaries | yuvtestsrc |
Pure YUV color field |
| Exhaustive color sweep | allyuv |
Every Y×Cb×Cr triplet (256×256 per Y plane) |
| Exhaustive color sweep | allrgb |
Every R×G×B triplet (4096×4096 image) |
| Reference swatches | colorchecker |
24-patch X-Rite / Macbeth chart |
| Solid fill | color |
One color, parametric size/rate/duration |
| Math-art | mandelbrot |
Zoomable Mandelbrot fractal |
| Math-art | cellauto |
1-D cellular automaton (Wolfram rules) |
| Math-art | life |
Conway's Game of Life |
| Gradient | gradients |
2-color+ animated gradient |
| Engineering | zoneplate |
Radial zone plate (aliasing / sharpening stress) |
| LUT authoring | haldclutsrc |
Identity Hald CLUT at level=N → N³×N³ image |
| Audio tone | sine |
Single sine (optionally beeping) |
| Audio silence | anullsrc |
True digital silence |
| Audio noise | anoisesrc |
White / pink / brown / blue / velvet noise |
| Audio expression | aevalsrc |
Arbitrary sample expression (chirps, custom) |
| Audio TTS | flite |
Synthetic speech (requires ffmpeg built with --enable-libflite) |
Probe whether your ffmpeg supports a given source before committing to it:
ffmpeg -hide_banner -sources lavfi 2>/dev/null # list lavfi devices (sometimes empty)
ffmpeg -hide_banner -h filter=smptehdbars # dump the options for one source
ffmpeg -hide_banner -filters | grep -E "smpte|testsrc|colorchecker"
Step 2 — Build the lavfi input
Every lavfi source is an -f lavfi -i <expression> pair. The expression is filter syntax, not a file path:
-f lavfi -i "SOURCE=key1=val1:key2=val2:..."
Always quote the expression (colons and commas need protection in most shells). Key knobs:
size=WxH(ors=WxH) — output frame size. Some sources have defaults (smptebars→320x240), some don't — set it explicitly for anything HD or above.rate=N(orr=N) — video framerate, fps.duration=N— source-level duration, seconds (orHH:MM:SS.ms). Leave it off → source runs forever until-tor EOF.sample_rate=N(orsr=N) — audio sample rate, Hz.channel_layout=mono|stereo|5.1— audio layout (notchannels=2for most sources; see gotchas).pixel_format=yuv420p|yuv422p|rgb24|…— some sources accept it; useformat=filter otherwise.c=<colorname>/color=<…>— solid-color sources accept CSS/HTML names,0xRRGGBB,#RRGGBB,random.
Compose video + audio by stacking two -f lavfi -i inputs:
-f lavfi -i smptehdbars=size=1920x1080:rate=30 \
-f lavfi -i "sine=frequency=1000:sample_rate=48000"
ffmpeg auto-maps one video + one audio from those inputs — no -map usually needed for a single V+A pair.
Step 3 — Encode, duration, and container
Synthetic sources are infinite by default. Always bound the output with one of:
duration=Ninside the source expression, or-t Non the output (before the output filename).
Mixing both works but is easy to get wrong — prefer -t on the command line so the same flag caps both streams.
Recipe A — SMPTE HD bars + 1 kHz tone, 10 s, 1080p30, MP4
ffmpeg -f lavfi -i smptehdbars=size=1920x1080:rate=30 \
-f lavfi -i "sine=frequency=1000:sample_rate=48000" \
-t 10 \
-c:v libx264 -pix_fmt yuv420p -crf 18 \
-c:a aac -b:a 192k -ar 48000 \
out.mp4
Recipe B — testsrc2 moving pattern + counter, 5 s, 720p30
ffmpeg -f lavfi -i testsrc2=size=1280x720:rate=30 -t 5 \
-c:v libx264 -pix_fmt yuv420p -crf 20 out.mp4
Recipe C — Solid red fill, 5 s, 720p
ffmpeg -f lavfi -i "color=c=red:size=1280x720:rate=30:duration=5" \
-c:v libx264 -pix_fmt yuv420p out.mp4
Recipe D — Animated red→blue gradient, 10 s 1080p
ffmpeg -f lavfi -i "gradients=s=1920x1080:duration=10:speed=0.01:c0=red:c1=blue" \
-c:v libx264 -pix_fmt yuv420p out.mp4
Recipe E — Mandelbrot zoom, 10 s 1080p30
ffmpeg -f lavfi -i mandelbrot=size=1920x1080:rate=30 -t 10 \
-c:v libx264 -pix_fmt yuv420p out.mp4
Recipe F — PAL 75 % bars, 10 s 576i
ffmpeg -f lavfi -i pal75bars=size=720x576:rate=25 -t 10 \
-c:v libx264 -pix_fmt yuv420p out.mp4
Recipe G — Silent stereo track attached to a video-only source
ffmpeg -f lavfi -i testsrc2=size=1280x720:rate=30 \
-f lavfi -i anullsrc=channel_layout=stereo:sample_rate=48000 \
-t 10 -c:v libx264 -pix_fmt yuv420p -c:a aac out.mp4
Recipe H — White / pink noise (audio only)
ffmpeg -f lavfi -i "anoisesrc=color=white:duration=5:sample_rate=48000:amplitude=0.3" \
-c:a pcm_s16le noise.wav
Recipe I — Beep-style sine (0.25 s beep every 1 s)
ffmpeg -f lavfi -i "sine=frequency=440:beep_factor=4:duration=10:sample_rate=48000" \
-c:a pcm_s16le beep.wav
Recipe J — Identity Hald CLUT (level 8 → 512×512 PNG)
ffmpeg -f lavfi -i haldclutsrc=level=8 -frames:v 1 hald.png
Recipe K — ColorChecker 24-patch PNG
ffmpeg -f lavfi -i colorchecker -frames:v 1 colorchecker.png
# Some builds accept size=: -f lavfi -i colorchecker=patch_size=128x128:preset=reference
Recipe L — Broadcast line-up: SMPTE HD bars + 1 kHz tone at -20 dBFS
ffmpeg -f lavfi -i smptehdbars=size=1920x1080:rate=30 \
-f lavfi -i "sine=frequency=1000:sample_rate=48000" \
-af "volume=-20dB" \
-t 60 -c:v libx264 -pix_fmt yuv420p -crf 16 \
-c:a pcm_s24le lineup.mxf
Recipe M — Overlay a running timecode on SMPTE bars
Chain with drawtext (see ffmpeg-video-filter):
ffmpeg -f lavfi -i smptehdbars=size=1920x1080:rate=30 \
-vf "drawtext=fontfile=/System/Library/Fonts/Menlo.ttc:text='%{pts\\:hms}':x=40:y=40:fontsize=64:fontcolor=white:box=1:boxcolor=black@0.6" \
-t 10 -c:v libx264 -pix_fmt yuv420p bars_tc.mp4
Gotchas
- Every lavfi source uses
-f lavfi -i <expression>.<expression>is filter-source syntax. It is NOT a filename; do not quote it as a path. - Duration is not implicit. Without
duration=on the source OR-ton the output, ffmpeg generates frames forever. Always bound one of them. - Use
-torduration=, not both casually. They interact:-tcaps the output,duration=caps the source. If they disagree the shorter one wins; prefer-tfor consistency across V+A inputs. sineduration lives on the SOURCE.-f lavfi -i "sine=frequency=1000:duration=10"— the filter owns its own length.-t 10also works and is clearer when you have two inputs.smptebarsvssmptehdbars.smptebars= pre-HD 75 % bars at320x240default.smptehdbars= HD 100 % bars, aspect-correct.pal75bars/pal100barsare the PAL variants. Don't default tosmptebarsfor HD deliverables.size=is often required.colorchecker,rgbtestsrc,yuvtestsrc,smptebarshave small/fixed defaults in older ffmpeg builds. Passsize=WxHexplicitly for HD.rateis VIDEO fps;sample_rateis AUDIO Hz. They're different knobs. Don't setrate=48000onsine.- Channel layout, not channel count. Most audio sources want
channel_layout=stereo(ormono,5.1). A rawchannels=2is accepted by some but not all — prefer the named layout. - Combining V+A = two
-f lavfi -iflags. Each input needs its own-f lavfibefore its-i. One-f lavfidoes not cover the next-i. - Container must accept both streams. MP4 + libx264 + aac is the safe default. Don't write a video source into a
.wav. Don't stuffpcm_s24leinto plain MP4 (use MOV/MXF/MKV). - Color names.
c=red/c=0xff0000/c=#ff0000/c=randomall work. Alpha viac=red@0.5. testsrc2already burns in a frame counter and moving element — if you want it clean, usetestsrc.haldclutsrc=level=Nproduces anN³ × N³PNG (level 6 → 216×216, level 8 → 512×512, level 12 → 1728×1728). Level 8 is the common LUT-authoring default.anullsrcis identical regardless ofduration. It's literal zeros. Use-tto cap length.- Images need
-frames:v 1. Otherwise ffmpeg keeps writing frames intoout.pngindefinitely (or errors on single-image muxers). - Seek does not apply to generators.
-ss 5 -f lavfi -i testsrcwill NOT start the source att=5. Generators start at zero; use-itsoffsetortpadif you need a cold start. - Broadcast line-up level. SMPTE-compliant line-up = 1 kHz sine at -20 dBFS (EBU R68 = -18 dBFS). Apply with
-af "volume=-20dB"— thesinesource emits full-scale by default. fliteTTS requires a custom build.ffmpeg -filters | grep flite— if empty, your build is stock; use external TTS and import the WAV.
Available scripts
scripts/synth.py— argparse wrapper with subcommands for the common synthetic assets:bars,test-pattern,color,tone,noise,silence,hald-identity. Prints the exact ffmpeg command, supports--dry-runand--verbose. Stdlib only, non-interactive.
Workflow
- Run the script for a standard recipe:
uv run ${CLAUDE_SKILL_DIR}/scripts/synth.py bars --output out.mp4 --duration 10 --resolution 1920x1080 --fps 30 --tone-hz 1000 --hd uv run ${CLAUDE_SKILL_DIR}/scripts/synth.py test-pattern --output out.mp4 --kind testsrc2 --duration 5 uv run ${CLAUDE_SKILL_DIR}/scripts/synth.py tone --output tone.wav --hz 1000 --duration 10 uv run ${CLAUDE_SKILL_DIR}/scripts/synth.py noise --output noise.wav --color pink --duration 10 uv run ${CLAUDE_SKILL_DIR}/scripts/synth.py hald-identity --output hald.png --level 8 - For anything off the menu (custom gradient, aevalsrc expression, zoneplate, cellauto) build the
-f lavfi -ichain by hand using Step 2.
Reference docs
- Read
references/sources.mdfor full per-source option tables, color name list,aevalsrcgrammar, and a recipe gallery (broadcast cal, ABX noise, 4K 60 fps stress, identity Hald, synthetic overlays).
Examples
Example 1: Broadcast cal leader, 1 minute, MXF
ffmpeg -f lavfi -i smptehdbars=size=1920x1080:rate=30 \
-f lavfi -i "sine=frequency=1000:sample_rate=48000" \
-af "volume=-20dB" \
-t 60 -c:v libx264 -pix_fmt yuv420p -crf 16 -c:a pcm_s24le \
leader.mxf
Example 2: 4 K 60 fps encoder stress-test, 30 s
ffmpeg -f lavfi -i testsrc2=size=3840x2160:rate=60 \
-f lavfi -i anullsrc=channel_layout=stereo:sample_rate=48000 \
-t 30 -c:v libx264 -preset ultrafast -pix_fmt yuv420p stress.mp4
Example 3: Identity Hald CLUT round-trip
ffmpeg -f lavfi -i haldclutsrc=level=8 -frames:v 1 identity.png
# Grade identity.png in your DaVinci/Photoshop/LUT tool, save back as graded.png
# Apply the graded CLUT to any footage via ffmpeg-lut-grade:
# ffmpeg -i source.mov -i graded.png -filter_complex "haldclut" out.mov
Example 4: Solid-color plate for lower-third compositing
ffmpeg -f lavfi -i "color=c=0x0a1f44:size=1920x200:rate=30:duration=10" \
-c:v libx264 -pix_fmt yuv420p lower_third_bg.mp4
Troubleshooting
Error: No such filter: 'lavfi'
Cause: you passed -i lavfi:... instead of -f lavfi -i "...".
Solution: -f lavfi is a format flag; it must precede -i, and the expression goes in -i.
Error: Option size not found
Cause: the specific source does not accept size= in your ffmpeg version (older builds of colorchecker, rgbtestsrc).
Solution: pipe through a scale filter, e.g. -vf "scale=1920:1080", or upgrade ffmpeg.
Output file is empty / 0 bytes
Cause: no duration bound — ffmpeg was producing frames with no EOF signal and you killed it early.
Solution: add -t N or duration=N inside the source expression.
sine audio is silent / file is tiny
Cause: forgot sample_rate=48000 and the default (44100) is being resampled to 0 by a bad -ar.
Solution: set sample_rate= on the source AND -ar 48000 on the output if you want a specific rate.
Invalid duration specification for color
Cause: quoting ate the colons. color=c=red:size=1280x720:rate=30:duration=5 must be a single token to ffmpeg.
Solution: wrap the whole expression in double quotes: -i "color=c=red:size=1280x720:rate=30:duration=5".
anoisesrc complains about color=pink
Cause: older ffmpeg builds only shipped white and pink; blue/brown/velvet came later.
Solution: upgrade ffmpeg to 4.4+ for the full palette, or fall back to color=white and shape with -af "highpass=...".
Seeking into a synthetic source does nothing
Cause: -ss on a lavfi input is a no-op (no seekable timeline).
Solution: offset via -itsoffset, or prepend blank frames with tpad=start_duration=….