How to Convert DVDs and TiVo MPEG2 Videos to H.264

H.264
Previously, I have showed you how to set up software to be able to rip DVDs to your hard drive, and I’ve also shown you how to copy your TiVo videos nightly. I’ve described the method I use to transcode the videos, but haven’t provided the script… Until now. After months of using and tweaking, here’s what I use. I call it VidProc.

#!/usr/bin/perl
# VidProc
# Transcode videos that have been either ripped by ripper or copied from a TiVo
#    to H.264 format
# by Ed Salisbury (ed@edsalisbury.net)
# http://www.edsalisbury.net
# (c)2009 Ed Salisbury, Some Rights Reserved
#
# External Utilities Required:
# * HandBrakeCLI
# * MPlayer/Mencoder
#
# Notes:
# * Settings for HandBrake and mencoder are what I have come up with after
#   doing a fair bit of research, and seem to work pretty well - if you have
#   any concrete suggestions on *better* general-purpose settings, let me know.
#
# License:
# Except where otherwise noted, this work is licensed under Creative Commons
#   Attribution ShareAlike 3.0.
#
# You are free:
#   * to Share -- to copy, distribute and transmit the work
#   * to Remix -- to adapt the work
#
# Under the following conditions:
#   * Attribution. You must attribute the work in the manner specified by the
#     author or licensor (but not in any way that suggests that they endorse
#     you or your use of the work).
#   * Share Alike. If you alter, transform, or build upon this work, you may
#     distribute the resulting work only under the same, similar or a
#     compatible license.
#   * For any reuse or distribution, you must make clear to others the license
#     terms of this work. The best way to do this is with a link to the
#     license's web page (http://creativecommons.org/licenses/by-sa/3.0/)
#   * Any of the above conditions can be waived if you get permission from the
#     copyright holder.
#   * Nothing in this license impairs or restricts the author's moral rights.

use warnings;
use strict;
use File::Copy;
use File::Basename;

sub system_int($);

# Locations
my $DEST = "/path/to/destdir";
my $QUEUE = "/path/to/queue.txt";

# Encoding Options
my $DEINT = "pp=md";    # Deinterlacer
my $VBITRATE = 1000;    # Video Bitrate
my $SAMPLE_RATE = 44.1; # Audio Sample Rate
my $ABITRATE = 192;     # Audio Bitrate
my $OVERSCAN = 6;       # Overscan size (# of lines)

# File Extensions
my $SRC_EXT = "mpg";
my $DEST_EXT = "mp4";

# External Utilities
my $HANDBRAKE = "/usr/local/bin/HandBrakeCLI";
my $MENCODER = "/usr/bin/mencoder";
my $MPLAYER = "/usr/bin/mplayer";

# Add backslashes to spaces
$DEST =~ s/ /\ /g;

