269 changes: 269 additions & 0 deletions trunk/export_SVCD.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
package export_SVCD;

# Load the nuv utilities
use nuv_utils;

# Make sure we have pointers to the main:: namespace for certain variables
*Prog = *main::Prog;
*gui = *main::gui;

sub new {
my $class = shift;
my $self = {
'name' => 'Export to SVCD',
'enabled' => 1,
'started' => 0,
'fifodir' => "fifodir.$$",
'children' => [],
'errors' => undef,
'episode' => undef,
'savepath' => '.',
'outfile' => 'out.mpg',
'tmp_a' => 'out.mp2',
'tmp_v' => 'out.m2v',
'use_cutlist' => 0,
'a_bitrate' => 192,
'v_bitrate' => 2500,
'quantisation' => 5, # 4 through 6 is probably right...
'noise_reduction' => 1,
@_ #allows user-specified attributes to override the defaults
};
bless($self, $class);
# Make sure that we have an mp2 encoder
$Prog{mp2_encoder} = find_program('toolame', 'mp2enc');
push @{$self->{errors}}, 'You need toolame or mp2enc to export an svcd.' unless ($Prog{mp2_encoder});
# 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});
# Make sure that we have the other necessary programs
find_program('mpeg2enc')
or push @{$self->{errors}}, 'You need mpeg2enc to export an svcd.';
find_program('yuvscaler')
or push @{$self->{errors}}, 'You need yuvscaler to export an svcd.';
# Do we have yuvdenoise?
$Prog{yuvdenoise} = find_program('yuvdenoise');
# Any errors? disable this function
$self->{enabled} = 0 if ($self->{errors} && @{$self->{errors}} > 0);
# Return
return $self;
}

sub gather_data {
my $self = shift;
my $default_filename = '';
# Get the save path
$self->{savepath} = $gui->query_savepath();
# Ask the user for the filename
if($self->{episode}->{show_name} ne 'Untitled' and $self->{episode}->{title} ne 'Untitled')
{
$default_filename = $self->{episode}->{show_name}.' - '.$self->{episode}->{title};
}
elsif($self->{episode}->{show_name} ne 'Untitled')
{
$default_filename = $self->{episode}->{show_name};
}
elsif($self->{episode}->{title} ne 'Untitled')
{
$default_filename = $self->{episode}->{title};
}

$self->{outfile} = $gui->query_filename($default_filename, 'mpg', $self->{savepath});
# Ask the user if he/she wants to use the cutlist
if ($self->{episode}->{cutlist} && $self->{episode}->{cutlist} =~ /\d/) {
$self->{use_cutlist} = $gui->query_text('Enable Myth cutlist?',
'yesno',
$self->{use_cutlist} ? 'Yes' : 'No');
}
else {
$gui->notify('No cutlist found. Hopefully this means that you already removed the commercials.');
}
# Ask the user what audio bitrate he/she wants
my $a_bitrate = $gui->query_text('Audio bitrate?',
'int',
$self->{a_bitrate});
while ($a_bitrate < 64 && $a_bitrate > 384) {
if ($a_bitrate < 64) {
$gui->notify('Too low; please choose a bitrate >= 64.');
}
elsif ($a_bitrate > 384) {
$gui->notify('Too high; please choose a bitrate <= 384.');
}
$a_bitrate = $gui->query_text('Audio bitrate?',
'int',
$self->{a_bitrate});
}
$self->{a_bitrate} = $a_bitrate;
# 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
$self->{v_bitrate} = 2742 - $self->{a_bitrate} ? 2500 : 2742 - $self->{a_bitrate};
my $v_bitrate = $gui->query_text('Maximum video bitrate for VBR?',
'int',
$self->{v_bitrate});
while ($v_bitrate < 1000 && $v_bitrate > $max_v_bitrate) {
if ($v_bitrate < 1000) {
$gui->notify('Too low; please choose a bitrate >= 1000.');
}
elsif ($v_bitrate > $max_v_bitrate) {
$gui->notify("Too high; please choose a bitrate <= $self->{v_bitrate}.");
}
$v_bitrate = $gui->query_text('Maximum video bitrate for VBR?',
'int',
$self->{v_bitrate});
}
$self->{v_bitrate} = $v_bitrate;
# Ask the user what vbr quality (quantisation) he/she wants - 2..31
my $quantisation = $gui->query_text('VBR quality/quantisation (2-31)?', 'float', $self->{quantisation});
while ($quantisation < 2 && $quantisation > 31) {
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";
}
$quantisation = $gui->query_text('VBR quality/quantisation (2-31)?',
'float',
$self->{quantisation});
}
$self->{quantisation} = $quantisation;
# Ask the user what vbr quality (quantisation) he/she wants - 2..31
if ($Prog{yuvdenoise}) {
$self->{noise_reduction} = $gui->query_text('Enable noise reduction (slower, but better results)?',
'yesno',
$self->{noise_reduction} ? 'Yes' : 'No');
}
else {
$gui->notify('Couldn\'t find yuvdenoise. Please install it if you want noise reduction.');
}
# Do we want bin/cue files, or just an mpeg?
# nothing, at the moment.
}

