Difference between revisions of "Copy and transcode.pl"

From MythTV Official Wiki
Jump to: navigation, search
(Add extra check for empty seektable map, and mythtranscode --buildindex)
(Link back to this page from the script itself)
 
Line 54: Line 54:
 
#
 
#
 
# = KNOWN BUGS
 
# = KNOWN BUGS
 +
#
 +
# = SOURCE
 +
# https://www.mythtv.org/wiki/Copy_and_transcode.pl
 
#
 
#
 
# = REVISION
 
# = REVISION

Latest revision as of 18:57, 30 July 2017

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


This script has just been updated for MythTV 0.28. For older versions (I think the previous one was for 0.21), look in the history of this Wiki page.

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
#
# = SOURCE
# https://www.mythtv.org/wiki/Copy_and_transcode.pl
#
# = REVISION
# $Id$
#
# = 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;

# Did mythtranscode actually generate a seekmap?
my $emptyMap = 0;

# 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;
}
# ============================================================================
sub UniqueRecordedID
{
    $query = $db->prepare("SELECT recordedid FROM recorded;");
    $query->execute || Die "Unable to query recorded.recordedid";
    $ref = $query->fetchall_hashref('recordedid');
    $query->finish;

    for (my $id=1; ! $ref->{$id}; $id++)
    {	return $id   }
}
# ============================================================================
# Parse command-line arguments, check there is something to do:
#
if ( ! @ARGV )
{   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 '-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 )
{
    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 --chanid $chanid --starttime '$starttime' --outfile $newfilename";

if ($cutlist)
{   $command .= ' --honorcutlist'   }

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

if ( $DEBUG )
{   $command .= " --loglevel debug"   }

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"  }

    my $count = 0;
    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:$command"
            }
	    $count++;
        }
        else
        {   Die "Position map file has bad data line"  }
    }
    close MAP;

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

    if ( $count < 4 ) {   $emptyMap = 1   }
}

# ============================================================================
# Last, copy the existing recorded details with the new file name.
#

my $uniqueID = &UniqueRecordedID;

$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);

#
# New DB key that has to be unique
#
$ref->{'recordedid'} = $uniqueID;

#
# 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:$command";

    if ( $emptyMap )
    {
	$command = "mythtranscode --mpeg2 --buildindex --allkeys --showprogress --infile $newfilename";

	chdir $dir;
	system $command;
    }
}

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

$db->disconnect;

1;