106 changes: 106 additions & 0 deletions trunk/export/MPEG2_cut.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
#!/usr/bin/perl -w
#Last Updated: 2004.09.26 (gjhurlbu)
#
# export::MPEG2_cut
# Maintained by Gavin Hurlbut <gjhurlbu@gmail.com>
#

package export::MPEG2_cut;
use base 'export::generic';

# Load the myth and nuv utilities, and make sure we're connected to the database
use nuv_export::shared_utils;
use nuv_export::ui;
use mythtv::db;
use mythtv::recordings;

# Load the following extra parameters from the commandline

sub new {
my $class = shift;
my $self = {
'cli' => qr/\bmpeg2cut\b/i,
'name' => 'MPEG2->MPEG2 cut only',
'enabled' => 1,
'errors' => [],
# Exporter-related settings
};
bless($self, $class);
# Make sure we have tcprobe
$Prog{'tcprobe'} = find_program('tcprobe');
push @{$self->{'errors'}}, 'You need tcprobe to use this.' unless ($Prog{'tcprobe'});
# Make sure we have lvemux
$Prog{'mplexer'} = find_program('lvemux');
push @{$self->{'errors'}}, 'You need lvemux to use this.' unless ($Prog{'mplexer'});
# Make sure we have avidemux2
$Prog{'avidemux'} = find_program('avidemux2');
push @{$self->{'errors'}}, 'You need avidemux2 to use this.' unless ($Prog{'avidemux'});
# Make sure we have mpeg2cut
$Prog{'mpeg2cut'} = find_program('mpeg2cut');
push @{$self->{'errors'}}, 'You need mpeg2cut to use this.' unless ($Prog{'mpeg2cut'});

# Any errors? disable this function
$self->{'enabled'} = 0 if ($self->{'errors'} && @{$self->{'errors'}} > 0);
# Return
return $self;
}

sub gather_settings {
my $self = shift;
# No parameters to setup
}

sub export {
my $self = shift;
my $episode = shift;
# Load nuv info
load_finfo($episode);

# Check for an MPEG2 recording with a cutlist
if (!$episode->{'finfo'}{'is_mpeg'}) {
print 'Not an MPEG recording, moving on';
return 0;
}
if (!($episode->{'cutlist'} && $episode->{'cutlist'} =~ /\d/)) {
print 'No cutlist found. This won\'t do!';
return 0;
}

# Generate some names for the temporary audio and video files
my $safe_outfile = shell_escape($self->{'path'}.'/'.$episode->{'outfile'});

my $command = "mpeg2cut $episode->{'filename'} $safe_outfile.mpg $episode->{'lastgop'} ";

@cuts = split("\n",$episode->{'cutlist'});
my @skiplist;
foreach my $cut (@cuts) {
push @skiplist, (split(" - ", $cut))[0];
push @skiplist, (split(" - ", $cut))[1];
}

my $cutnum = 0;
if ($skiplist[0] ne 0) {
$command .= "-";
$cutnum = 1;
}

foreach my $cut (@skiplist) {
if ($cutnum eq 0) {
if( $cut ne 0 ) {
$cutnum = 1;
$cut++;
$command .= "$cut-";
}
} else {
$cutnum = 0;
$cut--;
$command .= "$cut ";
}
}

system($command);
}

1; #return true

# vim:ts=4:sw=4:ai:et:si:sts=4
149 changes: 149 additions & 0 deletions trunk/export/NUV_SQL.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
#!/usr/bin/perl -w
#Last Updated: 2004.09.26 (xris)
#
# export::NUV_SQL
# Maintained by Chris Petersen <mythtv@forevermore.net>
#

package export::NUV_SQL;

use File::Copy;
use File::Basename;
use DBI;
use Term::ANSIColor;

# Load the myth and nuv utilities, and make sure we're connected to the database
use nuv_export::shared_utils;
use nuv_export::ui;
use mythtv::db;
use mythtv::recordings;

sub new {
my $class = shift;
my $self = {
'cli' => qr/\bnuv[\-_]sql\b/i,
'name' => 'Export to .nuv and .sql',
'enabled' => 1,
'errors' => [],
# Delete original files?
'delete' => 0,
};
bless($self, $class);

# Any errors? disable this function
$self->{'enabled'} = 0 if ($self->{'errors'} && @{$self->{'errors'}} > 0);
# Return
return $self;
}

sub gather_settings {
my $self = shift;
# Let the user know what's going on
print "\nYou have chosen to extract the .nuv.\n"
."This will extract it from the MythTV database into .nuv and .sql \n"
."files to import into another MythTV installation.\n\n";
# Make sure the user knows what he/she is doing
$self->{'delete'} = query_text("Do you want to removed it from this server when finished?",
'yesno',
$self->{'delete'} ? 'Yes' : 'No');
# Make EXTRA sure
if ($self->{'delete'}) {
$self->{'delete'} = query_text("\nAre you ".colored('sure', 'bold').' you want to remove it from this server?',
'yesno',
'No');
}
# Load the save path, if requested
$self->{'path'} = query_savepath();
}