sub execute {
my $self = shift;
# make sure that the fifo dir is clean
if (-e "$self->{fifodir}/vidout" || -e "$self->{fifodir}/audout") {
die "Possibly stale mythtranscode fifo's in $self->{fifodir}.\nPlease remove them before running nuvexport.\n\n";
}
# Gather any necessary data
$self->{episode} = shift;
$self->gather_data;
# Load nuv info
my %nuv_info = nuv_info($self->{episode}->{filename});
# Set this to true so that the cleanup routine actually runs
$self->{started} = 1;
# Create a directory for mythtranscode's fifo's
unless (-d $self->{fifodir}) {
mkdir($self->{fifodir}, 0755) or die "Can't create $self->{fifodir}: $!\n\n";
}
# Generate some names for the temporary audio and video files
($self->{tmp_a} = $self->{episode}->{filename}) =~ s/\.nuv$/.mp2/;
($self->{tmp_v} = $self->{episode}->{filename}) =~ s/\.nuv$/.m2v/;
# Here, we have to fork off a copy of mythtranscode
my $command = "nice -n 19 mythtranscode -p autodetect -c $self->{episode}->{channel} -s $self->{episode}->{start_time_sep} -f $self->{fifodir} --fifosync";
$command .= ' --honorcutlist' if ($self->{use_cutlist});
if ($DEBUG) {
print "\nmythtranscode command:\n\n$command\n";
}
else {
push @{$self->{children}}, fork_command($command);
}
# Sleep a bit to let mythtranscode start up
if (!$DEBUG) {
my $overload = 0;
while (++$overload < 30 && !(-e "$self->{fifodir}/audout" && -e "$self->{fifodir}/vidout")) {
sleep 1;
print "Waiting for mythtranscode to set up the fifos.\n";
}
unless (-e "$self->{fifodir}/audout" && -e "$self->{fifodir}/vidout") {
die "Waited too long for mythtranscode to create its fifos. Please try again.\n\n";
}
}
# Now we fork off a process to encode the audio
if ($Prog{mp2_encoder} =~ /\btoolame$/) {
$sample = $nuv_info{audio_sample_rate} / 1000;
$command = "nice -n 19 toolame -s $sample -m j -b $self->{a_bitrate} $self->{fifodir}/audout $self->{tmp_a}";
}
else {
$command = "nice -n 19 ffmpeg -f s16le -ar $nuv_info{audio_sample_rate} -ac 2 -i $self->{fifodir}/audout -vn -f wav -"
." | nice -n 19 mp2enc -b $self->{a_bitrate} -r $nuv_info{audio_sample_rate} -s -o $self->{tmp_a}";
}
if ($DEBUG) {
print "\ntoolame command:\n\n$command\n";
}
else {
push @{$self->{children}}, fork_command($command);
}
# And lastly, we fork off a process to encode the video
# Multiple CPU's? Let's multiprocess
$cpus = num_cpus();
# pulldown does NOT work - keeps complaining about unsupport fps even when it's already set to 29.97
#my $pulldown = 0;
# Build the command to rescale the image and encode the video
my $framerate;
$command = "nice -n 19 ffmpeg -f rawvideo -s $nuv_info{width}x$nuv_info{height} -r $nuv_info{fps} -i $self->{fifodir}/vidout -f yuv4mpegpipe -";
# Certain options for PAL
if ($nuv_info{fps} =~ /^2(?:5|4\.9)/) {
$command .= " | nice -n 19 yuvdenoise -r 16" if ($self->{noise_reduction});
$command .= " | nice -n 19 yuvscaler -v 0 -n p -M BICUBIC -O SVCD";
$framerate = 3;
}
# Other options for NTSC
else {
# SOMEDAY I'd like to be able to get 3:2 pulldown working properly....
#$command .= " | yuvkineco -F 1" if ($pulldown);
$command .= " | nice -n 19 yuvdenoise -r 16" if ($self->{noise_reduction});
$command .= " | nice -n 19 yuvscaler -v 0 -n n -M BICUBIC -O SVCD";
$framerate = 4;
}
# Finish building $command, and execute it
$command .= " | nice -n 19 mpeg2enc --format 5 --quantisation $self->{quantisation} --quantisation-reduction 2"
." --video-bitrate $self->{v_bitrate} --aspect 2 --frame-rate $framerate"
#.($pulldown ? ' --frame-rate 1 --3-2-pulldown' : " --frame-rate $framerate")
#." --interlace-mode 1 --motion-search-radius 24 --video-buffer 230"
." --interlace-mode 0 --motion-search-radius 24 --video-buffer 230"
." --nonvideo-bitrate $self->{a_bitrate} --sequence-length 795"
." --reduction-4x4 1 --reduction-2x2 1 --keep-hf"
.($cpus > 1 ? " --multi-thread $cpus" : '')
." -o $self->{tmp_v}";
if ($DEBUG) {
print "\nmpeg2enc command:\n\n$command\n";
}
else {
push @{$self->{children}}, fork_command($command);
}
# Wait for child processes to finish
1 while (wait > 0);
$self->{children} = undef;
# Multiplex the streams
my $safe_outfile = shell_escape($self->{outfile});
if ($Prog{mplexer} =~ /\btcmplex$/) {
$command = "nice -n 19 tcmplex -m s -i $self->{tmp_v} -p $self->{tmp_a} -o $safe_outfile";
}
else {
$command = "nice -n 19 mplex -f 5 $self->{tmp_v} $self->{tmp_a} -o $safe_outfile";
}
if ($DEBUG) {
print "\nmultiplex command:\n\n$command\n\n";
exit;
}
system($command);
}

