Tuesday, January 29, 2013

Transcode Bluray with HandBrakeCLI

#!/usr/bin/perl -w
# ============================================================================
# = NAME
# x264_transcode_720p.pl
#
# = PURPOSE
# Transcode Bluray mkv generated from makemkv to mkv with h264 and aac audio, keeping log files.
#
# = USAGE
my $usage = 'Usage:
x264_transcode_720p.pl -f %FILE%
';

###############################################################################################################################
# Require and Use Clauses.
###############################################################################################################################
#
# Need HandBrakeCLI installed
# A nice toole is mediainfo, which is used after transcoding is complete to give stats on video and audio
use strict;
use XML::Simple;

###############################################################################################################################
# User defined quantities. Change these parameters to suit your needs
###############################################################################################################################
#
# Transcode speed, quality and size
my $howfast             = "veryslow";     # Choices are ultrafast, superfast, veryfast, faster, fast, medium, slow, slower, veryslow, placebo
my $videoqualitynumber         = "18";     # Sensible ranges are between 20 - 25, lower values are higher quality, bigger filesize
my $videosource            = "Bluray";    # Choices are Bluray, DVD, Other.
my $tuning            = "none";    # x264 tune options are none, film, animation, grain, stillimage, psnr, ssim, fastdecode, zerolatency
my $presetprofile        = "none";    # x264 profiles are none, baseline, main, high, high10, high422, high444
my $audiochoice            = "ac3";    # Choices are copy, dtshd, dts, ac3, flac, aac, mp3
my $subtitles            = "none";    # Choices are none, english etc

###############################################################################################################################
# Paths
###############################################################################################################################
# Set your desired output directory
my $outputdir             = "/home/david/Movies";    # Not working. Currently output goes in same folder as original
# Set your desired logging directory
my $path = "/var/log/transcode";             # (no trailing slash)
# Set your personal tag
my $personaltag         = "farmerdave";    # This is added to the output filename

###############################################################################################################################
# Prototype and Constant definitions
###############################################################################################################################
#
#Message logger so we can track what's been going on
sub logmsg(@);

# What file are we copying/transcoding?
my $file  = '';

# do nothing?
my $noexec = 0;

# Booleans
my $true = 1;
my $false = 0;

###############################################################################################################################
# Transcoding
###############################################################################################################################
#
# some globals
my ($videofilters,$anamorphic,$displaysize,$x264speed,$x264tune,$x264profile,$deinterlace,$detelecine,$decomb,$denoise,$deblock);
my ($audiocodec,$audiobitrate,$audiochannels,$audiosamplerate,$audiodrc,$audiostream,$audionum);

# transcode audio options
$audiostream = "-a 1";

if ( $audiochoice eq "copy" )
{
  $audiocodec = "-E copy --audio-copy-mask ac3,dts,dtshd --audio-fallback faac";
  $audiobitrate = "";
  $audiochannels = "";
  $audiosamplerate = "";
  $audiodrc = "";
}
elsif ( $audiochoice eq "dtshd" )
{
  $audiocodec = "-E copy:dtshd";
  $audiobitrate = "";
  $audiochannels = "";
  $audiosamplerate = "";
  $audiodrc = "";
}
elsif ( $audiochoice eq "dts" )
{
  $audiocodec = "-E copy:dts";
  $audiobitrate = "";
  $audiochannels = "";
  $audiosamplerate = "";
  $audiodrc = "";
}
elsif ( $audiochoice eq "ac3" )
{
  $audiocodec = "-E ffac3";
  $audiobitrate = "-B 448";
  $audiochannels = "";
  $audiosamplerate = "";
  $audiodrc = "";
}
elsif ( $audiochoice eq "aac" )
{
  $audiocodec = "-E faac";
  $audionum = "256";
  $audiobitrate = "-B $audionum";
  $audiochannels = "-6 dpl2";
  $audiosamplerate = "-R Auto";
  $audiodrc = "-D 0.0";
}
elsif ( $audiochoice eq "mp3" )
{
  $audiocodec = "-E lame";
  $audionum = "192";
  $audiobitrate = "-B $audionum";
  $audiochannels = "-6 dpl2";
  $audiosamplerate = "-R Auto";
  $audiodrc = "-D 0.0";
}
elsif ( $audiochoice eq "flac" )
{
  $audiocodec = "-E ffflac";
  $audionum = "";
  $audiobitrate = "-C 8";
  $audiochannels = "-6 dpl2";
  $audiosamplerate = "-R Auto";
  $audiodrc = "-D 0.0";
}