sub export {
my $self = shift;
my $episode = shift;
# Make sure we have finfo
load_finfo($episode);
# Load the three files we'll be using
my $txt_file = basename($episode->{'filename'}, '.nuv') . '.txt';
my $sql_file = basename($episode->{'filename'}, '.nuv') . '.sql';
my $nuv_file = basename($episode->{'filename'});
# Create a txt file with descriptive info in it
open(DATA, ">$self->{'path'}/$txt_file") or die "Can't create $self->{'path'}/$txt_file: $!\n\n";
print DATA ' Show: ', $episode->{'show_name'}, "\n",
' Episode: ', $episode->{'title'}, "\n",
' Recorded: ', $episode->{'showtime'}, "\n",
'Description: ', wrap($episode->{'description'}, 64,'', '', "\n "), "\n",
"\n",
' Orig. File: ', $episode->{'filename'}, "\n",
' Type: ', $episode->{'finfo'}{'video_type'}, "\n",
' Dimensions: ', $episode->{'finfo'}{'width'}.'x'.$episode->{'finfo'}{'height'}, "\n",
;
close DATA;
# Start saving the SQL
open(DATA, ">$self->{'path'}/$sql_file") or die "Can't create $self->{'path'}/$sql_file: $!\n\n";
# Define some query-related variables
my ($q, $sh);
# Load and save the related database info
print DATA "USE mythconverg;\n\n";
foreach $table ('recorded', 'oldrecorded', 'recordedmarkup') {
$q = "SELECT * FROM $table WHERE chanid=? AND starttime=?";
$sh = $dbh->prepare($q);
$sh->execute($episode->{'channel'}, $episode->{'start_time'})
or die "Count not execute ($q): $!\n\n";
my $count = 0;
my @keys = undef;
while (my $row = $sh->fetchrow_hashref) {
# First row - let's add the insert statement;
if ($count++ == 0) {
@keys = keys(%$row);
print DATA "INSERT INTO $table (", join(', ', @keys), ") VALUES\n\t(";
}
else {
print DATA ",\n\t(";
}
# Print the data
my $count2 = 0;
foreach $key (@keys) {
print DATA ', ' if ($count2++);
print DATA mysql_escape($row->{$key});
}
print DATA ')';
}
print DATA ";\n\n";
}
# Done saving the database info
close DATA;
# Rename/move the file
if ($self->{'delete'}) {
print "\nMoving $episode->{'filename'} to $self->{'path'}/$nuv_file\n";
move("$episode->{'filename'}", "$self->{'path'}/$nuv_file")
or die "Couldn't move specified .nuv file: $!\n\n";
# Remove the entry from recordedmarkup
$q = 'DELETE FROM recordedmarkup WHERE chanid=? AND starttime=?';
$sh = $dbh->prepare($q);
$sh->execute($episode->{'channel'}, $episode->{'start_time'})
or die "Could not execute ($q): $!\n\n";
# Remove this entry from the database
$q = 'DELETE FROM recorded WHERE chanid=? AND starttime=? AND endtime=?';
$sh = $dbh->prepare($q);
$sh->execute($episode->{'channel'}, $episode->{'start_time'}, $episode->{'end_time'})
or die "Could not execute ($q): $!\n\n";
# Tell the other nodes that changes have been made
$q = 'UPDATE settings SET data="yes" WHERE value="RecordChanged"';
$sh = $dbh->prepare($q);
$sh->execute()
or die "Could not execute ($q): $!\n\n";
}
# Copy the file
else {
print "\nCopying $episode->{'filename'} to $self->{'path'}/$nuv_file\n";
copy("$episode->{'filename'}", "$self->{'path'}/$nuv_file")
or die "Couldn't copy specified .nuv file: $!\n\n";
}
}

sub cleanup {
# Nothing to do here
}

1; #return true

# vim:ts=4:sw=4:ai:et:si:sts=4
139 changes: 139 additions & 0 deletions trunk/export/SVCD.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
#!/usr/bin/perl -w
#Last Updated: 2004.09.26 (xris)
#
# export::SVCD
# Maintained by Chris Petersen <mythtv@forevermore.net>
#

package export::SVCD;
use base 'export::transcode';

# Load the myth and nuv utilities, and make sure we're connected to the database
use nuv_export::shared_utils;
use nuv_export::ui;
use mythtv::db;
use mythtv::recordings;

# Load the following extra parameters from the commandline
$cli_args{'quantisation|q=i'} = 1; # Quantisation
$cli_args{'a_bitrate|a=i'} = 1; # Audio bitrate
$cli_args{'v_bitrate|v=i'} = 1; # Video bitrate

sub new {
my $class = shift;
my $self = {
'cli' => qr/\bsvcd\b/i,
'name' => 'Export to SVCD',
'enabled' => 1,
'errors' => [],
# Transcode-related settings
'denoise' => 1,
'deinterlace' => 1,
# SVCD-specific settings
'quantisation' => 5, # 4 through 6 is probably right...
'a_bitrate' => 192,
'v_bitrate' => 2500,
};
bless($self, $class);

# Initialize and check for transcode
$self->init_transcode();
# Make sure that we have an mplexer
$Prog{'mplexer'} = find_program('tcmplex', 'mplex');
push @{$self->{'errors'}}, 'You need tcmplex or mplex to export an svcd.' unless ($Prog{'mplexer'});

# Any errors? disable this function
$self->{'enabled'} = 0 if ($self->{'errors'} && @{$self->{'errors'}} > 0);
# Return
return $self;
}

sub gather_settings {
my $self = shift;
# Load the parent module's settings
$self->SUPER::gather_settings();
# Ask the user what audio bitrate he/she wants
$self->{'a_bitrate'} = $Args{'a_bitrate'} if ($Args{'a_bitrate'});
if (!$Args{'a_bitrate'} || $Args{'confirm'}) {
while (1) {
my $a_bitrate = query_text('Audio bitrate?',
'int',
$self->{'a_bitrate'});
if ($a_bitrate < 64) {
print "Too low; please choose a bitrate >= 64.\n";
}
elsif ($a_bitrate > 384) {
print "Too high; please choose a bitrate <= 384.\n";
}
else {
$self->{'a_bitrate'} = $a_bitrate;
last;
}
}
}
# Ask the user what video bitrate he/she wants, or calculate the max bitrate (2756 max, though we round down a bit since some dvd players can't handle the max)
# Then again, mpeg2enc seems to have trouble with bitrates > 2500
my $max_v_bitrate = 2742 - $self->{'a_bitrate'};
$self->{'v_bitrate'} = $Args{'v_bitrate'} if ($Args{'v_bitrate'});
if (!$Args{'v_bitrate'} || $Args{'confirm'}) {
$self->{'v_bitrate'} = ($max_v_bitrate < 2500) ? 2742 - $self->{'a_bitrate'} : 2500;
while (1) {
my $v_bitrate = query_text('Maximum video bitrate for VBR?',
'int',
$self->{'v_bitrate'});
if ($v_bitrate < 1000) {
print "Too low; please choose a bitrate >= 1000.\n";
}
elsif ($v_bitrate > $max_v_bitrate) {
print "Too high; please choose a bitrate <= $self->{'v_bitrate'}.\n";
}
else {
$self->{'v_bitrate'} = $v_bitrate;
last;
}
}
}
# Ask the user what vbr quality (quantisation) he/she wants - 2..31
$self->{'quantisation'} = $Args{'quantisation'} if ($Args{'quantisation'});
if (!$Args{'quantisation'} || $Args{'confirm'}) {
while (1) {
my $quantisation = query_text('VBR quality/quantisation (2-31)?', 'float', $self->{'quantisation'});
if ($quantisation < 2) {
print "Too low; please choose a number between 2 and 31.\n";
}
elsif ($quantisation > 31) {
print "Too high; please choose a number between 2 and 31\n";
}
else {
$self->{'quantisation'} = $quantisation;
last;
}
}
}
}