sub cleanup {
my $self = shift;
return unless ($self->{started});
# Make sure any child processes also go away
if ($self->{children} && @{$self->{children}}) {
foreach my $child (@{$self->{children}}) {
kill('INT', $child);
}
1 while (wait > 0);
}
# Remove any temporary files
foreach my $file ("$self->{fifodir}/audout", "$self->{fifodir}/vidout", $self->{tmp_a}, $self->{tmp_v}) {
unlink $file if (-e $file);
}
rmdir $self->{fifodir} if (-e $self->{fifodir});
}

1; #return true
182 changes: 182 additions & 0 deletions trunk/export_VCD.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
package export_VCD;

# Load the nuv utilities
use nuv_utils;

# Make sure we have pointers to the main:: namespace for certain variables
*Prog = *main::Prog;
*gui = *main::gui;

sub new {
my $class = shift;
my $self = {
'name' => 'Export to VCD',
'enabled' => 1,
'started' => 0,
'fifodir' => "fifodir.$$",
'children' => [],
'errors' => undef,
'episode' => undef,
'savepath' => '.',
'outfile' => 'out.mpg',
'tmp_a' => 'out.mp2',
'tmp_v' => 'out.m2v',
'use_cutlist' => 0,
'noise_reduction' => 1,
@_ #allows user-specified attributes to override the defaults
};
bless($self, $class);
# Make sure that we have an mp2 encoder
$Prog{mp2_encoder} = find_program('toolame', 'mp2enc');
push @{$self->{errors}}, 'You need toolame or mp2enc to export an svcd.' unless ($Prog{mp2_encoder});
# 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_data {
my $self = shift;
my $default_filename;
# Get the save path
$self->{savepath} = $gui->query_savepath();
# Ask the user for the filename
if($self->{episode}->{show_name} ne 'Untitled' and $self->{episode}->{title} ne 'Untitled')
{
$default_filename = $self->{episode}->{show_name}.' - '.$self->{episode}->{title};
}
elsif($self->{episode}->{show_name} ne 'Untitled')
{
$default_filename = $self->{episode}->{show_name};
}
elsif($self->{episode}->{title} ne 'Untitled')
{
$default_filename = $self->{episode}->{title};
}

$self->{outfile} = $gui->query_filename($default_filename, 'mpg', $self->{savepath});
# Ask the user if he/she wants to use the cutlist
if ($self->{episode}->{cutlist} && $self->{episode}->{cutlist} =~ /\d/) {
$self->{use_cutlist} = $gui->query_text('Enable Myth cutlist?',
'yesno',
$self->{use_cutlist} ? 'Yes' : 'No');
}
else {
$gui->notify('No cutlist found. Hopefully this means that you already removed the commercials.');
}
# Ask the user if he/she wants noise reduction
$self->{noise_reduction} = $gui->query_text('Enable noise reduction (slower, but better results)?',
'yesno',
$self->{noise_reduction} ? 'Yes' : 'No');
# Do we want bin/cue files, or just an mpeg?
# nothing, at the moment.
}