# filters
$deinterlace = "--deinterlace=\"slower\"";
$detelecine = "--detelecine";
$decomb = "--decomb=\"bob\"";
$denoise = "--denoise=\"medium\"";
$deblock = "--deblock 1:0";
$videofilters = "";

# strict or loose anamorphic?
if ( $videosource eq "Bluray" )
{
  $anamorphic = "--loose-anamorphic";
  $displaysize = "1280"; # Choices are 1920, 1280 etc
  #$videofilters = "$deblock";
  #$videofilters = "$videofilters $denoise";
  #$videofilters = "$videofilters fast_pskip=0";
  #$videofilters = "$videofilters -x ref=9:subme=9:me=umh:merange=32:trellis=2:rc-lookahead=80";
  $videofilters = "$videofilters -r 23.976";
}
elsif ( $videosource eq "DVD" )
{
  $anamorphic = "--loose-anamorphic";
  $displaysize = "720";
  #$videofilters = "$detelecine $deinterlace $decomb $deblock $denoise";
  #$videofilters = "$denoise";
  $videofilters = "$videofilters -x subme=9:me=umh:merange=36:trellis=2";
}
else
{
  $anamorphic = "--loose-anamorphic";
  $displaysize = "1280";
  $videofilters = "$detelecine $deinterlace";
}

# transcode video options
my $videoquality = "-q $videoqualitynumber";
#my $videoquality = "--vb 2500 -2 -T";
my $transcodesize="-X $displaysize";
my $videocodec = "-e x264";
my $ftype = "-f mkv";
my $chapters = "-m";

# Subtitle options
my $subtitleoptions;
if ( $subtitles eq "none")
{ $subtitleoptions = ""}
else
{ $subtitleoptions = "-s 1" }

# Assemble video options and audio options
my $videooptions = "$videocodec $videoquality $ftype $chapters $subtitleoptions $transcodesize $videofilters $anamorphic";
my $audiooptions = "$audiostream $audiocodec $audiobitrate $audiochannels $audiosamplerate $audiodrc";
my $transcodeoptions = "$videooptions $audiooptions";

# --x264-preset
$x264speed = "--x264-preset $howfast";

# --x264-tune
if ( $tuning eq "none" )
{ $x264tune = "" }
else
{ $x264tune= "--x264-tune $tuning" }

# --x264-profile
if ( $presetprofile eq "none" )
{ $x264profile = "" }
else
{ $x264profile= "--x264-profile $presetprofile" }

# Assemble x264options
my $x264options = "$x264speed $x264tune $x264profile";

###############################################################################################################################
# HandBrakeCLI Builtin commands
###############################################################################################################################
# Default x264 options
# -x ref=3:bframes=3:b-pyramid=normal:weightp=2:8x8dct=1:cabac=1:b-adapt=1:me=hex:subme=7:mixed-refs=1:weightb=1:trellis=1:rc-lookahead=40:mbtree=1:analyse=p8x8,b8x8,i8x8,i4x4:aq-mode=1:direct=spatial
#
# Normal Profile
# HandBrakeCLI -i DVD -o ~/Movies/movie.mp4  -e x264 -q 20.0 -a 1 -E faac -B 160 -6 dpl2 -R Auto -D 0.0 -f mp4 --strict-anamorphic -m -x ref=2:bframes=2:subme=6:mixed-refs=0:weightb=0:8x8dct=0:trellis=0
#
# High Profile
# HandBrakeCLI -i DVD -o ~/Movies/movie.mp4  -e x264 -q 20.0 -a 1,1 -E faac,copy:ac3 -B 160,160 -6 dpl2,auto -R Auto,Auto -D 0.0,0.0 -f mp4 --detelecine --decomb --loose-anamorphic -m -x b-adapt=2:rc-lookahead=50

