Copy and transcode.pl

From MythTV Official Wiki
Revision as of 17:05, 10 July 2010 by Newacct (Talk | contribs)

Jump to: navigation, search

Important.png Note: The correct title of this article is copy_and_transcode.pl. It appears incorrectly here due to technical restrictions.


Author Nigel Pearson
Description This script wraps the normal mythtranscode job, for the purpose of transcoding to a duplicate file, allowing the user to re-cut the original.
Supports


Usage

copy_and_transcode.pl -f %FILE% -p autodetect
copy_and_transcode.pl -j %JOBID% -p autodetect
copy_and_transcode.pl --file file [--debug] [--noaction]  [transcode-arguments]


Script.png copy_and_transcode.pl

#!/usr/bin/perl -w
# ============================================================================
# = NAME
# copy_and_transcode.pl
#
# = PURPOSE
# Copy a recording before transcoding, so the user can re-cut the original
#
# = USAGE
my $usage = 'Usage:
copy_and_transcode.pl -f %FILE% -p autodetect
copy_and_transcode.pl -j %JOBID% -p autodetect
copy_and_transcode.pl --file file [--debug] [--noaction]  [transcode-arguments]
';
# The first two forms would invoke this as a MythTV User Job
#
# = DESCRIPTION
# The exsisting mythtranscode system is designed for replacing a recording
# with a transcoded copy. There is a setting which causes the backend to
# keep the original file, but both the seektable and the cutlist are deleted.
# If you are using transcode to extract multiple clips from a recording,
# this is very inconvenient.
#
# This script transcodes a recording into a new recording,
# with a trivially different starttime.
# If the original recording had a cutlist, that should be honoured in the copy.
#
# = INSTALLATION
# Copy this script to a known location, and then add the command
# in mythtv-setup as a User Job (pg. 7 & 9 of General button) e.g.
#
# cp copy_and_transcode.pl /usr/local/bin
# UserJob1     /usr/local/bin/copy_and_transcode.pl -f %FILE% -p autodetect
# UserJobDesc1 Copy and Transcode
#
# = KNOWN BUGS
#
# = REVISION
# $Id: copy_and_transcode.pl 20349 2009-04-11 00:04:30Z xris $
#
# = AUTHORS
# Nigel Pearson
# ============================================================================

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);
my ($newfilename, $newstarttime);

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


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

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

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

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

if ( $noexec )
{   print "NoExecute mode. No transcoding or SQL database changes.\n"  }

# ============================================================================
# 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;
        #print "$d/$file exists\n";
        last
    }
    else
    {   print "$d/$file does not exist\n"   }
}

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

# ============================================================================
# First, generate a new filename,
# by adding one second to the starttime until we get a unique one
#
my ($year,$month,$day,$hour,$mins,$secs) = split m/[- :]/, $starttime;
my $oldShortTime = sprintf "%04d%02d%02d%02d%02d%02d",
                   $year, $month, $day, $hour, $mins, $secs;
do
{
    $secs ++;
    if ( $secs > 59 )
    {   $secs -= 60, $mins ++  }
    if ( $mins > 59 )
    {   $mins -= 60, $hour ++  }
    if ( $hour > 23 )
    {   Die "Cannot generate a new file"  }

    $newstarttime = sprintf "%04d-%02d-%02d %02d:%02d:%02d",
                    $year, $month, $day, $hour, $mins, $secs;
    my $newShrtTm = sprintf "%04d%02d%02d%02d%02d%02d",
                    $year, $month, $day, $hour, $mins, $secs;
    $newfilename  = $file;
    $newfilename  =~ s/_$oldShortTime/_$newShrtTm/;
} while ( -e "$dir/$newfilename" );

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

# ============================================================================
# Second, do the transcode, obeying the cutlist if there is one
#
$query = $db->prepare("SELECT cutlist FROM recorded $whereChanAndStarttime;");
$query->execute || Die "Unable to query recorded table";
my ($cutlist) = $query->fetchrow_array;
$ref = $query->fetchrow_hashref;
$query->finish;

$command = "mythtranscode -c $chanid -s '$starttime' --outfile $newfilename";
if ($cutlist)
{   $command .= ' --honorcutlist'   }

if ( @ARGV )
{   $command .= ' ' . join(' ', @ARGV)  }

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

if ( ! $noexec )
{
    chdir $dir;
    system $command;

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


    # Insert the generated position map into the recorded markup table.
    # File has a structure like this:
    #
    #Type: 9
    #0 14
    #12 380942
    #
    open(MAP, "$newfilename.map") || Die "Cannot find position map file";

    my $type;
    if ( <MAP> =~ m/^Type: (\d+)$/ )
    {   $type = $1  }
    else
    {   Die "Position map file is incomplete"  }

    while ( <MAP> )
    {
        if ( m/^(\d+) (\d+)$/ )
        {
            $command = "INSERT INTO recordedseek" .
                       "        (chanid,starttime,mark,offset,type)" .
                       " VALUES ($chanid,'$newstarttime',$1,$2,$type);";

	    # This outputs too many lines for normal debugging
            #if ( $DEBUG || $noexec )
            #{   print "# $command\n"  }

            if ( ! $noexec )
            {
                $db->do($command) ||
                    Die "Couldn't insert data into recordedmarkup"
            }
        }
        else
        {   Die "Position map file has bad data line"  }
    }
    close MAP;

    unlink "$newfilename.map" || Die "Cannot delete position map file";
}

# ============================================================================
# Last, copy the existing recorded details with the new file name.
#
$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';
if ( $cutlist )
{   $extra = 'Cut'   }

if ( $ref->{'subtitle'} !~ m/$extra$/ )
{
    if ( $ref->{'subtitle'} )
    {   $ref->{'subtitle'} .= " - $extra"  }
    else
    {   $ref->{'subtitle'} = $extra  }
}

#
# 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;