sub execute {
my $self = shift;
# make sure that the fifo dir is clean
if (-e "$self->{fifodir}/vidout" || -e "$self->{fifodir}/audout") {
die "Possibly stale mythtranscode fifo's in $self->{fifodir}.\nPlease remove them before running nuvexport.\n\n";
}
# Gather any necessary data
$self->{episode} = shift;
$self->gather_data;
# Load nuv info
my %nuv_info = nuv_info($self->{episode}->{filename});
# Set this to true so that the cleanup routine actually runs
$self->{started} = 1;
# Create a directory for mythtranscode's fifo's
unless (-d $self->{fifodir}) {
mkdir($self->{fifodir}, 0755) or die "Can't create $self->{fifodir}: $!\n\n";
}
# Generate some names for the temporary audio and video files
($self->{tmp_a} = $self->{episode}->{filename}) =~ s/\.nuv$/.mp2/;
($self->{tmp_v} = $self->{episode}->{filename}) =~ s/\.nuv$/.m1v/;
# Here, we have to fork off a copy of mythtranscode
my $command = "nice -n 19 mythtranscode -p autodetect -c $self->{episode}->{channel} -s $self->{episode}->{start_time_sep} -f $self->{fifodir} --fifosync";
$command .= ' --honorcutlist' if ($self->{use_cutlist});
push @{$self->{children}}, fork_command($command);
# Sleep a bit to let mythtranscode start up
my $overload = 0;
while (++$overload < 30 && !(-e "$self->{fifodir}/audout" && -e "$self->{fifodir}/vidout")) {
sleep 1;
print "Waiting for mythtranscode to set up the fifos.\n";
}
unless (-e "$self->{fifodir}/audout" && -e "$self->{fifodir}/vidout") {
die "Waited too long for mythtranscode to create its fifos. Please try again.\n\n";
}
# Now we fork off a process to encode the audio
if ($Prog{mp2_encoder} =~ /\btoolame$/) {
$sample = $nuv_info{audio_sample_rate} / 1000;
$command = "nice -n 19 toolame -s $sample -m j -b 192 $self->{fifodir}/audout $self->{tmp_a}";
}
else {
$command = "nice -n 19 ffmpeg -f s16le -ar $nuv_info{audio_sample_rate} -ac 2 -i $self->{fifodir}/audout -vn -f wav -"
." | nice -n 19 mp2enc -b 192 -r $nuv_info{audio_sample_rate} -s -o $self->{tmp_a}";
}
push @{$self->{children}}, fork_command($command);
# And lastly, we fork off a process to encode the video
# Multiple CPU's? Let's multiprocess
$cpus = num_cpus();
# pulldown does NOT work - keeps complaining about unsupport fps even when it's already set to 29.97
#my $pulldown = 0;
# Build the command to rescale the image and encode the video
my $framerate;
$command = "nice -n 19 ffmpeg -f rawvideo -s $nuv_info{width}x$nuv_info{height} -r $nuv_info{fps} -i $self->{fifodir}/vidout -f yuv4mpegpipe -";
# Certain options for PAL
if ($nuv_info{fps} =~ /^2(?:5|4\.9)/) {
$command .= " | nice -n 19 yuvdenoise -r 16" if ($self->{noise_reduction});
$command .= " | nice -n 19 yuvscaler -v 0 -n p -M BICUBIC -O VCD";
$framerate = 3;
}
# Other options for NTSC
else {
# SOMEDAY I'd like to be able to get 3:2 pulldown working properly....
#$command .= " | yuvkineco -F 1" if ($pulldown);
$command .= " | nice -n 19 yuvdenoise -r 16" if ($self->{noise_reduction});
$command .= " | nice -n 19 yuvscaler -v 0 -n n -O VCD";
$framerate = 4;
}
# Finish building $command, and execute it
$command .= " | nice -n 19 mpeg2enc --format 1 --quantisation-reduction 2"
." --frame-rate $framerate -n n"
#.($pulldown ? ' --frame-rate 1 --3-2-pulldown' : " --frame-rate $framerate")
." --sequence-length 600"
." --reduction-4x4 1 --reduction-2x2 1 --keep-hf"
.($cpus > 1 ? " --multi-thread $cpus" : '')
." -o $self->{tmp_v}";
push @{$self->{children}}, fork_command($command);
# Wait for child processes to finish
1 while (wait > 0);
$self->{children} = undef;
# Multiplex the streams
my $safe_outfile = shell_escape($self->{outfile});
if ($Prog{mplexer} =~ /\btcmplex$/) {
system("nice -n 19 tcmplex -m v -i $self->{tmp_v} -p $self->{tmp_a} -o $safe_outfile");
}
else {
system("nice -n 19 mplex -f 1 $self->{tmp_v} $self->{tmp_a} -o $safe_outfile");
}
}

sub cleanup {
my $self = shift;
return unless ($self->{started});
# Make sure any child processes also go away
if ($self->{children} && @{$self->{children}}) {
foreach my $child (@{$self->{children}}) {
kill('INT', $child);
}
1 while (wait > 0);
}
# Remove any temporary files
foreach my $file ("$self->{fifodir}/audout", "$self->{fifodir}/vidout", $self->{tmp_a}, $self->{tmp_v}) {
unlink $file if (-e $file);
}
rmdir $self->{fifodir} if (-e $self->{fifodir});
}

1; #return true
137 changes: 137 additions & 0 deletions trunk/export_WMV.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package export_WMV;

# Load the nuv utilities
use nuv_utils;

# Make sure we have pointers to the main:: namespace for certain variables
*Prog = *main::Prog;
*gui = *main::gui;

sub new {
my $class = shift;
my $self = {
'name' => 'Export WMV',
'enabled' => 1,
'errors' => undef,
'episode' => undef,
'savepath' => '.',
'outfile' => 'out.wmv',
'use_cutlist' => 0,
'a_bitrate' => 64,
'v_bitrate' => 256,
'h_res' => 320,
'v_res' => 240,
'sql_file' => undef,
@_ #allows user-specified attributes to override the defaults
};
bless($self, $class);
# Any errors? disable this function
$self->{enabled} = 0 if ($self->{errors} && @{$self->{errors}} > 0);
# Return
return $self;
}

