VHS Capture on Linux

It has become increasingly apparent that VHS Tapes that inherently fragile, prone to mold, and urgently require capturing to a digital file format.

Goals

VHS Capture Flow

Equipment:

VCR - JVC HR-S9500

DVD Recorder (for Passthrough) - Panasonic DMR-E50

USB Capture Device - Hauppauge USB-Live2

The ideal VCR to use for this project is an S-VHS VCR with an S-Video output. I picked a JVC HR-S9500 which met these requirements and also had a built in Timebase corrector (TBC).

If you can't find a VCR with a TBC, I'd recommend purchasing a Panasonic DVD Recorder from the mid 2000's era as these have "Frame adjusters" which work in a similar manner to a TBC and can be quite effective at producing a clean output for your capture card.

For the capture card, I tried 3 devices:

I tried the EasyCAP first as it was one that I had lying about and found the results to be incredibly poor and unusable with a low frame rate, lack of detail, and overly saturated colours. Frankly, it was only worth throwing in the bin!

I had a Dazzle DVC-100 USB Capture Device available to me by a relative. Unfortunately, while the picture quality was OK, the audio was out of sync when capturing on Linux. It was possible to use the device on Windows with VirtualDub but I did not want to do this.

Finally, I went on Amazon and bought a Hauppauge USB-Live2 for a reasonable price of £32 (At the time of writing). While you could pay more for a capture card, I do think the return on results would diminish after this point (especially considering we are dealing with VHS tapes which were never fantastic quality to begin with!)

FFMPEG Script - Capture

The first stage of this process is to capture the raw interlaced video from the capture card. For the video codec, I have written a bash script to handle all of my requirements for the process:

#!/bin/bash

# VCR Capture Script for a Hauppage! USB-Live2

# === Declarations ===
videosource="/dev/video2"
audiosource="hw:3"
VideoStandard=5
START_TIME="00:00:00"
RecordTime="02:00:00" # 2 Hours for SP

VIDEO_INPUT=1 #1 for S-Video
VIDEO_WIDTH=768
VIDEO_HEIGHT=576
PIXEL_FORMAT="YUYV"

#=== Configure Capture Card ===
#Set to PAL-I
v4l2-ctl -d $videosource -s PAL-I
#Set the Input to S-Video for the USB-Live2 Card
v4l2-ctl -d $videosource --set-input=$VIDEO_INPUT --set-fmt-video=width=$VIDEO_WIDTH,height=$VIDEO_HEIGHT,pixelformat=$PIXEL_FORMAT --set-ctrl=mute=0

#=== Capture raw and lossless ===

#Determine the filename for the raw file
Filename=$(date +%Y%m%d_%H%M%S)

~/nvidia/ffmpeg/ffmpeg -ar 48000 -f alsa -thread_queue_size 1024 -i $audiosource -f v4l2 -ts abs -channel $VIDEO_INPUT -video_size $VIDEO_WIDTH'x'$VIDEO_HEIGHT -pix_fmt rgb565be -thread_queue_size 1024 -i $videosource -c:v huffyuv -level 3 -g 1 -aspect 4:3 -flags +ilme+ildct -aspect 4:3 -pix_fmt yuv422p -aspect 4:3 -c:a pcm_s16le $Filename.avi

Not only does this script handle the capturing of the video, but it also ensures that the capture card is configured to suit my requirements (e.g. Using S-Video instead of defaulting to Composite, PAL Format)

FFMPEG Script - Transcode

To transcode the video, I wrote a bash script which will loop through a directory of AVI files and deinterlace and upscale outputting to an MP4 File:

#!/bin/bash

# VHS/Hi8 Deinterlace and Upscale Script
# William Wright M6WIQ

# === Declarations ===

# See https://ffmpeg.org/ffmpeg-filters.html for information,

# remove overscan:
FIELDORDER="tff"
CROP_FRAME="crop=(iw-10):(ih-14):3:0, pad=iw+10:ih+14:(ow-iw)/2:(oh-ih)/2"
# noise reduction
NOISE_REDUCTION="nlmeans=1.0:7:5:3:3"
SHARPEN="unsharp=3:3:1"
DEINTERLACE="bwdif=mode=1:parity=$FIELDORDER:deint=all"
# upscale
SCALING="zscale=w=960:h=720:filter=bicubic"
TRANSCODE_VIDEO_FILTERS="fieldorder=$FIELDORDER,$DEINTERLACE,$NOISE_REDUCTION,$SCALING,$SHARPEN,$CROP_FRAME,setsar=1:1"
PIXEL_FORMAT="yuv420p"
VIDEO_CODEC="hevc_nvenc" #hevc_nvenc
AUDIO_CODEC="aac"
DEINTERLACED_FRAMERATE=50
RC_MODE="vbr"

mkdir output

for f in *.avi
do
   OUTPUT="output/${f%.*}_%03d.mp4"

   # Check to see if file exists
   if [ -f "$OUTPUT" ]; then
      echo "$OUTPUT exists."
   else 
      echo "$OUTPUT does not exist."
      echo "Converting $f..."

     ~/nvidia/ffmpeg/ffmpeg -hwaccel cuda -hwaccel_output_format cuda -threads 8 -i "$f" -standard PAL -aspect 4:3 -r $DEINTERLACED_FRAMERATE -c:v $VIDEO_CODEC -rc $RC_MODE -crf 23 -b:v 0 -filter:v "$TRANSCODE_VIDEO_FILTERS" -pix_fmt $PIXEL_FORMAT -movflags +faststart -c:a $AUDIO_CODEC -b:a 256k -hide_banner "$OUTPUT"

done

Essentially, the script functions break down into:

The results

To see the results of this, I have uploaded some of the videos to Youtube...

Conclusion

A simple way to handle the capturing and processing of over 50+ Hours of old VHS Tapes to preserve for the future.

Sources cited

I am extremely grateful for the various sources on the Web which assisted me in developing this process, such as:

LinuxTV.org V4L Capture Script

SuperUser - ffmpeg settings for VHS mpeg2/mp2 to H264/MP4 VBR conversion and reducing file size

The Doom 9 Forum

Videohelp Forum