sub export {
my $self = shift;
my $episode = shift;
# Load nuv info
load_finfo($episode);
# PAL or NTSC?
my $size = ($episode->{'finfo'}{'fps'} =~ /^2(?:5|4\.9)/) ? '480x576' : '480x480';
# Build the transcode string
$self->{'transcode_xtra'} = " -y mpeg2enc,mp2enc -Z $size"
.' -F 5,"-q '.$self->{'quantisation'}.'"'
.' -w '.$self->{'v_bitrate'}
.' -E 44100 -b '.$self->{'a_bitrate'};
# Add the temporary files that will need to be deleted
push @tmpfiles, "$self->{'path'}/$episode->{'outfile'}.m2v", "$self->{'path'}/$episode->{'outfile'}.mpa";
# Execute the parent method
$self->SUPER::export($episode);
# Multiplex the streams
my $safe_outfile = shell_escape($self->{'path'}.'/'.$episode->{'outfile'});
$command = "nice -n 19 mplex -f 5 $safe_outfile.m2v $safe_outfile.mpa -o $safe_outfile.mpg";
system($command);
}

1; #return true

# vim:ts=4:sw=4:ai:et:si:sts=4
72 changes: 72 additions & 0 deletions trunk/export/VCD.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#!/usr/bin/perl -w
#Last Updated: 2004.09.26 (xris)
#
# export::VCD
# Maintained by Gavin Hurlbut <gjhurlbu@gmail.com>
#

package export::VCD;
use base 'export::transcode';

# Load the myth and nuv utilities, and make sure we're connected to the database
use nuv_export::shared_utils;
use nuv_export::ui;
use mythtv::db;
use mythtv::recordings;

# Load the following extra parameters from the commandline

sub new {
my $class = shift;
my $self = {
'cli' => qr/\bvcd\b/i,
'name' => 'Export to VCD',
'enabled' => 1,
'errors' => [],
# Transcode-related settings
'denoise' => 1,
'deinterlace' => 1,
};
bless($self, $class);

# Initialize and check for transcode
$self->init_transcode();
# Make sure that we have an mplexer
$Prog{'mplexer'} = find_program('tcmplex', 'mplex');
push @{$self->{'errors'}}, 'You need tcmplex or mplex to export a vcd.' unless ($Prog{'mplexer'});

# Any errors? disable this function
$self->{'enabled'} = 0 if ($self->{'errors'} && @{$self->{'errors'}} > 0);
# Return
return $self;
}

sub gather_settings {
my $self = shift;
# Load the parent module's settings
$self->SUPER::gather_settings();
}

sub export {
my $self = shift;
my $episode = shift;
# Load nuv info
load_finfo($episode);
# PAL or NTSC?
my $size = ($episode->{'finfo'}{'fps'} =~ /^2(?:5|4\.9)/) ? '352x288' : '352x240';
# Build the transcode string
$self->{'transcode_xtra'} = " -y mpeg2enc,mp2enc -Z $size"
.' -F 1 -E 44100 -b 224';
# Add the temporary files that will need to be deleted
push @tmpfiles, "$self->{'path'}/$episode->{'outfile'}.m1v", "$self->{'path'}/$episode->{'outfile'}.mpa";
# Execute the parent method
$self->SUPER::export($episode);
# Multiplex the streams
my $safe_outfile = shell_escape($self->{'path'}.'/'.$episode->{'outfile'});
$command = "nice -n 19 mplex -f 1 $safe_outfile.m1v $safe_outfile.mpa -o $safe_outfile.mpg";
system($command);
}

1; #return true

# vim:ts=4:sw=4:ai:et:si:sts=4
115 changes: 115 additions & 0 deletions trunk/export/WMV.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
#!/usr/bin/perl -w
#Last Updated: 2004.09.27 (xris)
#
# export::WMV
# Maintained by Gavin Hurlbut <gjhurlbu@gmail.com>
#

package export::WMV;
use base 'export::ffmpeg';

# Load the myth and nuv utilities, and make sure we're connected to the database
use nuv_export::shared_utils;
use nuv_export::ui;
use mythtv::db;
use mythtv::recordings;

# Load the following extra parameters from the commandline
$cli_args{'a_bitrate|a=i'} = 1; # Audio bitrate
$cli_args{'v_bitrate|v=i'} = 1; # Video bitrate
$cli_args{'height|v_res|h=i'} = 1; # Height
$cli_args{'width|h_res|w=i'} = 1; # Width

sub new {
my $class = shift;
my $self = {
'cli' => qr/\bwmv\b/i,
'name' => 'Export to WMV',
'enabled' => 1,
'errors' => [],
# ffmpeg-related settings
'noise_reduction' => 1,
'deinterlace' => 1,
# WMV-specific settings
'a_bitrate' => 64,
'v_bitrate' => 256,
'width' => 320,
'height' => 240,
};
bless($self, $class);

# Initialize and check for ffmpeg
$self->init_ffmpeg();

# Any errors? disable this function
$self->{'enabled'} = 0 if ($self->{'errors'} && @{$self->{'errors'}} > 0);
# Return
return $self;
}