sub gather_data {
my $self = shift;
my $default_filename;
# Get the save path
$self->{savepath} = $gui->query_savepath();
# Ask the user for the filename
if($self->{episode}->{show_name} ne 'Untitled' and $self->{episode}->{title} ne 'Untitled')
{
$default_filename = $self->{episode}->{show_name}.' - '.$self->{episode}->{title};
}
elsif($self->{episode}->{show_name} ne 'Untitled')
{
$default_filename = $self->{episode}->{show_name};
}
elsif($self->{episode}->{title} ne 'Untitled')
{
$default_filename = $self->{episode}->{title};
}

$self->{outfile} = $gui->query_filename($default_filename, 'wmv', $self->{savepath});
# Ask the user if he/she wants to use the cutlist
if ($self->{episode}->{cutlist} && $self->{episode}->{cutlist} =~ /\d/) {
$self->{use_cutlist} = $gui->query_text('Enable Myth cutlist?',
'yesno',
$self->{use_cutlist} ? 'Yes' : 'No');
}
else {
$gui->notify('No cutlist found. Hopefully this means that you already removed the commercials.');
}
# Ask the user what audio bitrate he/she wants
my $a_bitrate = $gui->query_text('Audio bitrate?',
'int',
$self->{a_bitrate});
$self->{a_bitrate} = $a_bitrate;
# Ask the user what video bitrate he/she wants
my $v_bitrate = $gui->query_text('Video bitrate?',
'int',
$self->{v_bitrate});
$self->{v_bitrate} = $v_bitrate;
# Ask the user what horiz res he/she wants
my $h_res = $gui->query_text('Horizontal resolution?', 'int', $self->{h_res});
$self->{h_res} = $h_res;
# Ask the user what vert res he/she wants
my $v_res = $gui->query_text('Vertical resolution?', 'int', $self->{v_res});
$self->{v_res} = $v_res;
}

sub execute {
my $self = shift;
# make sure that the fifo dir is clean
if (-e 'fifodir/vidout' || -e 'fifodir/audout') {
die "Possibly stale mythtranscode fifo's in fifodir.\nPlease remove them before running nuvexport.\n\n";
}
# Gather any necessary data
$self->{episode} = shift;
$self->gather_data;
# Load nuv info
my %nuv_info = nuv_info($self->{episode}->{filename});
# Set this to true so that the cleanup routine actually runs
$self->{started} = 1;
# Create a directory for mythtranscode's fifo's
unless (-d 'fifodir') {
mkdir('fifodir', 0755) or die "Can't create fifodir: $!\n\n";
}
# Here, we have to fork off a copy of mythtranscode
my $command = "nice -n 19 mythtranscode -p autodetect -c $self->{episode}->{channel} -s $self->{episode}->{start_time_sep} -f fifodir";
$command .= ' --honorcutlist' if ($self->{use_cutlist});
push @{$self->{children}}, fork_command($command);
# Sleep a bit to let mythtranscode start up
my $overload = 0;
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";
}
# Now we fork off a process to encode everything
$safe_outfile = shell_escape($self->{outfile});
$command = "nice -n 19 ffmpeg -y -f s16le -ar $nuv_info{audio_sample_rate} -ac 2 -i fifodir/audout -f rawvideo -s $nuv_info{width}x$nuv_info{height} -r $nuv_info{fps} -i fifodir/vidout -b $self->{v_bitrate} -ab $self->{a_bitrate} -s $self->{h_res}x$self->{v_res} $safe_outfile";
push @{$self->{children}}, fork_command($command);
# Wait for child processes to finish
1 while (wait > 0);
$self->{children} = undef;
}

sub cleanup {
my $self = shift;
return unless ($self->{started});
# Make sure any child processes also go away
if ($self->{children} && @{$self->{children}}) {
foreach my $child (@{$self->{children}}) {
kill('INT', $child);
}
1 while (wait > 0);
}
# Remove any temporary files
foreach my $file ('fifodir/audout', 'fifodir/vidout') {
unlink $file if (-e $file);
}
rmdir 'fifodir' if (-e 'fifodir');
}

1; #return true
263 changes: 263 additions & 0 deletions trunk/gui_text.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
package gui_text;

use File::Path;

# Make sure we have pointers to the main:: namespace for certain variables
*Shows = *main::Shows;
*Functions = *main::Functions;
*num_shows = *main::num_shows;

sub new {
my $class = shift;
my $self = {
'query_stage' => 'show',
'show_choice' => '',
'episode_choice' => undef,
@_ #allows user-specified attributes to override the defaults
};
return bless($self, $class);
}

sub main_loop {
my $self = shift;
# Display the show list
while (1) {
# Clear the screen
system('clear');
# Stage "quit" means, well, quit...
last if ($self->stage eq 'quit');
# Are we asking the user which show to encode?
if (!$self->{show_choice} || $self->stage eq 'show') {
$self->query_shows;
}
# Nope. What about the episode choice?
elsif (!$self->{episode_choice} || $self->stage eq 'episode') {
$self->query_episodes;
}
# Time to decide what we want to do?
elsif ($self->stage eq 'function') {
$self->query_functions;
}
}
}

