Difference between revisions of "High Quality Transcode"

From MythTV Official Wiki
Jump to: navigation, search
(Perl script to transcode 6Gb recordings to 1Gb x264 files)
 
m (version change)
Line 7: Line 7:
 
|category=User Job Scripts
 
|category=User Job Scripts
 
|file=x264_transcode_high.pl
 
|file=x264_transcode_high.pl
|S25=yes
+
|S24=yes
 
}}
 
}}
 
User script may be run as:  
 
User script may be run as:  

Revision as of 03:42, 11 February 2011


Author Robert Houghton. Edits from Nigel Pearson's "Copy and transcode"
Description This script performs a high quality transcode for HD playback on PS3 and some Android devices over ethernet or wireless network. The transcoded file is added to the recorded table alongside the original file. Settings produce a 3.5Mbps x264 file with 5.1 AAC audio if available, 2.0 AAC audio otherwise.

Output files are approximately 1Gb per 42 minutes of HDTV.
Output files are named "Series_SXXEXX_Title.mp4" for archiving purposes.

Supports Version24.png  


User script may be run as:

x264_transcode_high.pl -j %JOBID% 
  --- or ---
x264_transcode_high.pl -f %FILE% 

Requires ffmpeg compiled with --enable-libx264 and --enable-libfaac Fedora HOWTO


Application-x-perl.png x264_transcode_high.pl

#!/usr/bin/perl -w
# ============================================================================
# = NAME 
# x264_transcode_high.pl
#
# = PURPOSE
# Convert mpeg2 file from myth to h264 with aac audio.
#
# = USAGE
my $usage = 'Usage:
mpg2_to_x264.pl -j %JOBID% 
mpg2_to_x264.pl -f %FILE% 
';

# ============================================================================

use strict;
use MythTV;

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

# do nothing?
my $noexec = 0;

# extra console output?
my $DEBUG = 1;

# some globals
my ($chanid, $command, $query, $ref, $starttime, $showtitle, $episodetitle);
my ($seasonnumber, $episodenumber);
my ($newfilename, $newstarttime);
# globals for stream and resolution mapping
my ($output, $videostream, $audiostreamsurround, $audiostreamstereo, $framerate);

# transcode options
my $deinterlace = "-deinterlace"; # disabled if video is found to be progressive
my $size = "hd720";
my $audiocodec = "libfaac";
my $audiobitrate = "160k";
my $audiofrequency = "48000";
my $audiochannels = 6; # changed to 2 if input carries no surround audio
my $audiostream = 1.0; # default audio channel
my $ftype = "mp4";
my $threads = 2;
my $nicevalue = 17; # don't hog the CPU
my $videopreset = "hq"; # video preset to HQ settings for x264 encoder
my $videocodec = "libx264";
my $videobitrate = "3300k"; # target bitrate

my $mt = '';
my $db = '';

sub Reconnect()
{
    $mt = new MythTV();
    $db = $mt->{'dbh'};
}

# ============================================================================
sub Die($)
{
    print STDERR "@_\n";
    exit -1;
}
# ============================================================================
# Parse command-line arguments, check there is something to do:
#
if ( ! @ARGV )
{   Die "$usage"  }
Reconnect;

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 '-j' || $arg eq '--jobid' )
    {   $jobid = shift @ARGV  }
    elsif ( $arg eq '-f' || $arg eq '--file' )
    {   $file = shift @ARGV  }
    else
    {
        unshift @ARGV, $arg;
        last;
    }
}

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

