nav-left cat-right RSS
cat-right

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 $input\n";

    # 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 "FAILED\n";
            exit();
        }
        print "($crop)\n";
        print '-' x 80 . "\n";

        print "Encoding first pass\n";
        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 "\n\n";
        print '-' x 80 . "\n";
        print "Encoding second pass\n";
        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 queue\n";
    print "\n\n";
    open(IN, $QUEUE);
    open(OUT, ">$QUEUE.tmp");
    while (my $qline = <IN>)
    {
        chomp ($qline);
        unless ($line eq $qline)
        {
            print OUT "$qline\n";
        }
    }
    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.

blog comments powered by Disqus