|
| 1 | +#!/usr/bin/perl -w |
| 2 | +#Last Updated: 2005.02.27 (icemaann) |
| 3 | +# |
| 4 | +# mencoder.pm |
| 5 | +# |
| 6 | +# routines for setting up mencoder |
| 7 | +# |
| 8 | + |
| 9 | +package export::mencoder; |
| 10 | + use base 'export::generic'; |
| 11 | + |
| 12 | + use export::generic; |
| 13 | + |
| 14 | + use Time::HiRes qw(usleep); |
| 15 | + use POSIX; |
| 16 | + |
| 17 | + use nuv_export::shared_utils; |
| 18 | + use nuv_export::cli; |
| 19 | + use nuv_export::ui; |
| 20 | + use mythtv::recordings; |
| 21 | + |
| 22 | +# Load the following extra parameters from the commandline |
| 23 | + add_arg('zoom_filter:s', 'Which zoom filter to use.'); |
| 24 | + |
| 25 | +# This superclass defines several object variables: |
| 26 | +# |
| 27 | +# path (defined by generic) |
| 28 | +# use_cutlist (defined by generic) |
| 29 | +# noise_reduction |
| 30 | +# deinterlace |
| 31 | +# crop |
| 32 | +# zoom_filter |
| 33 | +# |
| 34 | + |
| 35 | +# Check for mencoder |
| 36 | + sub init_mencoder { |
| 37 | + my $self = shift; |
| 38 | + # Make sure we have mencoder |
| 39 | + find_program('mencoder') |
| 40 | + or push @{$self->{'errors'}}, 'You need mencoder to use this exporter.'; |
| 41 | + } |
| 42 | + |
| 43 | +# Gather data for mencoder |
| 44 | + sub gather_settings { |
| 45 | + my $self = shift; |
| 46 | + my $skip = shift; |
| 47 | + # Gather generic settings |
| 48 | + $self->SUPER::gather_settings($skip ? $skip - 1 : 0); |
| 49 | + return if ($skip); |
| 50 | + # Zoom Filter |
| 51 | + if (defined arg('zoom_filter')) { |
| 52 | + if (!arg('zoom_filter')) { |
| 53 | + $self->{'zoom_filter'} = 'B_spline'; |
| 54 | + } |
| 55 | + elsif (arg('zoom_filter') =~ /^(?:Lanczos3|Bell|Box|Mitchell|Hermite|B_spline|Triangle)$/) { |
| 56 | + $self->{'zoom_filter'} = arg('zoom_filter'); |
| 57 | + } |
| 58 | + else { |
| 59 | + die "Unknown zoom_filter: ".arg('zoom_filter')."\n"; |
| 60 | + } |
| 61 | + } |
| 62 | + } |
| 63 | + |
| 64 | + #Fix/build mencoder filter chain |
| 65 | + sub build_vop_line{ |
| 66 | + my $cmdline = shift; |
| 67 | + my $vop = ''; |
| 68 | + while($cmdline =~ m/.*?-vop\s+([^\s]+)\s/gs){ |
| 69 | + $vop .= ",$1"; |
| 70 | + } |
| 71 | + $vop =~ s/^,+//; |
| 72 | + $vop =~ s/,+$//; |
| 73 | + $cmdline =~ s/-vop\s+[^\s]+\s/ /g; |
| 74 | + $cmdline .= " -vop $vop "; |
| 75 | + return $cmdline; |
| 76 | + } |
| 77 | + |
| 78 | + |
| 79 | + sub export { |
| 80 | + my $self = shift; |
| 81 | + my $episode = shift; |
| 82 | + my $suffix = (shift or ''); |
| 83 | + my $skip_audio = shift; |
| 84 | + # Init the commands |
| 85 | + my $mencoder = ''; |
| 86 | + my $mythtranscode = ''; |
| 87 | + # Load nuv info |
| 88 | + load_finfo($episode); |
| 89 | + # Start the mencoder command |
| 90 | + $mencoder = "$NICE mencoder"; |
| 91 | + # Import aspect ratio |
| 92 | + if ($episode->{'finfo'}{'aspect'}) { |
| 93 | + $mencoder .= ' -aspect '; |
| 94 | + if ($episode->{'finfo'}{'aspect'} == 1 || $episode->{'finfo'}{'aspect'} eq '1:1') { |
| 95 | + $mencoder .= '1:1'; |
| 96 | + } |
| 97 | + elsif ($episode->{'finfo'}{'aspect'} =~ m/^1.3/ || $episode->{'finfo'}{'aspect'} eq '4:3') { |
| 98 | + $mencoder .= '4:3'; |
| 99 | + } |
| 100 | + elsif ($episode->{'finfo'}{'aspect'} =~ m/^1.7/ || $episode->{'finfo'}{'aspect'} eq '16:9') { |
| 101 | + $mencoder .= '16:9'; |
| 102 | + } |
| 103 | + elsif ($episode->{'finfo'}{'aspect'} == 2.21 || $episode->{'finfo'}{'aspect'} eq '2.21:1') { |
| 104 | + $mencoder .= '2.21:1'; |
| 105 | + } |
| 106 | + } |
| 107 | + # Not an mpeg mencoder can not do cutlists (from what I can tell..) |
| 108 | + unless ($episode->{'finfo'}{'is_mpeg'} && !$self->{'use_cutlist'}) { |
| 109 | + # swap red/blue -- used with svcd, need to see if it's needed everywhere |
| 110 | + # $mencoder .= ' -vop rgb2bgr '; #this is broken in mencoder 1.0preX |
| 111 | + # Set up the fifo dirs? |
| 112 | + if (-e "/tmp/fifodir_$$/vidout" || -e "/tmp/fifodir_$$/audout") { |
| 113 | + die "Possibly stale mythtranscode fifo's in /tmp/fifodir_$$/.\nPlease remove them before running nuvexport.\n\n"; |
| 114 | + } |
| 115 | + # Here, we have to fork off a copy of mythtranscode (need to use --fifosync with mencoder? needs testing) |
| 116 | + $mythtranscode = "$NICE mythtranscode --showprogress -p autodetect -c $episode->{'channel'} -s $episode->{'start_time_sep'} -f \"/tmp/fifodir_$$/\""; |
| 117 | + # On no-audio encodes, we need to do something to keep mythtranscode's audio buffers from filling up available RAM |
| 118 | + # $mythtranscode .= ' --fifosync' if ($skip_audio); |
| 119 | + # let mythtranscode handle the cutlist |
| 120 | + $mythtranscode .= ' --honorcutlist' if ($self->{'use_cutlist'}); |
| 121 | + } |
| 122 | + # Figure out the input files |
| 123 | + if ($episode->{'finfo'}{'is_mpeg'} && !$self->{'use_cutlist'}) { |
| 124 | + $mencoder .= " -idx $episode->{'filename'} "; |
| 125 | + } |
| 126 | + else { |
| 127 | + $mencoder .= " -noskip -idx /tmp/fifodir_$$/vidout -audiofile /tmp/fifodir_$$/audout " |
| 128 | + .' -rawvideo' |
| 129 | + .' on:w='.$episode->{'finfo'}{'width'}.':h='.$episode->{'finfo'}{'height'} |
| 130 | + .':fps='.$episode->{'finfo'}{'fps'} |
| 131 | + .' -rawaudio on:rate='.$episode->{'finfo'}{'audio_sample_rate'}.':channels='.$episode->{'finfo'}{'audio_channels'} |
| 132 | + ; |
| 133 | + } |
| 134 | + # NOTE: this comes before the standard filters below, because |
| 135 | + # mencoder applies filters in reverse |
| 136 | + # Add any additional settings from the child module |
| 137 | + $mencoder .= ' '.$self->{'mencoder_xtra'}; |
| 138 | + # Crop? |
| 139 | + if ($self->{'crop'}) { |
| 140 | + my $w = sprintf('%.0f', .98 * $episode->{'finfo'}{'width'}); |
| 141 | + my $h = sprintf('%.0f', .98 * $episode->{'finfo'}{'height'}); |
| 142 | + $w-- if ($w > 0 && $w % 2); # mencoder freaks out if these are odd numbers (does it?) |
| 143 | + $h-- if ($h > 0 && $h % 2); |
| 144 | + $mencoder .= " -vop crop=$w:$h " if ($h || $w); |
| 145 | + } |
| 146 | + # Use the cutlist? (only for mpeg files -- nuv files are handled by mythtranscode) |
| 147 | + # Can we cut with mencoder? |
| 148 | + # Filters (remember, mencoder reads these in reverse order (so deint should be last if used) |
| 149 | + # Normally you would do -vop filter1=<val>,filter2=<val>,lavcdeint... |
| 150 | + if ($self->{'noise_reduction'}) { |
| 151 | + $mencoder .= " -vop denoise3d"; |
| 152 | + } |
| 153 | + if ($self->{'deinterlace'}) { |
| 154 | + $mencoder .= " -vop lavcdeint"; |
| 155 | + #smartyuv|smartdeinter|dilyuvmmx |
| 156 | + } |
| 157 | + # Output directory set to null means the first pass of a multipass |
| 158 | + if (!$self->{'path'} || $self->{'path'} =~ /^\/dev\/null\b/) { |
| 159 | + $mencoder .= ' -o /dev/null'; |
| 160 | + } |
| 161 | + # Add the output filename |
| 162 | + else { |
| 163 | + $mencoder .= ' -o '.shell_escape($self->get_outfile($episode, $suffix)); |
| 164 | + } |
| 165 | + # mencoder pids |
| 166 | + my ($mythtrans_pid, $mencoder_pid, $mythtrans_h, $mencoder_h); |
| 167 | + # Set up and run mythtranscode? |
| 168 | + if ($mythtranscode) { |
| 169 | + # Create a directory for mythtranscode's fifo's |
| 170 | + mkdir("/tmp/fifodir_$$/", 0755) or die "Can't create /tmp/fifodir_$$/: $!\n\n"; |
| 171 | + ($mythtrans_pid, $mythtrans_h) = fork_command("$mythtranscode 2>&1"); |
| 172 | + $children{$mythtrans_pid} = 'mythtranscode' if ($mythtrans_pid); |
| 173 | + fifos_wait("/tmp/fifodir_$$/"); |
| 174 | + push @tmpfiles, "/tmp/fifodir_$$", "/tmp/fifodir_$$/audout", "/tmp/fifodir_$$/vidout"; |
| 175 | + } |
| 176 | + #Fix -vop options before we execute mencoder |
| 177 | + $mencoder = build_vop_line($mencoder); |
| 178 | + # Execute mencoder |
| 179 | + print "Starting mencoder.\n" unless ($DEBUG); |
| 180 | + ($mencoder_pid, $mencoder_h) = fork_command("$mencoder 2>&1"); |
| 181 | + $children{$mencoder_pid} = 'mencoder' if ($mencoder_pid); |
| 182 | + # Get ready to count the frames that have been processed |
| 183 | + my ($frames, $fps); |
| 184 | + $frames = 0; |
| 185 | + $fps = 0.0; |
| 186 | + my $total_frames = $episode->{'lastgop'} * (($episode->{'finfo'}{'fps'} =~ /^2(?:5|4\.9)/) ? 12 : 15); |
| 187 | + # Keep track of any warnings |
| 188 | + my $warnings = ''; |
| 189 | + my $death_timer = 0; |
| 190 | + my $last_death = ''; |
| 191 | + # Wait for child processes to finish |
| 192 | + while ((keys %children) > 0) { |
| 193 | + my $l; |
| 194 | + my $pct; |
| 195 | + # Show progress |
| 196 | + if ($frames && $total_frames) { |
| 197 | + $pct = sprintf('%.2f', 100 * $frames / $total_frames); |
| 198 | + } |
| 199 | + else { |
| 200 | + $pct = "0.00"; |
| 201 | + } |
| 202 | + print "\rprocessed: $frames of $total_frames frames ($pct\%), $fps fps "; |
| 203 | + # Read from the mencoder handle |
| 204 | + while (has_data($mencoder_h) and $l = <$mencoder_h>) { |
| 205 | + if ($l =~ /^Pos:.*?(\d+)f.*?\(.*?(\d+)fps/) { |
| 206 | + $frames = int($1); |
| 207 | + $fps = $2; |
| 208 | + } |
| 209 | + # Look for error messages |
| 210 | + elsif ($l =~ m/\[mencoder\] warning/) { |
| 211 | + $warnings .= $l; |
| 212 | + } |
| 213 | + elsif ($l =~ m/\[mencoder\] critical/) { |
| 214 | + $warnings .= $l; |
| 215 | + die "\nmencoder had critical errors:\n\n$warnings"; |
| 216 | + } |
| 217 | + } |
| 218 | + # Read from the mythtranscode handle? |
| 219 | + if ($mythtranscode && $mythtrans_pid) { |
| 220 | + while (has_data($mythtrans_h) and $l = <$mythtrans_h>) { |
| 221 | + if ($l =~ /Processed:\s*(\d+)\s*of\s*(\d+)\s*frames\s*\((\d+)\s*seconds\)/) { |
| 222 | + #$frames = int($1); |
| 223 | + $total_frames = $2; |
| 224 | + } |
| 225 | + } |
| 226 | + } |
| 227 | + # Has the deathtimer been started? Stick around for awhile, but not too long |
| 228 | + if ($death_timer > 0 && time() - $death_timer > 30) { |
| 229 | + $str = "\n\n$last_death died early."; |
| 230 | + if ($warnings) { |
| 231 | + $str .= "See mencoder warnings:\n\n$warnings"; |
| 232 | + } |
| 233 | + else { |
| 234 | + $str .= "Please use the --debug option to figure out what went wrong.\n\n"; |
| 235 | + } |
| 236 | + die $str; |
| 237 | + } |
| 238 | + # The pid? |
| 239 | + $pid = waitpid(-1, &WNOHANG); |
| 240 | + if ($children{$pid}) { |
| 241 | + print "\n$children{$pid} finished.\n" unless ($DEBUG); |
| 242 | + $last_death = $children{$pid}; |
| 243 | + $death_timer = time(); |
| 244 | + delete $children{$pid}; |
| 245 | + } |
| 246 | + # Sleep for 1/100 second so we don't go too fast and annoy the cpu |
| 247 | + usleep(100000); |
| 248 | + } |
| 249 | + # Remove the fifodir? (in case we're doing multipass, so we don't generate errors on the next time through) |
| 250 | + if ($mythtranscode) { |
| 251 | + unlink "/tmp/fifodir_$$/audout", "/tmp/fifodir_$$/vidout"; |
| 252 | + rmdir "/tmp/fifodir_$$"; |
| 253 | + } |
| 254 | + } |
| 255 | + |
| 256 | + |
| 257 | +# Return true |
| 258 | +1; |
| 259 | + |
| 260 | +# vim:ts=4:sw=4:ai:et:si:sts=4 |
0 commit comments