sub gather_settings {
my $self = shift;
# Load the parent module's settings
$self->SUPER::gather_settings();

# Audio Bitrate
if ($Args{'a_bitrate'}) {
$self->{'a_bitrate'} = $Args{'a_bitrate'};
die "Audio bitrate must be > 0\n" unless ($Args{'a_bitrate'} > 0);
}
else {
$self->{'a_bitrate'} = query_text('Audio bitrate?',
'int',
$self->{'a_bitrate'});
}
# Ask the user what video bitrate he/she wants
if ($Args{'v_bitrate'}) {
die "Video bitrate must be > 0\n" unless ($Args{'v_bitrate'} > 0);
$self->{'v_bitrate'} = $Args{'v_bitrate'};
}
elsif ($self->{'multipass'} || !$self->{'vbr'}) {
# make sure we have v_bitrate on the commandline
$self->{'v_bitrate'} = query_text('Video bitrate?',
'int',
$self->{'v_bitrate'});
}
# Ask the user what resolution he/she wants
if ($Args{'width'}) {
die "Width must be > 0\n" unless ($Args{'width'} > 0);
$self->{'width'} = $Args{'width'};
}
else {
$self->{'width'} = query_text('Width?',
'int',
$self->{'width'});
}
if ($Args{'height'}) {
die "Height must be > 0\n" unless ($Args{'height'} > 0);
$self->{'height'} = $Args{'height'};
}
else {
$self->{'height'} = query_text('Height?',
'int',
$self->{'height'});
}
}

sub export {
my $self = shift;
my $episode = shift;
# Load nuv info
load_finfo($episode);
# Build the ffmpeg string
$self->{'ffmpeg_xtra'} = " -b " . $self->{'v_bitrate'}
. " -vcodec msmpeg4"
. " -ab " . $self->{'a_bitrate'}
. " -acodec mp3"
. " -s " . $self->{'width'} . "x" . $self->{'height'}
. " -f asf";
# Execute the parent method
$self->SUPER::export($episode, ".wmv");
}

1; #return true

# vim:ts=4:sw=4:ai:et:si:sts=4
193 changes: 193 additions & 0 deletions trunk/export/XviD.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
#!/usr/bin/perl -w
#Last Updated: 2004.09.26 (xris)
#
# export::XviD
# Maintained by Chris Petersen <mythtv@forevermore.net>
#

package export::XviD;
use base 'export::transcode';

# Load the myth and nuv utilities, and make sure we're connected to the database
use nuv_export::shared_utils;
use nuv_export::ui;
use mythtv::db;
use mythtv::recordings;

# Load the following extra parameters from the commandline
$cli_args{'quantisation|q=i'} = 1; # Quantisation
$cli_args{'a_bitrate|a=i'} = 1; # Audio bitrate
$cli_args{'v_bitrate|v=i'} = 1; # Video bitrate
$cli_args{'height|v_res|h=i'} = 1; # Height
$cli_args{'width|h_res|w=i'} = 1; # Width
$cli_args{'multipass'} = 1; # Two-pass encoding

sub new {
my $class = shift;
my $self = {
'cli' => qr/\bxvid\b/i,
'name' => 'Export to XviD',
'enabled' => 1,
'errors' => [],
# Transcode-related settings
'denoise' => 1,
'deinterlace' => 1,
# VBR-specific settings
'vbr' => 1, # This enables vbr, and the multipass/quantisation options
'multipass' => 0, # You get multipass or quantisation, multipass will override
'quantisation' => 6, # 4 through 6 is probably right...
# Other video options
'a_bitrate' => 128,
'v_bitrate' => 960, # Remember, quantisation overrides video bitrate
'width' => 624,
};
bless($self, $class);

# Initialize and check for transcode
$self->init_transcode();
# Make sure that we have an mplexer
$Prog{'mplexer'} = find_program('tcmplex', 'mplex');
push @{$self->{'errors'}}, 'You need tcmplex or mplex to export an svcd.' unless ($Prog{'mplexer'});

# Any errors? disable this function
$self->{'enabled'} = 0 if ($self->{'errors'} && @{$self->{'errors'}} > 0);
# Return
return $self;
}

sub gather_settings {
my $self = shift;
# Load the parent module's settings
$self->SUPER::gather_settings();
# Audio Bitrate
if ($Args{'a_bitrate'}) {
$self->{'a_bitrate'} = $Args{'a_bitrate'};
die "Audio bitrate must be > 0\n" unless ($Args{'a_bitrate'} > 0);
}
else {
$self->{'a_bitrate'} = query_text('Audio bitrate?',
'int',
$self->{'a_bitrate'});
}
# VBR options
if ($Args{'multipass'}) {
$self->{'multipass'} = 1;
$self->{'vbr'} = 1;
}
elsif ($Args{'quantisation'}) {
die "Quantisation must be a number between 1 and 31 (lower means better quality).\n" if ($Args{'quantisation'} < 1 || $Args{'quantisation'} > 31);
$self->{'quantisation'} = $Args{'quantisation'};
$self->{'vbr'} = 1;
}
elsif (!$is_cli) {
$self->{'vbr'} = query_text('Variable bitrate video?',
'yesno',
$self->{'vbr'} ? 'Yes' : 'No');
if ($self->{'vbr'}) {
$self->{'multipass'} = query_text('Multi-pass (slower, but better quality)?',
'yesno',
$self->{'multipass'} ? 'Yes' : 'No');
if (!$self->{'multipass'}) {
while (1) {
my $quantisation = query_text('VBR quality/quantisation (1-31)?', 'float', $self->{'quantisation'});
if ($quantisation < 1) {
print "Too low; please choose a number between 1 and 31.\n";
}
elsif ($quantisation > 31) {
print "Too high; please choose a number between 1 and 31\n";
}
else {
$self->{'quantisation'} = $quantisation;
last;
}
}
}
}
}
# Ask the user what audio and video bitrates he/she wants
if ($Args{'v_bitrate'}) {
die "Video bitrate must be > 0\n" unless ($Args{'v_bitrate'} > 0);
$self->{'v_bitrate'} = $Args{'v_bitrate'};
}
elsif ($self->{'multipass'} || !$self->{'vbr'}) {
# make sure we have v_bitrate on the commandline
$self->{'v_bitrate'} = query_text('Video bitrate?',
'int',
$self->{'v_bitrate'});
}
# Ask the user what resolution he/she wants
if ($Args{'width'}) {
die "Width must be > 0\n" unless ($Args{'width'} > 0);
$self->{'width'} = $Args{'width'};
}
else {
$self->{'width'} = query_text('Width?',
'int',
$self->{'width'});
}
# Height will default to whatever is the appropriate aspect ratio for the width
# someday, we should check the aspect ratio here, too...
$self->{'height'} = sprintf('%.0f', $self->{'width'} * 3/4);
# Ask about the height
if ($Args{'height'}) {
die "Height must be > 0\n" unless ($Args{'height'} > 0);
$self->{'height'} = $Args{'height'};
}
else {
$self->{'height'} = query_text('Height?',
'int',
$self->{'height'});
}
}

