481 changes: 481 additions & 0 deletions nuvexport/export/ffmpeg.pm

Large diffs are not rendered by default.

177 changes: 177 additions & 0 deletions nuvexport/export/ffmpeg/ASF.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
#
# $Date$
# $Revision$
# $Author$
#
# export::ffmpeg::ASF
# Maintained by Gavin Hurlbut <gjhurlbu@gmail.com>
#

package export::ffmpeg::ASF;
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::cli;
use nuv_export::ui;
use mythtv::recordings;

# Load the following extra parameters from the commandline
add_arg('quantisation|q=i', 'Quantisation');
add_arg('a_bitrate|a=i', 'Audio bitrate');
add_arg('v_bitrate|v=i', 'Video bitrate');
add_arg('multipass!', 'Enable two-pass encoding.');

sub new {
my $class = shift;
my $self = {
'cli' => qr/\basf\b/i,
'name' => 'Export to ASF',
'enabled' => 1,
'errors' => [],
'defaults' => {},
};
bless($self, $class);

# Initialize the default parameters
$self->load_defaults();

# Verify any commandline or config file options
die "Audio bitrate must be > 0\n" unless (!defined $self->val('a_bitrate') || $self->{'a_bitrate'} > 0);
die "Video bitrate must be > 0\n" unless (!defined $self->val('v_bitrate') || $self->{'v_bitrate'} > 0);
die "Width must be > 0\n" unless (!defined $self->val('width') || $self->{'width'} =~ /^\s*\D/ || $self->{'width'} > 0);
die "Height must be > 0\n" unless (!defined $self->val('height') || $self->{'height'} =~ /^\s*\D/ || $self->{'height'} > 0);

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

# Can we even encode asf?
if (!$self->can_encode('msmpeg4')) {
push @{$self->{'errors'}}, "Your ffmpeg installation doesn't support encoding to msmpeg4.";
}
if (!$self->can_encode('mp3') && !$self->can_encode('libmp3lame')) {
push @{$self->{'errors'}}, "Your ffmpeg installation doesn't support encoding to mp3 audio.";
}
# Any errors? disable this function
$self->{'enabled'} = 0 if ($self->{'errors'} && @{$self->{'errors'}} > 0);
# Return
return $self;
}

# Load default settings
sub load_defaults {
my $self = shift;
# Load the parent module's settings
$self->SUPER::load_defaults();
# Default bitrates and resolution
$self->{'defaults'}{'a_bitrate'} = 64;
$self->{'defaults'}{'v_bitrate'} = 256;
$self->{'defaults'}{'width'} = 320;
}