sub query_shows {
my $self = shift;
# Build the query
my $query = "\nYou have recorded the following shows:\n\n";
my ($count, @show_choices);
foreach $show (sort keys %Shows) {
$count++;
# Print out this choice, adjusting space where necessary
$query .= ' ';
$query .= ' ' if ($num_shows > 10 && $count < 10);
$query .= ' ' if ($num_shows > 100 && $count < 100);
$query .= "$count. ";
# print out the name of this show, and an episode count
my $num_episodes = @{$Shows{$show}};
$query .= "$show ($num_episodes episode".($num_episodes == 1 ? '' : 's').")\n";
$show_choices[$count-1] = $show;
}
$query .= "\n q. Quit\n\nChoose a show: ";
# Query the user
my $choice = $self->query_text($query, 'string', '');
# Quit?
return $self->stage('quit') if ($choice =~ /^\W*q/i);
# Move on to the next stage if the user chose a valid show
$choice =~ s/^\D*/0/s; # suppress warnings
if ($choice > 0 && $show_choices[$choice-1]) {
$self->{show_choice} = $show_choices[$choice-1];
$self->stage('episode');
}
}

sub query_episodes {
my $self = shift;
my $num_episodes = @{$Shows{$self->{show_choice}}};
# Define a newline + whitespace so we can tab out extra lines of episode description
my $newline = "\n" . ' ' x (4 + length $num_episodes);
# Build the query
my $query = "\nYou have recorded the following episodes of $self->{show_choice}:\n\n";
my ($count, @episode_choices);
foreach $episode (@{$Shows{$self->{show_choice}}}) {
$count++;
# Print out this choice, adjusting space where necessary
$query .= ' ';
$query .= ' ' if ($num_episodes > 10 && $count < 10);
$query .= ' ' if ($num_episodes > 100 && $count < 100);
$query .= "$count. ";
# print out the name of this show, and an episode count
$query .= join($newline, "$episode->{title} ($episode->{showtime})",
$episode->{description})."\n";
$episode_choices[$count-1] = $episode;
}
$query .= "\n r. Return to shows menu\n q. Quit\n\nChoose an episode: ";
# Query the user
my $choice = $self->query_text($query, 'string', '');
# Quit?
return $self->stage('quit') if ($choice =~ /^\W*q/i);
# Backing up a stage?
return $self->stage('show') if ($choice =~ /^\W*[rb]/i);
# Move on to the next stage if the user chose a valid episode
$choice =~ s/^\D*/0/s; # suppress warnings
if ($choice > 0 && $episode_choices[$choice-1]) {
$self->{episode_choice} = $episode_choices[$choice-1];
$self->stage('function');
}
}

sub query_functions {
my $self = shift;
# Build the query
my $query = "What would you like to do with your recording?\n\n"
." Show: $self->{show_choice}\n"
." Episode: $self->{episode_choice}->{title}\n"
." Airtime: $self->{episode_choice}->{showtime}\n\n";
# What are our function options?
my ($count);
foreach my $function (@Functions) {
$count++;
$query .= (' ' x (3 - length($count)))."$count. ".$function->{name};
$query .= ' (disabled)' unless ($function->{enabled});
$query .= "\n";
}
$query .= "\n r. Return to episode menu\n q. Quit\n\nChoose a function: ";
# Query the user
my $choice = $self->query_text($query, 'string', '');
# Quit?
return $self->stage('quit') if ($choice =~ /^\W*q/i);
# Backing up a stage?
return $self->stage('episode') if ($choice =~ /^\W*[rb]/i);
# Execute the chosen function, and then quit
$choice =~ s/^\D*/0/s; # suppress warnings
# No choice given?
if ($choice < 1) {
next;
}
# Make sure that this function is enabled
elsif ($choice < 1 || !$Functions[$choice-1]->{enabled}) {
if ($Functions[$choice-1]->{errors} && @{$Functions[$choice-1]->{errors}}) {
$self->notify("\n".join("\n", @{$Functions[$choice-1]->{errors}})."\n");
}
else {
$self->notify('Function "'.$Functions[$choice-1]->{name}."\" is disabled.\n");
}
$self->notify("Press ENTER to continue.\n");
<STDIN>;
}
elsif ($Functions[$choice-1]->{enabled}) {
$Functions[$choice-1]->execute($self->{episode_choice});
$self->stage('quit');
}
}

sub query_filename {
my $self = shift;
my $default = shift;
my $suffix = shift;
my $savepath = shift;
my $outfile = undef;
until ($outfile) {
$outfile = $self->query_text('Output filename? ', 'string', "$default.$suffix");
$outfile =~ s/(?:\.$suffix)?$/.$suffix/si;
if (-e "$savepath/$outfile") {
if (-f "$savepath/$outfile") {
unless ($self->query_text("$savepath/$outfile exists. Overwrite?", 'yesno', 'No')) {
$outfile = undef;
}
}
else {
$self->notify("$savepath/$outfile exists and is not a regular file; please choose another.");
$outfile = undef;
}
}
}
return $outfile;
}