# x264 presets converted to HandBrakeCLI commands
#my $ultrafast = "-x ref=1:bframes=0:cabac=0:8x8dct=0:weightp=0:me=dia:subq=0:rc-lookahead=0:mbtree=0:analyse=none:trellis=0:aq-mode=0:scenecut=0:no-deblock=1";
#my $superfast = "-x ref=1:weightp=1:me=dia:subq=1:rc-lookahead=0:mbtree=0:analyse=i4x4,i8x8:trellis=0";
#my $veryfast = "-x ref=1:weightp=1:subq=2:rc-lookahead=10:trellis=0";
#my $faster = "-x ref=2:mixed-refs=0:weightp=1:subq=4:rc-lookahead=20";
#my $fast = "-x ref=2:weightp=1:subq=6:rc-lookahead=30";
#my medium = "-x ref=3:bframes=3:b-adapt=1:direct=spatial:me=hex:merange=16:subq=7:rc-lookahead=40:analyse=most:trellis=1:no-fast-pskip=0";
#my $slow = "-x ref=5:b-adapt=2:direct=auto:me=umh:subq=8:rc-lookahead=50";
#my $slower = "-x ref=8:b-adapt=2:direct=auto:me=umh:subq=9:rc-lookahead=60:analyse=all:trellis=2";
#my $veryslow = "-x ref=16:bframes=8:b-adapt=2:direct=auto:me=umh:merange=24:subq=10:rc-lookahead=60:analyse=all:trellis=2";
#my $placebo = "-x ref=16:bframes=16:b-adapt=2:direct=auto:me=tesa:merange=24:subq=11:rc-lookahead=60:analyse=all:trellis=2:no-fast-pskip=1";
#my $film = ":psy-rd=1.0,0.15:deblock=-1,-1";
#my $x264options = "$ultrafast$film";

###############################################################################################################################
# Kill
###############################################################################################################################
#
sub Die($)
{
    logmsg STDERR "@_\n";
    exit -1;
}

###############################################################################################################################
# Parse command-line arguments, check there is something to do:
###############################################################################################################################
#
my $debug = 1;
if ( ! @ARGV )
{  print && Die "$usage"  }

while ( @ARGV && $ARGV[0] =~ m/^-/ )
{
    my $arg = shift @ARGV;

    if ( $arg eq '-d' || $arg eq '--debug' )
    {   $debug = 1  }
    elsif ( $arg eq '-n' || $arg eq '--noaction' )
    {   $noexec = 1  }
    elsif ( $arg eq '-f' || $arg eq '--file' )
    {   $file = shift @ARGV  }
    else
    {
        unshift @ARGV, $arg;
        last;
    }
}

if ( ! $file == -1 )
{
    Die "No file specified. $usage";
}


###############################################################################################################################
# Create new filename
###############################################################################################################################
#
# some globals
my $dir  = undef;
my ($newfilename,$command,$sourcetag,$displaytag,$audiotag);

# Initialise
my $iter = 0;
my $secs = 0;

# Chop file extension
(my $filetitle = $file) =~ s/\.[^.]+$//;

# Build personaltag
if ( $videosource eq "Bluray")
{ $sourcetag = ".BRRip" }
elsif ($videosource eq "DVD")
{ $sourcetag = ".DVDRip" }
else
{ $sourcetag = "" }

if ( $displaysize eq "1920")
{ $displaytag = ".1080p" }
elsif ($displaysize eq "1280")
{ $displaytag = ".720p" }
elsif ($displaysize eq "720")
{ $displaytag = ".576" }
elsif ($displaysize eq "576")
{ $displaytag = ".576" }
else
{ $displaytag = "" }

if ( $audiochoice eq "copy")
{ $audiotag = ".passthru" }
elsif ( $audiochoice eq "dtshd")
{ $audiotag = ".dtshd" }
elsif ( $audiochoice eq "dts")
{ $audiotag = ".dts" }
elsif ( $audiochoice eq "ac3")
{ $audiotag = ".ac3" }
elsif ( $audiochoice eq "flac")
{ $audiotag = ".flac" }
elsif ( $audiochoice eq "aac")
{ $audiotag = ".aac.$audionum" }
elsif ( $audiochoice eq "mp3")
{ $audiotag = ".mp3.$audionum" }
else
{ $audiotag = "" }