# Gather settings from the user
sub gather_settings {
my $self = shift;
# Load the parent module's settings
$self->SUPER::gather_settings();
# Audio Bitrate
$self->{'a_bitrate'} = query_text('Audio bitrate?',
'int',
$self->val('a_bitrate'));
# VBR options
if (!$is_cli) {
$self->{'vbr'} = query_text('Variable bitrate video?',
'yesno',
$self->val('vbr'));
if ($self->{'vbr'}) {
$self->{'multipass'} = query_text('Multi-pass (slower, but better quality)?',
'yesno',
$self->val('multipass'));
if (!$self->{'multipass'}) {
while (1) {
my $quantisation = query_text('VBR quality/quantisation (1-31)?', 'float', $self->val('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 ($self->{'multipass'} || !$self->{'vbr'}) {
$self->{'v_bitrate'} = query_text('Video bitrate?',
'int',
$self->val('v_bitrate'));
}
}
# Query the resolution
$self->query_resolution();
}

sub export {
my $self = shift;
my $episode = shift;
# Make sure we have the framerate
$self->{'out_fps'} = $episode->{'finfo'}{'fps'};
# Dual pass?
if ($self->{'multipass'}) {
# Add the temporary file to the list
push @tmpfiles, "/tmp/asf.$$.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->{'ffmpeg_xtra'} = ' -vcodec msmpeg4'
. $self->param('bit_rate', $self->{'v_bitrate'})
. ' -minrate 32 -maxrate '.(2*$self->{'v_bitrate'}).' -bt 32'
. ' -bufsize 65535'
# . ' -lumi_mask 0.05 -dark_mask 0.02 -scplx_mask 0.7'
. " -pass 1 -passlogfile '/tmp/asf.$$.log'"
. ' -f asf';
$self->SUPER::export($episode, '');
# Restore the path
$self->{'path'} = $path_bak;
# Second pass
print "Final pass...\n";
$self->{'ffmpeg_xtra'} = ' -vcodec msmpeg4'
. ' -b ' . $self->{'v_bitrate'}
. ' -minrate 32 -maxrate '.(2*$self->{'v_bitrate'}).' -bt 32'
. ' -bufsize 65535'
# . ' -lumi_mask 0.05 -dark_mask 0.02 -scplx_mask 0.7'
. ' -acodec '
.($self->can_encode('libmp3lame') ? 'libmp3lame' : 'mp3')
.' '.$self->param('ab', $self->{'a_bitrate'})
. " -pass 2 -passlogfile '/tmp/asf.$$.log'"
. ' -f asf';
}
# Single Pass
else {
$self->{'ffmpeg_xtra'} = ' -vcodec msmpeg4'
. ' -b ' . $self->{'v_bitrate'}
. (($self->{'vbr'})
? " -qmin $self->{'quantisation'}"
. ' -qmax 31 -minrate 32'
. ' -maxrate '.(2*$self->{'v_bitrate'})
. ' -bt 32 -bufsize 65535'
: '')
# . ' -lumi_mask 0.05 -dark_mask 0.02'
# . ' -scplx_mask 0.7'
. ' -acodec '
.($self->can_encode('libmp3lame') ? 'libmp3lame' : 'mp3')
.' '.$self->param('ab', $self->{'a_bitrate'})
. ' -f asf';
}
# Execute the (final pass) encode
$self->SUPER::export($episode, '.asf');
}

1; #return true

# vim:ts=4:sw=4:ai:et:si:sts=4
89 changes: 89 additions & 0 deletions nuvexport/export/ffmpeg/DVCD.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#
# $Date$
# $Revision$
# $Author$
#
# export::ffmpeg::DVCD
# Maintained by Gavin Hurlbut <gjhurlbu@gmail.com>
#

package export::ffmpeg::DVCD;
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::cli;
use nuv_export::ui;
use mythtv::recordings;

# Load the following extra parameters from the commandline

sub new {
my $class = shift;
my $self = {
'cli' => qr/\bdvcd\b/i,
'name' => 'Export to DVCD (VCD with 48kHz audio for making DVDs)',
'enabled' => 1,
'errors' => [],
'defaults' => {},
};
bless($self, $class);

# Initialize the default parameters
$self->load_defaults();

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

# Can we even encode vcd?
if (!$self->can_encode('mpeg1video')) {
push @{$self->{'errors'}}, "Your ffmpeg installation doesn't support encoding to mpeg1video.";
}
if (!$self->can_encode('mp2')) {
push @{$self->{'errors'}}, "Your ffmpeg installation doesn't support encoding to mp2 audio.";
}
# Any errors? disable this function
$self->{'enabled'} = 0 if ($self->{'errors'} && @{$self->{'errors'}} > 0);
# Return
return $self;
}

# Load default settings
sub load_defaults {
my $self = shift;
# Load the parent module's settings
$self->SUPER::load_defaults();
# Not really anything to add
}

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

sub export {
my $self = shift;
my $episode = shift;
# Force to 4:3 aspect ratio
$self->{'out_aspect'} = 1.3333;
$self->{'aspect_stretched'} = 1;
# PAL or NTSC?
my $standard = ($episode->{'finfo'}{'fps'} =~ /^2(?:5|4\.9)/) ? 'PAL' : 'NTSC';
$self->{'width'} = 352;
$self->{'height'} = ($standard eq 'PAL') ? '288' : '240';
$self->{'out_fps'} = ($standard eq 'PAL') ? 25 : 29.97;
# Build the transcode string
$self->{'ffmpeg_xtra'} = $self->param('bit_rate', 1150)
." -vcodec mpeg1video"
.$self->param('ab', 224)
." -ar 48000 -acodec mp2"
." -f vcd";
# Execute the parent method
$self->SUPER::export($episode, ".mpg");
}

1; #return true

# vim:ts=4:sw=4:ai:et:si:sts=4
151 changes: 151 additions & 0 deletions nuvexport/export/ffmpeg/DVD.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
#
# $Date$
# $Revision$
# $Author$
#
# export::ffmpeg::DVD
# Maintained by Gavin Hurlbut <gjhurlbu@gmail.com>
#

package export::ffmpeg::DVD;
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::cli;
use nuv_export::ui;
use mythtv::recordings;

# Load the following extra parameters from the commandline
add_arg('quantisation|q=i', 'Quantisation');
add_arg('a_bitrate|a=i', 'Audio bitrate');
add_arg('v_bitrate|v=i', 'Video bitrate');

sub new {
my $class = shift;
my $self = {
'cli' => qr/dvd/i,
'name' => 'Export to DVD',
'enabled' => 1,
'errors' => [],
'defaults' => {},
};
bless($self, $class);

# Initialize the default parameters
$self->load_defaults();

# Verify any commandline or config file options
die "Audio bitrate must be > 0\n" unless (!defined $self->val('a_bitrate') || $self->{'a_bitrate'} > 0);
die "Video bitrate must be > 0\n" unless (!defined $self->val('v_bitrate') || $self->{'v_bitrate'} > 0);

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

# Can we even encode dvd?
if (!$self->can_encode('mpeg2video')) {
push @{$self->{'errors'}}, "Your ffmpeg installation doesn't support encoding to mpeg2video.";
}
if (!$self->can_encode('mp2')) {
push @{$self->{'errors'}}, "Your ffmpeg installation doesn't support encoding to mp2 audio.";
}
# Any errors? disable this function
$self->{'enabled'} = 0 if ($self->{'errors'} && @{$self->{'errors'}} > 0);
# Return
return $self;
}

# Load default settings
sub load_defaults {
my $self = shift;
# Load the parent module's settings
$self->SUPER::load_defaults();
# Add the dvd preferred bitrates
$self->{'defaults'}{'quantisation'} = 5;
$self->{'defaults'}{'a_bitrate'} = 384;
$self->{'defaults'}{'v_bitrate'} = 6000;
}

# Gather settings from the user
sub gather_settings {
my $self = shift;
# Load the parent module's settings
$self->SUPER::gather_settings();
# Audio Bitrate
while (1) {
my $a_bitrate = query_text('Audio bitrate?',
'int',
$self->val('a_bitrate'));
if ($a_bitrate < 64) {
print "Too low; please choose a bitrate between 64 and 384.\n";
}
elsif ($a_bitrate > 384) {
print "Too high; please choose a bitrate between 64 and 384.\n";
}
else {
$self->{'a_bitrate'} = $a_bitrate;
last;
}
}
# Ask the user what video bitrate he/she wants, or calculate the max bitrate
my $max_v_bitrate = 9500 - $self->{'a_bitrate'};
if ($self->val('v_bitrate') > $max_v_bitrate) {
$self->{'v_bitrate'} = $max_v_bitrate;
}
while (1) {
my $v_bitrate = query_text('Maximum video bitrate for VBR?',
'int',
$self->val('v_bitrate'));
if ($v_bitrate < 1000) {
print "Too low; please choose a bitrate between 1000 and $max_v_bitrate.\n";
}
elsif ($v_bitrate > $max_v_bitrate) {
print "Too high; please choose a bitrate between 1000 and $max_v_bitrate.\n";
}
else {
$self->{'v_bitrate'} = $v_bitrate;
last;
}
}
# Ask the user what vbr quality (quantisation) he/she wants - 1..31
while (1) {
my $quantisation = query_text('VBR quality/quantisation (1-31)?',
'float',
$self->val('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;
}
}
}

sub export {
my $self = shift;
my $episode = shift;
# Force to 4:3 aspect ratio
$self->{'out_aspect'} = 1.3333;
$self->{'aspect_stretched'} = 1;
# PAL or NTSC?
my $standard = ($episode->{'finfo'}{'fps'} =~ /^2(?:5|4\.9)/) ? 'PAL' : 'NTSC';
$self->{'width'} = 720;
$self->{'height'} = ($standard eq 'PAL') ? '576' : '480';
$self->{'out_fps'} = ($standard eq 'PAL') ? 25 : 29.97;
# Build the ffmpeg string
$self->{'ffmpeg_xtra'} = $self->param('bit_rate', $self->{'v_bitrate'})
. ' -vcodec mpeg2video'
. ' -qmin ' . $self->{'quantisation'}
.$self->param('ab', $self->{'a_bitrate'})
. " -ar 48000 -acodec mp2 -f dvd";
# Execute the parent method
$self->SUPER::export($episode, ".mpg");
}

1; #return true

# vim:ts=4:sw=4:ai:et:si:sts=4
193 changes: 193 additions & 0 deletions nuvexport/export/ffmpeg/DivX.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
#
# $Date$
# $Revision$
# $Author$
#
# export::ffmpeg::DivX
# Maintained by Gavin Hurlbut <gjhurlbu@gmail.com>
#

package export::ffmpeg::DivX;
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::cli;
use nuv_export::ui;
use mythtv::recordings;

# Load the following extra parameters from the commandline
add_arg('quantisation|q=i', 'Quantisation');
add_arg('a_bitrate|a=i', 'Audio bitrate');
add_arg('v_bitrate|v=i', 'Video bitrate');
add_arg('multipass!', 'Enable two-pass encoding.');

sub new {
my $class = shift;
my $self = {
'cli' => qr/\bdivx\b/i,
'name' => 'Export to DivX',
'enabled' => 1,
'errors' => [],
'defaults' => {},
};
bless($self, $class);

# Initialize the default parameters
$self->load_defaults();

# Verify any commandline or config file options
die "Audio bitrate must be > 0\n" unless (!defined $self->val('a_bitrate') || $self->{'a_bitrate'} > 0);
die "Video bitrate must be > 0\n" unless (!defined $self->val('v_bitrate') || $self->{'v_bitrate'} > 0);
die "Width must be > 0\n" unless (!defined $self->val('width') || $self->{'width'} =~ /^\s*\D/ || $self->{'width'} > 0);
die "Height must be > 0\n" unless (!defined $self->val('height') || $self->{'height'} =~ /^\s*\D/ || $self->{'height'} > 0);

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

# Can we even encode divx?
if (!$self->can_encode('mpeg4')) {
push @{$self->{'errors'}}, "Your ffmpeg installation doesn't support encoding to mpeg4.";
}
if (!$self->can_encode('mp3') && !$self->can_encode('libmp3lame')) {
push @{$self->{'errors'}}, "Your ffmpeg installation doesn't support encoding to mp3 audio.";
}
# Any errors? disable this function
$self->{'enabled'} = 0 if ($self->{'errors'} && @{$self->{'errors'}} > 0);
# Return
return $self;
}

# Load default settings
sub load_defaults {
my $self = shift;
# Load the parent module's settings
$self->SUPER::load_defaults();
# Default bitrates and resolution
$self->{'defaults'}{'a_bitrate'} = 128;
$self->{'defaults'}{'v_bitrate'} = 960;
$self->{'defaults'}{'width'} = 624;
}

# Gather settings from the user
sub gather_settings {
my $self = shift;
# Load the parent module's settings
$self->SUPER::gather_settings();
# Audio Bitrate
$self->{'a_bitrate'} = query_text('Audio bitrate?',
'int',
$self->val('a_bitrate'));
# VBR options
if (!$is_cli) {
$self->{'vbr'} = query_text('Variable bitrate video?',
'yesno',
$self->val('vbr'));
if ($self->{'vbr'}) {
$self->{'multipass'} = query_text('Multi-pass (slower, but better quality)?',
'yesno',
$self->val('multipass'));
if (!$self->{'multipass'}) {
while (1) {
my $quantisation = query_text('VBR quality/quantisation (1-31)?',
'float',
$self->val('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;
}
}
}
} else {
$self->{'multipass'} = 0;
}
# Ask the user what video bitrate he/she wants
$self->{'v_bitrate'} = query_text('Video bitrate?',
'int',
$self->val('v_bitrate'));
}
# Query the resolution
$self->query_resolution();
}

sub export {
my $self = shift;
my $episode = shift;
# Make sure we have the framerate
$self->{'out_fps'} = $episode->{'finfo'}{'fps'};
# Dual pass?
if ($self->{'multipass'}) {
# Add the temporary file to the list
push @tmpfiles, "/tmp/divx.$$.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->{'ffmpeg_xtra'} = ' -vcodec mpeg4'
. $self->param('bit_rate', $self->{'v_bitrate'})
. $self->param('rc_min_rate', 32)
. $self->param('rc_max_rate', (2 * $self->{'v_bitrate'}))
. $self->param('bit_rate_tolerance', 32)
. ' -bufsize 65535'
. ' -lumi_mask 0.05 -dark_mask 0.02 -scplx_mask 0.5'
. ' -mv4'
. ' -part'
. ' -vtag divx'
. " -pass 1 -passlogfile '/tmp/divx.$$.log'"
. ' -f avi';
$self->SUPER::export($episode, '');
# Restore the path
$self->{'path'} = $path_bak;
# Second pass
print "Final pass...\n";
$self->{'ffmpeg_xtra'} = ' -vcodec mpeg4'
. $self->param('bit_rate', $self->{'v_bitrate'})
. $self->param('rc_min_rate', 32)
. $self->param('rc_max_rate', (2 * $self->{'v_bitrate'}))
. $self->param('bit_rate_tolerance', 32)
. ' -bufsize 65535'
. ' -lumi_mask 0.05 -dark_mask 0.02 -scplx_mask 0.5'
. ' -mv4'
. ' -part'
. ' -vtag divx'
. ' -acodec '
.($self->can_encode('libmp3lame') ? 'libmp3lame' : 'mp3')
.' '.$self->param('ab', $self->{'a_bitrate'})
. " -pass 2 -passlogfile '/tmp/divx.$$.log'"
. ' -f avi';
}
# Single Pass
else {
$self->{'ffmpeg_xtra'} = ' -vcodec mpeg4'
. $self->param('bit_rate', $self->{'v_bitrate'})
. ($self->{'vbr'}
? " -qmin $self->{'quantisation'}"
. ' -qmax 31'
. $self->param('rc_min_rate', 32)
. $self->param('rc_max_rate', (2 * $self->{'v_bitrate'}))
. $self->param('bit_rate_tolerance', 32)
. ' -bufsize 65535'
: '')
. ' -lumi_mask 0.05 -dark_mask 0.02 -scplx_mask 0.5'
. ' -mv4'
. ' -part'
. ' -vtag divx'
. ' -acodec '
.($self->can_encode('libmp3lame') ? 'libmp3lame' : 'mp3')
.' '.$self->param('ab', $self->{'a_bitrate'})
. ' -f avi';
}
# Execute the (final pass) encode
$self->SUPER::export($episode, '.avi');
}

1; #return true

# vim:ts=4:sw=4:ai:et:si:sts=4
99 changes: 99 additions & 0 deletions nuvexport/export/ffmpeg/MP3.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#
# $Date$
# $Revision$
# $Author$
#
# export::ffmpeg::MP3
# Maintained by Chris Petersen <mythtv@forevermore.net>
#

package export::ffmpeg::MP3;
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::cli;
use nuv_export::ui;
use mythtv::recordings;

# Load the following extra parameters from the commandline
add_arg('bitrate=i', 'Audio bitrate');

sub new {
my $class = shift;
my $self = {
'cli' => qr/\bmp3\b/i,
'name' => 'Export to MP3',
'enabled' => 1,
'errors' => [],
'defaults' => {},
};
bless($self, $class);

# Initialize the default parameters
$self->load_defaults();

# Verify any commandline or config file options
$self->{'bitrate'} = $self->val('a_bitrate') unless (defined $self->val('bitrate'));
die "Bitrate must be > 0\n" unless (!defined $self->val('bitrate') || $self->{'bitrate'} > 0);

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

# Make sure that we have an mplexer
find_program('id3tag')
or push @{$self->{'errors'}}, 'You need id3tag to export an mp3.';

# Can we even encode vcd?
if (!$self->can_encode('mp3') && !$self->can_encode('libmp3lame')) {
push @{$self->{'errors'}}, "Your ffmpeg installation doesn't support encoding to mp3 audio.";
}
# Any errors? disable this function
$self->{'enabled'} = 0 if ($self->{'errors'} && @{$self->{'errors'}} > 0);
# Return
return $self;
}

# Load default settings
sub load_defaults {
my $self = shift;
# Load the parent module's settings
$self->SUPER::load_defaults();
# MP3 bitrate
$self->{'defaults'}{'bitrate'} = 128;
}

# Gather settings from the user
sub gather_settings {
my $self = shift;
# Load the parent module's settings (skipping one parent, since we don't need the ffmpeg-specific options)
$self->SUPER::gather_settings(1);
# Audio Bitrate
$self->{'bitrate'} = query_text('Audio bitrate?',
'int',
$self->val('bitrate'));
}

sub export {
my $self = shift;
my $episode = shift;
# Build the ffmpeg string
$self->{'ffmpeg_xtra'} = $self->param('ab', $self->val('bitrate'))
.' -acodec '
.($self->can_encode('libmp3lame') ? 'libmp3lame' : 'mp3')
.' -f mp3';
# Execute ffmpeg
$self->SUPER::export($episode, '.mp3');
# Now tag it
my $safe_subtitle = shell_escape($episode->{'subtitle'});
my $safe_channel = shell_escape($episode->{'callsign'}.', '.$episode->{'channame'});
my $safe_description = shell_escape($episode->{'description'});
my $safe_title = shell_escape($episode->{'title'});
my $safe_outfile = shell_escape($self->get_outfile($episode, '.mp3'));
my $command = "id3tag -A $safe_subtitle -a $safe_channel -c $safe_description -s $safe_title $safe_outfile";
system($command);
}

1; #return true

# vim:ts=4:sw=4:ai:et:si:sts=4
304 changes: 304 additions & 0 deletions nuvexport/export/ffmpeg/MP4.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,304 @@
# vim:ts=4:sw=4:ai:et:si:sts=4
#
# ffmpeg-based MP4 (iPod) video module for nuvexport.
#
# Many thanks to cartman in #ffmpeg, and for the instructions at
# http://rob.opendot.cl/index.php?active=3&subactive=1
# http://videotranscoding.wikispaces.com/EncodeForIPodorPSP
#
# @url $URL$
# @date $Date$
# @version $Revision$
# @author $Author$
# @copyright Silicon Mechanics
#

package export::ffmpeg::MP4;
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::cli;
use nuv_export::ui;
use mythtv::recordings;

# Load the following extra parameters from the commandline
add_arg('quantisation|q=i', 'Quantisation');
add_arg('a_bitrate|a=i', 'Audio bitrate');
add_arg('v_bitrate|v=i', 'Video bitrate');
add_arg('multipass!', 'Enable two-pass encoding.');
add_arg('mp4_codec=s', 'Video codec to use for MP4/iPod video (mpeg4 or h264).');
add_arg('mp4_fps=s', 'Framerate to use: auto, 25, 23.97, 29.97.');
add_arg('ipod!', 'Produce ipod-compatible output.');

sub new {
my $class = shift;
my $self = {
'cli' => qr/\b(?:mp4|ipod)\b/i,
'name' => 'Export to MP4 (iPod)',
'enabled' => 1,
'errors' => [],
'defaults' => {},
};
bless($self, $class);

# Initialize the default parameters
$self->load_defaults();

# Verify any commandline or config file options
die "Audio bitrate must be > 0\n" unless (!defined $self->val('a_bitrate') || $self->{'a_bitrate'} > 0);
die "Video bitrate must be > 0\n" unless (!defined $self->val('v_bitrate') || $self->{'v_bitrate'} > 0);
die "Width must be > 0\n" unless (!defined $self->val('width') || $self->{'width'} =~ /^\s*\D/ || $self->{'width'} > 0);
die "Height must be > 0\n" unless (!defined $self->val('height') || $self->{'height'} =~ /^\s*\D/ || $self->{'height'} > 0);

# VBR, multipass, etc.
if ($self->val('multipass')) {
$self->{'vbr'} = 1;
}
elsif ($self->val('quantisation')) {
die "Quantisation must be a number between 1 and 31 (lower means better quality).\n" if ($self->{'quantisation'} < 1 || $self->{'quantisation'} > 31);
$self->{'vbr'} = 1;
}

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

# Can we even encode mp4?
if (!$self->can_encode('mp4')) {
push @{$self->{'errors'}}, "Your ffmpeg installation doesn't support encoding to mp4 file formats.";
}
if (!$self->can_encode('aac') && !$self->can_encode('libfaac')) {
push @{$self->{'errors'}}, "Your ffmpeg installation doesn't support encoding to aac audio.";
}
if (!$self->can_encode('mpeg4') && !$self->can_encode('h264') && !$self->can_encode('libx264')) {
push @{$self->{'errors'}}, "Your ffmpeg installation doesn't support encoding to either mpeg4 or h264 video.";
}
# Any errors? disable this function
$self->{'enabled'} = 0 if ($self->{'errors'} && @{$self->{'errors'}} > 0);
# Return
return $self;
}

# Load default settings
sub load_defaults {
my $self = shift;
# Load the parent module's settings
$self->SUPER::load_defaults();
# Default settings
$self->{'defaults'}{'v_bitrate'} = 384;
$self->{'defaults'}{'a_bitrate'} = 64;
$self->{'defaults'}{'width'} = 320;
$self->{'defaults'}{'mp4_codec'} = 'mpeg4';
# Verify commandline options
if ($self->val('mp4_codec') !~ /^(?:mpeg4|h264)$/i) {
die "mp4_codec must be either mpeg4 or h264.\n";
}
$self->{'mp4_codec'} =~ tr/A-Z/a-z/;

}

# Gather settings from the user
sub gather_settings {
my $self = shift;
# Load the parent module's settings
$self->SUPER::gather_settings();
# Audio Bitrate
$self->{'a_bitrate'} = query_text('Audio bitrate?',
'int',
$self->val('a_bitrate'));
# Video options
if (!$is_cli) {
# iPod compatibility mode?
$self->{'ipod'} = query_text('Enable iPod compatibility?',
'yesno',
$self->val('ipod'));
# Video codec
while (1) {
my $codec = query_text('Video codec (mpeg4 or h264)?',
'string',
$self->{'mp4_codec'});
if ($codec =~ /^m/) {
$self->{'mp4_codec'} = 'mpeg4';
last;
}
elsif ($codec =~ /^h/) {
$self->{'mp4_codec'} = 'h264';
last;
}
print "Please choose either mpeg4 or h264\n";
}
# Video bitrate options
$self->{'vbr'} = query_text('Variable bitrate video?',
'yesno',
$self->val('vbr'));
if ($self->{'vbr'}) {
$self->{'multipass'} = query_text('Multi-pass (slower, but better quality)?',
'yesno',
$self->val('multipass'));
if (!$self->{'multipass'}) {
while (1) {
my $quantisation = query_text('VBR quality/quantisation (1-31)?',
'float',
$self->val('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;
}
}
}
} else {
$self->{'multipass'} = 0;
}
# Ask the user what video bitrate he/she wants
$self->{'v_bitrate'} = query_text('Video bitrate?',
'int',
$self->val('v_bitrate'));
}
# Loop, in case we need to verify ipod compatibility
while (1) {
# Query the resolution
$self->query_resolution();
# Warn about ipod resolution
if ($self->val('ipod') && ($self->{'height'} > 480 || $self->{'width'} > 640)) {
my $note = "WARNING: Video larger than 640x480 will not play on an iPod.\n";
die $note if ($is_cli);
print $note;
next;
}
# Done looping
last;
}
}

sub export {
my $self = shift;
my $episode = shift;
# Make sure this is set to anamorphic mode
$self->{'aspect_stretched'} = 1;
# Framerate
my $standard = ($episode->{'finfo'}{'fps'} =~ /^2(?:5|4\.9)/) ? 'PAL' : 'NTSC';
if ($standard eq 'PAL') {
$self->{'out_fps'} = 25;
}
elsif ($self->val('mp4_fps') =~ /^23/) {
$self->{'out_fps'} = 23.97;
}
elsif ($self->val('mp4_fps') =~ /^29/) {
$self->{'out_fps'} = 29.97;
}
else {
$self->{'out_fps'} = ($self->{'width'} > 320 || $self->{'height'} > 288) ? 29.97 : 23.97;
}
# Embed the title
$safe_title = $episode->{'title'};
if ($episode->{'subtitle'} ne 'Untitled') {
$safe_title .= ' - '.$episode->{'subtitle'};
}
my $safe_title = shell_escape($safe_title);
# Codec name changes between ffmpeg versions
my $codec = $self->{'mp4_codec'};
if ($codec eq 'h264' && $self->can_encode('libx264')) {
$codec = 'libx264';
}
# Build the common ffmpeg string
my $ffmpeg_xtra =
' -vcodec '.$codec
.$self->param('bit_rate', $self->{'v_bitrate'})
;
# Options required for the codecs separately
if ($self->{'mp4_codec'} eq 'h264') {
$ffmpeg_xtra .= ' -level 30'
.' -flags loop+slice'
.' -g 250 -keyint_min 25'
.' -sc_threshold 40'
.' -rc_eq \'blurCplx^(1-qComp)\''
.$self->param('bit_rate_tolerance', $self->{'v_bitrate'})
.$self->param('rc_max_rate', 1500 - $self->{'a_bitrate'})
.$self->param('rc_buffer_size', 2000)
.$self->param('i_quant_factor', 0.71428572)
.$self->param('b_quant_factor', 0.76923078)
.$self->param('max_b_frames', 0)
.' -me_method umh'
;
}
else {
$ffmpeg_xtra .= ' -flags +mv4+loop+aic'
.' -trellis 1'
.' -mbd 1'
.' -cmp 2 -subcmp 2'
;
}
# Some shared options
if ($self->{'multipass'} || $self->{'vbr'}) {
$ffmpeg_xtra .= $self->param('qcompress', 0.6)
.$self->param('qmax', 51)
.$self->param('max_qdiff', 4)
;
}
# Dual pass?
if ($self->{'multipass'}) {
# Apparently, the -passlogfile option doesn't work for h264, so we need
# to be aware of other processes that might be working in this directory
if ($self->{'mp4_codec'} eq 'h264' && (-e 'x264_2pass.log.temp' || -e 'x264_2pass.log')) {
die "ffmpeg does not allow us to specify the name of the multi-pass log\n"
."file, and x264_2pass.log exists in this directory already. Please\n"
."wait for the other process to finish, or remove the stale file.\n";
}
# Add all possible temporary files to the list
push @tmpfiles, 'x264_2pass.log',
'x264_2pass.log.temp',
'x264_2pass.log.mbtree',
'ffmpeg2pass-0.log';
# Build the ffmpeg string
print "First pass...\n";
$self->{'ffmpeg_xtra'} = ' -pass 1'
.$ffmpeg_xtra
.' -f mp4';
if ($self->{'mp4_codec'} eq 'h264') {
$self->{'ffmpeg_xtra'} .= ' -refs 1 -subq 1'
.' -trellis 0'
;
}
$self->SUPER::export($episode, '', 1);
# Second Pass
print "Final pass...\n";
$ffmpeg_xtra = ' -pass 2 '
.$ffmpeg_xtra;
}
# Single Pass
else {
if ($self->{'vbr'}) {
$ffmpeg_xtra .= ' -qmin '.$self->{'quantisation'};
}
}
# Single/final pass options
if ($self->{'mp4_codec'} eq 'h264') {
$ffmpeg_xtra .= ' -refs '.($self->val('ipod') ? 2 : 7)
.' -subq 7'
.' -partitions parti4x4+parti8x8+partp4x4+partp8x8+partb8x8'
.' -flags2 +bpyramid+wpred+mixed_refs+8x8dct'
.' -me_range 21'
.' -trellis 2'
.' -cmp 1'
# These should match the defaults:
.' -deblockalpha 0 -deblockbeta 0'
;
}
# Audio codec name changes between ffmpeg versions
my $acodec = $self->can_encode('libfaac') ? 'libfaac' : 'aac';
# Don't forget the audio, etc.
$self->{'ffmpeg_xtra'} = $ffmpeg_xtra
." -acodec $acodec -ar 48000 -async 1"
.$self->param('ab', $self->{'a_bitrate'});
# Execute the (final pass) encode
$self->SUPER::export($episode, '.mp4');
}

1; #return true

198 changes: 198 additions & 0 deletions nuvexport/export/ffmpeg/PSP.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
#
# $Date$
# $Revision$
# $Author$
#
# export::ffmpeg::PSP
#
# obtained and slightly modified from http://mysettopbox.tv/phpBB2/viewtopic.php?t=5030&
#

package export::ffmpeg::PSP;
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::cli;
use nuv_export::ui;
use mythtv::recordings;

# Load the following extra parameters from the commandline
add_arg('psp_fps:s', 'PSP frame rate (high=29.97, low=14.985)');
add_arg('psp_bitrate:s', 'PSP Resolution (high=768, low=384)');
add_arg('psp_resolution:s', 'PSP Resolution (320x240, 368x208, 400x192)');
add_arg('psp_thumbnail!', 'Create a thumbnail to go with the PSP video export?');

sub new {
my $class = shift;
my $self = {
'cli' => qr/\bpsp\b/i,
'name' => 'Export to PSP',
'enabled' => 1,
'errors' => [],
'defaults' => {},
};
bless($self, $class);

# Initialize the default parameters
$self->load_defaults();

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

# Can we even encode psp?
if (!$self->can_encode('psp')) {
push @{$self->{'errors'}}, "Your ffmpeg installation doesn't support encoding to psp video.";
}
if (!$self->can_encode('aac') || !$self->can_encode('libfaac')) {
push @{$self->{'errors'}}, "Your ffmpeg installation doesn't support encoding to aac audio.";
}
# Any errors? disable this function
$self->{'enabled'} = 0 if ($self->{'errors'} && @{$self->{'errors'}} > 0);
# Return
return $self;
}

# Load default settings
sub load_defaults {
my $self = shift;
# Load the parent module's settings
$self->SUPER::load_defaults();
# Not really anything to add
$self->{'defaults'}{'psp_fps'} = 'high';
$self->{'defaults'}{'psp_bitrate'} = 'high';
$self->{'defaults'}{'psp_resolution'} = '320x240';
$self->{'defaults'}{'psp_thumbnail'} = 1;
}

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

# restrict the output FPS to one of the 2 valid for the PSP format
while (1) {
my $fps = query_text('Frame rate (high=29.97, low=14.985)?',
'string',
$self->val('psp_fps'));
if ($fps =~ /^(h|29|30)/i) {
$self->{'out_fps'} = 29.97;
}
elsif ($fps =~ /^(l|14|15)/i) {
$self->{'out_fps'} = 14.985;
}
else {
if ($is_cli) {
die "Frame rate for PSP must be high (29.97) or low (14.985)\n";
}
print "Unrecognized response. Please try again.\n";
next;
}
last;
}

# restrict the output resolution to one of the 3 valid for the PSP format
while (1) {
my $res = query_text('Resolution (320x240, 368x208, 400x192)?',
'string',
$self->val('psp_resolution'));
if ($res =~ /^32/i) {
$self->{'width'} = 320;
$self->{'height'} = 240;
}
elsif ($res =~ /^36/i) {
$self->{'width'} = 368;
$self->{'height'} = 208;
}
elsif ($res =~ /^4/i) {
$self->{'width'} = 400;
$self->{'height'} = 192;
}
else {
if ($is_cli) {
die "Resolution for PSP must be 320x240, 368x208 or 400x192\n";
}
print "Unrecognized response. Please try again.\n";
next;
}
last;
}

# restrict the video bitrate to one of the 2 valid for the PSP format
while (1) {
my $btr = query_text('Video bitrate (high=768, low=384)?',
'string',
$self->val('psp_bitrate'));
if ($btr =~ /^(h|7)/i) {
$self->{'v_bitrate'} = 768;
}
elsif ($btr =~ /^(l|3)/i) {
$self->{'v_bitrate'} = 384;
}
else {
if ($is_cli) {
die "PSP bitrate must be high (768) or low (384)\n";
}
print "Unrecognized response. Please try again.\n";
next;
}
last;
}

# Offer to create a thumbnail for the user
$self->{'psp_thumbnail'} = query_text('Create thumbnail for video?',
'yesno',
$self->val('psp_thumbnail'));

# Let the user know how to install the files.
print "\n\nIn order for the movie files to be of use on your PSP, you must\n",
"copy the movie file (and thumbnail if present) to the PSP's memory\n",
"stick in the following location (create the directories if necessary):\n\n",
" \\mp_root\\100mnv01\\ \n\n",
"The movie must be renamed into the format M4V<5 digit number>.MP4.\n";
if ($self->{'psp_thumbnail'}) {
print "If you have a thumbnail, it should be named in the same way, but have\n",
"a THM file extension\n";
}
print "\n";
}

sub export {
my $self = shift;
my $episode = shift;
# Force to 4:3 aspect ratio
$self->{'out_aspect'} = 1.3333;
$self->{'aspect_stretched'} = 1;
# Audio codec name changes between ffmpeg versions
my $acodec = $self->can_encode('libfaac') ? 'libfaac' : 'aac';
# Build the ffmpeg string
my $safe_title = shell_escape($episode->{'title'}.' - '.$episode->{'subtitle'});
$self->{'ffmpeg_xtra'} = $self->param('bit_rate', $self->{'v_bitrate'})
.' -bufsize 65535'
.$self->param('ab', 32)
.' -acodec '.$acodec
." -f psp";
# Execute the parent method
$self->SUPER::export($episode, '.MP4');

# Make the thumbnail if needed
if ($self->{'psp_thumbnail'}) {
my $ffmpeg = find_program('ffmpeg')
or die("where is ffmpeg, we had it when we did an ffmpeg_init?");

$ffmpeg .= ' -y -i ' .shell_escape($self->get_outfile($episode, '.MP4'))
.' -s 160x90 -padtop 16 -padbottom 14 -r 1 -t 1'
.' -ss 7:00.00 -an -f mjpeg '
.shell_escape($self->get_outfile($episode, '.THM'));
`$ffmpeg` unless ($DEBUG);

if ($DEBUG) {
print "\n$ffmpeg 2>&1\n";
}
}
}

1; #return true

# vim:ts=4:sw=4:ai:et:si:sts=4
155 changes: 155 additions & 0 deletions nuvexport/export/ffmpeg/SVCD.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
#
# $Date$
# $Revision$
# $Author$
#
# export::ffmpeg::SVCD
# Maintained by Chris Petersen <mythtv@forevermore.net>
#

package export::ffmpeg::SVCD;
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::cli;
use nuv_export::ui;
use mythtv::recordings;

# Load the following extra parameters from the commandline
add_arg('quantisation|q=i', 'Quantisation');
add_arg('a_bitrate|a=i', 'Audio bitrate');
add_arg('v_bitrate|v=i', 'Video bitrate');

sub new {
my $class = shift;
my $self = {
'cli' => qr/\bsvcd\b/i,
'name' => 'Export to SVCD',
'enabled' => 1,
'errors' => [],
'defaults' => {},
};
bless($self, $class);

# Initialize the default parameters
$self->load_defaults();

# Verify any commandline or config file options
die "Audio bitrate must be > 0\n" unless (!defined $self->val('a_bitrate') || $self->{'a_bitrate'} > 0);
die "Video bitrate must be > 0\n" unless (!defined $self->val('v_bitrate') || $self->{'v_bitrate'} > 0);

# Initialize and check for ffmpeg
$self->init_ffmpeg();
# Can we even encode svcd?
if (!$self->can_encode('mpeg2video')) {
push @{$self->{'errors'}}, "Your ffmpeg installation doesn't support encoding to mpeg2video.";
}
if (!$self->can_encode('mp2')) {
push @{$self->{'errors'}}, "Your ffmpeg installation doesn't support encoding to mp2 audio.";
}
# Any errors? disable this function
$self->{'enabled'} = 0 if ($self->{'errors'} && @{$self->{'errors'}} > 0);
# Return
return $self;
}

# Load default settings
sub load_defaults {
my $self = shift;
# Load the parent module's settings
$self->SUPER::load_defaults();
# Add the svcd preferred bitrates
$self->{'defaults'}{'quantisation'} = 5;
$self->{'defaults'}{'a_bitrate'} = 192;
$self->{'defaults'}{'v_bitrate'} = 2500;
}

# Gather settings from the user
sub gather_settings {
my $self = shift;
# Load the parent module's settings
$self->SUPER::gather_settings();
# Audio Bitrate
while (1) {
my $a_bitrate = query_text('Audio bitrate?',
'int',
$self->val('a_bitrate'));
if ($a_bitrate < 64) {
print "Too low; please choose a bitrate between 64 and 384.\n";
}
elsif ($a_bitrate > 384) {
print "Too high; please choose a bitrate between 64 and 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 = min(2500, 2742 - $self->{'a_bitrate'});
if ($self->val('v_bitrate') > $max_v_bitrate || $self->val('confirm')) {
if ($self->val('v_bitrate') > $max_v_bitrate) {
$self->{'v_bitrate'} = $max_v_bitrate;
}
while (1) {
my $v_bitrate = query_text('Maximum video bitrate for VBR?',
'int',
$self->val('v_bitrate'));
if ($v_bitrate < 1000) {
print "Too low; please choose a bitrate between 1000 and $max_v_bitrate.\n";
}
elsif ($v_bitrate > $max_v_bitrate) {
print "Too high; please choose a bitrate between 1000 and $max_v_bitrate.\n";
}
else {
$self->{'v_bitrate'} = $v_bitrate;
last;
}
}
}
# Ask the user what vbr quality (quantisation) he/she wants - 1..31
while (1) {
my $quantisation = query_text('VBR quality/quantisation (1-31)?',
'float',
$self->val('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;
}
}
}

sub export {
my $self = shift;
my $episode = shift;
# Force to 4:3 aspect ratio
$self->{'out_aspect'} = 1.3333;
$self->{'aspect_stretched'} = 1;
# PAL or NTSC?
my $standard = ($episode->{'finfo'}{'fps'} =~ /^2(?:5|4\.9)/) ? 'PAL' : 'NTSC';
$self->{'width'} = 480;
$self->{'height'} = ($standard eq 'PAL') ? '576' : '480';
$self->{'out_fps'} = ($standard eq 'PAL') ? 25 : 29.97;
# Build the ffmpeg string
$self->{'ffmpeg_xtra'} = $self->param('bit_rate', $self->{'v_bitrate'})
.' -vcodec mpeg2video'
.' -qmin ' . $self->{'quantisation'}
.$self->param('ab', $self->{'a_bitrate'})
." -ar 44100 -acodec mp2"
." -f svcd";
# Execute the parent method
$self->SUPER::export($episode, ".mpg");
}

1; #return true

# vim:ts=4:sw=4:ai:et:si:sts=4
89 changes: 89 additions & 0 deletions nuvexport/export/ffmpeg/VCD.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#
# $Date$
# $Revision$
# $Author$
#
# export::ffmpeg::VCD
# Maintained by Gavin Hurlbut <gjhurlbu@gmail.com>
#

package export::ffmpeg::VCD;
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::cli;
use nuv_export::ui;
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' => [],
'defaults' => {},
};
bless($self, $class);

# Initialize the default parameters
$self->load_defaults();

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

# Can we even encode vcd?
if (!$self->can_encode('mpeg1video')) {
push @{$self->{'errors'}}, "Your ffmpeg installation doesn't support encoding to mpeg1video.";
}
if (!$self->can_encode('mp2')) {
push @{$self->{'errors'}}, "Your ffmpeg installation doesn't support encoding to mp2 audio.";
}
# Any errors? disable this function
$self->{'enabled'} = 0 if ($self->{'errors'} && @{$self->{'errors'}} > 0);
# Return
return $self;
}

# Load default settings
sub load_defaults {
my $self = shift;
# Load the parent module's settings
$self->SUPER::load_defaults();
# Not really anything to add
}

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

sub export {
my $self = shift;
my $episode = shift;
# Force to 4:3 aspect ratio
$self->{'out_aspect'} = 1.3333;
$self->{'aspect_stretched'} = 1;
# PAL or NTSC?
my $standard = ($episode->{'finfo'}{'fps'} =~ /^2(?:5|4\.9)/) ? 'PAL' : 'NTSC';
$self->{'width'} = 352;
$self->{'height'} = ($standard eq 'PAL') ? '288' : '240';
$self->{'out_fps'} = ($standard eq 'PAL') ? 25 : 29.97;
# Build the transcode string
$self->{'ffmpeg_xtra'} = $self->param('bit_rate', 1150)
." -vcodec mpeg1video"
.$self->param('ab', 224)
." -ar 44100 -acodec mp2"
." -f vcd";
# Execute the parent method
$self->SUPER::export($episode, ".mpg");
}

1; #return true

# vim:ts=4:sw=4:ai:et:si:sts=4
195 changes: 195 additions & 0 deletions nuvexport/export/ffmpeg/XviD.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
#
# ffmpeg-based XviD export module for nuvexport.
#
# @url $URL$
# @date $Date$
# @version $Revision$
# @author $Author$
# @copyright Silicon Mechanics
#

package export::ffmpeg::XviD;
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::cli;
use nuv_export::ui;
use mythtv::recordings;

# Load the following extra parameters from the commandline
add_arg('quantisation|q=i', 'Quantisation');
add_arg('a_bitrate|a=i', 'Audio bitrate');
add_arg('v_bitrate|v=i', 'Video bitrate');
add_arg('multipass!', 'Enable two-pass encoding.');

sub new {
my $class = shift;
my $self = {
'cli' => qr/\bxvid\b/i,
'name' => 'Export to XviD',
'enabled' => 1,
'errors' => [],
'defaults' => {},
};
bless($self, $class);

# Initialize the default parameters
$self->load_defaults();

# Verify any commandline or config file options
die "Audio bitrate must be > 0\n" unless (!defined $self->val('a_bitrate') || $self->{'a_bitrate'} > 0);
die "Video bitrate must be > 0\n" unless (!defined $self->val('v_bitrate') || $self->{'v_bitrate'} > 0);
die "Width must be > 0\n" unless (!defined $self->val('width') || $self->{'width'} =~ /^\s*\D/ || $self->{'width'} > 0);
die "Height must be > 0\n" unless (!defined $self->val('height') || $self->{'height'} =~ /^\s*\D/ || $self->{'height'} > 0);

# VBR, multipass, etc.
if ($self->val('multipass')) {
$self->{'vbr'} = 1;
}
elsif ($self->val('quantisation')) {
die "Quantisation must be a number between 1 and 31 (lower means better quality).\n" if ($self->{'quantisation'} < 1 || $self->{'quantisation'} > 31);
$self->{'vbr'} = 1;
}

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

# Can we even encode xvid?
if (!$self->can_encode('xvid') && !$self->can_encode('libxvid')) {
push @{$self->{'errors'}}, "Your ffmpeg installation doesn't support encoding to xvid.\n"
." (It must be compiled with the --enable-libxvid option)";
}
if (!$self->can_encode('mp3') && !$self->can_encode('libmp3lame')) {
push @{$self->{'errors'}}, "Your ffmpeg installation doesn't support encoding to mp3 audio.";
}

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

# Load default settings
sub load_defaults {
my $self = shift;
# Load the parent module's settings
$self->SUPER::load_defaults();
# Default bitrates and resolution
$self->{'defaults'}{'a_bitrate'} = 128;
$self->{'defaults'}{'v_bitrate'} = 960;
$self->{'defaults'}{'width'} = 624;
}

# Gather settings from the user
sub gather_settings {
my $self = shift;
# Load the parent module's settings
$self->SUPER::gather_settings();
# Audio Bitrate
$self->{'a_bitrate'} = query_text('Audio bitrate?',
'int',
$self->val('a_bitrate'));
# VBR options
if (!$is_cli) {
$self->{'vbr'} = query_text('Variable bitrate video?',
'yesno',
$self->val('vbr'));
if ($self->{'vbr'}) {
$self->{'multipass'} = query_text('Multi-pass (slower, but better quality)?',
'yesno',
$self->val('multipass'));
if (!$self->{'multipass'}) {
while (1) {
my $quantisation = query_text('VBR quality/quantisation (1-31)?',
'float',
$self->val('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;
}
}
}
} else {
$self->{'multipass'} = 0;
}
# Ask the user what video bitrate he/she wants
$self->{'v_bitrate'} = query_text('Video bitrate?',
'int',
$self->val('v_bitrate'));
}
# Query the resolution
$self->query_resolution();
}

sub export {
my $self = shift;
my $episode = shift;
# Make sure we have the framerate
$self->{'out_fps'} = $episode->{'finfo'}{'fps'};
# Embed the title
$safe_title = $episode->{'title'};
if ($episode->{'subtitle'} ne 'Untitled') {
$safe_title .= ' - '.$episode->{'subtitle'};
}
my $safe_title = shell_escape($safe_title);
# Codec name changes between ffmpeg versions
my $codec = $self->can_encode('libxvid') ? 'libxvid' : 'xvid';
# Build the common ffmpeg string
my $ffmpeg_xtra = ' -vcodec '.$codec
.$self->param('bit_rate', $self->{'v_bitrate'})
.($self->{'vbr'}
? $self->param('rc_min_rate', 32)
. $self->param('rc_max_rate', (2 * $self->{'v_bitrate'}))
. $self->param('bit_rate_tolerance', 32)
. ' -bufsize 65535'
: '')
.' -flags +mv4+loop+aic+cgop'
.' -trellis 1'
.' -mbd 1'
.' -cmp 2 -subcmp 2'
.$self->param('b_quant_factor', 150)
.$self->param('b_quant_offset', 100)
.$self->param('max_b_frames', 1)
;
# Dual pass?
if ($self->{'multipass'}) {
# Add the temporary file to the list
push @tmpfiles, "/tmp/xvid.$$.log";
# First pass
print "First pass...\n";
$self->{'ffmpeg_xtra'} = $ffmpeg_xtra
." -pass 1 -passlogfile '/tmp/xvid.$$.log'"
.' -f avi';
$self->SUPER::export($episode, '', 1);
# Second pass
print "Final pass...\n";
$self->{'ffmpeg_xtra'} = $ffmpeg_xtra
. " -pass 2 -passlogfile '/tmp/xvid.$$.log'";
}
# Single Pass
else {
$self->{'ffmpeg_xtra'} = $ffmpeg_xtra
.($self->{'vbr'}
? ' -qmax 31 -qmin '.$self->{'quantisation'}
: '');
}
# Don't forget the audio, etc.
$self->{'ffmpeg_xtra'} .= ' -acodec '
.($self->can_encode('libmp3lame') ? 'libmp3lame' : 'mp3')
.' -async 1 '
.$self->param('ab', $self->{'a_bitrate'})
.' -f avi';
# Execute the (final pass) encode
$self->SUPER::export($episode, '.avi');
}

1; #return true

# vim:ts=4:sw=4:ai:et:si:sts=4
437 changes: 437 additions & 0 deletions nuvexport/export/generic.pm

Large diffs are not rendered by default.

245 changes: 245 additions & 0 deletions nuvexport/export/mencoder.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
#
# $Date$
# $Revision$
# $Author$
#
# mencoder.pm
#
# routines for setting up mencoder
#

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

use export::generic;

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

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

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

# Load default settings
sub load_defaults {
my $self = shift;
# Load the parent module's settings
$self->SUPER::load_defaults();
# Not really anything to add
}

# Gather settings from the user
sub gather_settings {
my $self = shift;
# Gather generic settings
$self->SUPER::gather_settings($skip ? $skip - 1 : 0);
return if ($skip);
}

#Fix/build mencoder filter chain
sub build_vop_line{
my $cmdline = shift;
my $vop = '';
while ($cmdline =~ m/.*?-vf\s+([^\s]+)\s/gs){
$vop .= ",$1";
}
$vop =~ s/^,+//;
$vop =~ s/,+$//;
$cmdline =~ s/-vf\s+[^\s]+\s/ /g;
$cmdline .= " -vf $vop ";
return $cmdline;
}

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

# Generate a cutlist?
$self->gen_cutlist($episode);

# Start the mencoder command
$mencoder = "$NICE mencoder";
# Import aspect ratio
$mencoder .= ' -aspect '.$episode->{'finfo'}{'aspect_f'};
# Not an mpeg mencoder can not do cutlists (from what I can tell..)
unless ($episode->{'finfo'}{'is_mpeg'} && !$self->{'use_cutlist'}) {
# swap red/blue -- used with svcd, need to see if it's needed everywhere
# $mencoder .= ' -vf rgb2bgr '; #this is broken in mencoder 1.0preX
# 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 (need to use --fifosync with mencoder? needs testing)
my $mythtranscode_bin = find_program('mythtranscode');
$mythtranscode = "$NICE $mythtranscode_bin --showprogress"
." -p '$episode->{'transcoder'}'"
." -c '$episode->{'chanid'}'"
." -s '".unix_to_myth_time($episode->{'recstartts'})."'"
." -f \"/tmp/fifodir_$$/\"";
# On no-audio encodes, we need to do something to keep mythtranscode's audio buffers from filling up available RAM
# $mythtranscode .= ' --fifosync' if ($skip_audio);
# let mythtranscode handle the cutlist
$mythtranscode .= ' --honorcutlist' if ($self->{'use_cutlist'});
}
# Figure out the input files
if ($episode->{'finfo'}{'is_mpeg'} && !$self->{'use_cutlist'}) {
$mencoder .= ' -idx '.shell_escape($episode->{'local_path'});
}
else {
$mencoder .= " -noskip -idx /tmp/fifodir_$$/vidout -audiofile /tmp/fifodir_$$/audout "
.' -demuxer 20 -audio-demuxer 20 -rawaudio'
.' rate='.$episode->{'finfo'}{'audio_sample_rate'}.':channels='.$episode->{'finfo'}{'audio_channels'}
.' -demuxer 26 -rawvideo'
.' w='.$episode->{'finfo'}{'width'}.':h='.$episode->{'finfo'}{'height'}
.':fps='.$episode->{'finfo'}{'fps'}
;
}
# Crop?
if ($self->{'crop'}) {
my $t = sprintf('%.0f', ($self->val('crop_top') / 100) * $episode->{'finfo'}{'height'});
my $r = sprintf('%.0f', ($self->val('crop_right') / 100) * $episode->{'finfo'}{'width'});
my $b = sprintf('%.0f', ($self->val('crop_bottom') / 100) * $episode->{'finfo'}{'height'});
my $l = sprintf('%.0f', ($self->val('crop_left') / 100) * $episode->{'finfo'}{'width'});
# Keep the crop numbers even
$t-- if ($t > 0 && $t % 2);
$r-- if ($r > 0 && $r % 2);
$b-- if ($b > 0 && $b % 2);
$l-- if ($l > 0 && $l % 2);
# Figure out the new width/height
my $w = $episode->{'finfo'}{'width'} - $r - $l;
my $h = $episode->{'finfo'}{'height'} - $t - $b;
$mencoder .= " -vf crop=$w:$h:$l:$t " if ($t || $r || $b || $l);
}
# Filters
if ($self->{'deinterlace'}) {
$mencoder .= " -vf lavcdeint";
}
if ($self->{'noise_reduction'}) {
$mencoder .= " -vf denoise3d";
}
# Add any additional settings from the child module.
$mencoder .= ' '.$self->{'mencoder_xtra'};
# Output directory set to null means the first pass of a multipass
if (!$self->{'path'} || $self->{'path'} =~ /^\/dev\/null\b/) {
$mencoder .= ' -o /dev/null';
}
# Add the output filename
else {
$mencoder .= ' -o '.shell_escape($self->get_outfile($episode, $suffix));
}
# mencoder pids
my ($mythtrans_pid, $mencoder_pid, $mythtrans_h, $mencoder_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");
$children{$mythtrans_pid} = 'mythtranscode' if ($mythtrans_pid);
fifos_wait("/tmp/fifodir_$$/");
push @tmpfiles, "/tmp/fifodir_$$", "/tmp/fifodir_$$/audout", "/tmp/fifodir_$$/vidout";
}
#Fix -vf options before we execute mencoder
$mencoder = build_vop_line($mencoder);
# Execute mencoder
print "Starting mencoder.\n" unless ($DEBUG);
($mencoder_pid, $mencoder_h) = fork_command("$mencoder 2>&1");
$children{$mencoder_pid} = 'mencoder' if ($mencoder_pid);
# Get ready to count the frames that have been processed
my ($frames, $fps);
$frames = 0;
$fps = 0.0;
my $total_frames = $episode->{'last_frame'} > 0
? $episode->{'last_frame'} - $episode->{'cutlist_frames'}
: 0;
# Keep track of any warnings
my $warnings = '';
my $death_timer = 0;
my $last_death = '';
# 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";
}
unless (arg('noprogress')) {
print "\rprocessed: $frames of $total_frames frames ($pct\%), $fps fps ";
}
# Read from the mencoder handle
while (has_data($mencoder_h) and $l = <$mencoder_h>) {
if ($l =~ /^Pos:.*?(\d+)f.*?\(.*?(\d+(?:\.\d+)?)fps/) {
$frames = int($1);
$fps = $2;
}
# Look for error messages
elsif ($l =~ m/\[mencoder\] warning/) {
$warnings .= $l;
}
elsif ($l =~ m/\[mencoder\] critical/) {
$warnings .= $l;
die "\nmencoder had critical errors:\n\n$warnings";
}
}
# 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 - $episode->{'cutlist_frames'};
}
}
}
# Has the deathtimer been started? Stick around for awhile, but not too long
if ($death_timer > 0 && time() - $death_timer > 30) {
$str = "\n\n$last_death died early.";
if ($warnings) {
$str .= "See mencoder warnings:\n\n$warnings";
}
else {
$str .= "Please use the --debug option to figure out what went wrong.\n"
."http://www.mythtv.org/wiki/index.php/Nuvexport#Debug_Mode\n\n";
}
die $str;
}
# The pid?
$pid = waitpid(-1, &WNOHANG);
if ($children{$pid}) {
print "\n$children{$pid} finished.\n" unless ($DEBUG);
$last_death = $children{$pid};
$death_timer = time();
delete $children{$pid};
}
# Sleep for 1/100 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
169 changes: 169 additions & 0 deletions nuvexport/export/mencoder/XviD.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
#
# $Date$
# $Revision$
# $Author$
#
# export::mencoder::XviD
# Copied from transcode.pm
# and modified by Ryan Dearing <mythtv@mythtv.us>
#

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

# 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 nuv_export::ui;
use mythtv::recordings;

# Load the following extra parameters from the commandline
add_arg('quantisation|q=i', 'Quantisation');
add_arg('a_bitrate|a=i', 'Audio bitrate');
add_arg('v_bitrate|v=i', 'Video bitrate');
add_arg('multipass!', 'Enable two-pass encoding.');

sub new {
my $class = shift;
my $self = {
'cli' => qr/\bxvid\b/i,
'name' => 'Export to XviD (using mencoder)',
'enabled' => 1,
'errors' => [],
'defaults' => {},
};
bless($self, $class);

# Initialize the default parameters
$self->load_defaults();

# Verify any commandline or config file options
die "Audio bitrate must be > 0\n" unless (!defined $self->val('a_bitrate') || $self->{'a_bitrate'} > 0);
die "Video bitrate must be > 0\n" unless (!defined $self->val('v_bitrate') || $self->{'v_bitrate'} > 0);
die "Width must be > 0\n" unless (!defined $self->val('width') || $self->{'width'} =~ /^\s*\D/ || $self->{'width'} > 0);
die "Height must be > 0\n" unless (!defined $self->val('height') || $self->{'height'} =~ /^\s*\D/ || $self->{'height'} > 0);

# VBR, multipass, etc.
if ($self->val('multipass')) {
$self->{'vbr'} = 1;
}
elsif ($self->val('quantisation')) {
die "Quantisation must be a number between 1 and 31 (lower means better quality).\n" if ($self->{'quantisation'} < 1 || $self->{'quantisation'} > 31);
$self->{'vbr'} = 1;
}

# Initialize and check for mencoder
$self->init_mencoder();

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

# Load default settings
sub load_defaults {
my $self = shift;
# Load the parent module's settings
$self->SUPER::load_defaults();
# Not really anything to add
}

# Gather settings from the user
sub gather_settings {
my $self = shift;
# Load the parent module's settings
$self->SUPER::gather_settings();
# Audio Bitrate
$self->{'a_bitrate'} = query_text('Audio bitrate?',
'int',
$self->val('a_bitrate'));
# VBR options
if (!$is_cli) {
$self->{'vbr'} = query_text('Variable bitrate video?',
'yesno',
$self->val('vbr'));
if ($self->{'vbr'}) {
$self->{'multipass'} = query_text('Multi-pass (slower, but better quality)?',
'yesno',
$self->val('multipass'));
if (!$self->{'multipass'}) {
while (1) {
my $quantisation = query_text('VBR quality/quantisation (1-31)?', 'float', $self->val('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 ($self->{'multipass'} || !$self->{'vbr'}) {
$self->{'v_bitrate'} = query_text('Video bitrate?',
'int',
$self->val('v_bitrate'));
}
}
# Query the resolution
$self->query_resolution();
}

sub export {
my $self = shift;
my $episode = shift;
# Build the mencoder string
my $params = " -ovc xvid -vf scale=$self->{'width'}:$self->{'height'}"
#." -N 0x55" # make *sure* we're exporting mp3 audio

#." -oac mp3lame -lameopts vbr=3:br=$self->{'a_bitrate'}"
;
# 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->{'mencoder_xtra'} = " $params"
." -passlogfile /tmp/xvid.$$.log"
." -oac copy"
." -xvidencopts bitrate=$self->{'v_bitrate'}:pass=1:quant_type=mpeg:threads=2:keyframe_boost=10:kfthreshold=1:kfreduction=20 ";
$self->SUPER::export($episode, '', 1);
# Restore the path
$self->{'path'} = $path_bak;
# Second pass
print "Final pass...\n";
$self->{'mencoder_xtra'} = " $params"
." -oac mp3lame -lameopts vbr=3:br=$self->{'a_bitrate'}"
." -passlogfile /tmp/xvid.$$.log"
." -xvidencopts bitrate=$self->{'v_bitrate'}:pass=2:quant_type=mpeg:threads=2:keyframe_boost=10:kfthreshold=1:kfreduction=20 ";
}
# Single pass
else {
$self->{'mencoder_xtra'} = " $params"
." -oac mp3lame -lameopts vbr=3:br=$self->{'a_bitrate'}";
if ($self->{'quantisation'}) {
$self->{'mencoder_xtra'} .= " -xvidencopts fixed_quant=".$self->{'quantisation'};
}
else {
$self->{'mencoder_xtra'} .= " -xvidencopts bitrate=$self->{'v_bitrate'} ";
}
}
# Execute the (final pass) encode
$self->SUPER::export($episode, '.avi');
}

1; #return true

# vim:ts=4:sw=4:ai:et:si:sts=4
435 changes: 435 additions & 0 deletions nuvexport/export/transcode.pm

Large diffs are not rendered by default.

92 changes: 92 additions & 0 deletions nuvexport/export/transcode/DVCD.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#
# $Date$
# $Revision$
# $Author$
#
# export::transcode::DVCD
# Maintained by Gavin Hurlbut <gjhurlbu@gmail.com>
#

package export::transcode::DVCD;
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::cli;
use nuv_export::ui;
use mythtv::recordings;

# Load the following extra parameters from the commandline

sub new {
my $class = shift;
my $self = {
'cli' => qr/\bdvcd\b/i,
'name' => 'Export to DVCD (VCD with 48kHz audio for making DVDs)',
'enabled' => 1,
'errors' => [],
'defaults' => {},
};
bless($self, $class);

# Initialize the default parameters
$self->load_defaults();

# Initialize and check for transcode
$self->init_transcode();

# Make sure that we have an mplexer
find_program('mplex')
or push @{$self->{'errors'}}, 'You need mplex to export a dvcd.';

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

# Load default settings
sub load_defaults {
my $self = shift;
# Load the parent module's settings
$self->SUPER::load_defaults();
# Not really anything to add
}

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

sub export {
my $self = shift;
my $episode = shift;
# Force to 4:3 aspect ratio
$self->{'out_aspect'} = 1.3333;
$self->{'aspect_stretched'} = 1;
# PAL or NTSC?
my $standard = ($episode->{'finfo'}{'fps'} =~ /^2(?:5|4\.9)/) ? 'PAL' : 'NTSC';
$self->{'width'} = 352;
$self->{'height'} = ($standard eq 'PAL') ? '288' : '240';
$self->{'out_fps'} = ($standard eq 'PAL') ? 25 : 29.97;
my $ntsc = ($standard eq 'PAL') ? '' : '-N';
# Build the transcode string
$self->{'transcode_xtra'} = " -y mpeg2enc,mp2enc"
.' -F 1 -E 48000 -b 224';
# Add the temporary files that will need to be deleted
push @tmpfiles, $self->get_outfile($episode, ".$$.m1v"), $self->get_outfile($episode, ".$$.mpa");
# Execute the parent method
$self->SUPER::export($episode, ".$$");
# Multiplex the streams
my $command = "$NICE mplex -f 1 -C"
.' -o '.shell_escape($self->get_outfile($episode, '.mpg'))
.' '.shell_escape($self->get_outfile($episode, ".$$.m1v"))
.' '.shell_escape($self->get_outfile($episode, ".$$.mpa"));
system($command);
}

1; #return true

# vim:ts=4:sw=4:ai:et:si:sts=4
156 changes: 156 additions & 0 deletions nuvexport/export/transcode/DVD.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
#
# $Date$
# $Revision$
# $Author$
#
# export::transcode::DVD
# Maintained by Gavin Hurlbut <gjhurlbu@gmail.com>
#

package export::transcode::DVD;
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::cli;
use nuv_export::ui;
use mythtv::recordings;

# Load the following extra parameters from the commandline
add_arg('quantisation|q=i', 'Quantisation');
add_arg('a_bitrate|a=i', 'Audio bitrate');
add_arg('v_bitrate|v=i', 'Video bitrate');

sub new {
my $class = shift;
my $self = {
'cli' => qr/dvd/i,
'name' => 'Export to DVD',
'enabled' => 1,
'errors' => [],
'defaults' => {},
};
bless($self, $class);

# Initialize the default parameters
$self->load_defaults();

# Verify any commandline or config file options
die "Audio bitrate must be > 0\n" unless (!defined $self->val('a_bitrate') || $self->{'a_bitrate'} > 0);
die "Video bitrate must be > 0\n" unless (!defined $self->val('v_bitrate') || $self->{'v_bitrate'} > 0);

# Initialize and check for transcode
$self->init_transcode();

# Make sure that we have an mplexer
find_program('mplex')
or push @{$self->{'errors'}}, 'You need mplex to export a dvd.';

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

# Load default settings
sub load_defaults {
my $self = shift;
# Load the parent module's settings
$self->SUPER::load_defaults();
# Add the dvd preferred bitrates
$self->{'defaults'}{'quantisation'} = 5;
$self->{'defaults'}{'a_bitrate'} = 384;
$self->{'defaults'}{'v_bitrate'} = 6000;
}

# Gather settings from the user
sub gather_settings {
my $self = shift;
# Load the parent module's settings
$self->SUPER::gather_settings();
# Audio Bitrate
while (1) {
my $a_bitrate = query_text('Audio bitrate?',
'int',
$self->val('a_bitrate'));
if ($a_bitrate < 64) {
print "Too low; please choose a bitrate between 64 and 384.\n";
}
elsif ($a_bitrate > 384) {
print "Too high; please choose a bitrate between 64 and 384.\n";
}
else {
$self->{'a_bitrate'} = $a_bitrate;
last;
}
}
# Ask the user what video bitrate he/she wants, or calculate the max bitrate
my $max_v_bitrate = 9500 - $self->{'a_bitrate'};
if ($self->val('v_bitrate') > $max_v_bitrate) {
$self->{'v_bitrate'} = $max_v_bitrate;
}
while (1) {
my $v_bitrate = query_text('Maximum video bitrate for VBR?',
'int',
$self->val('v_bitrate'));
if ($v_bitrate < 1000) {
print "Too low; please choose a bitrate between 1000 and $max_v_bitrate.\n";
}
elsif ($v_bitrate > $max_v_bitrate) {
print "Too high; please choose a bitrate between 1000 and $max_v_bitrate.\n";
}
else {
$self->{'v_bitrate'} = $v_bitrate;
last;
}
}
# Ask the user what vbr quality (quantisation) he/she wants - 1..31
while (1) {
my $quantisation = query_text('VBR quality/quantisation (1-31)?',
'float',
$self->val('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;
}
}
}

sub export {
my $self = shift;
my $episode = shift;
# Force to 4:3 aspect ratio
$self->{'out_aspect'} = 1.3333;
$self->{'aspect_stretched'} = 1;
# PAL or NTSC?
my $standard = ($episode->{'finfo'}{'fps'} =~ /^2(?:5|4\.9)/) ? 'PAL' : 'NTSC';
$self->{'width'} = 720;
$self->{'height'} = ($standard eq 'PAL') ? '576' : '480';
$self->{'out_fps'} = ($standard eq 'PAL') ? 25 : 29.97;
# Build the transcode string
$self->{'transcode_xtra'} = " -y mpeg2enc,mp2enc"
.' -F 8,"-q '.$self->{'quantisation'}
.' -Q 2 -V 230 -4 2 -2 1"'
.' -w '.$self->{'v_bitrate'}
.' -E 48000 -b '.$self->{'a_bitrate'};
# Add the temporary files that will need to be deleted
push @tmpfiles, $self->get_outfile($episode, ".$$.m2v"), $self->get_outfile($episode, ".$$.mpa");
# Execute the parent method
$self->SUPER::export($episode, ".$$");
# Multiplex the streams
my $command = "$NICE mplex -f 8 -V"
.' -o '.shell_escape($self->get_outfile($episode, '.mpg'))
.' '.shell_escape($self->get_outfile($episode, ".$$.m2v", 1))
.' '.shell_escape($self->get_outfile($episode, ".$$.mpa", 1));
system($command);
}

1; #return true

# vim:ts=4:sw=4:ai:et:si:sts=4
182 changes: 182 additions & 0 deletions nuvexport/export/transcode/SVCD.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
#
# $Date$
# $Revision$
# $Author$
#
# export::transcode::SVCD
# Maintained by Chris Petersen <mythtv@forevermore.net>
#

package export::transcode::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::cli;
use nuv_export::ui;
use mythtv::recordings;

# Load the following extra parameters from the commandline
add_arg('quantisation|q=i', 'Quantisation');
add_arg('a_bitrate|a=i', 'Audio bitrate');
add_arg('v_bitrate|v=i', 'Video bitrate');

sub new {
my $class = shift;
my $self = {
'cli' => qr/\bsvcd\b/i,
'name' => 'Export to SVCD',
'enabled' => 1,
'errors' => [],
'defaults' => {},
};
bless($self, $class);

# Initialize the default parameters
$self->load_defaults();

# Verify any commandline or config file options
die "Audio bitrate must be > 0\n" unless (!defined $self->val('a_bitrate') || $self->{'a_bitrate'} > 0);
die "Video bitrate must be > 0\n" unless (!defined $self->val('v_bitrate') || $self->{'v_bitrate'} > 0);

# Initialize and check for transcode
$self->init_transcode();

# Make sure that we have an mplexer
find_program('mplex')
or push @{$self->{'errors'}}, 'You need mplex to export an svcd.';

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

# Load default settings
sub load_defaults {
my $self = shift;
# Load the parent module's settings
$self->SUPER::load_defaults();
# Split every 795 megs
$self->{'defaults'}{'split_every'} = 795;
# Add the svcd preferred bitrates
$self->{'defaults'}{'quantisation'} = 5;
$self->{'defaults'}{'a_bitrate'} = 192;
$self->{'defaults'}{'v_bitrate'} = 2500;
}

# Gather settings from the user
sub gather_settings {
my $self = shift;
# Load the parent module's settings
$self->SUPER::gather_settings();
# Audio Bitrate
while (1) {
my $a_bitrate = query_text('Audio bitrate?',
'int',
$self->val('a_bitrate'));
if ($a_bitrate < 64) {
print "Too low; please choose a bitrate between 64 and 384.\n";
}
elsif ($a_bitrate > 384) {
print "Too high; please choose a bitrate between 64 and 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 = min(2500, 2742 - $self->{'a_bitrate'});
if ($self->val('v_bitrate') > $max_v_bitrate || $self->val('confirm')) {
if ($self->val('v_bitrate') > $max_v_bitrate) {
$self->{'v_bitrate'} = $max_v_bitrate;
}
while (1) {
my $v_bitrate = query_text('Maximum video bitrate for VBR?',
'int',
$self->val('v_bitrate'));
if ($v_bitrate < 1000) {
print "Too low; please choose a bitrate between 1000 and $max_v_bitrate.\n";
}
elsif ($v_bitrate > $max_v_bitrate) {
print "Too high; please choose a bitrate between 1000 and $max_v_bitrate.\n";
}
else {
$self->{'v_bitrate'} = $v_bitrate;
last;
}
}
}
# Ask the user what vbr quality (quantisation) he/she wants - 1..31
while (1) {
my $quantisation = query_text('VBR quality/quantisation (1-31)?',
'float',
$self->val('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;
}
}
# Split every # megs?
$self->{'split_every'} = query_text('Split after how many MB?',
'int',
$self->val('split_every'));
$self->{'split_every'} = $self->{'defaults'}{'split_every'} if ($self->val('split_every') < 1);
}

sub export {
my $self = shift;
my $episode = shift;
# Force to 4:3 aspect ratio
$self->{'out_aspect'} = 1.3333;
$self->{'aspect_stretched'} = 1;
# PAL or NTSC?
my $standard = ($episode->{'finfo'}{'fps'} =~ /^2(?:5|4\.9)/) ? 'PAL' : 'NTSC';
$self->{'width'} = 480;
$self->{'height'} = ($standard eq 'PAL') ? '576' : '480';
$self->{'out_fps'} = ($standard eq 'PAL') ? 25 : 29.97;
my $ntsc = ($standard eq 'PAL') ? '' : '-N';
# Build the transcode string
$self->{'transcode_xtra'} = " -y mpeg2enc,mp2enc"
.' -F 5,"-q '.$self->{'quantisation'}.'"' # could add "-M $num_cpus" for multi-cpu here, but it actually seems to slow things down
.' -w '.$self->{'v_bitrate'}
.' -E 44100 -b '.$self->{'a_bitrate'};
# Add the temporary files that will need to be deleted
push @tmpfiles, $self->get_outfile($episode, ".$$.m2v"), $self->get_outfile($episode, ".$$.mpa");
# Execute the parent method
$self->SUPER::export($episode, ".$$");
# Need to do this here to get integer context -- otherwise, we get errors about non-numeric stuff.
my $size = (-s $self->get_outfile($episode, ".$$.m2v") or 0);
$size += (-s $self->get_outfile($episode, ".$$.mpa") or 0);
# Create the split file?
my $split_file;
if ($size > 0.97 * $self->{'split_every'} * 1024 * 1024) {
$split_file = "/tmp/nuvexport-svcd.split.$$.$self->{'split_every'}";
open(DATA, ">$split_file") or die "Can't write to $split_file: $!\n\n";
print DATA "maxFileSize = $self->{'split_every'}\n";
close DATA;
push @tmpfiles, $split_file;
}
else {
print "Not splitting because combined file size of chunks is < ".(0.97 * $self->{'split_every'} * 1024 * 1024).", which is the requested split size.\n";
}
# Multiplex the streams
my $command = "$NICE mplex -f 4 -V"
.' -o '.shell_escape($self->get_outfile($episode, $split_file ? '.%d.mpg' : '.mpg'))
.' '.shell_escape($self->get_outfile($episode, ".$$.m2v"))
.' '.shell_escape($self->get_outfile($episode, ".$$.mpa"));
system($command);
}

1; #return true

# vim:ts=4:sw=4:ai:et:si:sts=4
Loading