sub query_savepath {
$self = shift;
# Where are we saving the files to?
my $savepath = undef;
until ($savepath) {
$savepath = $self->query_text('Where would you like to export the files to?', 'string', '.');;
$savepath =~ s/\/+$//s;
unless ($savepath eq '.') {
# Make sure this is a valid directory
if (-e $savepath && !-d $savepath) {
$savepath = undef;
$self->notify("$savepath exists, but is not a directory.");
}
# Doesn't exist - query the user to create it
elsif (!-e $savepath) {
my $create = $self->query_text("$savepath doesn't exist. Create it?", 'yesno', 'Yes');
if ($create) {
mkpath($savepath, 1, 0711) or die "Couldn't create $savepath: $!\n\n";
}
else {
$savepath = undef;
}
}
}
}
return $savepath;
}

sub query_text {
my $self = shift;
my $text = shift;
my $expect = shift;
my $default = shift;
my $default_extra = shift;
my $return = undef;
# Loop until we get a valid response
while (1) {
# Ask the question, get the answer
print $text,
($default ? " [$default]".($default_extra ? $default_extra : '').' '
: ' ');
chomp($return = <STDIN>);
# Nothing typed, is there a default value?
unless ($return =~ /\w/) {
next unless (defined $default);
$return = $default;
}
# Looking for a boolean/yesno response?
if ($expect =~ /yes.*no|bool/i) {
return $return =~ /^\W*[nf0]/i ? 0 : 1;
}
# Looking for an integer?
elsif ($expect =~ /int/i) {
$return =~ s/^\D*/0/;
if ($return != int($return)) {
print "Whole numbers only, please.\n";
next;
}
return $return;
}
# Looking for a float?
elsif ($expect =~ /float/i) {
$return =~ s/^\D*/0/;
return $return + 0;
}
# Well, then we must be looking for a string
else {
return $return;
}
}
}

sub notify {
my $self = shift;
print shift, "\n";
}

sub stage {
my $self = shift;
my $stage = shift;
$self->{query_stage} = $stage if (defined $stage);
$self->{query_stage} = 'show' unless ($self->{query_stage});
return $self->{query_stage};
}

1; #return true
11 changes: 11 additions & 0 deletions trunk/install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/sh

# First, install nuvexport itself
install -Dv -o root -g root -m 0755 nuvexport /usr/local/bin/nuvexport
install -Dv -o root -g root -m 0755 nuvinfo /usr/local/bin/nuvinfo

# Next, create the nuvexport shared directory
mkdir -pvm 0755 /usr/local/share/nuvexport

# Finally, install the other files
install -v -o root -g root -m 0644 *pm /usr/local/share/nuvexport
228 changes: 228 additions & 0 deletions trunk/nuv_utils.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
package nuv_utils;

use Exporter;
our @ISA = qw/ Exporter /;
our @EXPORT = qw/ generate_showtime find_program nuv_info num_cpus fork_command shell_escape mysql_escape Quit /;

# Returns a nicely-formatted timestamp from a specified time
sub generate_showtime {
$showtime = '';
# Get the
my ($year, $month, $day, $hour, $minute, $second) = @_;
$month = int($month);
$day = int($day);
# Get the current time, so we know whether or not to display certain fields (eg. year)
my ($this_second, $this_minute, $this_hour, $ignore, $this_month, $this_year) = localtime;
$this_year += 1900;
$this_month++;
# Default the meridian to AM
my $meridian = 'AM';
# Generate the showtime string
$showtime .= "$month/$day";
$showtime .= "/$year" unless ($year == $this_year);
if ($hour == 0) {
$hour = 12;
}
elsif ($hour > 12) {
$hour -= 12;
$meridian = 'PM';
}
$showtime .= ", $hour:$minute $meridian";
# Return
return $showtime;
}

# This searches the path for the specified programs, and returns the lowest-index-value program found
sub find_program {
# Load the programs, and get a count of the priorities
my(%programs, $num_programs);
foreach my $program (@_) {
$programs{$program} = ++$num_programs;
}
# No programs requested?
return undef unless ($num_programs > 0);
# Search for the program(s)
my %found;
foreach my $path (split(/:/, $ENV{PATH}), '.') {
foreach my $program (keys %programs) {
if (-e "$path/$program" && (!$found{name} || $programs{$program} < $programs{$found{name}})) {
$found{name} = $program;
$found{path} = $path;
}
# Leave early if we found the highest priority program
last if ($found{name} && $programs{$found{name}} == 1);
}
}
# Return
return undef unless ($found{path} && $found{name});
return $found{path}.'/'.$found{name};
}