sub export {
my $self = shift;
my $episode = shift;
# Make sure we have finfo
load_finfo($episode);
# Build the transcode string
my $params = " -Z $self->{'width'}x$self->{'height'}"
." -N 0x55" # make *sure* we're exporting mp3 audio
." -b $self->{'a_bitrate'},0,2,0"
;
# unless ($episode->{'finfo'}{'fps'} =~ /^2(?:5|4\.9)/) {
# $params .= " -J modfps=buffers=7 --export_fps 23.976";
# }
# Dual pass?
if ($self->{'multipass'}) {
# Add the temporary file to the list
push @tmpfiles, "/tmp/xvid.$$.log";
# Back up the path and use /dev/null for the first pass
my $path_bak = $self->{'path'};
$self->{'path'} = '/dev/null';
# First pass
print "First pass...\n";
$self->{'transcode_xtra'} = " -y xvid,null $params"
." -R 1,/tmp/xvid.$$.log"
." -w $self->{'v_bitrate'} ";
$self->SUPER::export($episode);
# Restore the path
$self->{'path'} = $path_bak;
# Second pass
print "Final pass...\n";
$self->{'transcode_xtra'} = " -y xvid $params"
." -R 2,/tmp/xvid.$$.log"
." -w $self->{'v_bitrate'} ";
$self->SUPER::export($episode, '.avi');
}
# Single pass
else {
$self->{'transcode_xtra'} = " -y xvid4 $params";
if ($self->{'quantisation'}) {
$self->{'transcode_xtra'} .= " -R 3 -w ".$self->{'quantisation'};
}
else {
$self->{'transcode_xtra'} .= " -w $self->{'v_bitrate'} ";
}
$self->SUPER::export($episode, '.avi');
}
}

1; #return true

# vim:ts=4:sw=4:ai:et:si:sts=4
214 changes: 214 additions & 0 deletions trunk/export/ffmpeg.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
#!/usr/bin/perl -w
#Last Updated: 2004.09.27 (xris)
#
# ffmpeg.pm
#
# routines for setting up ffmpeg
# Maintained by Gavin Hurlbut <gjhurlbu@gmail.com>
#

package export::ffmpeg;
use base 'export::generic';

use export::generic;

use Time::HiRes qw(usleep);
use POSIX;

use nuv_export::shared_utils;
use nuv_export::ui;
use mythtv::recordings;

# Load the following extra parameters from the commandline
$cli_args{'cutlist|use_cutlist'} = 1; # Use the myth cutlist
$cli_args{'deinterlace:s'} = 1; # Deinterlace video
$cli_args{'denoise|noise_reduction:s'} = 1; # Enable noise reduction

# This superclass defines several object variables:
#
# use_cutlist
# noise_reduction
# deinterlace
# crop
#

# Check for ffmpeg
sub init_ffmpeg {
my $self = shift;
my $audioonly = (shift or 0);
# Make sure we have ffmpeg
$Prog{'ffmpeg'} = find_program('ffmpeg');
push @{$self->{'errors'}}, 'You need ffmpeg to use this exporter.' unless ($Prog{'ffmpeg'});
$self->{'audioonly'} = $audioonly;
}

# Gather data for ffmpeg
sub gather_settings {
my $self = shift;
my $skip = shift;
# Gather generic settings
$self->SUPER::gather_settings($skip ? $skip - 1 : 0);
return if ($skip);
# Defaults?
$Args{'noise_reduction'} = '' if (defined $Args{'noise_reduction'} && $Args{'noise_reduction'} eq '');
$Args{'deinterlace'} = '' if (defined $Args{'deinterlace'} && $Args{'deinterlace'} eq '');
# Noise reduction?
$self->{'noise_reduction'} = query_text('Enable noise reduction (slower, but better results)?',
'yesno',
$self->{'noise_reduction'} ? 'Yes' : 'No');
# Deinterlace video?
$self->{'deinterlace'} = query_text('Enable deinterlacing?',
'yesno',
$self->{'deinterlace'} ? 'Yes' : 'No');
# Crop video to get rid of broadcast padding
# $self->{'crop'} = query_text('Crop ',
# 'yesno',
# $self->{'crop'} ? 'Yes' : 'No');
}