while (1)
{
    # Read in the queue
    open(QUEUE, $QUEUE);
    my $line = <QUEUE>;
    my @fields;
    if ($line)
    {
        chomp ($line);
        @fields = split(/|/, $line);
    }
    else
    {
        print "Waiting for new stuff to show up in the queue...n";
        close(QUEUE);
        sleep(60);
        next;
    }
    close (QUEUE);

    my $input = $fields[0];

    print "Processing $inputn";

    # Fix spaces
    $input =~ s/ /\ /g;
    $input =~ s/'/\'/g;
    $input =~ s/&/\&/g;
    $input =~ s/;/\;/g;
    my @extlist = (".$SRC_EXT");

    my $base = basename($input, @extlist);

    if ($fields[1])
    {
        # DVD, since it has a title number
        my $title = $fields[1];
        my $output = "$DEST/${base}_" . $fields[1] . ".$DEST_EXT";

        system_int("$HANDBRAKE --input $input --output $output --title $title --turbo --encoder x264 --vb $VBITRATE --audio 1 --aencoder faac --mixdown 6ch --arate $SAMPLE_RATE --ab $ABITRATE --detelecine --decomb --loosePixelratio --markers --two-pass --x264opts ref=3:mixed-refs:bframes=6:weightb:direct=auto:b-pyramid:me=umh:subme=9:analyse=all:8x8dct:trellis=1:nr=150:no-fast-pskip=1:psy-rd=1,1");
    }
    else
    {
        # TiVo file, since it has no title number
        my $output = "$DEST/${base}.$DEST_EXT";
        my $line;
        my $width;
        my $height;

        # Get video dimensions
        print "Getting video information... ";
        my @id = `$MPLAYER -nojoystick -nolirc -vo null -ao null -identify -frames 0 $input 2>&1`;
        foreach (@id)
        {
            if (/ID_VIDEO_WIDTH=(d+)/)
            {
                $width = $1;
            }
            if (/ID_VIDEO_HEIGHT=(d+)/)
            {
                $height = $1;
            }
        }
        if (!$width || !$height)
        {
            print "FAILED!";
        }
        else
        {
            print "(${width}x$height)n";
        }

        # Crop overscan
        $height-=$OVERSCAN;

        my %croptest;

        # Detect crop region
        print "Detecting crop region... ";
        my @cropdetect = `$MPLAYER -nojoystick -nolirc -vo null -ao null -vf crop=$width:$height:0:$OVERSCAN,cropdetect -ss 600 -endpos 180 $input 2>&1`;
        foreach $line (@cropdetect)
        {
            if ($line =~ /-vf crop=([d:]+)/)
            {
                $croptest{$1}++;
            }
        }

        # Get the crop value with the most hits
        my @croplist = sort { $croptest{$b} <=> $croptest{$a} } keys (%croptest);
        my $crop = $croplist[0];

        if (!$crop)
        {
            print "FAILEDn";
            exit();
        }
        print "($crop)n";
        print '-' x 80 . "n";

        print "Encoding first passn";
        print '-' x 80 . "n";
        system_int("$MENCODER -ovc x264 -x264encopts pass=1:turbo:bitrate=$VBITRATE:bframes=1:me=umh:partitions=all:trellis=1:qp_step=4:qcomp=0.7:direct_pred=auto:keyint=300 -vf $DEINT,crop=$crop,scale=-1:-10,harddup -oac copy -ofps 30000/1001 $input -o /dev/null");

        print "nn";
        print '-' x 80 . "n";
        print "Encoding second passn";
        print '-' x 80 . "n";
        system_int("$MENCODER -ovc x264 -x264encopts pass=2:turbo:bitrate=$VBITRATE:bframes=1:me=umh:partitions=all:trellis=1:qp_step=4:qcomp=0.7:direct_pred=auto:keyint=300 -vf $DEINT,crop=$crop,scale=-1:-10:,harddup -oac copy -ofps 30000/1001 $input -o $output");
    }
    print "Done. Removing from queuen";
    print "nn";
    open(IN, $QUEUE);
    open(OUT, ">$QUEUE.tmp");
    while (my $qline = <IN>)
    {
        chomp ($qline);
        unless ($line eq $qline)
        {
            print OUT "$qlinen";
        }
    }
    close (IN);
    close (OUT);
    unlink($QUEUE);
    move("$QUEUE.tmp", $QUEUE);
}

# Interruptible System Command
sub system_int($)
{
    my ($cmd) = @_;
    my $pid = fork();
    my $rc;
    if ($pid == 0)
    {
        exec($cmd);
    }
    else
    {
        waitpid($pid,0);
        $rc = $?;
    }
    return $rc;
}

It uses the same queue file that the other scripts will write to, so think of this as a continuation of the previous articles. To use it, stick it somewhere like /usr/local/bin, and then change the locations to be wherever your queue is, and where you want your videos to end up. The transcoding settings can get a little hairy, so I would say don’t mess with the actual command lines unless you know what you’re doing (but, if you did, you probably wouldn’t need this script!)

External Utilities Needed:

HandBrakeCLI – this can be downloaded from here
mplayer/mencoder – This can be installed via apt-get

A couple of Notes/FAQs:

Overscan removal:
If you deal with letterboxed videos copied from TV, you’ll know what a pain overscan can be – Overscan is the generic term for the fuzzy line above the video that gets really annoying when watching on your monitor, but not on your TV. This can wreak havoc with detecting where to crop the video, so I remove it.

