| 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 |
| 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 |
| 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 |
| 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 |
| 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 |
| 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 | ||
|
|
| 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 |
| 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 |
| 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 |
| 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 |
| 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 |
| 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 |
| 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 |
| 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 |
| 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 |