sub export {
my $self = shift;
my $episode = shift;
my $suffix = (shift or '');
# Init the commands
my $ffmpeg = '';
my $mythtranscode = '';

# Load nuv info
load_finfo($episode);

# Set up the fifo dirs?
if (-e "/tmp/fifodir_$$/vidout" || -e "/tmp/fifodir_$$/audout") {
die "Possibly stale mythtranscode fifo's in /tmp/fifodir_$$/.\nPlease remove them before running nuvexport.\n\n";
}

# Here, we have to fork off a copy of mythtranscode (no need to use --fifosync with transcode -- it seems to do this on its own)
$mythtranscode = "nice -n 19 mythtranscode --showprogress -p autodetect -c $episode->{channel} -s $episode->{start_time_sep} -f \"/tmp/fifodir_$$/\"";
$mythtranscode .= ' --honorcutlist' if ($self->{use_cutlist});

my $videofifo = "/tmp/fifodir_$$/vidout";
my $videotype = 'rawvideo';

# # Crop?
# if (1 || $self->{'crop'}) {
# my $cropw = sprintf('%.0f', .02 * $episode->{'finfo'}{'width'});
# my $croph = sprintf('%.0f', .02 * $episode->{'finfo'}{'height'});
# }

if ($self->{'audioonly'}) {
$ffmpeg .= "cat /tmp/fifodir_$$/vidout > /dev/null | ";
}
else {
# Do noise reduction
if ($self->{'noise_reduction'}) {
$ffmpeg .= "nice -n 19 ffmpeg -f rawvideo ";
$ffmpeg .= "-s " . $episode->{'finfo'}{'width'} . "x" . $episode->{'finfo'}{'height'};
$ffmpeg .= " -r " . $episode->{'finfo'}{'fps'};
$ffmpeg .= " -i /tmp/fifodir_$$/vidout -f yuv4mpegpipe -";
$ffmpeg .= " 2> /dev/null | ";
$ffmpeg .= "nice -n 19 yuvdenoise -F -r 16";
# $ffmpeg .= " -b $cropw,$croph,-$cropw,-$croph" if( $cropw || $croph );
$ffmpeg .= " 2> /dev/null | ";
$videofifo = "-";
$videotype = "yuv4mpegpipe";
}
}

# Start the ffmpeg command
$ffmpeg .= "nice -n 19 ffmpeg -y -f s16le";
$ffmpeg .= " -ar " . $episode->{'finfo'}{'audio_sample_rate'};
$ffmpeg .= " -ac " . $episode->{'finfo'}{'audio_channels'};
$ffmpeg .= " -i /tmp/fifodir_$$/audout";
if (!$self->{'audioonly'}) {
$ffmpeg .= " -f $videotype";
$ffmpeg .= " -s " . $episode->{'finfo'}{'width'} . "x" . $episode->{'finfo'}{'height'};
$ffmpeg .= " -r " . $episode->{'finfo'}{'fps'};
$ffmpeg .= " -i $videofifo";

# Filters
if ($self->{'deinterlace'}) {
$ffmpeg .= " -deinterlace";
}
}

# Add any additional settings from the child module
$ffmpeg .= ' '.$self->{'ffmpeg_xtra'};

# Output directory
if (!$self->{'path'} || $self->{'path'} =~ /^\/dev\/null\b/) {
$ffmpeg .= ' /dev/null';
}
else {
$ffmpeg .= " " . shell_escape($self->{'path'}.'/'.$episode->{'outfile'}.$suffix);
}
# ffmpeg pids
my ($mythtrans_pid, $ffmpeg_pid, $mythtrans_h, $ffmpeg_h);

# Create a directory for mythtranscode's fifo's
mkdir("/tmp/fifodir_$$/", 0755) or die "Can't create /tmp/fifodir_$$/: $!\n\n";
($mythtrans_pid, $mythtrans_h) = fork_command("$mythtranscode 2>&1 > /dev/null");
$children{$mythtrans_pid} = 'mythtranscode' if ($mythtrans_pid);
fifos_wait("/tmp/fifodir_$$/");
push @tmpfiles, "/tmp/fifodir_$$", "/tmp/fifodir_$$/audout", "/tmp/fifodir_$$/vidout";

# Execute ffmpeg
print "Starting ffmpeg.\n";
($ffmpeg_pid, $ffmpeg_h) = fork_command("$ffmpeg 2>&1");
$children{$ffmpeg_pid} = 'ffmpeg' if ($ffmpeg_pid);

# Get ready to count the frames that have been processed
my ($frames, $fps, $start);
$frames = 0;
$fps = 0.0;
$start = time();
my $total_frames = $episode->{'lastgop'} ? ($episode->{'lastgop'} * (($episode->{'finfo'}{'fps'} =~ /^2(?:5|4\.9)/) ? 12 : 15)) : 0;
# Wait for child processes to finish
while ((keys %children) > 0) {
my $l;
my $pct;
# Show progress
if ($frames && $total_frames) {
$pct = sprintf('%.2f', 100 * $frames / $total_frames);
$fps = ($frames * 1.0) / (time() - $start);
}
else {
$pct = "0.00";
}
printf "\rprocessed: $frames of $total_frames frames ($pct\%%), %6.2f fps ", $fps;

# Read from the ffmpeg handle
while (has_data($ffmpeg_h) and $l = <$ffmpeg_h>) {
if ($l =~ /frame=\s*(\d+)/) {
$frames = int($1);
}
}
# Read from the mythtranscode handle?
while (has_data($mythtrans_h) and $l = <$mythtrans_h>) {
if ($l =~ /Processed:\s*(\d+)\s*of\s*(\d+)\s*frames\s*\((\d+)\s*seconds\)/) {
if ($self->{'audioonly'}) {
$frames = int($1);
}
$total_frames = $2;
}
}
# The pid?
$pid = waitpid(-1, &WNOHANG);
if ($children{$pid}) {
print "\n$children{$pid} finished.\n";
delete $children{$pid};
##### do something here to track the time for the next process to die.
##### If we wait too long, something obviously ended too early.
}
# Sleep for 1/10 second so we don't go too fast and annoy the cpu
usleep(100000);
}
# Remove the fifodir?
unlink "/tmp/fifodir_$$/audout", "/tmp/fifodir_$$/vidout";
rmdir "/tmp/fifodir_$$";
}


# Return true
1;

# vim:ts=4:sw=4:ai:et:si:sts=4
137 changes: 137 additions & 0 deletions trunk/export/generic.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
#!/usr/bin/perl -w
#Last Updated: 2004.09.26 (xris)
#
# generic.pm
#
# generic routines for exporters
#

package export::generic;

use Time::HiRes qw(usleep);
use POSIX;

use nuv_export::shared_utils;
use nuv_export::ui;

BEGIN {
use Exporter;
our @ISA = qw/ Exporter /;

our @EXPORT = qw/ &fork_command &has_data &fifos_wait
/;
}

# Load the following extra parameters from the commandline
$cli_args{'path:s'} = 1; # Save path (only used with the noserver option)
$cli_args{'cutlist|use_cutlist'} = 1; # Use the myth cutlist

# Gather generic export settings
sub gather_settings {
my $self = shift;
# Load the save path, if requested
$self->{'path'} = query_savepath();
# Ask the user if he/she wants to use the cutlist
$self->{'use_cutlist'} = query_text('Enable Myth cutlist?',
'yesno',
'Yes');
}