HandBrake *AND* mencoder?? Why?
I found that for some reason, Handbrake didn’t like working with the files that were copied from the TiVo – I dealt with lots of weird sync issues. I posted it on the Handbrake forum, but never got a solution. I ended up just using mencoder for the TiVo files, and Handbrake for the DVD rips, and left it at that.

Your setting XXX sucks – you should use YYY!
If you can state that for any type of program (animation, TV, letterboxed, HD, etc.) that this is correct (and that it doesn’t make the process take much longer), I’ll happily thank you and update the script (as well as give you a mention here.) I spent a bit of time trying to find settings that would work well for what I wanted, and ended up choosing these. They’re not going to have the best quality, but the size and the time to transcode were good, so that’s what I stuck with.

  • http://www.edsalisbury.net/linux/how-to-convert-tivo-mpeg2-videos-to-h264/ Converting TiVo MPEG2 Videos to H.264 | EdSalisbury.net : Your Guide to User-Friendly Entertainment

    [...] Update: I have created another guide that includes the script that I use here. [...]

  • http://www.edsalisbury.net/linux/how-to-rip-dvds-on-ubuntu-linux-9-04-jaunty/ How to Rip DVDs on Ubuntu Linux 9.04 (Jaunty) | EdSalisbury.net : Your Guide to User-Friendly Entertainment

    [...] Update: I have posted a new guide and script for processing the DVD rips here. [...]

  • Bo

    Question, the queu file that was created with the rip script is empty. When I run the conversion script, its waiting for new stuff to show up in the queu.

    Any ideas?

  • Tommy599

    Thanks for the script! It’s the first time I ever encoded anything and it worked on the first try!! Woohoo! Awesome script!!

  • John

    Ed, thanks so much for sharing this script – it does EXACTLY what I’ve been struggling to do. I’m pulling files selectively off my tivo using itivo and manually writing the path(s) into my queue file.
    I am getting an error (and here’s where I show my ignorance of perl and programming in general) in line 93 where you are using “$input”, perl complains that this variable is uninitialized. But it looks like you initialize this variable in line 91.
    Hear that banging sound? That’s me hitting my head against the ceiling of my programming skills… Any advice would be appreciated. Thanks in advance!

  • Robert

    I think it would be useful to provide an example queue file (queue.txt) or modify the TiVo Series 3 document to write out a queue.txt file after it pulls for conversion. The DVD script has this feature but the TiVo one does not.

  • Planetside0

    Thanks for the work you've done here. I love your scripts! This is exactly what I've been looking for. I have a couple of Tivos and an AirVideo server, and I wanted to back up some DVDs that I watch frequently. This is working like a charm on my new Ubuntu media workhorse system that supplies the final media for streaming and playback on all my devices. I just wanted to mention that I had to remove the “–loosePixelratio” option for the current version of HandBrakeCLI. It must be deprecated as it doesn't recognize the option at all.

    One question — I'm having some difficulty playing the mp4 product from a DVD on my TIVO. It appears to support the format but disappears from the playback list.. Downloads are incomplete. Have you ever seen this happen?

  • http://www.gpscardvd.com cardvdgps

    Thanks for this great info, I was exactly looking for this website and finally find it.

  • http://www.weprintdiscs.com/ CD Printing

    Awesome information! Thanks for sharing the codes and I hope this works well.

  • Pinko

    Can I ask what your purposes for the transcoded files were? It seems that this script will output an AVI file renamed mp4 which may or most likely won’t be playable on the Tivo it was downloaded from. I am an avid user of your scripts, but it seems that my expectations of what this script is for are different than your intent/use. Thoughts?

  • Pinko

    I got this to work with a simple path to the filename. eg /media/9.tivo
    I think if you read the scripts, you can add a pipe “|” delimiter to add a title, but then the script assumes you are dealing with a dvd.

  • Anonymous

    I don’t typically use the TiVo for playing videos any more, and I’ve since updated the script to use the correct extension. I have an updated version of the script available – I’ll update the original that was posted so that you can see the differences.