What You Will Learn
- The kinds of statistics the
signalstatsfilter can extract - How to output per-frame video signal values as numbers
- How to export the data as CSV for graphing
- How to apply it to broadcast quality control
- How to overlay statistics on the video by combining it with
drawtext
Tested with: FFmpeg 6.1
Target OS: Windows / macOS / Linux
Tested with: FFmpeg 6.1 (verified against real FFmpeg)
What Is signalstats
signalstats is a video analysis filter built into FFmpeg. It outputs statistics for the luma (Y) and chroma (Cb/Cr) components of each frame as frame metadata. The chroma readings are handy when sampling the exact key color before green-screen compositing with chromakey.
Common uses:
- QC (quality control) of broadcast content
- Detecting clipping (regions where luma falls below 0 or exceeds 255)
- Analyzing the overall noise level of a video, for example to confirm how much detail is lost after applying a box blur
- Tracking brightness changes frame by frame
Available Statistics
| Statistic | Description |
|---|---|
YMIN | Minimum luma value (0–255) |
YMAX | Maximum luma value (0–255) |
YAVG | Average luma value |
YRMS | Luma RMS (root mean square) |
UMIN/UMAX/UAVG | Cb (blue-difference) component |
VMIN/VMAX/VAVG | Cr (red-difference) component |
SATMIN/SATMAX/SATAVG | Saturation |
HUEMED/HUEAVG | Median and average hue |
TOUT | Indicator for temporal outlier pixels |
VREP | Vertical line repetition (detects artifacts such as those from VHS) |
BRNG | Indicator for pixels outside broadcast range |
Basic Usage
Print the statistics to standard error:
ffmpeg -i input.mp4 -vf "signalstats,metadata=mode=print" -f null /dev/null
Example output (standard error):
[Parsed_metadata_1 @ ...] lavfi.signalstats.YMIN=16
[Parsed_metadata_1 @ ...] lavfi.signalstats.YMAX=235
[Parsed_metadata_1 @ ...] lavfi.signalstats.YAVG=118.2
Retrieving It as Frame Metadata
How to save the signalstats output as metadata and read it back with ffprobe:
ffmpeg -i input.mp4 -vf signalstats=stat=tout+vrep+brng,metadata=mode=print:file=stats.txt -f null /dev/null
The per-frame statistics are written to stats.txt.
Exporting as CSV for Graphing
ffprobe -f lavfi -i "movie=input.mp4,signalstats" \
-show_frames -select_streams v \
-print_format csv \
-show_entries frame_tags=lavfi.signalstats.YAVG,lavfi.signalstats.YMIN,lavfi.signalstats.YMAX \
> signalstats_output.csv
You can graph the exported CSV in Excel or Python.
Overlaying the Average Luma in Real Time
By combining signalstats with drawtext, you can overlay the statistics on each frame and burn them into the video:
ffmpeg -i input.mp4 \
-vf "signalstats,drawtext=fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf:text='YAVG\: %{metadata\:lavfi.signalstats.YAVG}':x=10:y=10:fontsize=20:fontcolor=white" \
output_stats.mp4
Enabling Only Specific Statistics (the stat Option)
ffmpeg -i input.mp4 \
-vf "signalstats=stat=tout+vrep+brng" \
-f null /dev/null
stat value | Statistic enabled |
|---|---|
tout | TOUT (clipped-pixel ratio) |
vrep | VREP (vertical line repetition) |
brng | BRNG (out-of-broadcast-range pixels) |
To specify multiple values, join them with +.
A Practical Broadcast QC Example
Detect “frames whose luma is below 16 or above 235,” a check commonly used for broadcast content:
ffprobe -f lavfi \
-i "movie=input.mp4,signalstats" \
-show_frames -select_streams v \
-print_format flat \
-show_entries frame_tags=lavfi.signalstats.YMIN,lavfi.signalstats.YMAX \
2>/dev/null | grep -E "(YMIN|YMAX)"
Saving the YAVG Trend to a Text File
Record the per-frame average luma as simple text:
ffmpeg -i input.mp4 \
-vf "signalstats,metadata=mode=print:key=lavfi.signalstats.YAVG:file=yavg.txt" \
-f null /dev/null
Common Usage Summary
| Goal | Command |
|---|---|
| Print all statistics to standard error | -vf "signalstats,metadata=mode=print" -f null /dev/null |
| TOUT/BRNG only | -vf signalstats=stat=tout+brng -f null /dev/null |
| Overlay onto frames | signalstats,drawtext=text='%{metadata\:...}' |
| Save as CSV | ffprobe -f lavfi -i "movie=input.mp4,signalstats" ... |
Measured Example
For a 1080p/30fps, 2-minute video, exporting YAVG, YMIN, and YMAX for every frame produces about 3,600 frame rows:
ffprobe -f lavfi -i "movie=input.mp4,signalstats" \
-show_frames -select_streams v \
-print_format csv \
-show_entries frame_tags=lavfi.signalstats.YAVG,lavfi.signalstats.YMIN,lavfi.signalstats.YMAX \
> signalstats_output.csv
The CSV is usually only hundreds of KB to a few MB, much smaller than the video. Runtime is close to decode speed because no re-encoding is performed. 10-bit or 4K sources increase decode cost and output volume. Results vary by environment.
Common Pitfalls
-
Symptom: no metadata is printed. Cause:
signalstatsalone stores values as frame metadata but prints nothing to the screen or log. Fix: always addmetadata=mode=printto the chain (e.g.-vf "signalstats,metadata=mode=print"). Send it to standard error by outputting to-f null /dev/null. -
Symptom:
/dev/nulldoes not work (Windows). Cause:/dev/nullis the Unix null device. Fix: useNULin PowerShell or cmd (e.g.-f null NUL). The file-based formmetadata=...:file=stats.txtworks on every OS. -
Symptom: TOUT, VREP, and BRNG are always 0. Cause: these are disabled by default or
stat=was omitted. Fix: enable them explicitly, e.g.signalstats=stat=tout+vrep+brng. Join multiple values with+. -
Symptom: the CSV row count is unexpected. Cause: the frame rate or total frame count was misjudged. Fix: the CSV has one row per frame, so rows ≈ total frames (roughly
duration_seconds × fps). For a 2-minute 30 fps clip, expect about 3,600 rows.
FAQ
Q. Does signalstats re-encode the video?
A. Not for analysis. If you discard the output with -f null, no re-encode happens and runtime is close to decode speed. Re-encoding is only needed when you burn the values in with drawtext.
Q. How do I detect clipping (blown highlights or crushed blacks)?
A. Look at YMIN and YMAX. The broadcast-legal range is 16–235, so search for frames where YMIN<16 or YMAX>235. The “broadcast QC example” above does exactly this.
Q. What are the units and ranges of the values? A. For 8-bit video, Y, U, and V all run 0–255. For 10-bit sources the scale changes, so reinterpret your thresholds against the source’s bit depth.
Q. Can I collect just one statistic cheaply?
A. Yes — enable only what you need with stat= to reduce computation. For a broadcast-range check alone, signalstats=stat=brng is enough.
Q. Can I graph the CSV directly?
A. Yes. A file written with -print_format csv loads in Excel or pandas. The first column is the frame number, followed by the frame_tags you requested in order.
Related Articles
- How Filtergraphs Work and How to Write Complex Filters
Primary sources: ffmpeg.org/ffmpeg-filters.html#signalstats / trac.ffmpeg.org/wiki/FfprobeShowStreamsWithEnhancedOutput