# This subroutine forks and executes one system command - nothing fancy
sub fork_command {
my $command = shift;
if ($DEBUG) {
$command =~ s#\ 2>/dev/null##sg;
print "\nforking:\n$command\n";
return undef;
}

# Get read/write handles so we can communicate with the forked process
my ($read, $write);
pipe $read, $write;

# Fork and return the child's pid
my $pid = undef;
if ($pid = fork) {
close $write;
# Return both the read handle and the pid?
if (wantarray) {
return ($pid, $read)
}
# Just the pid -- close the read handle
else {
close $read;
return $pid;
}
}
# $pid defined means that this is now the forked child
elsif (defined $pid) {
$is_child = 1;
close $read;
# Autoflush $write
select((select($write), $|=1)[0]);
# Run the requested command
my ($data, $buffer) = ('', '');
open(COM, "$command |") or die "couldn't run command: $!\n$command\n";
while (read(COM, $data, 10)) {
next unless (length $data > 0);
# Convert CR's to linefeeds so the data will flush properly
$data =~ tr/\r/\n/s;
# Some magic so that we only send whole lines (which helps us do
# nonblocking reads on the other end)
substr($data, 0, 0) = $buffer;
$buffer = '';
if ($data !~ /\n$/) {
($data, $buffer) = $data =~ /(.+\n)?([^\n]+)$/s;
}
# We have a line to print?
if ($data && length $data > 0) {
print $write $data;
}
# Sleep for 1/100 second so we don't go too fast and annoy the cpu,
# but still read fast enough that transcode won't slow down, either.
usleep(5000);
}
close COM;
# Print the return status of the child
my $status = $? >> 8;
print $write "!!! process $$ complete: $status !!!\n";
# Close the write handle
close $write;
# Exit using something that won't set off the END block
POSIX::_exit($status);
}
# Couldn't fork, guess we have to quit
die "Couldn't fork: $!\n\n$command\n\n";
}

sub has_data {
my $fh = shift;
my $r = '';
vec($r, fileno($fh), 1) = 1;
my $can = select($r, undef, undef, 0);
if ($can) {
return vec($r, fileno($fh), 1);
}
return 0;
}

sub fifos_wait {
# Sleep a bit to let mythtranscode start up
my $fifodir = shift;
my $overload = 0;
if (!$DEBUG) {
while (++$overload < 30 && !(-e "$fifodir/audout" && -e "$fifodir/vidout" )) {
sleep 1;
print "Waiting for mythtranscode to set up the fifos.\n";
}
unless (-e "$fifodir/audout" && -e "$fifodir/vidout") {
die "Waited too long for mythtranscode to create its fifos. Please try again.\n\n";
}
}
}

# Return true
1;

# vim:ts=4:sw=4:ai:et:si:sts=4
222 changes: 222 additions & 0 deletions trunk/export/transcode.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
#!/usr/bin/perl -w
#Last Updated: 2004.09.25 (xris)
#
# transcode.pm
#
# routines for setting up transcode
#

package export::transcode;
use base 'export::generic';

use export::generic;

use Time::HiRes qw(usleep);
use POSIX;

use nuv_export::shared_utils;
use nuv_export::ui;
use mythtv::recordings;

# Load the following extra parameters from the commandline
$cli_args{'deinterlace:s'} = 1; # Deinterlace video
$cli_args{'denoise|noise_reduction:s'} = 1; # Enable noise reduction
$cli_args{'deinterlace:s'} = 1; # Transcode-related settings
$cli_args{'zoom_filter:s'} = 1; # Which zoom filter to use

# This superclass defines several object variables:
#
# use_cutlist
# denoise
# deinterlace
# crop
#

# Check for transcode
sub init_transcode {
my $self = shift;
# Make sure we have transcode
$Prog{'transcode'} = find_program('transcode');
push @{$self->{'errors'}}, 'You need transcode to use this exporter.' unless ($Prog{'transcode'});
}

# Gather data for transcode
sub gather_settings {
my $self = shift;
my $skip = shift;
# Gather generic settings
$self->SUPER::gather_settings($skip ? $skip - 1 : 0);
return if ($skip);
# Zoom Filter
if (defined $Args{'zoom_filter'}) {
if (!$Args{'zoom_filter'}) {
$self->{'zoom_filter'} = 'B_spline';
}
elsif ($Args{'zoom_filter'} =~ /^(?:Lanczos3|Bell|Box|Mitchell|Hermite|B_spline|Triangle)$/) {
$self->{'zoom_filter'} = $Args{'zoom_filter'};
}
else {
die "Unknown zoom_filter: $Args{'zoom_filter'}\n";
}
}
# Defaults?
$Args{'denoise'} = '' if (defined $Args{'denoise'} && $Args{'denoise'} eq '');
$Args{'deinterlace'} = 'smartyuv' if (defined $Args{'deinterlace'} && $Args{'deinterlace'} eq '');
# Noise reduction?
$self->{'denoise'} = query_text('Enable noise reduction (slower, but better results)?',
'yesno',
$self->{'denoise'} ? 'Yes' : 'No');
# Deinterlace video?
$self->{'deinterlace'} = query_text('Enable deinterlacing?',
'yesno',
$self->{'deinterlace'} ? 'Yes' : 'No');
# Crop video to get rid of broadcast padding
# $self->{'crop'} = query_text('Crop ',
# 'yesno',
# $self->{'crop'} ? 'Yes' : 'No');
}