$personaltag = sprintf "%s%s%s%s-%s", $sourcetag, ".x264", $displaytag, $audiotag, $personaltag;

# Build newfilename
do {
  $newfilename = sprintf "%s%s", $filetitle, $personaltag;
  if ( $iter != "0" )
  {  $newfilename = sprintf "%s_%d%s", $newfilename, $iter, ".mkv"  } else { $newfilename = sprintf "%s%s", $newfilename, ".mkv" }
  $iter ++;
  $secs = $secs + $iter;
#} while  ( -e "$outputdir/$newfilename" );
} while  ( -e "$newfilename" );

#$debug && logmsg "$outputdir/$newfilename seems unique\n";
#$debug && logmsg "$newfilename seems unique\n";

###############################################################################################################################
# Logging
###############################################################################################################################
#
my $createdir="mkdir -p $path";
if (! -d $path)
{ print "mkdir failed\n" unless system($createdir) }

# extra console output?
my $LOG = "$path/transcode_$newfilename.log";
my $logsize = 4096;       #Maximum log size in kbytes, self pruning.
my $logdebug = $false;        #Debugging default is off.
my $logverbose = "-v 1";


###############################################################################################################################
# logmsg
# Little routine to write to the log file.
# Rotates around $logsize bytes.
###############################################################################################################################
#
sub logmsg(@)
{
    my ($string)=@_;
    my $time=scalar localtime;
    my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks);
    my (@lines,$line);

    ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks)=stat("$LOG");

    if (defined($size))
    {
        $size=$size/1024;                #size in kbyte

        if ($size >= $logsize)
        {
            unlink ("$LOG.old") if (-e("$LOG.old"));
            rename ($LOG,"$LOG.old");
        }
    }

    print "$time : $string\n" if ($logdebug==$true);

    if (open (LOG,">>$LOG"))
    {
        if ($string =~ /\n/)
        {
            @lines=split(/\n/,$string);
            foreach $line (@lines)
            {
                print LOG "$time : $line\n";
            }
        }
        else
        {
                print LOG "$time : $string\n";
        }
        close (LOG);
    }
    else
    {
        print "Unable to open LOG $LOG : $!";
    }
}

###############################################################################################################################
# Build and execute command
###############################################################################################################################
#
$command = "/usr/bin/HandBrakeCLI -i $file";
#$command = "$command -o $outputdir/$newfilename";
$command = "$command -o $newfilename";
$command = "$command $logverbose";
$command = "$command $x264options";
$command = "$command $transcodeoptions";


#chdir $outputdir;
# Open STDERR so that HandBrakeCLI outputs to the log file
open ( STDERR, ">>$LOG" );
select( STDERR );

# Executing system command
logmsg "###############################################################################################################################\n";
$debug && logmsg "Executing: $command\n";
logmsg "Options used are:\n";
logmsg "Preset is $howfast";
logmsg "Profile is $presetprofile";
logmsg "Tune is $tuning";
logmsg "Constant Rate Factor is $videoqualitynumber";
logmsg "Audio track is $audiochoice";
logmsg "Video filters are set to $videofilters";

# Bang!
system $command;

# Used this to debug
# my $touchcommand = "touch $newfilename";
# $debug && logmsg "Executing: $touchcommand\n";
# system $touchcommand;

# Close STDERR and re-open STDOUT
close( STDERR );
select( STDOUT );

###############################################################################################################################
# Finish the logfile off nicely
###############################################################################################################################
#
#if ( -e "$outputdir/$newfilename" )
$command = "/usr/bin/mediainfo $newfilename >> $LOG";
system $command;

if ( -e "$newfilename" )
{  
    logmsg "Transcoding $newfilename has completed.\n";
    logmsg "###############################################################################################################################\n";
}
else
{
    logmsg "Transcode of $newfilename has failed\n";
    logmsg "###############################################################################################################################\n";
}

###############################################################################################################################
# Done
###############################################################################################################################
1;