VHS Capture Notes
May 26, 2020 · View on GitHub
Notes on digitizing VHS tapes
Capturing
Hardware
Capture card
Finding hardware for capturing from legacy inputs is a small challenge.
Modern capture cards don't have
legacy inputs. There are well-reviewerd
USB 2.0 cards, but Linux support tends to be worse, even Windows support in tools like Virtualdub2 is worse,
USB 2.0 can be more finicky than PCIe, and moving raw 30fps 720x480 video takes around 256Mbps. USB 2.0 runs at
480 Mbps. Using over half the throughput on a high-latency (at least comparted to PCI) interface for a realtime
application is asking for dropped frames. USB capture cards tend to encode as MPEG2 (the one above might use x264)
before sending the data over the wire. Even if that's your target format, the quality won't be as good as with ffmpeg
and targeting a file size or quality. You'll probably also want to do a little post-processing like cropping to
remove head-switch noise, so again, raw is the way to go.
I settled on a Hauppauge ImpactVCB-e, but I wasn't thrilled with it. In the early 2000s, Hauppauge was known for making good analog capture cards with good Linux support. Linux support was ok, but the Windows drivers were old, and I wasn't able to use the card with VMWare PCI Passthrough or a Thunderbolt 3 enclosure.
VCR
lordsmurf has an amazing guide on VCR selection.
Software guide
Linux
linuxtv.org has a detailed wiki explaining VHS capture on Linux. It's a long, but worthwhile read. The only bits that were missing were how to choose which input source on the device (svideo, composite, etc.)
sudo v4l2-ctl --set-input=1
And how to change the capture volume:
sudo amixer -c Intel set Capture,0 35%,35% unmute cap
The standard audio capture rule applies, here: it's better to capture too quiet than too loud. It's impossible to truly recover from clipping, but sound at 50% of the final level is effectively 15-bit sound, and between a 48kHz capture sample rate, 10kHz frequency response on the VHS side, and a lossy codec once encoded, capturing a bit too quiet won't be very noticible.
Windows
I've used Virtualdub2 in the past, and it works well. If you're digitizing a lot of tapes, the Linux flow might work better because it's easier to script.
Encoding
Formats
As of 2020, these are the fomats I considered:
- DVD (MPEG2/AC3). For a nearly 25-year-old format, DVDs are still widely supported. Practically all Blu-ray players play DVDs, and both the new Playstation and Xbox consoles will include support.
- webm (VP9/Opus). Webm is much more recent, but it's a web standard, likely to be supported by browsers for at least a decade, and supported by Android devices, PCs, and Samsung smart TVs. VP9 has been accelerated on most devices since 2015.
- mp4 (x264/aac). Apple refuses to support VP9. These are their best, widely suppoted container and codecs.
- mkv (ffv1/flac). There isn't a "standard" archival format, but these lossless codecs and container are all open-source and well-supported.
I ruled out x265 because it isn't supported on older iPhones, and I ruled out AV1 because acceleration isn't widespread, yet, and it's "expermintal" in ffmpeg.
Workflow
As I discovered more and more issues with my captured and encoded videos, I put together a scripted pipeline for repeating the process from source videos.
-
Fix audio timing issues
When playing back captured videos in VLC, the audio was synced, but after clipping the video with eithe ffmpeg or Virtualdub2, the audio would be out-of-sync. This solved my issue:
ffmpeg -i $in \ -f lavfi -i "aevalsrc=0:c=2:s=48000" -filter_complex "[0:a][1:a]amerge[a]" \ -map 0:v -map "[a]" -ac 2 \ -acodec flac -vcodec ffv1 $out -
Extract clips from source file
ffmpeg -i $in -ss ... -to ... -c:v copy -c:a copy -y $outSome videos had multiple segments separated by bits of noise I need to remove. This script clips together multiple chunks from a single source video:
# Usage: trim <input> <output> <start1> <end1> <start2> <end2> ... function trim { in=\$1 out=\$2 args=( "$@" ) filelist=`mktemp --suffix .lst` x=2 while [ $x -lt "$#" ]; do file=`mktemp --suffix .mkv` echo "file $file" >> $filelist ffmpeg -i $in -ss ${args[$x]} -to ${args[$x+1]} -c:v copy -c:a copy -y $file let x=x+2 done ffmpeg -f concat -safe 0 -i $filelist -c copy $out cat $filelist cat $filelist | sed 's/^file //' | xargs rm rm $filelist } -
Normalize the sound Use EBU R 128 loudness normalization. ffmpeg supports this as loudnorm. Be sure to use the multipass approach:
tmp=$(mktemp) ffmpeg -i $in -af loudnorm=linear=true:print_format=json -f null -vcodec copy - 2>&1 | awk 'match(\$0, /.*Parsed_loudnorm_0.*/) { while (getline > 0) print;}' > $tmp args=$(cat $tmp | jq -r '"measured_I=\(.input_i):measured_LRA=\(.input_lra):measured_TP=\(.input_tp):measured_thresh=\(.input_thresh):offset=\(.target_offset):linear=true:print_format=summary"') ffmpeg -i $in -c:v copy -c:a flac -af loudnorm=$args -ar 48000 -sample_fmt s16 -f matroska $out -
Crop the video (and possibly fix interlacing)
This cropping isn't perfect, but it maintains the aspect ratio and removes borders and head-switching noise. I never figured out why, but my captured video had the interlaced fields swapped.
il=ls=1:cs=1will swap them. Be sure to swap the fields before cropping, or else you might flip the field order. You can recognize swapped fields by noticing static parts of the video that look someone interlaced. You can also recognize them deinterlacing with YADIF, doubling the frame rate, and interpolating. If the image jumps up and down by one pixel every frame, the fields are swapped.ffmpeg -i $in -vf il=ls=1:cs=1,crop=694:462:14:8 -vcodec ffv1 -acodec flac $out -
Encode MPEG2
This is a two-pass encode. The idea behind two-pass encoding is, for a given amount of space (a DVD is 4.7 GB), allocate the space for the best quality. You determine the target bitrate
-b:vby dividing the target size by the clip duration, subtracting the audio bitrate-b:a, and subtracting a bit for container overhead.ffmpeg -i $in -aspect 4:3 -top 1 -vf scale=352:480:interl=1 \ -sws_flags lanczos -pass 1 -vcodec mpeg2video \ -flags +ilme+ildct -mbd rd -trellis 2 -cmp 2 -subcmp 2 -r 29.97 -pix_fmt yuv420p -g 18 \ -b:v 3500000 -maxrate:v 9000000 -minrate:v 0 -bufsize:v 1835008 -packetsize 2048 \ -muxrate 10080000 -acodec flac -f rawvideo -y /dev/null ffmpeg -i $in -aspect 4:3 -top 1 -vf scale=352:480:interl=1 \ -sws_flags lanczos -pass 2 -codec:a ac3 -vcodec mpeg2video \ -flags +ilme+ildct -mbd rd -trellis 2 -cmp 2 -subcmp 2 -r 29.97 -pix_fmt yuv420p -g 18 \ -b:v 3500000 -maxrate:v 9000000 -minrate:v 0 -bufsize:v 1835008 -packetsize 2048 \ -muxrate 10080000 -b:a 224000 -ar 48000 -f dvd -y $out-aspect 4:3: DVDs support 4:3 and 16:9. VHS is 4:3.-top 1: My videos had the top field first. If you deinterlace the video with the wrong field dominance, you'll see every other frame being ahead or behind.vf scale=352:480:interl=1 -sws_flags lanczos: Resize to 352x480. Most NTSC DVDs you buy are 704x480 or 720x480, but 352x480 is also supported. Wikipedia claims VHS resolution is approximately 333×480. Use an interlacing-aware resize, and use the lanczos algorithm.-flags +ilme+ildct -mbd rd -trellis 2 -cmp 2 -subcmp 2: supposedly they flags improve the quality.-r 29.97: Framerate. DVDs only support a few, but NTSC is one of them.-pix_fmt yuv420p: Color space-g 18 -maxrate:v 9000000 -minrate:v 0 -bufsize:v 1835008 -packetsize 2048 -muxrate 10080000: ffmpeg has a-dvdarg, but it doesn't support 352x480. These settings come from the ffmpeg source.-b:v 3500000: Average video bitrate-b:a 224000: Audio bitrate-ar 48000: Audio sample rate-f dvd: Output format
-
Create a DVD image
disc1.xml:
<dvdauthor dest="disc1"> <vmgm /> <titleset> <titles> <video format="ntsc" resolution="352x480" /> <audio lang="EN" content="normal" /> <pgc><vob file="file1.vob" /></pgc> ... </titles> </titleset> </dvdauthor>VIDEO_FORMAT=NTSC dvdauthor -x disc1.xml mkisofs -dvd-video -o disc1.iso disc1 -
Encode webm files:
ffmpeg -i $in -aspect 4:3 -vf 'yadif=1:0,scale=640:480' \ -sws_flags lanczos -vcodec libvpx-vp9 -pass 1 -b:v 1130000 \ -speed 1 -row-mt 1 -pix_fmt yuv420p -acodec copy -f rawvideo -y /dev/null ffmpeg -i $in -aspect 4:3 -vf 'yadif=1:0,scale=640:480' \ -sws_flags lanczos -vcodec libvpx-vp9 -pass 2 -b:v 1130000 \ -speed 1 -row-mt 1 -pix_fmt yuv420p -acodec libopus -b:a 96k -f webm $outThe ffmpeg documentation does a good job explaining different pass and quality settings.
-aspect 4:3: use a 4:3 aspect ratio.-vf 'yadif=1:0,scale=640:480' -sws_flags lanczos: Use the yadif deinterlace algorithm with one frame per field, i.e. 59.94 fps, top field first, scale to 640x480, and use the lanczos resize algorithm after deinterlacing.-row-mt 1: enable multithreading
Unlike the DVD, I opted for square pixels to maximize compatibility.
Archiving
I chose MDisc DVD-Rs. They claim they're significantly more durable than standard DVDs and can last a millenium. For the truly paranoid, get discs from multiple vendors.
The other choice for a physical medium is hard drives. The USB interface has been around for over 20 years and is compatible with USB-C, so expect at least 10 more years of support. SATA is over 15 years old, and a drive from 2003 works effortlessly with modern hardware.
Avoid flash memory because it can lose data as it ages, especially when untouched.
FAT-32 support is supported almost everywhere, exFAT slightly less so.