| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| #!/usr/bin/perl -w | ||
| #Last Updated: 2004.09.26 (gjhurlbu) | ||
| # | ||
| # export::MPEG2_cut | ||
| # Maintained by Gavin Hurlbut <gjhurlbu@gmail.com> | ||
| # | ||
|
|
||
| package export::MPEG2_cut; | ||
| use base 'export::generic'; | ||
|
|
||
| # Load the myth and nuv utilities, and make sure we're connected to the database | ||
| use nuv_export::shared_utils; | ||
| use nuv_export::ui; | ||
| use mythtv::db; | ||
| use mythtv::recordings; | ||
|
|
||
| # Load the following extra parameters from the commandline | ||
|
|
||
| sub new { | ||
| my $class = shift; | ||
| my $self = { | ||
| 'cli' => qr/\bmpeg2cut\b/i, | ||
| 'name' => 'MPEG2->MPEG2 cut only', | ||
| 'enabled' => 1, | ||
| 'errors' => [], | ||
| # Exporter-related settings | ||
| }; | ||
| bless($self, $class); | ||
| # Make sure we have tcprobe | ||
| $Prog{'tcprobe'} = find_program('tcprobe'); | ||
| push @{$self->{'errors'}}, 'You need tcprobe to use this.' unless ($Prog{'tcprobe'}); | ||
| # Make sure we have lvemux | ||
| $Prog{'mplexer'} = find_program('lvemux'); | ||
| push @{$self->{'errors'}}, 'You need lvemux to use this.' unless ($Prog{'mplexer'}); | ||
| # Make sure we have avidemux2 | ||
| $Prog{'avidemux'} = find_program('avidemux2'); | ||
| push @{$self->{'errors'}}, 'You need avidemux2 to use this.' unless ($Prog{'avidemux'}); | ||
| # Make sure we have mpeg2cut | ||
| $Prog{'mpeg2cut'} = find_program('mpeg2cut'); | ||
| push @{$self->{'errors'}}, 'You need mpeg2cut to use this.' unless ($Prog{'mpeg2cut'}); | ||
|
|
||
| # Any errors? disable this function | ||
| $self->{'enabled'} = 0 if ($self->{'errors'} && @{$self->{'errors'}} > 0); | ||
| # Return | ||
| return $self; | ||
| } | ||
|
|
||
| sub gather_settings { | ||
| my $self = shift; | ||
| # No parameters to setup | ||
| } | ||
|
|
||
| sub export { | ||
| my $self = shift; | ||
| my $episode = shift; | ||
| # Load nuv info | ||
| load_finfo($episode); | ||
|
|
||
| # Check for an MPEG2 recording with a cutlist | ||
| if (!$episode->{'finfo'}{'is_mpeg'}) { | ||
| print 'Not an MPEG recording, moving on'; | ||
| return 0; | ||
| } | ||
| if (!($episode->{'cutlist'} && $episode->{'cutlist'} =~ /\d/)) { | ||
| print 'No cutlist found. This won\'t do!'; | ||
| return 0; | ||
| } | ||
|
|
||
| # Generate some names for the temporary audio and video files | ||
| my $safe_outfile = shell_escape($self->{'path'}.'/'.$episode->{'outfile'}); | ||
|
|
||
| my $command = "mpeg2cut $episode->{'filename'} $safe_outfile.mpg $episode->{'lastgop'} "; | ||
|
|
||
| @cuts = split("\n",$episode->{'cutlist'}); | ||
| my @skiplist; | ||
| foreach my $cut (@cuts) { | ||
| push @skiplist, (split(" - ", $cut))[0]; | ||
| push @skiplist, (split(" - ", $cut))[1]; | ||
| } | ||
|
|
||
| my $cutnum = 0; | ||
| if ($skiplist[0] ne 0) { | ||
| $command .= "-"; | ||
| $cutnum = 1; | ||
| } | ||
|
|
||
| foreach my $cut (@skiplist) { | ||
| if ($cutnum eq 0) { | ||
| if( $cut ne 0 ) { | ||
| $cutnum = 1; | ||
| $cut++; | ||
| $command .= "$cut-"; | ||
| } | ||
| } else { | ||
| $cutnum = 0; | ||
| $cut--; | ||
| $command .= "$cut "; | ||
| } | ||
| } | ||
|
|
||
| system($command); | ||
| } | ||
|
|
||
| 1; #return true | ||
|
|
||
| # vim:ts=4:sw=4:ai:et:si:sts=4 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,149 @@ | ||
| #!/usr/bin/perl -w | ||
| #Last Updated: 2004.09.26 (xris) | ||
| # | ||
| # export::NUV_SQL | ||
| # Maintained by Chris Petersen <mythtv@forevermore.net> | ||
| # | ||
|
|
||
| package export::NUV_SQL; | ||
|
|
||
| use File::Copy; | ||
| use File::Basename; | ||
| use DBI; | ||
| use Term::ANSIColor; | ||
|
|
||
| # Load the myth and nuv utilities, and make sure we're connected to the database | ||
| use nuv_export::shared_utils; | ||
| use nuv_export::ui; | ||
| use mythtv::db; | ||
| use mythtv::recordings; | ||
|
|
||
| sub new { | ||
| my $class = shift; | ||
| my $self = { | ||
| 'cli' => qr/\bnuv[\-_]sql\b/i, | ||
| 'name' => 'Export to .nuv and .sql', | ||
| 'enabled' => 1, | ||
| 'errors' => [], | ||
| # Delete original files? | ||
| 'delete' => 0, | ||
| }; | ||
| bless($self, $class); | ||
|
|
||
| # Any errors? disable this function | ||
| $self->{'enabled'} = 0 if ($self->{'errors'} && @{$self->{'errors'}} > 0); | ||
| # Return | ||
| return $self; | ||
| } | ||
|
|
||
| sub gather_settings { | ||
| my $self = shift; | ||
| # Let the user know what's going on | ||
| print "\nYou have chosen to extract the .nuv.\n" | ||
| ."This will extract it from the MythTV database into .nuv and .sql \n" | ||
| ."files to import into another MythTV installation.\n\n"; | ||
| # Make sure the user knows what he/she is doing | ||
| $self->{'delete'} = query_text("Do you want to removed it from this server when finished?", | ||
| 'yesno', | ||
| $self->{'delete'} ? 'Yes' : 'No'); | ||
| # Make EXTRA sure | ||
| if ($self->{'delete'}) { | ||
| $self->{'delete'} = query_text("\nAre you ".colored('sure', 'bold').' you want to remove it from this server?', | ||
| 'yesno', | ||
| 'No'); | ||
| } | ||
| # Load the save path, if requested | ||
| $self->{'path'} = query_savepath(); | ||
| } | ||
|
|
||
| sub export { | ||
| my $self = shift; | ||
| my $episode = shift; | ||
| # Make sure we have finfo | ||
| load_finfo($episode); | ||
| # Load the three files we'll be using | ||
| my $txt_file = basename($episode->{'filename'}, '.nuv') . '.txt'; | ||
| my $sql_file = basename($episode->{'filename'}, '.nuv') . '.sql'; | ||
| my $nuv_file = basename($episode->{'filename'}); | ||
| # Create a txt file with descriptive info in it | ||
| open(DATA, ">$self->{'path'}/$txt_file") or die "Can't create $self->{'path'}/$txt_file: $!\n\n"; | ||
| print DATA ' Show: ', $episode->{'show_name'}, "\n", | ||
| ' Episode: ', $episode->{'title'}, "\n", | ||
| ' Recorded: ', $episode->{'showtime'}, "\n", | ||
| 'Description: ', wrap($episode->{'description'}, 64,'', '', "\n "), "\n", | ||
| "\n", | ||
| ' Orig. File: ', $episode->{'filename'}, "\n", | ||
| ' Type: ', $episode->{'finfo'}{'video_type'}, "\n", | ||
| ' Dimensions: ', $episode->{'finfo'}{'width'}.'x'.$episode->{'finfo'}{'height'}, "\n", | ||
| ; | ||
| close DATA; | ||
| # Start saving the SQL | ||
| open(DATA, ">$self->{'path'}/$sql_file") or die "Can't create $self->{'path'}/$sql_file: $!\n\n"; | ||
| # Define some query-related variables | ||
| my ($q, $sh); | ||
| # Load and save the related database info | ||
| print DATA "USE mythconverg;\n\n"; | ||
| foreach $table ('recorded', 'oldrecorded', 'recordedmarkup') { | ||
| $q = "SELECT * FROM $table WHERE chanid=? AND starttime=?"; | ||
| $sh = $dbh->prepare($q); | ||
| $sh->execute($episode->{'channel'}, $episode->{'start_time'}) | ||
| or die "Count not execute ($q): $!\n\n"; | ||
| my $count = 0; | ||
| my @keys = undef; | ||
| while (my $row = $sh->fetchrow_hashref) { | ||
| # First row - let's add the insert statement; | ||
| if ($count++ == 0) { | ||
| @keys = keys(%$row); | ||
| print DATA "INSERT INTO $table (", join(', ', @keys), ") VALUES\n\t("; | ||
| } | ||
| else { | ||
| print DATA ",\n\t("; | ||
| } | ||
| # Print the data | ||
| my $count2 = 0; | ||
| foreach $key (@keys) { | ||
| print DATA ', ' if ($count2++); | ||
| print DATA mysql_escape($row->{$key}); | ||
| } | ||
| print DATA ')'; | ||
| } | ||
| print DATA ";\n\n"; | ||
| } | ||
| # Done saving the database info | ||
| close DATA; | ||
| # Rename/move the file | ||
| if ($self->{'delete'}) { | ||
| print "\nMoving $episode->{'filename'} to $self->{'path'}/$nuv_file\n"; | ||
| move("$episode->{'filename'}", "$self->{'path'}/$nuv_file") | ||
| or die "Couldn't move specified .nuv file: $!\n\n"; | ||
| # Remove the entry from recordedmarkup | ||
| $q = 'DELETE FROM recordedmarkup WHERE chanid=? AND starttime=?'; | ||
| $sh = $dbh->prepare($q); | ||
| $sh->execute($episode->{'channel'}, $episode->{'start_time'}) | ||
| or die "Could not execute ($q): $!\n\n"; | ||
| # Remove this entry from the database | ||
| $q = 'DELETE FROM recorded WHERE chanid=? AND starttime=? AND endtime=?'; | ||
| $sh = $dbh->prepare($q); | ||
| $sh->execute($episode->{'channel'}, $episode->{'start_time'}, $episode->{'end_time'}) | ||
| or die "Could not execute ($q): $!\n\n"; | ||
| # Tell the other nodes that changes have been made | ||
| $q = 'UPDATE settings SET data="yes" WHERE value="RecordChanged"'; | ||
| $sh = $dbh->prepare($q); | ||
| $sh->execute() | ||
| or die "Could not execute ($q): $!\n\n"; | ||
| } | ||
| # Copy the file | ||
| else { | ||
| print "\nCopying $episode->{'filename'} to $self->{'path'}/$nuv_file\n"; | ||
| copy("$episode->{'filename'}", "$self->{'path'}/$nuv_file") | ||
| or die "Couldn't copy specified .nuv file: $!\n\n"; | ||
| } | ||
| } | ||
|
|
||
| sub cleanup { | ||
| # Nothing to do here | ||
| } | ||
|
|
||
| 1; #return true | ||
|
|
||
| # vim:ts=4:sw=4:ai:et:si:sts=4 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,139 @@ | ||
| #!/usr/bin/perl -w | ||
| #Last Updated: 2004.09.26 (xris) | ||
| # | ||
| # export::SVCD | ||
| # Maintained by Chris Petersen <mythtv@forevermore.net> | ||
| # | ||
|
|
||
| package export::SVCD; | ||
| use base 'export::transcode'; | ||
|
|
||
| # Load the myth and nuv utilities, and make sure we're connected to the database | ||
| use nuv_export::shared_utils; | ||
| use nuv_export::ui; | ||
| use mythtv::db; | ||
| use mythtv::recordings; | ||
|
|
||
| # Load the following extra parameters from the commandline | ||
| $cli_args{'quantisation|q=i'} = 1; # Quantisation | ||
| $cli_args{'a_bitrate|a=i'} = 1; # Audio bitrate | ||
| $cli_args{'v_bitrate|v=i'} = 1; # Video bitrate | ||
|
|
||
| sub new { | ||
| my $class = shift; | ||
| my $self = { | ||
| 'cli' => qr/\bsvcd\b/i, | ||
| 'name' => 'Export to SVCD', | ||
| 'enabled' => 1, | ||
| 'errors' => [], | ||
| # Transcode-related settings | ||
| 'denoise' => 1, | ||
| 'deinterlace' => 1, | ||
| # SVCD-specific settings | ||
| 'quantisation' => 5, # 4 through 6 is probably right... | ||
| 'a_bitrate' => 192, | ||
| 'v_bitrate' => 2500, | ||
| }; | ||
| bless($self, $class); | ||
|
|
||
| # Initialize and check for transcode | ||
| $self->init_transcode(); | ||
| # Make sure that we have an mplexer | ||
| $Prog{'mplexer'} = find_program('tcmplex', 'mplex'); | ||
| push @{$self->{'errors'}}, 'You need tcmplex or mplex to export an svcd.' unless ($Prog{'mplexer'}); | ||
|
|
||
| # Any errors? disable this function | ||
| $self->{'enabled'} = 0 if ($self->{'errors'} && @{$self->{'errors'}} > 0); | ||
| # Return | ||
| return $self; | ||
| } | ||
|
|
||
| sub gather_settings { | ||
| my $self = shift; | ||
| # Load the parent module's settings | ||
| $self->SUPER::gather_settings(); | ||
| # Ask the user what audio bitrate he/she wants | ||
| $self->{'a_bitrate'} = $Args{'a_bitrate'} if ($Args{'a_bitrate'}); | ||
| if (!$Args{'a_bitrate'} || $Args{'confirm'}) { | ||
| while (1) { | ||
| my $a_bitrate = query_text('Audio bitrate?', | ||
| 'int', | ||
| $self->{'a_bitrate'}); | ||
| if ($a_bitrate < 64) { | ||
| print "Too low; please choose a bitrate >= 64.\n"; | ||
| } | ||
| elsif ($a_bitrate > 384) { | ||
| print "Too high; please choose a bitrate <= 384.\n"; | ||
| } | ||
| else { | ||
| $self->{'a_bitrate'} = $a_bitrate; | ||
| last; | ||
| } | ||
| } | ||
| } | ||
| # Ask the user what video bitrate he/she wants, or calculate the max bitrate (2756 max, though we round down a bit since some dvd players can't handle the max) | ||
| # Then again, mpeg2enc seems to have trouble with bitrates > 2500 | ||
| my $max_v_bitrate = 2742 - $self->{'a_bitrate'}; | ||
| $self->{'v_bitrate'} = $Args{'v_bitrate'} if ($Args{'v_bitrate'}); | ||
| if (!$Args{'v_bitrate'} || $Args{'confirm'}) { | ||
| $self->{'v_bitrate'} = ($max_v_bitrate < 2500) ? 2742 - $self->{'a_bitrate'} : 2500; | ||
| while (1) { | ||
| my $v_bitrate = query_text('Maximum video bitrate for VBR?', | ||
| 'int', | ||
| $self->{'v_bitrate'}); | ||
| if ($v_bitrate < 1000) { | ||
| print "Too low; please choose a bitrate >= 1000.\n"; | ||
| } | ||
| elsif ($v_bitrate > $max_v_bitrate) { | ||
| print "Too high; please choose a bitrate <= $self->{'v_bitrate'}.\n"; | ||
| } | ||
| else { | ||
| $self->{'v_bitrate'} = $v_bitrate; | ||
| last; | ||
| } | ||
| } | ||
| } | ||
| # Ask the user what vbr quality (quantisation) he/she wants - 2..31 | ||
| $self->{'quantisation'} = $Args{'quantisation'} if ($Args{'quantisation'}); | ||
| if (!$Args{'quantisation'} || $Args{'confirm'}) { | ||
| while (1) { | ||
| my $quantisation = query_text('VBR quality/quantisation (2-31)?', 'float', $self->{'quantisation'}); | ||
| if ($quantisation < 2) { | ||
| print "Too low; please choose a number between 2 and 31.\n"; | ||
| } | ||
| elsif ($quantisation > 31) { | ||
| print "Too high; please choose a number between 2 and 31\n"; | ||
| } | ||
| else { | ||
| $self->{'quantisation'} = $quantisation; | ||
| last; | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| sub export { | ||
| my $self = shift; | ||
| my $episode = shift; | ||
| # Load nuv info | ||
| load_finfo($episode); | ||
| # PAL or NTSC? | ||
| my $size = ($episode->{'finfo'}{'fps'} =~ /^2(?:5|4\.9)/) ? '480x576' : '480x480'; | ||
| # Build the transcode string | ||
| $self->{'transcode_xtra'} = " -y mpeg2enc,mp2enc -Z $size" | ||
| .' -F 5,"-q '.$self->{'quantisation'}.'"' | ||
| .' -w '.$self->{'v_bitrate'} | ||
| .' -E 44100 -b '.$self->{'a_bitrate'}; | ||
| # Add the temporary files that will need to be deleted | ||
| push @tmpfiles, "$self->{'path'}/$episode->{'outfile'}.m2v", "$self->{'path'}/$episode->{'outfile'}.mpa"; | ||
| # Execute the parent method | ||
| $self->SUPER::export($episode); | ||
| # Multiplex the streams | ||
| my $safe_outfile = shell_escape($self->{'path'}.'/'.$episode->{'outfile'}); | ||
| $command = "nice -n 19 mplex -f 5 $safe_outfile.m2v $safe_outfile.mpa -o $safe_outfile.mpg"; | ||
| system($command); | ||
| } | ||
|
|
||
| 1; #return true | ||
|
|
||
| # vim:ts=4:sw=4:ai:et:si:sts=4 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| #!/usr/bin/perl -w | ||
| #Last Updated: 2004.09.26 (xris) | ||
| # | ||
| # export::VCD | ||
| # Maintained by Gavin Hurlbut <gjhurlbu@gmail.com> | ||
| # | ||
|
|
||
| package export::VCD; | ||
| use base 'export::transcode'; | ||
|
|
||
| # Load the myth and nuv utilities, and make sure we're connected to the database | ||
| use nuv_export::shared_utils; | ||
| use nuv_export::ui; | ||
| use mythtv::db; | ||
| use mythtv::recordings; | ||
|
|
||
| # Load the following extra parameters from the commandline | ||
|
|
||
| sub new { | ||
| my $class = shift; | ||
| my $self = { | ||
| 'cli' => qr/\bvcd\b/i, | ||
| 'name' => 'Export to VCD', | ||
| 'enabled' => 1, | ||
| 'errors' => [], | ||
| # Transcode-related settings | ||
| 'denoise' => 1, | ||
| 'deinterlace' => 1, | ||
| }; | ||
| bless($self, $class); | ||
|
|
||
| # Initialize and check for transcode | ||
| $self->init_transcode(); | ||
| # Make sure that we have an mplexer | ||
| $Prog{'mplexer'} = find_program('tcmplex', 'mplex'); | ||
| push @{$self->{'errors'}}, 'You need tcmplex or mplex to export a vcd.' unless ($Prog{'mplexer'}); | ||
|
|
||
| # Any errors? disable this function | ||
| $self->{'enabled'} = 0 if ($self->{'errors'} && @{$self->{'errors'}} > 0); | ||
| # Return | ||
| return $self; | ||
| } | ||
|
|
||
| sub gather_settings { | ||
| my $self = shift; | ||
| # Load the parent module's settings | ||
| $self->SUPER::gather_settings(); | ||
| } | ||
|
|
||
| sub export { | ||
| my $self = shift; | ||
| my $episode = shift; | ||
| # Load nuv info | ||
| load_finfo($episode); | ||
| # PAL or NTSC? | ||
| my $size = ($episode->{'finfo'}{'fps'} =~ /^2(?:5|4\.9)/) ? '352x288' : '352x240'; | ||
| # Build the transcode string | ||
| $self->{'transcode_xtra'} = " -y mpeg2enc,mp2enc -Z $size" | ||
| .' -F 1 -E 44100 -b 224'; | ||
| # Add the temporary files that will need to be deleted | ||
| push @tmpfiles, "$self->{'path'}/$episode->{'outfile'}.m1v", "$self->{'path'}/$episode->{'outfile'}.mpa"; | ||
| # Execute the parent method | ||
| $self->SUPER::export($episode); | ||
| # Multiplex the streams | ||
| my $safe_outfile = shell_escape($self->{'path'}.'/'.$episode->{'outfile'}); | ||
| $command = "nice -n 19 mplex -f 1 $safe_outfile.m1v $safe_outfile.mpa -o $safe_outfile.mpg"; | ||
| system($command); | ||
| } | ||
|
|
||
| 1; #return true | ||
|
|
||
| # vim:ts=4:sw=4:ai:et:si:sts=4 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,115 @@ | ||
| #!/usr/bin/perl -w | ||
| #Last Updated: 2004.09.27 (xris) | ||
| # | ||
| # export::WMV | ||
| # Maintained by Gavin Hurlbut <gjhurlbu@gmail.com> | ||
| # | ||
|
|
||
| package export::WMV; | ||
| use base 'export::ffmpeg'; | ||
|
|
||
| # Load the myth and nuv utilities, and make sure we're connected to the database | ||
| use nuv_export::shared_utils; | ||
| use nuv_export::ui; | ||
| use mythtv::db; | ||
| use mythtv::recordings; | ||
|
|
||
| # Load the following extra parameters from the commandline | ||
| $cli_args{'a_bitrate|a=i'} = 1; # Audio bitrate | ||
| $cli_args{'v_bitrate|v=i'} = 1; # Video bitrate | ||
| $cli_args{'height|v_res|h=i'} = 1; # Height | ||
| $cli_args{'width|h_res|w=i'} = 1; # Width | ||
|
|
||
| sub new { | ||
| my $class = shift; | ||
| my $self = { | ||
| 'cli' => qr/\bwmv\b/i, | ||
| 'name' => 'Export to WMV', | ||
| 'enabled' => 1, | ||
| 'errors' => [], | ||
| # ffmpeg-related settings | ||
| 'noise_reduction' => 1, | ||
| 'deinterlace' => 1, | ||
| # WMV-specific settings | ||
| 'a_bitrate' => 64, | ||
| 'v_bitrate' => 256, | ||
| 'width' => 320, | ||
| 'height' => 240, | ||
| }; | ||
| bless($self, $class); | ||
|
|
||
| # Initialize and check for ffmpeg | ||
| $self->init_ffmpeg(); | ||
|
|
||
| # Any errors? disable this function | ||
| $self->{'enabled'} = 0 if ($self->{'errors'} && @{$self->{'errors'}} > 0); | ||
| # Return | ||
| return $self; | ||
| } | ||
|
|
||
| sub gather_settings { | ||
| my $self = shift; | ||
| # Load the parent module's settings | ||
| $self->SUPER::gather_settings(); | ||
|
|
||
| # Audio Bitrate | ||
| if ($Args{'a_bitrate'}) { | ||
| $self->{'a_bitrate'} = $Args{'a_bitrate'}; | ||
| die "Audio bitrate must be > 0\n" unless ($Args{'a_bitrate'} > 0); | ||
| } | ||
| else { | ||
| $self->{'a_bitrate'} = query_text('Audio bitrate?', | ||
| 'int', | ||
| $self->{'a_bitrate'}); | ||
| } | ||
| # Ask the user what video bitrate he/she wants | ||
| if ($Args{'v_bitrate'}) { | ||
| die "Video bitrate must be > 0\n" unless ($Args{'v_bitrate'} > 0); | ||
| $self->{'v_bitrate'} = $Args{'v_bitrate'}; | ||
| } | ||
| elsif ($self->{'multipass'} || !$self->{'vbr'}) { | ||
| # make sure we have v_bitrate on the commandline | ||
| $self->{'v_bitrate'} = query_text('Video bitrate?', | ||
| 'int', | ||
| $self->{'v_bitrate'}); | ||
| } | ||
| # Ask the user what resolution he/she wants | ||
| if ($Args{'width'}) { | ||
| die "Width must be > 0\n" unless ($Args{'width'} > 0); | ||
| $self->{'width'} = $Args{'width'}; | ||
| } | ||
| else { | ||
| $self->{'width'} = query_text('Width?', | ||
| 'int', | ||
| $self->{'width'}); | ||
| } | ||
| if ($Args{'height'}) { | ||
| die "Height must be > 0\n" unless ($Args{'height'} > 0); | ||
| $self->{'height'} = $Args{'height'}; | ||
| } | ||
| else { | ||
| $self->{'height'} = query_text('Height?', | ||
| 'int', | ||
| $self->{'height'}); | ||
| } | ||
| } | ||
|
|
||
| sub export { | ||
| my $self = shift; | ||
| my $episode = shift; | ||
| # Load nuv info | ||
| load_finfo($episode); | ||
| # Build the ffmpeg string | ||
| $self->{'ffmpeg_xtra'} = " -b " . $self->{'v_bitrate'} | ||
| . " -vcodec msmpeg4" | ||
| . " -ab " . $self->{'a_bitrate'} | ||
| . " -acodec mp3" | ||
| . " -s " . $self->{'width'} . "x" . $self->{'height'} | ||
| . " -f asf"; | ||
| # Execute the parent method | ||
| $self->SUPER::export($episode, ".wmv"); | ||
| } | ||
|
|
||
| 1; #return true | ||
|
|
||
| # vim:ts=4:sw=4:ai:et:si:sts=4 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,193 @@ | ||
| #!/usr/bin/perl -w | ||
| #Last Updated: 2004.09.26 (xris) | ||
| # | ||
| # export::XviD | ||
| # Maintained by Chris Petersen <mythtv@forevermore.net> | ||
| # | ||
|
|
||
| package export::XviD; | ||
| use base 'export::transcode'; | ||
|
|
||
| # Load the myth and nuv utilities, and make sure we're connected to the database | ||
| use nuv_export::shared_utils; | ||
| use nuv_export::ui; | ||
| use mythtv::db; | ||
| use mythtv::recordings; | ||
|
|
||
| # Load the following extra parameters from the commandline | ||
| $cli_args{'quantisation|q=i'} = 1; # Quantisation | ||
| $cli_args{'a_bitrate|a=i'} = 1; # Audio bitrate | ||
| $cli_args{'v_bitrate|v=i'} = 1; # Video bitrate | ||
| $cli_args{'height|v_res|h=i'} = 1; # Height | ||
| $cli_args{'width|h_res|w=i'} = 1; # Width | ||
| $cli_args{'multipass'} = 1; # Two-pass encoding | ||
|
|
||
| sub new { | ||
| my $class = shift; | ||
| my $self = { | ||
| 'cli' => qr/\bxvid\b/i, | ||
| 'name' => 'Export to XviD', | ||
| 'enabled' => 1, | ||
| 'errors' => [], | ||
| # Transcode-related settings | ||
| 'denoise' => 1, | ||
| 'deinterlace' => 1, | ||
| # VBR-specific settings | ||
| 'vbr' => 1, # This enables vbr, and the multipass/quantisation options | ||
| 'multipass' => 0, # You get multipass or quantisation, multipass will override | ||
| 'quantisation' => 6, # 4 through 6 is probably right... | ||
| # Other video options | ||
| 'a_bitrate' => 128, | ||
| 'v_bitrate' => 960, # Remember, quantisation overrides video bitrate | ||
| 'width' => 624, | ||
| }; | ||
| bless($self, $class); | ||
|
|
||
| # Initialize and check for transcode | ||
| $self->init_transcode(); | ||
| # Make sure that we have an mplexer | ||
| $Prog{'mplexer'} = find_program('tcmplex', 'mplex'); | ||
| push @{$self->{'errors'}}, 'You need tcmplex or mplex to export an svcd.' unless ($Prog{'mplexer'}); | ||
|
|
||
| # Any errors? disable this function | ||
| $self->{'enabled'} = 0 if ($self->{'errors'} && @{$self->{'errors'}} > 0); | ||
| # Return | ||
| return $self; | ||
| } | ||
|
|
||
| sub gather_settings { | ||
| my $self = shift; | ||
| # Load the parent module's settings | ||
| $self->SUPER::gather_settings(); | ||
| # Audio Bitrate | ||
| if ($Args{'a_bitrate'}) { | ||
| $self->{'a_bitrate'} = $Args{'a_bitrate'}; | ||
| die "Audio bitrate must be > 0\n" unless ($Args{'a_bitrate'} > 0); | ||
| } | ||
| else { | ||
| $self->{'a_bitrate'} = query_text('Audio bitrate?', | ||
| 'int', | ||
| $self->{'a_bitrate'}); | ||
| } | ||
| # VBR options | ||
| if ($Args{'multipass'}) { | ||
| $self->{'multipass'} = 1; | ||
| $self->{'vbr'} = 1; | ||
| } | ||
| elsif ($Args{'quantisation'}) { | ||
| die "Quantisation must be a number between 1 and 31 (lower means better quality).\n" if ($Args{'quantisation'} < 1 || $Args{'quantisation'} > 31); | ||
| $self->{'quantisation'} = $Args{'quantisation'}; | ||
| $self->{'vbr'} = 1; | ||
| } | ||
| elsif (!$is_cli) { | ||
| $self->{'vbr'} = query_text('Variable bitrate video?', | ||
| 'yesno', | ||
| $self->{'vbr'} ? 'Yes' : 'No'); | ||
| if ($self->{'vbr'}) { | ||
| $self->{'multipass'} = query_text('Multi-pass (slower, but better quality)?', | ||
| 'yesno', | ||
| $self->{'multipass'} ? 'Yes' : 'No'); | ||
| if (!$self->{'multipass'}) { | ||
| while (1) { | ||
| my $quantisation = query_text('VBR quality/quantisation (1-31)?', 'float', $self->{'quantisation'}); | ||
| if ($quantisation < 1) { | ||
| print "Too low; please choose a number between 1 and 31.\n"; | ||
| } | ||
| elsif ($quantisation > 31) { | ||
| print "Too high; please choose a number between 1 and 31\n"; | ||
| } | ||
| else { | ||
| $self->{'quantisation'} = $quantisation; | ||
| last; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| # Ask the user what audio and video bitrates he/she wants | ||
| if ($Args{'v_bitrate'}) { | ||
| die "Video bitrate must be > 0\n" unless ($Args{'v_bitrate'} > 0); | ||
| $self->{'v_bitrate'} = $Args{'v_bitrate'}; | ||
| } | ||
| elsif ($self->{'multipass'} || !$self->{'vbr'}) { | ||
| # make sure we have v_bitrate on the commandline | ||
| $self->{'v_bitrate'} = query_text('Video bitrate?', | ||
| 'int', | ||
| $self->{'v_bitrate'}); | ||
| } | ||
| # Ask the user what resolution he/she wants | ||
| if ($Args{'width'}) { | ||
| die "Width must be > 0\n" unless ($Args{'width'} > 0); | ||
| $self->{'width'} = $Args{'width'}; | ||
| } | ||
| else { | ||
| $self->{'width'} = query_text('Width?', | ||
| 'int', | ||
| $self->{'width'}); | ||
| } | ||
| # Height will default to whatever is the appropriate aspect ratio for the width | ||
| # someday, we should check the aspect ratio here, too... | ||
| $self->{'height'} = sprintf('%.0f', $self->{'width'} * 3/4); | ||
| # Ask about the height | ||
| if ($Args{'height'}) { | ||
| die "Height must be > 0\n" unless ($Args{'height'} > 0); | ||
| $self->{'height'} = $Args{'height'}; | ||
| } | ||
| else { | ||
| $self->{'height'} = query_text('Height?', | ||
| 'int', | ||
| $self->{'height'}); | ||
| } | ||
| } | ||
|
|
||
| sub export { | ||
| my $self = shift; | ||
| my $episode = shift; | ||
| # Make sure we have finfo | ||
| load_finfo($episode); | ||
| # Build the transcode string | ||
| my $params = " -Z $self->{'width'}x$self->{'height'}" | ||
| ." -N 0x55" # make *sure* we're exporting mp3 audio | ||
| ." -b $self->{'a_bitrate'},0,2,0" | ||
| ; | ||
| # unless ($episode->{'finfo'}{'fps'} =~ /^2(?:5|4\.9)/) { | ||
| # $params .= " -J modfps=buffers=7 --export_fps 23.976"; | ||
| # } | ||
| # Dual pass? | ||
| if ($self->{'multipass'}) { | ||
| # Add the temporary file to the list | ||
| push @tmpfiles, "/tmp/xvid.$$.log"; | ||
| # Back up the path and use /dev/null for the first pass | ||
| my $path_bak = $self->{'path'}; | ||
| $self->{'path'} = '/dev/null'; | ||
| # First pass | ||
| print "First pass...\n"; | ||
| $self->{'transcode_xtra'} = " -y xvid,null $params" | ||
| ." -R 1,/tmp/xvid.$$.log" | ||
| ." -w $self->{'v_bitrate'} "; | ||
| $self->SUPER::export($episode); | ||
| # Restore the path | ||
| $self->{'path'} = $path_bak; | ||
| # Second pass | ||
| print "Final pass...\n"; | ||
| $self->{'transcode_xtra'} = " -y xvid $params" | ||
| ." -R 2,/tmp/xvid.$$.log" | ||
| ." -w $self->{'v_bitrate'} "; | ||
| $self->SUPER::export($episode, '.avi'); | ||
| } | ||
| # Single pass | ||
| else { | ||
| $self->{'transcode_xtra'} = " -y xvid4 $params"; | ||
| if ($self->{'quantisation'}) { | ||
| $self->{'transcode_xtra'} .= " -R 3 -w ".$self->{'quantisation'}; | ||
| } | ||
| else { | ||
| $self->{'transcode_xtra'} .= " -w $self->{'v_bitrate'} "; | ||
| } | ||
| $self->SUPER::export($episode, '.avi'); | ||
| } | ||
| } | ||
|
|
||
| 1; #return true | ||
|
|
||
| # vim:ts=4:sw=4:ai:et:si:sts=4 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,214 @@ | ||
| #!/usr/bin/perl -w | ||
| #Last Updated: 2004.09.27 (xris) | ||
| # | ||
| # ffmpeg.pm | ||
| # | ||
| # routines for setting up ffmpeg | ||
| # Maintained by Gavin Hurlbut <gjhurlbu@gmail.com> | ||
| # | ||
|
|
||
| package export::ffmpeg; | ||
| use base 'export::generic'; | ||
|
|
||
| use export::generic; | ||
|
|
||
| use Time::HiRes qw(usleep); | ||
| use POSIX; | ||
|
|
||
| use nuv_export::shared_utils; | ||
| use nuv_export::ui; | ||
| use mythtv::recordings; | ||
|
|
||
| # Load the following extra parameters from the commandline | ||
| $cli_args{'cutlist|use_cutlist'} = 1; # Use the myth cutlist | ||
| $cli_args{'deinterlace:s'} = 1; # Deinterlace video | ||
| $cli_args{'denoise|noise_reduction:s'} = 1; # Enable noise reduction | ||
|
|
||
| # This superclass defines several object variables: | ||
| # | ||
| # use_cutlist | ||
| # noise_reduction | ||
| # deinterlace | ||
| # crop | ||
| # | ||
|
|
||
| # Check for ffmpeg | ||
| sub init_ffmpeg { | ||
| my $self = shift; | ||
| my $audioonly = (shift or 0); | ||
| # Make sure we have ffmpeg | ||
| $Prog{'ffmpeg'} = find_program('ffmpeg'); | ||
| push @{$self->{'errors'}}, 'You need ffmpeg to use this exporter.' unless ($Prog{'ffmpeg'}); | ||
| $self->{'audioonly'} = $audioonly; | ||
| } | ||
|
|
||
| # Gather data for ffmpeg | ||
| sub gather_settings { | ||
| my $self = shift; | ||
| my $skip = shift; | ||
| # Gather generic settings | ||
| $self->SUPER::gather_settings($skip ? $skip - 1 : 0); | ||
| return if ($skip); | ||
| # Defaults? | ||
| $Args{'noise_reduction'} = '' if (defined $Args{'noise_reduction'} && $Args{'noise_reduction'} eq ''); | ||
| $Args{'deinterlace'} = '' if (defined $Args{'deinterlace'} && $Args{'deinterlace'} eq ''); | ||
| # Noise reduction? | ||
| $self->{'noise_reduction'} = query_text('Enable noise reduction (slower, but better results)?', | ||
| 'yesno', | ||
| $self->{'noise_reduction'} ? 'Yes' : 'No'); | ||
| # Deinterlace video? | ||
| $self->{'deinterlace'} = query_text('Enable deinterlacing?', | ||
| 'yesno', | ||
| $self->{'deinterlace'} ? 'Yes' : 'No'); | ||
| # Crop video to get rid of broadcast padding | ||
| # $self->{'crop'} = query_text('Crop ', | ||
| # 'yesno', | ||
| # $self->{'crop'} ? 'Yes' : 'No'); | ||
| } | ||
|
|
||
| sub export { | ||
| my $self = shift; | ||
| my $episode = shift; | ||
| my $suffix = (shift or ''); | ||
| # Init the commands | ||
| my $ffmpeg = ''; | ||
| my $mythtranscode = ''; | ||
|
|
||
| # Load nuv info | ||
| load_finfo($episode); | ||
|
|
||
| # Set up the fifo dirs? | ||
| if (-e "/tmp/fifodir_$$/vidout" || -e "/tmp/fifodir_$$/audout") { | ||
| die "Possibly stale mythtranscode fifo's in /tmp/fifodir_$$/.\nPlease remove them before running nuvexport.\n\n"; | ||
| } | ||
|
|
||
| # Here, we have to fork off a copy of mythtranscode (no need to use --fifosync with transcode -- it seems to do this on its own) | ||
| $mythtranscode = "nice -n 19 mythtranscode --showprogress -p autodetect -c $episode->{channel} -s $episode->{start_time_sep} -f \"/tmp/fifodir_$$/\""; | ||
| $mythtranscode .= ' --honorcutlist' if ($self->{use_cutlist}); | ||
|
|
||
| my $videofifo = "/tmp/fifodir_$$/vidout"; | ||
| my $videotype = 'rawvideo'; | ||
|
|
||
| # # Crop? | ||
| # if (1 || $self->{'crop'}) { | ||
| # my $cropw = sprintf('%.0f', .02 * $episode->{'finfo'}{'width'}); | ||
| # my $croph = sprintf('%.0f', .02 * $episode->{'finfo'}{'height'}); | ||
| # } | ||
|
|
||
| if ($self->{'audioonly'}) { | ||
| $ffmpeg .= "cat /tmp/fifodir_$$/vidout > /dev/null | "; | ||
| } | ||
| else { | ||
| # Do noise reduction | ||
| if ($self->{'noise_reduction'}) { | ||
| $ffmpeg .= "nice -n 19 ffmpeg -f rawvideo "; | ||
| $ffmpeg .= "-s " . $episode->{'finfo'}{'width'} . "x" . $episode->{'finfo'}{'height'}; | ||
| $ffmpeg .= " -r " . $episode->{'finfo'}{'fps'}; | ||
| $ffmpeg .= " -i /tmp/fifodir_$$/vidout -f yuv4mpegpipe -"; | ||
| $ffmpeg .= " 2> /dev/null | "; | ||
| $ffmpeg .= "nice -n 19 yuvdenoise -F -r 16"; | ||
| # $ffmpeg .= " -b $cropw,$croph,-$cropw,-$croph" if( $cropw || $croph ); | ||
| $ffmpeg .= " 2> /dev/null | "; | ||
| $videofifo = "-"; | ||
| $videotype = "yuv4mpegpipe"; | ||
| } | ||
| } | ||
|
|
||
| # Start the ffmpeg command | ||
| $ffmpeg .= "nice -n 19 ffmpeg -y -f s16le"; | ||
| $ffmpeg .= " -ar " . $episode->{'finfo'}{'audio_sample_rate'}; | ||
| $ffmpeg .= " -ac " . $episode->{'finfo'}{'audio_channels'}; | ||
| $ffmpeg .= " -i /tmp/fifodir_$$/audout"; | ||
| if (!$self->{'audioonly'}) { | ||
| $ffmpeg .= " -f $videotype"; | ||
| $ffmpeg .= " -s " . $episode->{'finfo'}{'width'} . "x" . $episode->{'finfo'}{'height'}; | ||
| $ffmpeg .= " -r " . $episode->{'finfo'}{'fps'}; | ||
| $ffmpeg .= " -i $videofifo"; | ||
|
|
||
| # Filters | ||
| if ($self->{'deinterlace'}) { | ||
| $ffmpeg .= " -deinterlace"; | ||
| } | ||
| } | ||
|
|
||
| # Add any additional settings from the child module | ||
| $ffmpeg .= ' '.$self->{'ffmpeg_xtra'}; | ||
|
|
||
| # Output directory | ||
| if (!$self->{'path'} || $self->{'path'} =~ /^\/dev\/null\b/) { | ||
| $ffmpeg .= ' /dev/null'; | ||
| } | ||
| else { | ||
| $ffmpeg .= " " . shell_escape($self->{'path'}.'/'.$episode->{'outfile'}.$suffix); | ||
| } | ||
| # ffmpeg pids | ||
| my ($mythtrans_pid, $ffmpeg_pid, $mythtrans_h, $ffmpeg_h); | ||
|
|
||
| # Create a directory for mythtranscode's fifo's | ||
| mkdir("/tmp/fifodir_$$/", 0755) or die "Can't create /tmp/fifodir_$$/: $!\n\n"; | ||
| ($mythtrans_pid, $mythtrans_h) = fork_command("$mythtranscode 2>&1 > /dev/null"); | ||
| $children{$mythtrans_pid} = 'mythtranscode' if ($mythtrans_pid); | ||
| fifos_wait("/tmp/fifodir_$$/"); | ||
| push @tmpfiles, "/tmp/fifodir_$$", "/tmp/fifodir_$$/audout", "/tmp/fifodir_$$/vidout"; | ||
|
|
||
| # Execute ffmpeg | ||
| print "Starting ffmpeg.\n"; | ||
| ($ffmpeg_pid, $ffmpeg_h) = fork_command("$ffmpeg 2>&1"); | ||
| $children{$ffmpeg_pid} = 'ffmpeg' if ($ffmpeg_pid); | ||
|
|
||
| # Get ready to count the frames that have been processed | ||
| my ($frames, $fps, $start); | ||
| $frames = 0; | ||
| $fps = 0.0; | ||
| $start = time(); | ||
| my $total_frames = $episode->{'lastgop'} ? ($episode->{'lastgop'} * (($episode->{'finfo'}{'fps'} =~ /^2(?:5|4\.9)/) ? 12 : 15)) : 0; | ||
| # Wait for child processes to finish | ||
| while ((keys %children) > 0) { | ||
| my $l; | ||
| my $pct; | ||
| # Show progress | ||
| if ($frames && $total_frames) { | ||
| $pct = sprintf('%.2f', 100 * $frames / $total_frames); | ||
| $fps = ($frames * 1.0) / (time() - $start); | ||
| } | ||
| else { | ||
| $pct = "0.00"; | ||
| } | ||
| printf "\rprocessed: $frames of $total_frames frames ($pct\%%), %6.2f fps ", $fps; | ||
|
|
||
| # Read from the ffmpeg handle | ||
| while (has_data($ffmpeg_h) and $l = <$ffmpeg_h>) { | ||
| if ($l =~ /frame=\s*(\d+)/) { | ||
| $frames = int($1); | ||
| } | ||
| } | ||
| # Read from the mythtranscode handle? | ||
| while (has_data($mythtrans_h) and $l = <$mythtrans_h>) { | ||
| if ($l =~ /Processed:\s*(\d+)\s*of\s*(\d+)\s*frames\s*\((\d+)\s*seconds\)/) { | ||
| if ($self->{'audioonly'}) { | ||
| $frames = int($1); | ||
| } | ||
| $total_frames = $2; | ||
| } | ||
| } | ||
| # The pid? | ||
| $pid = waitpid(-1, &WNOHANG); | ||
| if ($children{$pid}) { | ||
| print "\n$children{$pid} finished.\n"; | ||
| delete $children{$pid}; | ||
| ##### do something here to track the time for the next process to die. | ||
| ##### If we wait too long, something obviously ended too early. | ||
| } | ||
| # Sleep for 1/10 second so we don't go too fast and annoy the cpu | ||
| usleep(100000); | ||
| } | ||
| # Remove the fifodir? | ||
| unlink "/tmp/fifodir_$$/audout", "/tmp/fifodir_$$/vidout"; | ||
| rmdir "/tmp/fifodir_$$"; | ||
| } | ||
|
|
||
|
|
||
| # Return true | ||
| 1; | ||
|
|
||
| # vim:ts=4:sw=4:ai:et:si:sts=4 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,137 @@ | ||
| #!/usr/bin/perl -w | ||
| #Last Updated: 2004.09.26 (xris) | ||
| # | ||
| # generic.pm | ||
| # | ||
| # generic routines for exporters | ||
| # | ||
|
|
||
| package export::generic; | ||
|
|
||
| use Time::HiRes qw(usleep); | ||
| use POSIX; | ||
|
|
||
| use nuv_export::shared_utils; | ||
| use nuv_export::ui; | ||
|
|
||
| BEGIN { | ||
| use Exporter; | ||
| our @ISA = qw/ Exporter /; | ||
|
|
||
| our @EXPORT = qw/ &fork_command &has_data &fifos_wait | ||
| /; | ||
| } | ||
|
|
||
| # Load the following extra parameters from the commandline | ||
| $cli_args{'path:s'} = 1; # Save path (only used with the noserver option) | ||
| $cli_args{'cutlist|use_cutlist'} = 1; # Use the myth cutlist | ||
|
|
||
| # Gather generic export settings | ||
| sub gather_settings { | ||
| my $self = shift; | ||
| # Load the save path, if requested | ||
| $self->{'path'} = query_savepath(); | ||
| # Ask the user if he/she wants to use the cutlist | ||
| $self->{'use_cutlist'} = query_text('Enable Myth cutlist?', | ||
| 'yesno', | ||
| 'Yes'); | ||
| } | ||
|
|
||
| # This subroutine forks and executes one system command - nothing fancy | ||
| sub fork_command { | ||
| my $command = shift; | ||
| if ($DEBUG) { | ||
| $command =~ s#\ 2>/dev/null##sg; | ||
| print "\nforking:\n$command\n"; | ||
| return undef; | ||
| } | ||
|
|
||
| # Get read/write handles so we can communicate with the forked process | ||
| my ($read, $write); | ||
| pipe $read, $write; | ||
|
|
||
| # Fork and return the child's pid | ||
| my $pid = undef; | ||
| if ($pid = fork) { | ||
| close $write; | ||
| # Return both the read handle and the pid? | ||
| if (wantarray) { | ||
| return ($pid, $read) | ||
| } | ||
| # Just the pid -- close the read handle | ||
| else { | ||
| close $read; | ||
| return $pid; | ||
| } | ||
| } | ||
| # $pid defined means that this is now the forked child | ||
| elsif (defined $pid) { | ||
| $is_child = 1; | ||
| close $read; | ||
| # Autoflush $write | ||
| select((select($write), $|=1)[0]); | ||
| # Run the requested command | ||
| my ($data, $buffer) = ('', ''); | ||
| open(COM, "$command |") or die "couldn't run command: $!\n$command\n"; | ||
| while (read(COM, $data, 10)) { | ||
| next unless (length $data > 0); | ||
| # Convert CR's to linefeeds so the data will flush properly | ||
| $data =~ tr/\r/\n/s; | ||
| # Some magic so that we only send whole lines (which helps us do | ||
| # nonblocking reads on the other end) | ||
| substr($data, 0, 0) = $buffer; | ||
| $buffer = ''; | ||
| if ($data !~ /\n$/) { | ||
| ($data, $buffer) = $data =~ /(.+\n)?([^\n]+)$/s; | ||
| } | ||
| # We have a line to print? | ||
| if ($data && length $data > 0) { | ||
| print $write $data; | ||
| } | ||
| # Sleep for 1/100 second so we don't go too fast and annoy the cpu, | ||
| # but still read fast enough that transcode won't slow down, either. | ||
| usleep(5000); | ||
| } | ||
| close COM; | ||
| # Print the return status of the child | ||
| my $status = $? >> 8; | ||
| print $write "!!! process $$ complete: $status !!!\n"; | ||
| # Close the write handle | ||
| close $write; | ||
| # Exit using something that won't set off the END block | ||
| POSIX::_exit($status); | ||
| } | ||
| # Couldn't fork, guess we have to quit | ||
| die "Couldn't fork: $!\n\n$command\n\n"; | ||
| } | ||
|
|
||
| sub has_data { | ||
| my $fh = shift; | ||
| my $r = ''; | ||
| vec($r, fileno($fh), 1) = 1; | ||
| my $can = select($r, undef, undef, 0); | ||
| if ($can) { | ||
| return vec($r, fileno($fh), 1); | ||
| } | ||
| return 0; | ||
| } | ||
|
|
||
| sub fifos_wait { | ||
| # Sleep a bit to let mythtranscode start up | ||
| my $fifodir = shift; | ||
| my $overload = 0; | ||
| if (!$DEBUG) { | ||
| while (++$overload < 30 && !(-e "$fifodir/audout" && -e "$fifodir/vidout" )) { | ||
| sleep 1; | ||
| print "Waiting for mythtranscode to set up the fifos.\n"; | ||
| } | ||
| unless (-e "$fifodir/audout" && -e "$fifodir/vidout") { | ||
| die "Waited too long for mythtranscode to create its fifos. Please try again.\n\n"; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| # Return true | ||
| 1; | ||
|
|
||
| # vim:ts=4:sw=4:ai:et:si:sts=4 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,222 @@ | ||
| #!/usr/bin/perl -w | ||
| #Last Updated: 2004.09.25 (xris) | ||
| # | ||
| # transcode.pm | ||
| # | ||
| # routines for setting up transcode | ||
| # | ||
|
|
||
| package export::transcode; | ||
| use base 'export::generic'; | ||
|
|
||
| use export::generic; | ||
|
|
||
| use Time::HiRes qw(usleep); | ||
| use POSIX; | ||
|
|
||
| use nuv_export::shared_utils; | ||
| use nuv_export::ui; | ||
| use mythtv::recordings; | ||
|
|
||
| # Load the following extra parameters from the commandline | ||
| $cli_args{'deinterlace:s'} = 1; # Deinterlace video | ||
| $cli_args{'denoise|noise_reduction:s'} = 1; # Enable noise reduction | ||
| $cli_args{'deinterlace:s'} = 1; # Transcode-related settings | ||
| $cli_args{'zoom_filter:s'} = 1; # Which zoom filter to use | ||
|
|
||
| # This superclass defines several object variables: | ||
| # | ||
| # use_cutlist | ||
| # denoise | ||
| # deinterlace | ||
| # crop | ||
| # | ||
|
|
||
| # Check for transcode | ||
| sub init_transcode { | ||
| my $self = shift; | ||
| # Make sure we have transcode | ||
| $Prog{'transcode'} = find_program('transcode'); | ||
| push @{$self->{'errors'}}, 'You need transcode to use this exporter.' unless ($Prog{'transcode'}); | ||
| } | ||
|
|
||
| # Gather data for transcode | ||
| sub gather_settings { | ||
| my $self = shift; | ||
| my $skip = shift; | ||
| # Gather generic settings | ||
| $self->SUPER::gather_settings($skip ? $skip - 1 : 0); | ||
| return if ($skip); | ||
| # Zoom Filter | ||
| if (defined $Args{'zoom_filter'}) { | ||
| if (!$Args{'zoom_filter'}) { | ||
| $self->{'zoom_filter'} = 'B_spline'; | ||
| } | ||
| elsif ($Args{'zoom_filter'} =~ /^(?:Lanczos3|Bell|Box|Mitchell|Hermite|B_spline|Triangle)$/) { | ||
| $self->{'zoom_filter'} = $Args{'zoom_filter'}; | ||
| } | ||
| else { | ||
| die "Unknown zoom_filter: $Args{'zoom_filter'}\n"; | ||
| } | ||
| } | ||
| # Defaults? | ||
| $Args{'denoise'} = '' if (defined $Args{'denoise'} && $Args{'denoise'} eq ''); | ||
| $Args{'deinterlace'} = 'smartyuv' if (defined $Args{'deinterlace'} && $Args{'deinterlace'} eq ''); | ||
| # Noise reduction? | ||
| $self->{'denoise'} = query_text('Enable noise reduction (slower, but better results)?', | ||
| 'yesno', | ||
| $self->{'denoise'} ? 'Yes' : 'No'); | ||
| # Deinterlace video? | ||
| $self->{'deinterlace'} = query_text('Enable deinterlacing?', | ||
| 'yesno', | ||
| $self->{'deinterlace'} ? 'Yes' : 'No'); | ||
| # Crop video to get rid of broadcast padding | ||
| # $self->{'crop'} = query_text('Crop ', | ||
| # 'yesno', | ||
| # $self->{'crop'} ? 'Yes' : 'No'); | ||
| } | ||
|
|
||
| sub export { | ||
| my $self = shift; | ||
| my $episode = shift; | ||
| my $suffix = (shift or ''); | ||
| # Init the commands | ||
| my $transcode = ''; | ||
| my $mythtranscode = ''; | ||
| # Load nuv info | ||
| load_finfo($episode); | ||
|
|
||
| # Start the transcode command | ||
| $transcode = 'nice -n 19 transcode' | ||
| .' -V' # use YV12/I420 instead of RGB, for faster processing | ||
| ; | ||
| # Not an mpeg | ||
| unless ($episode->{'finfo'}{'is_mpeg'}) { | ||
| # swap red/blue -- used with svcd, need to see if it's needed everywhere | ||
| $transcode .= ' -k'; | ||
| # Set up the fifo dirs? | ||
| if (-e "/tmp/fifodir_$$/vidout" || -e "/tmp/fifodir_$$/audout") { | ||
| die "Possibly stale mythtranscode fifo's in /tmp/fifodir_$$/.\nPlease remove them before running nuvexport.\n\n"; | ||
| } | ||
| # Here, we have to fork off a copy of mythtranscode (no need to use --fifosync with transcode -- it seems to do this on its own) | ||
| $mythtranscode = "nice -n 19 mythtranscode --showprogress -p autodetect -c $episode->{channel} -s $episode->{start_time_sep} -f \"/tmp/fifodir_$$/\""; | ||
| $mythtranscode .= ' --honorcutlist' if ($self->{'use_cutlist'}); | ||
| } | ||
| # Figure out the input files | ||
| if ($episode->{'finfo'}{'is_mpeg'}) { | ||
| $transcode .= " -i $episode->{'filename'} -x mpeg2"; | ||
| } | ||
| else { | ||
| $transcode .= " -i /tmp/fifodir_$$/vidout -p /tmp/fifodir_$$/audout" | ||
| .' -H 0 -x raw' | ||
| .' -g '.join('x', $episode->{'finfo'}{'width'}, $episode->{'finfo'}{'height'}) | ||
| .' -f '.$episode->{'finfo'}{'fps'}.',4' | ||
| .' -n 0x1' | ||
| .' -e '.join(',', $episode->{'finfo'}{'audio_sample_rate'}, $episode->{'finfo'}{'audio_bits_per_sample'}, $episode->{'finfo'}{'audio_channels'}) | ||
| ; | ||
| } | ||
| # Crop? | ||
| if (1 || $self->{'crop'}) { | ||
| my $w = sprintf('%.0f', .02 * $episode->{'finfo'}{'width'}); | ||
| my $h = sprintf('%.0f', .02 * $episode->{'finfo'}{'height'}); | ||
| $transcode .= " -j $h,$w,$h,$w" if ($h || $w); | ||
| } | ||
| # Use the cutlist? (only for mpeg files -- nuv files are handled by mythtranscode) | ||
| if ($episode->{'finfo'}{'is_mpeg'} && $self->{'use_cutlist'} && $episode->{'cutlist'} && $episode->{'cutlist'} =~ /\d/) { | ||
| my @skiplist; | ||
| foreach my $cut (split("\n", $episode->{'cutlist'})) { | ||
| push @skiplist, (split(" - ", $cut))[0]."-".(split(" - ", $cut))[1]; | ||
| } | ||
| $transcode .= " -J skip=\"".join(" ", @skiplist)."\""; | ||
| } | ||
| # Filters | ||
| if ($self->{'zoom_filter'}) { | ||
| $transcode .= ' --zoom_filter '.$self->{'zoom_filter'}; | ||
| } | ||
| if ($self->{'deinterlace'}) { | ||
| $transcode .= " -J smartyuv"; | ||
| #smartyuv|smartdeinter|dilyuvmmx | ||
| } | ||
| if ($self->{'denoise'}) { | ||
| $transcode .= " -J yuvdenoise=mode=2"; | ||
| } | ||
| # Add any additional settings from the child module | ||
| $transcode .= ' '.$self->{'transcode_xtra'}; | ||
| # Output directory | ||
| if (!$self->{'path'} || $self->{'path'} =~ /^\/dev\/null\b/) { | ||
| $transcode .= ' -o /dev/null'; | ||
| } | ||
| else { | ||
| $transcode .= ' -o '.shell_escape($self->{'path'}.'/'.$episode->{'outfile'}.$suffix); | ||
| } | ||
| # Transcode pids | ||
| my ($mythtrans_pid, $trans_pid, $mythtrans_h, $trans_h); | ||
| # Set up and run mythtranscode? | ||
| if ($mythtranscode) { | ||
| # Create a directory for mythtranscode's fifo's | ||
| mkdir("/tmp/fifodir_$$/", 0755) or die "Can't create /tmp/fifodir_$$/: $!\n\n"; | ||
| ($mythtrans_pid, $mythtrans_h) = fork_command("$mythtranscode 2>&1 > /dev/null"); | ||
| $children{$mythtrans_pid} = 'mythtranscode' if ($mythtrans_pid); | ||
| fifos_wait("/tmp/fifodir_$$/"); | ||
| push @tmpfiles, "/tmp/fifodir_$$", "/tmp/fifodir_$$/audout", "/tmp/fifodir_$$/vidout"; | ||
| } | ||
| # Execute transcode | ||
| print "Starting transcode.\n"; | ||
| ($trans_pid, $trans_h) = fork_command("$transcode 2>&1"); | ||
| $children{$trans_pid} = 'transcode' if ($trans_pid); | ||
| # Get ready to count the frames that have been processed | ||
| my ($frames, $fps); | ||
| $frames = 0; | ||
| $fps = 0.0; | ||
| my $total_frames = $episode->{'lastgop'} ? ($episode->{'lastgop'} * (($episode->{'finfo'}{'fps'} =~ /^2(?:5|4\.9)/) ? 12 : 15)) : 0; | ||
| # Wait for child processes to finish | ||
| while ((keys %children) > 0) { | ||
| my $l; | ||
| my $pct; | ||
| # Show progress | ||
| if ($frames && $total_frames) { | ||
| $pct = sprintf('%.2f', 100 * $frames / $total_frames); | ||
| } | ||
| else { | ||
| $pct = "0.00"; | ||
| } | ||
| print "\rprocessed: $frames of $total_frames frames ($pct\%), $fps fps "; | ||
| # Read from the transcode handle | ||
| while (has_data($trans_h) and $l = <$trans_h>) { | ||
| if ($l =~ /encoding\s+frames\s+\[(\d+)-(\d+)\],\s*([\d\.]+)\s*fps,\s+EMT:\s*([\d:]+),/) { | ||
| $frames = int($2); | ||
| $fps = $3; | ||
| } | ||
| } | ||
| # Read from the mythtranscode handle? | ||
| if ($mythtranscode && $mythtrans_pid) { | ||
| while (has_data($mythtrans_h) and $l = <$mythtrans_h>) { | ||
| if ($l =~ /Processed:\s*(\d+)\s*of\s*(\d+)\s*frames\s*\((\d+)\s*seconds\)/) { | ||
| #$frames = int($1); | ||
| $total_frames = $2; | ||
| } | ||
| } | ||
| } | ||
| # The pid? | ||
| $pid = waitpid(-1, &WNOHANG); | ||
| if ($children{$pid}) { | ||
| print "\n$children{$pid} finished.\n"; | ||
| delete $children{$pid}; | ||
| ##### do something here to track the time for the next process to die. | ||
| ##### If we wait too long, something obviously ended too early. | ||
| } | ||
| # Sleep for 1/10 second so we don't go too fast and annoy the cpu | ||
| usleep(100000); | ||
| } | ||
| # Remove the fifodir? (in case we're doing multipass, so we don't generate errors on the next time through) | ||
| if ($mythtranscode) { | ||
| unlink "/tmp/fifodir_$$/audout", "/tmp/fifodir_$$/vidout"; | ||
| rmdir "/tmp/fifodir_$$"; | ||
| } | ||
| } | ||
|
|
||
|
|
||
| # Return true | ||
| 1; | ||
|
|
||
| # vim:ts=4:sw=4:ai:et:si:sts=4 |