# Opens a .nuv file and returns information about it
sub nuv_info {
my $file = shift;
my(%info, $buffer);
# open the file
open(DATA, "$main::video_dir/$file") or die "Can't open $file: $!\n\n";
# Read the file info header
read(DATA, $buffer, 72);
# Unpack the data structure
($info{finfo}, # "NuppelVideo" + \0
$info{version}, # "0.05" + \0
$info{width},
$info{height},
$info{desiredheight}, # 0 .. as it is
$info{desiredwidth}, # 0 .. as it is
$info{pimode}, # P .. progressive, I .. interlaced (2 half pics) [NI]
$info{aspect}, # 1.0 .. square pixel (1.5 .. e.g. width=480: width*1.5=720 for capturing for svcd material
$info{fps},
$info{videoblocks}, # count of video-blocks -1 .. unknown 0 .. no video
$info{audioblocks}, # count of audio-blocks -1 .. unknown 0 .. no audio
$info{textsblocks}, # count of text-blocks -1 .. unknown 0 .. no text
$info{keyframedist}
) = unpack('Z12 Z5 xxx i i i i a xxx d d i i i i', $buffer);
# Is this even a NUV file?
return mpeg_info($file) unless ($info{finfo} =~ /\w/);
# Perl occasionally over-reads on the previous read()
seek(DATA, 72, 0);
# Read and parse the first frame header
read(DATA, $buffer, 12);
my ($frametype,
$comptype,
$keyframe,
$filters,
$timecode,
$packetlength) = unpack('a a a a i i', $buffer);
# Parse the frame
die "Illegal nuv file format: $file\n\n" unless ($frametype eq 'D');
# Read some more stuff if we have to
read(DATA, $buffer, $packetlength) if ($packetlength);
# Read the remaining frame headers
while (12 == read(DATA, $buffer, 12)) {
# Parse the frame header
($frametype,
$comptype,
$keyframe,
$filters,
$timecode,
$packetlength) = unpack('a a a a i i', $buffer);
# Read some more stuff if we have to
read(DATA, $buffer, $packetlength) if ($packetlength);
# Look for the audio frame
if ($frametype eq 'X') {
my $frame_version;
($frame_version,
$info{video_fourcc},
$info{audio_fourcc},
$info{audio_sample_rate},
$info{audio_bits_per_sample},
$info{audio_channels},
$info{audio_compression_ratio},
$info{audio_quality},
$info{rtjpeg_quality},
$info{rtjpeg_luma_filter},
$info{rtjpeg_chroma_filter},
$info{lavc_bitrate},
$info{lavc_qmin},
$info{lavc_qmax},
$info{lavc_maxqdiff},
$info{seektable_offset},
$info{keyframeadjust_offset}
) = unpack('iiiiiiiiiiiiiiill', $buffer);
# Found the audio data we want - time to leave
last;
}
# Done reading frames - let's leave
else {
last;
}
}
# Close the file
close DATA;
# Make sure some things are actually numbers
$info{width} += 0;
$info{height} += 0;
# Return
return %info;
}

# Uses one of two mpeg info programs to load data about mpeg-based nuv files
sub mpeg_info {
my $file = "$main::video_dir/".shift;
$file =~ s/'/\\'/sg;
my %info;
# First, we check for the existence of an mpeg info program
my $program = find_program('tcprobe', 'mpgtx');
# Nothing found? Die
die "You need tcprobe (transcode) or mpgtx to use this script on mpeg-based nuv files.\n\n" unless ($program);
# Grab tcprobe info
if ($program =~ /tcprobe$/) {
my $data = `$program -i '$file'`;
($info{width}, $info{height}) = $data =~ /frame\s+size:\s+-g\s+(\d+)x(\d+)\b/m;
($info{fps}) = $data =~ /frame\s+rate:\s+-f\s+(\d+(?:\.\s+)?)\b/m;
($info{audio_sample_rate}) = $data =~ /audio\s+track:.+?-e\s+(\d+)\b/m;
}
# Grab tcmplex info
elsif ($program =~ /mpgtx$/) {
my $data = `$program -i '$file'`;
($info{width}, $info{height}, $info{fps}) = $data =~ /\bSize\s+\[(\d+)\s*x\s*(\d+)\]\s+(\d+(?:\.\d+)?)\s*fps/m;
($info{audio_sample_rate}) = $data =~ /\b(\d+)\s*Hz/m;
}
# Return
return %info;
}

# Queries /proc/cpuinfo to find out how many cpu's are available on this machine
sub num_cpus {
my $cpuinfo = `cat /proc/cpuinfo`;
$num = 0;
while ($cpuinfo =~ /^processor\s*:\s*\d+/mg) {
$num++;
}
return $num;
}

# This subroutine forks and executes one system command - nothing fancy
sub fork_command {
my $command = shift;
# Fork and return the child's pid
my $pid = undef;
if ($pid = fork) {
return $pid
}
# $pid defined means that this is now the forked child
elsif (defined $pid) {
system($command);
# Don't forget to exit, or we'll keep going back into places that the child shouldn't play
exit(0);
}
# Couldn't fork, guess we have to quit
die "Couldn't fork: $!\n\n$command\n\n";
}

sub shell_escape {
$file = shift;
$file =~ s/(["\$])/\\$1/sg;
return "\"$file\"";
}

sub mysql_escape {
$string = shift;
return 'NULL' unless (defined $string);
$string =~ s/'/\\'/sg;
return "'$string'";
}

sub Quit {
# Allow the functions to clean up after themselves
if (@main::Functions) {
foreach $function (@main::Functions) {
$function->cleanup;
}
}
# Print a nice goodbye message, and leave
print "\nThanks for using nuvexport!\n\n";
exit;
}

1; #return true
1,357 changes: 35 additions & 1,322 deletions trunk/nuvexport

Large diffs are not rendered by default.