sub export {
my $self = shift;
my $episode = shift;
my $suffix = (shift or '');
# Init the commands
my $transcode = '';
my $mythtranscode = '';
# Load nuv info
load_finfo($episode);

# Start the transcode command
$transcode = 'nice -n 19 transcode'
.' -V' # use YV12/I420 instead of RGB, for faster processing
;
# Not an mpeg
unless ($episode->{'finfo'}{'is_mpeg'}) {
# swap red/blue -- used with svcd, need to see if it's needed everywhere
$transcode .= ' -k';
# Set up the fifo dirs?
if (-e "/tmp/fifodir_$$/vidout" || -e "/tmp/fifodir_$$/audout") {
die "Possibly stale mythtranscode fifo's in /tmp/fifodir_$$/.\nPlease remove them before running nuvexport.\n\n";
}
# Here, we have to fork off a copy of mythtranscode (no need to use --fifosync with transcode -- it seems to do this on its own)
$mythtranscode = "nice -n 19 mythtranscode --showprogress -p autodetect -c $episode->{channel} -s $episode->{start_time_sep} -f \"/tmp/fifodir_$$/\"";
$mythtranscode .= ' --honorcutlist' if ($self->{'use_cutlist'});
}
# Figure out the input files
if ($episode->{'finfo'}{'is_mpeg'}) {
$transcode .= " -i $episode->{'filename'} -x mpeg2";
}
else {
$transcode .= " -i /tmp/fifodir_$$/vidout -p /tmp/fifodir_$$/audout"
.' -H 0 -x raw'
.' -g '.join('x', $episode->{'finfo'}{'width'}, $episode->{'finfo'}{'height'})
.' -f '.$episode->{'finfo'}{'fps'}.',4'
.' -n 0x1'
.' -e '.join(',', $episode->{'finfo'}{'audio_sample_rate'}, $episode->{'finfo'}{'audio_bits_per_sample'}, $episode->{'finfo'}{'audio_channels'})
;
}
# Crop?
if (1 || $self->{'crop'}) {
my $w = sprintf('%.0f', .02 * $episode->{'finfo'}{'width'});
my $h = sprintf('%.0f', .02 * $episode->{'finfo'}{'height'});
$transcode .= " -j $h,$w,$h,$w" if ($h || $w);
}
# Use the cutlist? (only for mpeg files -- nuv files are handled by mythtranscode)
if ($episode->{'finfo'}{'is_mpeg'} && $self->{'use_cutlist'} && $episode->{'cutlist'} && $episode->{'cutlist'} =~ /\d/) {
my @skiplist;
foreach my $cut (split("\n", $episode->{'cutlist'})) {
push @skiplist, (split(" - ", $cut))[0]."-".(split(" - ", $cut))[1];
}
$transcode .= " -J skip=\"".join(" ", @skiplist)."\"";
}
# Filters
if ($self->{'zoom_filter'}) {
$transcode .= ' --zoom_filter '.$self->{'zoom_filter'};
}
if ($self->{'deinterlace'}) {
$transcode .= " -J smartyuv";
#smartyuv|smartdeinter|dilyuvmmx
}
if ($self->{'denoise'}) {
$transcode .= " -J yuvdenoise=mode=2";
}
# Add any additional settings from the child module
$transcode .= ' '.$self->{'transcode_xtra'};
# Output directory
if (!$self->{'path'} || $self->{'path'} =~ /^\/dev\/null\b/) {
$transcode .= ' -o /dev/null';
}
else {
$transcode .= ' -o '.shell_escape($self->{'path'}.'/'.$episode->{'outfile'}.$suffix);
}
# Transcode pids
my ($mythtrans_pid, $trans_pid, $mythtrans_h, $trans_h);
# Set up and run mythtranscode?
if ($mythtranscode) {
# Create a directory for mythtranscode's fifo's
mkdir("/tmp/fifodir_$$/", 0755) or die "Can't create /tmp/fifodir_$$/: $!\n\n";
($mythtrans_pid, $mythtrans_h) = fork_command("$mythtranscode 2>&1 > /dev/null");
$children{$mythtrans_pid} = 'mythtranscode' if ($mythtrans_pid);
fifos_wait("/tmp/fifodir_$$/");
push @tmpfiles, "/tmp/fifodir_$$", "/tmp/fifodir_$$/audout", "/tmp/fifodir_$$/vidout";
}
# Execute transcode
print "Starting transcode.\n";
($trans_pid, $trans_h) = fork_command("$transcode 2>&1");
$children{$trans_pid} = 'transcode' if ($trans_pid);
# Get ready to count the frames that have been processed
my ($frames, $fps);
$frames = 0;
$fps = 0.0;
my $total_frames = $episode->{'lastgop'} ? ($episode->{'lastgop'} * (($episode->{'finfo'}{'fps'} =~ /^2(?:5|4\.9)/) ? 12 : 15)) : 0;
# Wait for child processes to finish
while ((keys %children) > 0) {
my $l;
my $pct;
# Show progress
if ($frames && $total_frames) {
$pct = sprintf('%.2f', 100 * $frames / $total_frames);
}
else {
$pct = "0.00";
}
print "\rprocessed: $frames of $total_frames frames ($pct\%), $fps fps ";
# Read from the transcode handle
while (has_data($trans_h) and $l = <$trans_h>) {
if ($l =~ /encoding\s+frames\s+\[(\d+)-(\d+)\],\s*([\d\.]+)\s*fps,\s+EMT:\s*([\d:]+),/) {
$frames = int($2);
$fps = $3;
}
}
# Read from the mythtranscode handle?
if ($mythtranscode && $mythtrans_pid) {
while (has_data($mythtrans_h) and $l = <$mythtrans_h>) {
if ($l =~ /Processed:\s*(\d+)\s*of\s*(\d+)\s*frames\s*\((\d+)\s*seconds\)/) {
#$frames = int($1);
$total_frames = $2;
}
}
}
# The pid?
$pid = waitpid(-1, &WNOHANG);
if ($children{$pid}) {
print "\n$children{$pid} finished.\n";
delete $children{$pid};
##### do something here to track the time for the next process to die.
##### If we wait too long, something obviously ended too early.
}
# Sleep for 1/10 second so we don't go too fast and annoy the cpu
usleep(100000);
}
# Remove the fifodir? (in case we're doing multipass, so we don't generate errors on the next time through)
if ($mythtranscode) {
unlink "/tmp/fifodir_$$/audout", "/tmp/fifodir_$$/vidout";
rmdir "/tmp/fifodir_$$";
}
}


# Return true
1;

# vim:ts=4:sw=4:ai:et:si:sts=4
179 changes: 0 additions & 179 deletions trunk/export_DVCD.pm

This file was deleted.

310 changes: 0 additions & 310 deletions trunk/export_DVD.pm

This file was deleted.

128 changes: 0 additions & 128 deletions trunk/export_DivX.pm

This file was deleted.

136 changes: 0 additions & 136 deletions trunk/export_MP3.pm

This file was deleted.

124 changes: 0 additions & 124 deletions trunk/export_MPEG2_cut.pm

This file was deleted.

137 changes: 0 additions & 137 deletions trunk/export_NUV_SQL.pm

This file was deleted.

Loading