How to Read VITC in FFmpeg with readvitc

This guide explains how to use FFmpeg's readvitc filter to parse Vertical Interval Timecode (VITC) from interlaced analog video captures. You will learn the correct filter syntax, how to adjust detection thresholds for noisy analog signals, how to print the extracted timecode to a text file or terminal, and how to burn the timecode directly onto the video frames.

Understanding the readvitc Filter

Analog tape formats like Betacam, U-matic, and broadcast VHS often encode timecode data into the non-visible scan lines of the Vertical Blanking Interval (VBI). When these tapes are captured without cropping the top blanking lines, the readvitc filter can read this binary data and expose it as frame metadata under the key lavfi.readvitc.tc_str.

For the filter to work, the video capture must include the top vertical blanking lines (typically lines 10–20 in NTSC or lines 6–22 in PAL), and the video must not be cropped or deinterlaced prior to running the filter.

Filter Options

The readvitc filter supports the following optional parameters:

  • scan_max: Sets the maximum field line to scan for VITC data. The default is 32. If your capture includes standard VBI space, the default is usually sufficient, but you can lower or raise it depending on where the timecode sits.
  • thr_b: Sets the black/white threshold for black (0.0 to 1.0). The default is 0.2.
  • thr_w: Sets the black/white threshold for white (0.0 to 1.0). The default is 0.6.

Adjusting thr_b and thr_w is useful when dealing with weak or degraded analog signals where standard luma levels have shifted.

Extracting and Printing VITC Data

To scan a video file and print every detected timecode value into a text file, combine readvitc with the metadata filter:

ffmpeg -i input_capture.mov -vf "readvitc=scan_max=32,metadata=mode=print:key=lavfi.readvitc.tc_str:file=vitc_log.txt" -f null -

This command parses the entire file without re-encoding video frames, creating vitc_log.txt containing entries such as:

frame:0    pts:0       pts_time:0
lavfi.readvitc.tc_str=10:14:32:04
frame:1    pts:1001    pts_time:0.0333667
lavfi.readvitc.tc_str=10:14:32:05

Reading VITC with ffprobe

If you need to query a specific frame's VITC via script without processing the entire file with FFmpeg, use ffprobe:

ffprobe -v error -f lavfi -i "movie=input_capture.mov,readvitc=scan_max=32" -show_entries frame_tags=lavfi.readvitc.tc_str -of default=noprint_wrappers=1:nokey=0

Burning VITC onto the Video

To create a reference or review copy that visibly displays the original tape timecode, pipe the metadata string into the drawtext filter:

ffmpeg -i input_capture.mov -vf "readvitc=scan_max=32,drawtext=fontfile=/path/to/font.ttf:text='%{metadata\:lavfi.readvitc.tc_str}':x=w-tw-40:y=40:fontsize=32:fontcolor=white:box=1:boxcolor=black@0.6" -c:v libx264 -crf 18 -c:a copy preview_vitc.mp4

In this command:

  • readvitc extracts the timecode on a per-frame basis.
  • drawtext accesses the metadata using %{metadata\:lavfi.readvitc.tc_str} and renders it in the top-right corner over a semi-transparent black box.

Troubleshooting Weak Signals

If readvitc fails to detect timecode on an analog capture:

  1. Verify VBI Presence: Open the raw capture in a player and confirm that flickering white dotted lines are visible along the very top of the frame.

  2. Loosen Thresholds: Broaden the detection margins to accommodate luma drift by adjusting thr_b down and thr_w up:

    readvitc=scan_max=36:thr_b=0.15:thr_w=0.50
  3. Check Processing Order: Ensure readvitc is the absolute first filter in your filterchain. Cropping (crop), scaling (scale), or deinterlacing (yadif, bwdif) before readvitc will corrupt the raw waveform lines and prevent decoding.