What You Will Learn

  • The kinds of statistics the signalstats filter 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

StatisticDescription
YMINMinimum luma value (0–255)
YMAXMaximum luma value (0–255)
YAVGAverage luma value
YRMSLuma RMS (root mean square)
UMIN/UMAX/UAVGCb (blue-difference) component
VMIN/VMAX/VAVGCr (red-difference) component
SATMIN/SATMAX/SATAVGSaturation
HUEMED/HUEAVGMedian and average hue
TOUTIndicator for temporal outlier pixels
VREPVertical line repetition (detects artifacts such as those from VHS)
BRNGIndicator 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 valueStatistic enabled
toutTOUT (clipped-pixel ratio)
vrepVREP (vertical line repetition)
brngBRNG (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

GoalCommand
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 framessignalstats,drawtext=text='%{metadata\:...}'
Save as CSVffprobe -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: signalstats alone stores values as frame metadata but prints nothing to the screen or log. Fix: always add metadata=mode=print to the chain (e.g. -vf "signalstats,metadata=mode=print"). Send it to standard error by outputting to -f null /dev/null.

  • Symptom: /dev/null does not work (Windows). Cause: /dev/null is the Unix null device. Fix: use NUL in PowerShell or cmd (e.g. -f null NUL). The file-based form metadata=...:file=stats.txt works 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.


  • How Filtergraphs Work and How to Write Complex Filters

Primary sources: ffmpeg.org/ffmpeg-filters.html#signalstats / trac.ffmpeg.org/wiki/FfprobeShowStreamsWithEnhancedOutput