# ============================================================================
# If we were supplied a jobid, lookup chanid
# and starttime so that we can find the filename
#
if ( $jobid != -1 )
{
    $query = $db->prepare("SELECT chanid, starttime " .
                          "FROM jobqueue WHERE id=$jobid;");
    $query->execute || Die "Unable to query jobqueue table";
    $ref       = $query->fetchrow_hashref;
    $chanid    = $ref->{'chanid'};
    $starttime = $ref->{'starttime'};
    $query->finish;

    if ( ! $chanid || ! $starttime )
    {   Die "Cannot find details for job $jobid"  }

    $query = $db->prepare("SELECT basename FROM recorded " .
                          "WHERE chanid=$chanid AND starttime='$starttime';");
    $query->execute || Die "Unable to query recorded table";
    ($file) = $query->fetchrow_array;
    $query->finish;

    if ( ! $file )
    {   Die "Cannot find recording for chan $chanid, starttime $starttime"  }

    if ( $DEBUG )
    {
        print "Job $jobid refers to recording chanid=$chanid,",
              " starttime=$starttime\n"
    }
}
else
{
    if ( $file =~ m/(\d+)_(\d\d\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/ )
    {   $chanid = $1, $starttime = "$2-$3-$4 $5:$6:$7"  }
    else
    {
        print "File $file has a strange name. Searching in recorded table\n";
        $query = $db->prepare("SELECT chanid, starttime " .
                              "FROM recorded WHERE basename='$file';");
        $query->execute || Die "Unable to query recorded table";
        ($chanid,$starttime) = $query->fetchrow_array;
        $query->finish;

        if ( ! $chanid || ! $starttime )
        {   Die "Cannot find details for filename $file"  }
    }
}




# A commonly used SQL row selector:
my $whereChanAndStarttime = "WHERE chanid=$chanid AND starttime='$starttime'";

# ============================================================================
# Find the directory that contains the recordings, check the file exists
#
my $dir  = undef;
my $dirs = $mt->{'video_dirs'};

foreach my $d ( @$dirs )
{
	if ( ! -e $d )
	{   Die "Cannot find directory $dir that contains recordings"  }

	if ( -e "$d/$file" )
	{
		$dir = $d;
		last
	}
	else
	{   print "$d/$file does not exist\n"   }
}

if ( ! $dir )
{   Die "Cannot find recording"  }

# ============================================================================
# Get ffmpegs info
#

$audiostreamstereo   = 0;
$audiostreamsurround = 0;
$command = "ffmpeg -i $dir/$file ";
open(FF_info, "$command 2>&1 |");
while ( defined(my $line = <FF_info>) ) {
	chomp($line);
	if ( $line =~ /^\s*Stream.*#(\S\.\S).*:\sVideo.*\s(\S*)\stbr/ )
	{
		$framerate = $2;
		$videostream = $1;
		next;
	}
	if ( $line =~ /^\s*Stream.*#(\S\.\S).*:\sAudio.*stereo/ )
	{
		$audiostreamstereo = $1;
		next;
	}
	if ( $line =~ /^\s*Stream.*#(\S\.\S).*:\sAudio.*5.1/ )
	{
		$audiostreamsurround = $1;
		next;
	}
}
if ( $framerate <= 30.0 ) { $deinterlace = "-deinterlace" } elsif ( $framerate > 30 && $framerate <= 60 ) { $deinterlace = "" }

# ============================================================================
# First, generate a new filename,
#

$query = $db->prepare("SELECT title FROM recorded $whereChanAndStarttime;");
$query->execute || Die "Unable to query recorded table";
$showtitle = $query->fetchrow_array;
$ref = $query->fetchrow_hashref;
$query->finish;

$query = $db->prepare("SELECT subtitle FROM recorded $whereChanAndStarttime;");
$query->execute || Die "Unable to query recorded table";
$episodetitle = $query->fetchrow_array;
$ref = $query->fetchrow_hashref;
$query->finish;


$seasonnumber = `/usr/share/mythtv/metadata/Television/ttvdb.py -N "$showtitle" "$episodetitle"`;

my ($year,$month,$day,$hour,$mins,$secs) = split m/[- :]/, $starttime;
my $oldShortTime = sprintf "%04d%02d%02d",
                   $year, $month, $day;
my $iter = 0;

do {
  if ( $episodetitle eq "" )
  {
    $newfilename = sprintf "%s_%s.%s.%s", $showtitle, $month, $day, $year;
  } else {
    $newfilename = sprintf "%s_%s_%s", $showtitle, $seasonnumber, $episodetitle;
  }
  $newfilename =~ s/\;/   AND   /g;
  $newfilename =~ s/\&/   AND   /g;
  $newfilename =~ s/\s+/ /g;
  $newfilename =~ s/\s/_/g;
  $newfilename =~ s/:/_/g;
  $newfilename =~ s/__/_/g;
  $newfilename =~ s/\(//g;
  $newfilename =~ s/\)//g;
  $newfilename =~ s/'//g;
  $newfilename =~ s/\!//g;
  $newfilename =~ s/\///g;
  if ( $iter != "0" ) 
  {  $newfilename = sprintf "%s_%d%s", $newfilename, $iter, ".mp4"  } else { $newfilename = sprintf "%s%s", $newfilename, ".mp4" }
  $iter ++;
  $secs = $secs + $iter;
  $newstarttime = sprintf "%04d-%02d-%02d %02d:%02d:%02d",
                    $year, $month, $day, $hour, $mins, $secs;
} while  ( -e "$dir/$newfilename" );

$DEBUG && print "$dir/$newfilename seems unique\n";


# ============================================================================
# Third, do the transcode
#
$audiochannels = 6;
$audiostream = $audiostreamsurround;
if ( $audiostreamsurround eq "" )
{
  my $audiochannels = 2;
  my $audiostream = $audiostreamstereo;
} 

$command = "nice -n $nicevalue ffmpeg -i $file";
$command = "$command -acodec $audiocodec";
$command = "$command -ar $audiofrequency";
$command = "$command -ac $audiochannels";
$command = "$command -ab $audiobitrate";
$command = "$command -async 1";
$command = "$command -copyts";
$command = "$command -s $size";
$command = "$command -f $ftype";
$command = "$command -vcodec $videocodec";
$command = "$command -vpre $videopreset";
$command = "$command -b $videobitrate";
$command = "$command -threads $threads";
$command = "$command -level 31";
$command = "$command -map $videostream";
$command = "$command -map $audiostream";
$command = "$command $deinterlace";
$command = "$command $newfilename";
  
chdir $dir;
system $command;

if ( ! -e "$dir/$newfilename" )
{   Die "Transcode failed\n"  }


# ============================================================================
# Last, copy the existing recorded details with the new file name.
#
Reconnect;
$query = $db->prepare("SELECT * FROM recorded $whereChanAndStarttime;");
$query->execute ||  Die "Unable to query recorded table";
$ref = $query->fetchrow_hashref;
$query->finish;

$ref->{'starttime'} = $newstarttime;
$ref->{'basename'}  = $newfilename;
if ( $DEBUG && ! $noexec )
{
    print 'Old file size = ' . (-s "$dir/$file")        . "\n";
    print 'New file size = ' . (-s "$dir/$newfilename") . "\n";
}
$ref->{'filesize'}  = -s "$dir/$newfilename";

my $extra = 'Copy';


#
# The new recording file has no cutlist, so we don't insert that field
#
my @recKeys = grep(!/^cutlist$/, keys %$ref);

#
# Build up the SQL insert command:
#
$command = 'INSERT INTO recorded (' . join(',', @recKeys) . ') VALUES ("';
foreach my $key ( @recKeys )
{
    if (defined $ref->{$key})
    {   $command .= quotemeta($ref->{$key}) . '","'   }
    else
    {   chop $command; $command .= 'NULL,"'   }
}

chop $command; chop $command;  # remove trailing comma quote

$command .= ');';

if ( $DEBUG || $noexec )
{   print "# $command\n"  }

if ( ! $noexec )
{   $db->do($command)  || Die "Couldn't create new recording's record"   }

# ============================================================================

$db->disconnect;
1;