176 changes: 35 additions & 141 deletions nuvexport/mythtv/recordings.pm
Original file line number Diff line number Diff line change
Expand Up @@ -21,135 +21,66 @@ package mythtv::recordings;
use Exporter;
our @ISA = qw/ Exporter /;

our @EXPORT = qw/ &load_finfo &load_recordings $video_dir %Shows /;
our @EXPORT = qw/ &load_finfo &load_recordings %Shows /;

# These are available for export, but for the most part should only be needed here
our @EXPORT_OK = qw/ &generate_showtime $num_shows /;
our @EXPORT_OK = qw/ $num_shows /;
}

# Variables we intend to export
our ($video_dir, %Shows, $num_shows);
our (%Shows, $num_shows);

# Autoflush buffers
$|++;

# Make sure we have the db filehandle
die "Not connected to the database.\n" unless ($dbh);

# Load the following extra parameters from the commandline
add_arg('date:s', 'Date format used for human-readable dates.');

#
# Load all known recordings
#
sub load_recordings {
my $Myth = shift;
# Make sure we have the db filehandle
die "Not connected to mythbackend.\n" unless ($Myth);

# Let the user know what's going on
clear();
print "Loading MythTV recording info.\n";

# Query variables we'll use below
my ($q, $sh, @files, $file, $count);

# Find the directory where the recordings are located
$q = 'SELECT data FROM settings WHERE value="RecordFilePrefix" AND hostname=?';
$sh = $dbh->prepare($q);
$sh->execute($hostname) or die "Could not execute ($q): $!\n\n";
($video_dir) = $sh->fetchrow_array();
die "This host not configured for myth.\n(No RecordFilePrefix defined for $hostname in the settings table.)\n\n" unless ($video_dir);
die "Recordings directory $video_dir doesn't exist!\n\n" unless (-d $video_dir);
$video_dir =~ s/\/+$//;

# Try a basename file search
my $rows;
$sh = $dbh->prepare('SELECT *, basename FROM recorded');
$rows = $sh->execute();
if (defined $rows) {
while ($file = $sh->fetchrow_hashref()) {
next unless ($file->{'basename'} && -e "$video_dir/".$file->{'basename'});
push @files, $file;
}
}
$sh->finish;
# Load the list
my %rows = $Myth->backend_rows('QUERY_RECORDINGS Delete');

# Nothing?!
die "No valid recordings found!\n\n" unless (@files);

# Prepare a query to look up GOP info used to determine mpeg recording length
$q = 'SELECT MAX(mark) FROM recordedseek WHERE chanid=? AND starttime=?';
$sh = $dbh->prepare($q);
die "nuvexport $VERSION requires MythTV 0.20\n" unless ($sh);

# Prepare a query to pull out cutlist information
my $c_q = 'SELECT type, mark FROM recordedmarkup WHERE chanid=? AND starttime=? AND type IN (0,1) ORDER BY mark';
my $c_sh = $dbh->prepare($c_q);
die "No valid recordings found!\n\n" unless (@{$rows{'rows'}});

$num_shows = $count = 0;
foreach $file (@files) {
foreach $file (@{$rows{'rows'}}) {
$count++;
# Print the progress indicator
print "\r", sprintf('%.0f', 100 * ($num_shows / @files)), '% ';
# Info hash
my %info = %{$file};
# Import the commercial flag list
### FIXME: how do I do this?
# Import the cutlist
$info{'cutlist'} = '';
my $cutlist_frames = 0;
my $last_mark = 0;
$c_sh->execute($info{'chanid'}, $info{'starttime'})
or die "Could not execute ($c_q): $!\n\n";
while (my ($type, $mark) = $c_sh->fetchrow_array) {
if ($type == 1) {
$info{'cutlist'} .= " $mark";
$last_mark = $mark;
}
elsif ($type == 0) {
$info{'cutlist'} .= "-$mark";
$cutlist_frames += $mark - $last_mark;
}
}
if ($type && $type == 1) {
$info{'cutlist'} .= '-'.$self->{'last_frame'};
$cutlist_frames += $self->{'last_frame'} - $last_mark;
}
print "\r", sprintf('%.0f', 100 * ($num_shows / @{$rows{'rows'}})), '% ';
# Load into an object
$file = $Myth->new_recording(@$file);
# Skip shows without cutlists?
next if (arg('require_cutlist') && !$info{'cutlist'});
# Pull out GOP info for mpeg files
$sh->execute($info{'chanid'}, $info{'starttime'})
or die "Could not execute ($q): $!\n\n";
($info{'last_frame'}) = $sh->fetchrow_array();
# Cleanup
$info{'starttime_sep'} = $info{'starttime'};
$info{'starttime_sep'} =~ s/\D+/-/sg;
$info{'starttime'} =~ tr/0-9//cd;
$info{'endtime'} =~ tr/0-9//cd;
next if (arg('require_cutlist') && !$file->{'has_cutlist'});
# Skip files that aren't local (until file info is stored in the db)
next unless ($file->{'local_path'} && -e $file->{'local_path'});
# Defaults
$info{'title'} = 'Untitled' unless ($info{'title'} =~ /\S/);
$info{'subtitle'} = 'Untitled' unless ($info{'subtitle'} =~ /\S/);
$info{'description'} = 'No Description' unless ($info{'description'} =~ /\S/);
#$description =~ s/(?:''|``)/"/sg;
push @{$Shows{$info{'title'}}}, {'filename' => "$video_dir/".$info{'basename'},
'channel' => $info{'chanid'},
'start_time' => $info{'starttime'},
'end_time' => $info{'endtime'},
'start_time_sep' => $info{'starttime_sep'},
'show_name' => $info{'title'},
'title' => $info{'subtitle'},
'description' => $info{'description'},
'transcoder' => ($info{'transcoder'} or 'autodetect'),
'hostname' => ($info{'hostname'} or ''),
'cutlist' => ($info{'cutlist'} or ''),
'last_frame' => ($info{'last_frame'} or 0),
'cutlist_frames' => ($cutlist_frames or 0),
'showtime' => generate_showtime(split(/-/, $info{'starttime_sep'})),
# This field is too slow to populate here, so it will be populated in ui.pm on-demand
'finfo' => undef
};
$file->{'title'} = 'Untitled' unless ($file->{'title'} =~ /\S/);
$file->{'subtitle'} = 'Untitled' unless ($file->{'subtitle'} =~ /\S/);
$file->{'description'} = 'No Description' unless ($file->{'description'} =~ /\S/);
# Get a user-configurable showtime string
if (arg('date')) {
$file->{'showtime'} = UnixDate("epoch $file->{'starttime'}", arg($date));
}
else {
$file->{'showtime'} = UnixDate("epoch $file->{'starttime'}", '%a %b %e %I:%M %p');
}
# Add to the list
push @{$Shows{$file->{'title'}}}, $file;
# Counter
$num_shows++;
}
$c_sh->finish();
$sh->finish();
print "\n";

# No shows found?
Expand All @@ -162,56 +93,19 @@ package mythtv::recordings;
# We now have a hash of show names, containing an array of episodes
# We should probably do some sorting by timestamp (and also count how many shows there are)
foreach my $show (sort keys %Shows) {
@{$Shows{$show}} = sort {$a->{'start_time'} <=> $b->{'start_time'} || $a->{'channel'} <=> $b->{'channel'}} @{$Shows{$show}};
@{$Shows{$show}} = sort {$a->{'starttime'} <=> $b->{'starttime'} || $a->{'callsign'} cmp $b->{'callsign'}} @{$Shows{$show}};
}
}

sub load_finfo {
my $episode = shift;
return if ($episode->{'finfo'});
%{$episode->{'finfo'}} = nuv_info($episode->{'filename'});
my $show = shift;
return if ($show->{'finfo'});
$show->load_file_info();
# Aspect override?
if ($exporter->val('force_aspect')) {
$episode->{'finfo'}{'aspect'} = aspect_str($exporter->val('force_aspect'));
$episode->{'finfo'}{'aspect_f'} = aspect_float($exporter->val('force_aspect'));
}
}

#
# Returns a nicely-formatted timestamp from a specified time
#
sub generate_showtime {
my $showtime = '';
# Get the requested date
my ($year, $month, $day, $hour, $minute, $second) = @_;
$month = int($month);
$day = int($day);
# Special datetime format?
if ($showtime = arg('date')) {
$showtime = UnixDate(ParseDate("$year-$month-$day $hour:$minute:$second"), $showtime);
}
# Default to the standard
else {
# Get the current time, so we know whether or not to display certain fields (eg. year)
my ($this_second, $this_minute, $this_hour, $ignore, $this_month, $this_year) = localtime;
$this_year += 1900;
$this_month++;
# Default the meridian to AM
my $meridian = 'AM';
# Generate the showtime string
$showtime .= "$month/$day";
$showtime .= "/$year" unless ($year == $this_year);
if ($hour == 0) {
$hour = 12;
}
elsif ($hour > 12) {
$hour -= 12;
$meridian = 'PM';
}
$showtime .= ", $hour:$minute $meridian";
$show->{'finfo'}{'aspect'} = aspect_str($exporter->val('force_aspect'));
$show->{'finfo'}{'aspect_f'} = aspect_float($exporter->val('force_aspect'));
}
# Return
return $showtime;
}

# vim:ts=4:sw=4:ai:et:si:sts=4
2 changes: 2 additions & 0 deletions nuvexport/nuv_export/cli.pm
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,8 @@ package nuv_export::cli;

add_arg('profile=s', 'nuvexportrc profile to load');

add_arg('save_info', 'Save text file with program details.');
add_arg('only_save_info', 'ONLY save program details; do not encode.');

# Load the commandline options
add_arg('help:s', 'Show nuvexport help');
Expand Down
37 changes: 17 additions & 20 deletions nuvexport/nuv_export/ui.pm
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ package nuv_export::ui;
# Load the myth and nuv utilities, and make sure we're connected to the database
use nuv_export::shared_utils;
use nuv_export::cli;
use mythtv::db;
use mythtv::recordings;
use Date::Manip;

BEGIN {
use Exporter;
Expand All @@ -41,7 +41,7 @@ package nuv_export::ui;
foreach my $episode (@{$Shows{$show}}) {
my $subtitle = arg('subtitle');
my $description = arg('description');
next unless (!$subtitle || $episode->{'title'} =~ /$subtitle/si);
next unless (!$subtitle || $episode->{'subtitle'} =~ /$subtitle/si);
next unless (!$description || $episode->{'description'} =~ /$description/si);
# Make sure we have finfo
load_finfo($episode);
Expand All @@ -55,11 +55,11 @@ package nuv_export::ui;
# Look for a filename or channel/starttime
else {
my $chanid = arg('chanid');
my $starttime = arg('starttime');
my $starttime = UnixDate(arg('starttime'), '%s');
# Filename specified on the command line -- extract the chanid and starttime
if (arg('infile')) {
# Try to pick out the chanid and starttime from the database
my $sh = $dbh->prepare('SELECT chanid, starttime FROM recorded WHERE basename=?');
my $sh = $dbh->prepare('SELECT chanid, UNIX_TIMESTAMP(starttime) FROM recorded WHERE basename=?');
if ($sh) {
# Stip off the video directory so the basename will actually match
my $infile = arg('infile');
Expand All @@ -68,10 +68,6 @@ package nuv_export::ui;
$rows = $sh->execute($infile);
if (defined $rows) {
($chanid, $starttime) = $sh->fetchrow_array();
if ($starttime) {
# strip non-numbers to get the proper format
$starttime =~ tr/0-9//cd;
}
}
$sh->finish;
}
Expand All @@ -89,7 +85,7 @@ package nuv_export::ui;
# Make sure the requested show exists
foreach my $show (sort keys %Shows) {
foreach my $episode (@{$Shows{$show}}) {
next unless ($chanid == $episode->{'channel'} && $starttime == $episode->{'start_time'});
next unless ($chanid == $episode->{'channel'} && $starttime == $episode->{'starttime'});
load_finfo($episode);
push @matches, $episode;
last;
Expand All @@ -105,12 +101,13 @@ package nuv_export::ui;
if (@matches) {
print "\nMatching Shows:\n\n";
foreach my $episode (@matches) {
print " title: $episode->{'show_name'}\n",
" subtitle: $episode->{'title'}\n",
" chanid: $episode->{'channel'}\n",
" starts: $episode->{'start_time'}\n",
" ends: $episode->{'end_time'}\n",
" filename: $video_dir/$episode->{'filename'}\n\n";
print " title: $episode->{'title'}\n",
" subtitle: $episode->{'subtitle'}\n",
" channel: $episode->{'callsign'}, $episode->{'channame'}\n",
" starts: ", unix_to_myth_time($episode->{'starttime'}), "\n",
" ends: ", unix_to_myth_time($episode->{'endtime'}), "\n",
" filename: ", ($episode->{'local_path'} or $episode->{'filename'}), "\n",
"\n";
}
}
else {
Expand Down Expand Up @@ -177,7 +174,7 @@ package nuv_export::ui;
# Figure out if there are any episodes of this program that haven't already been selected
my $num_episodes = @{$Shows{$show}};
foreach my $selected (@episodes) {
next unless ($show eq $selected->{'show_name'});
next unless ($show eq $selected->{'title'});
$num_episodes--;
last if ($num_episodes < 1);
}
Expand Down Expand Up @@ -233,7 +230,7 @@ package nuv_export::ui;
my $aspect = "";
foreach my $selected (@episodes) {
next unless ($selected->{'channel'} == $episode->{'channel'}
&& $selected->{'start_time'} eq $episode->{'start_time'});
&& $selected->{'starttime'} == $episode->{'starttime'});
$found = 1;
last;
}
Expand All @@ -248,7 +245,7 @@ package nuv_export::ui;
$query .= ' ' if ($num_episodes > 100 && $count < 100);
$query .= "$count. ";
# Print out the show name
$query .= "$episode->{'title'} " if ($episode->{'title'} && $episode->{'title'} ne 'Untitled');
$query .= "$episode->{'subtitle'} " if ($episode->{'subtitle'} && $episode->{'subtitle'} ne 'Untitled');
$query .= "($episode->{'showtime'}) ".$episode->{'finfo'}{'width'}.'x'.$episode->{'finfo'}->{'height'}
.' '.$episode->{'finfo'}{'video_type'}.' ('.$episode->{'finfo'}{'aspect'}.')';
$query .= wrap($episode->{'description'}, 80, $newline, '', $newline) if ($episode->{'description'});
Expand Down Expand Up @@ -315,8 +312,8 @@ package nuv_export::ui;
$query .= ' ' if ($num_episodes > 100 && $count < 100);
$query .= "$count. ";
# Print out the show name
$query .= "$episode->{'show_name'}:$newline";
$query .= "$episode->{'title'} " if ($episode->{'title'} && $episode->{'title'} ne 'Untitled');
$query .= "$episode->{'title'}:$newline";
$query .= "$episode->{'subtitle'} " if ($episode->{'subtitle'} && $episode->{'subtitle'} ne 'Untitled');
$query .= "($episode->{'showtime'}) ".$episode->{'finfo'}{'width'}.'x'.$episode->{'finfo'}->{'height'}
.' '.$episode->{'finfo'}{'video_type'}.' ('.$episode->{'finfo'}{'aspect'}.')';
$query .= wrap($episode->{'description'}, 80, $newline, '', $newline) if ($episode->{'description'});
Expand Down
28 changes: 21 additions & 7 deletions nuvexport/nuvexport
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@
use Cwd 'abs_path';
use lib dirname(abs_path($0 or $PROGRAM_NAME)), '/usr/share/nuvexport', '/usr/local/share/nuvexport';

# Load the MythTV object
use MythTV;
our $Myth = new MythTV();

# Load the myth and nuv utilities, and connect to the database
use nuv_export::shared_utils;
use nuv_export::cli;
Expand Down Expand Up @@ -118,10 +122,15 @@
}

# Load the recordings, and the $video_dir variable
load_recordings();
load_recordings($Myth);

# Which exporter to use
$exporter = query_exporters($export_prog);
if (arg('only_save_info')) {
$exporter = export::NUV_SQL->new;
}
else {
$exporter = query_exporters($export_prog);
}

# Load episodes from the commandline (and display/quit if this is search-only)
@episodes = load_cli_episodes();
Expand All @@ -135,16 +144,21 @@
# Encode right here?
if (arg('noserver')) {
foreach my $episode (@episodes) {
# Save the info?
if (arg('save_info') || arg('only_save_info')) {
$exporter->save_txt_details($episode);
next if (arg('only_save_info'));
}
# Keep track of when we started
if ($DEBUG) {
print "\n--------------------------------",
"\nTo encode: ", $episode->{'show_name'};
print ': ', $episode->{'title'} if ($episode->{'title'});
"\nTo encode: ", $episode->{'title'};
print ': ', $episode->{'subtitle'} if ($episode->{'subtitle'});
print "\nUse the following commands:\n";
}
else {
print "\nNow encoding: ", $episode->{'show_name'};
print ': ', $episode->{'title'} if ($episode->{'title'});
print "\nNow encoding: ", $episode->{'title'};
print ': ', $episode->{'subtitle'} if ($episode->{'subtitle'});
print "\nEncode started: ".localtime()."\n";
}
my $start = time();
Expand All @@ -164,7 +178,7 @@
my $minutes = int($seconds / 60);
$timestr .= $minutes.'m ' if ($minutes > 0);
$seconds = $seconds % 60;
# Generate a nice
# Generate a nice time string
$timestr .= $seconds.'s' if ($seconds > 0 || $timestr eq '');
# Notify the user
print "Encode lasted: $timestr\n" unless ($DEBUG);
Expand Down
7 changes: 4 additions & 3 deletions nuvexport/nuvexport.spec
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ Requires: perl >= 5.6
Requires: perl-DateManip
Requires: perl-DBD-MySQL
Requires: perl-DBI
Requires: transcode >= 0.6.12
Requires: ffmpeg >= 0.4.9
Requires: mjpegtools >= 1.6.2
Requires: perl-MythTV >= 0.21
Requires: transcode >= 0.6.12
Requires: ffmpeg >= 0.4.9
Requires: mjpegtools >= 1.6.2
Requires: mplayer

Requires: /usr/bin/id3tag
Expand Down
139 changes: 102 additions & 37 deletions nuvexport/nuvexportrc
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@
# prior to making any aspect conversions like removing black bars from 4:3
# recordings to make a 16:9 export.
#
crop_pct = 2
crop_pct = 1.5

#
# Alternatively, you can override the general crop_pct to crop a different
Expand All @@ -113,7 +113,7 @@
#
# Default to export to the current directory
#
path = .
path = /home/xris/Video

#
# Use the cutlist (not to be confused with the commercial flag list) when
Expand Down Expand Up @@ -141,7 +141,7 @@
# expense of some quality. You can also access this on the commandline via
# the --denoise (or --nodenoise) flag.
#
noise_reduction = yes
noise_reduction = no

#
# Deinterlace the video so that it looks better on software players.
Expand Down Expand Up @@ -231,15 +231,15 @@
quantisation = 6 # 4 through 6 is probably right... 1..31 are allowed (lower is better quality)

a_bitrate = 128 # Audio bitrate of 128 kbps
v_bitrate = 960 # Remember, quantisation overrides video bitrate
v_bitrate = 768 # Remember, quantisation overrides video bitrate

width = 624 # Height adjusts automatically to width, according to aspect ratio
width = 512 # Height adjusts automatically to width, according to aspect ratio
height = auto

</XviD>

#
# Default mp3 bitrate in MythTV is 128
# Default mp3 bitrate is 128
#
<MP3>
bitrate = 128
Expand All @@ -250,25 +250,6 @@
# use its full name, and it will override any more generic settings.
#

#
# The MP4 encoder for ffmpeg has a couple of options unique to itself
#
<ffmpeg::MP4>

# Codec to use (mpeg4 or h264). Please note that h264 support requires the
# SVN version of ffmpeg (not CVS!). In fact, even the mpeg4 codec works
# better with the SVN version.
mp4_codec = mpeg4

# Framerate to use: auto, 25, 23.97, 29.97. PAL will always be 25 fps, and
# auto will set 29.97 for everything over 320x288 and 23.97 for the rest.
mp4_fps = auto

</ffmpeg::MP4>

#
# As does the PSP exporter
#
<ffmpeg::PSP>

# PSP framerate (high=29.97, low=14.985)
Expand All @@ -285,14 +266,28 @@

</ffmpeg::PSP>

#
# You can also add flags to the one and only mencoder option
#
<mencoder::XviD>
<ffmpeg::MP4>

# Codec to use (xvid or h264). Please note that h264 support is experimental.
# I have not yet been able to export a file using h264 that actuall works,
# despite the fact that I've tried my best to copy the ffmpeg parameters from
# iSquint, which does work.
mp4_codec = h264

noise_reduction = no
deinterlace = yes
multipass = yes
v_bitrate = 768
a_bitrate = 128

multipass = no
width = 528
height = 480

</mencoder::XviD>
# Framerate to use: auto, 25, 23.97, 29.97. PAL will always be 25 fps, and
# auto will set 29.97 for everything over 320x288 and 23.97 for the rest.
mp4_fps = auto

</ffmpeg::MP4>

#
# You can also make specific profiles called with the --profile parameter that
Expand All @@ -301,13 +296,83 @@
# For example, you could make a profile that would encode your favorite show
# with your favorite settings.
#
<profile::sample>
<profile::bourdain>

title = bourdain

export_prog = ffmpeg
mode = mp4

crop_pct = 0
crop_right = 1.5
crop_left = 1.5

width = 528
height = 480

</profile::bourdain>

<profile::follow>

title = follow that

export_prog = ffmpeg
mode = mp4

crop_pct = 0

width = 352
height = 480

# The episodes all say "follow that" at the beginning
filename=%s

</profile::follow>

<profile::goodeats>

title = good eats

export_prog = ffmpeg
mode = mp4

crop_pct = 0
crop_top = 1
crop_right = 1
crop_left = 1

width = 528
height = 480

</profile::goodeats>

<profile::iron>

title = iron chef

export_prog = ffmpeg
mode = mp4

crop_pct = 0
crop_top = 1
crop_left = 1

width = 528
height = 480

</profile::iron>

<profile::greg>

title = greg the

title = test
export_prog = ffmpeg
mode = mp4

export_prog = transcode
mode = xvid
confirm = true
crop_pct = 0
crop_left = 1

</profile::sample>
width = 528
height = 480

</profile::greg>
49 changes: 36 additions & 13 deletions nuvexport/nuvinfo
Original file line number Diff line number Diff line change
@@ -1,33 +1,56 @@
#!/usr/bin/perl -w
#
# $Date$
# $Revision$
# $Author$
# Return details about a particular MythTV recording
#
# @url $URL$
# @date $Date$
# @version $Revision$
# @author $Author$
# @license GPL
#
# nuvinfo.pl

# Add a couple of include paths so we can load the various export and gui modules
use English;
use File::Basename;
use Cwd 'abs_path';
use lib dirname(abs_path($0 or $PROGRAM_NAME)), '/usr/share/nuvexport', '/usr/local/share/nuvexport';

# Load the nuv utilities
use mythtv::nuvinfo;
# Load the MythTV utilities
use MythTV;

# No file specified?
die "usage:\n\n nuvinfo /path/to/file.nuv\n\n" unless ($ARGV[0]);
unless ($ARGV[0]) {
print <<EOF;
usage:
nuvinfo /path/to/file.nuv
nuvinfo chanid starttime
EOF
exit 0;
}

# Connect to the backend
my $myth = new MythTV;

# Get the info about the requested file
my $file = $ARGV[0];
my %info = nuv_info($file);
my ($show, $search);
if ($ARGV[1]) {
$search = "chanid: $ARGV[0], starttime: $ARGV[1]";
$show = $myth->new_recording($ARGV[0], $ARGV[1]);
}
else {
$search = 'filename: '.basename($ARGV[0]);
$show = $myth->new_recording(basename($ARGV[0]));
}

# Print the info
print "\ninfo for: $file\n\n";
foreach $key (sort keys %info) {
next unless ($key && defined $info{$key});
print "\ninfo for $search\n\n";
foreach $key (sort keys %{$show}) {
next unless ($key && defined $show->{$key});
next if ($key eq '_mythtv' || $key eq 'channel');
print ' ' x (23 - length($key)) if (length($key) < 23);
print "$key: $info{$key}\n";
print "$key: $show->{$key}\n";
}
print "\ndone\n\n";

Expand Down