1 | # |
---|
2 | # $Date: 2010-07-26 22:49:28 +0200 (lun. 26 juil. 2010) $ |
---|
3 | # $Revision: 25431 $ |
---|
4 | # $Author: xris $ |
---|
5 | # |
---|
6 | # ffmpeg.pm |
---|
7 | # |
---|
8 | # routines for setting up ffmpeg |
---|
9 | # Maintained by Gavin Hurlbut <gjhurlbu@gmail.com> |
---|
10 | # |
---|
11 | |
---|
12 | # |
---|
13 | # Need some kind of hash to deal with the various ffmpeg command version |
---|
14 | # differences: |
---|
15 | # |
---|
16 | # http://svn.mplayerhq.hu/ffmpeg/trunk/libavcodec/utils.c?r1=6252&r2=6257&pathrev=6257 |
---|
17 | # |
---|
18 | |
---|
19 | |
---|
20 | package export::ffmpeg; |
---|
21 | use base 'export::generic'; |
---|
22 | |
---|
23 | use export::generic; |
---|
24 | |
---|
25 | use Time::HiRes qw(usleep); |
---|
26 | use POSIX; |
---|
27 | use Config; |
---|
28 | |
---|
29 | use nuv_export::shared_utils; |
---|
30 | use nuv_export::cli; |
---|
31 | use nuv_export::ui; |
---|
32 | use mythtv::recordings; |
---|
33 | use MythTV; |
---|
34 | |
---|
35 | # In case people would rather use yuvdenoise to deinterlace |
---|
36 | add_arg('deint_in_yuvdenoise|deint-in-yuvdenoise!', 'Deinterlace in yuvdenoise instead of ffmpeg'); |
---|
37 | |
---|
38 | # Check for ffmpeg |
---|
39 | sub init_ffmpeg { |
---|
40 | my $self = shift; |
---|
41 | my $audioonly = (shift or 0); |
---|
42 | # Temp var |
---|
43 | my $data; |
---|
44 | # Make sure we have ffmpeg |
---|
45 | my $ffmpeg = find_program('ffmpeg') |
---|
46 | or push @{$self->{'errors'}}, 'You need ffmpeg to use this exporter.'; |
---|
47 | # Make sure we have yuvdenoise |
---|
48 | my $yuvdenoise = find_program('yuvdenoise') |
---|
49 | or push @{$self->{'errors'}}, 'You need yuvdenoise (part of mjpegtools) to use this exporter.'; |
---|
50 | # Check the yuvdenoise version |
---|
51 | # if (!defined $self->{'denoise_vmaj'}) { |
---|
52 | # $data = `cat /dev/null | yuvdenoise `; |
---|
53 | # if ($data =~ m/yuvdenoise version (\d+(?:\.\d+)?)(\.\d+)?/i) { |
---|
54 | # $self->{'denoise_vmaj'} = $1; |
---|
55 | # $self->{'denoise_vmin'} = ($2 or 0); |
---|
56 | # } |
---|
57 | # elsif ($data =~ m/version: mjpegtools (\d+(?:\.\d+)?)(\.\d+)?/i) { |
---|
58 | # $self->{'denoise_vmaj'} = $1; |
---|
59 | # $self->{'denoise_vmin'} = ($2 or 0); |
---|
60 | # } |
---|
61 | # else { |
---|
62 | # push @{$self->{'errors'}}, 'Unrecognizeable yuvdenoise version string.'; |
---|
63 | # $self->{'noise_reduction'} = 0; |
---|
64 | # $self->{'denoise_vmaj'} = 0; |
---|
65 | # $self->{'denoise_vmin'} = 0; |
---|
66 | # } |
---|
67 | # New yuvdenoise can't deinterlace |
---|
68 | # if ($self->{'denoise_vmaj'} > 1.6 || ($self->{'denoise_vmaj'} == 1.6 && $self->{'denoise_vmin'} > 2)) { |
---|
69 | # $self->{'deint_in_yuvdenoise'} = 0; |
---|
70 | #$self->{'deint_in_yuvdenoise'} = 1; |
---|
71 | # } |
---|
72 | # } |
---|
73 | # Check the ffmpeg version |
---|
74 | # if (!defined $self->{'ffmpeg_vers'}) { |
---|
75 | # $data = `$ffmpeg -version `; |
---|
76 | # if ($data =~ m/ffmpeg\sversion\s(0.\d)[\-,]/si) { |
---|
77 | # $self->{'ffmpeg_vers'} = $1; |
---|
78 | # } |
---|
79 | # elsif ($data =~ m/ffmpeg\sversion\sSVN-r(\d+),/si) { |
---|
80 | # $self->{'ffmpeg_vers'} = $1; |
---|
81 | # } |
---|
82 | # elsif ($data =~ m/ffmpeg\sversion\sSVN-r(\d+\.\d+)(?:\.\d+)?-.*?,/si) { |
---|
83 | # Ubuntu 10.04 |
---|
84 | # FFmpeg version SVN-r0.5.1-4:0.5.1-1ubuntu1, Copyright <<SNIP>> |
---|
85 | # $self->{'ffmpeg_vers'} = $1; |
---|
86 | # } |
---|
87 | # Disabled unti I need the formatting again to detect wacky ffmpeg |
---|
88 | # versions if they go back to releasing things the old way. |
---|
89 | #elsif ($data =~ m/ffmpeg\sversion\s0.4.9-\d+_r(\d+)\.\w+\.at/si) { |
---|
90 | # $self->{'ffmpeg_vers'} = 'svn'; |
---|
91 | # $self->{'ffmpeg_build'} = $1; |
---|
92 | #} |
---|
93 | #elsif ($data =~ m/ffmpeg\sversion\s(.+?),(?:\sbuild\s(\d+))?/si) { |
---|
94 | # $self->{'ffmpeg_vers'} = lc($1); |
---|
95 | # $self->{'ffmpeg_build'} = $2; |
---|
96 | # if ($self->{'ffmpeg_vers'} =~ /^svn-r(.+?)$/) { |
---|
97 | # $self->{'ffmpeg_vers'} = 'svn'; |
---|
98 | # $self->{'ffmpeg_build'} = $1; |
---|
99 | # } |
---|
100 | #} |
---|
101 | # else { |
---|
102 | # push @{$self->{'errors'}}, 'Unrecognizeable ffmpeg version string.'; |
---|
103 | # } |
---|
104 | # } |
---|
105 | # if ($self->{'ffmpeg_vers'} < 0.5) { |
---|
106 | # push @{$self->{'errors'}}, "This version of nuvexport requires ffmpeg 0.5.\n"; |
---|
107 | # } |
---|
108 | # Audio only? |
---|
109 | $self->{'audioonly'} = $audioonly; |
---|
110 | # Gather the supported codecs |
---|
111 | $data = `$ffmpeg -formats `; |
---|
112 | my ($formats) = $data =~ /(?:^|\n\s*)File\sformats:\s*\n(.+?\n)\s*\n/s; |
---|
113 | my ($codecs) = $data =~ /(?:^|\n\s*)Codecs:\s*\n(.+?\n)\s*\n/s; |
---|
114 | unless ($codecs) { |
---|
115 | $data = `$ffmpeg -codecs `; |
---|
116 | ($codecs) = $data =~ /(?:^|\n\s*)Codecs:\s*\n(.+?\n)\s*\n/s; |
---|
117 | } |
---|
118 | if ($formats) { |
---|
119 | while ($formats =~ /^\s(..)\s(\S+)\b/mg) { |
---|
120 | $self->{'formats'}{$2} = $1; |
---|
121 | } |
---|
122 | } |
---|
123 | if ($codecs) { |
---|
124 | while ($codecs =~ /^\s(.{6})\s(\S+)\b/mg) { |
---|
125 | $self->{'codecs'}{$2} = $1; |
---|
126 | } |
---|
127 | } |
---|
128 | } |
---|
129 | |
---|
130 | # Returns true or false for the requested codec |
---|
131 | sub can_decode { |
---|
132 | my $self = shift; |
---|
133 | my $codec = shift; |
---|
134 | return ($self->{'codecs'}{$codec} && $self->{'codecs'}{$codec} =~ /^D/ |
---|
135 | || $self->{'formats'}{$codec} && $self->{'formats'}{$codec} =~ /^D/) ? 1 : 0; |
---|
136 | } |
---|
137 | sub can_encode { |
---|
138 | my $self = shift; |
---|
139 | my $codec = shift; |
---|
140 | return ($self->{'codecs'}{$codec} && $self->{'codecs'}{$codec} =~ /^.E/ |
---|
141 | || $self->{'formats'}{$codec} && $self->{'formats'}{$codec} =~ /^.E/) ? 1 : 0; |
---|
142 | } |
---|
143 | |
---|
144 | # ffmpeg keeps changing their parameter names... so we work around it. |
---|
145 | sub param { |
---|
146 | my $self = shift; |
---|
147 | my $param = lc(shift); |
---|
148 | my $value = shift; |
---|
149 | # No more version checks (until ffmpeg starts doing crazy releases again) |
---|
150 | return param_pair('ab', $value.'k') if ($param eq 'ab'); |
---|
151 | return param_pair('ac', $value) if ($param eq 'channels'); |
---|
152 | return param_pair('ar', $value) if ($param eq 'sample_rate'); |
---|
153 | return param_pair('b', $value.'k') if ($param eq 'bit_rate'); |
---|
154 | return param_pair('b_qfactor', $value) if ($param eq 'b_quant_factor'); |
---|
155 | return param_pair('b_qoffset', $value) if ($param eq 'b_quant_offset'); |
---|
156 | return param_pair('bf', $value) if ($param eq 'max_b_frames'); |
---|
157 | return param_pair('bt', $value.'k') if ($param eq 'bit_rate_tolerance'); |
---|
158 | return param_pair('bufsize', $value.'k') if ($param eq 'rc_buffer_size'); |
---|
159 | return param_pair('bug', $value) if ($param eq 'bugs'); |
---|
160 | return param_pair('error', $value) if ($param eq 'error_rate'); |
---|
161 | return param_pair('g', $value) if ($param eq 'gop_size'); |
---|
162 | return param_pair('i_qfactor', $value) if ($param eq 'i_quant_factor'); |
---|
163 | return param_pair('i_qoffset', $value) if ($param eq 'i_quant_offset'); |
---|
164 | return param_pair('maxrate', $value.'k') if ($param eq 'rc_max_rate'); |
---|
165 | return param_pair('mblmax', $value) if ($param eq 'mb_lmax'); |
---|
166 | return param_pair('mblmin', $value) if ($param eq 'mb_lmin'); |
---|
167 | return param_pair('mepc', $value) if ($param eq 'me_penalty_compensation'); |
---|
168 | return param_pair('minrate', $value.'k') if ($param eq 'rc_min_rate'); |
---|
169 | return param_pair('qcomp', $value) if ($param eq 'qcompress'); |
---|
170 | return param_pair('qdiff', $value) if ($param eq 'max_qdiff'); |
---|
171 | return param_pair('qsquish', $value) if ($param eq 'rc_qsquish'); |
---|
172 | return param_pair('rc_init_cplx', $value) if ($param eq 'rc_initial_cplx'); |
---|
173 | return param_pair('skip_exp', $value) if ($param eq 'frame_skip_exp'); |
---|
174 | return param_pair('skip_factor', $value) if ($param eq 'frame_skip_factor'); |
---|
175 | return param_pair('skip_threshold', $value) if ($param eq 'frame_skip_threshold'); |
---|
176 | return param_pair('threads', $value) if ($param eq 'thread_count'); |
---|
177 | # Unknown version or nothing wacky, just return the parameter |
---|
178 | return param_pair($param, $value); |
---|
179 | } |
---|
180 | |
---|
181 | # Load default settings |
---|
182 | sub load_defaults { |
---|
183 | my $self = shift; |
---|
184 | # Load the parent module's settings |
---|
185 | $self->SUPER::load_defaults(); |
---|
186 | # Not really anything to add |
---|
187 | } |
---|
188 | |
---|
189 | # Gather settings from the user |
---|
190 | sub gather_settings { |
---|
191 | my $self = shift; |
---|
192 | my $skip = shift; |
---|
193 | # Gather generic settings |
---|
194 | $self->SUPER::gather_settings(); |
---|
195 | } |
---|
196 | |
---|
197 | sub export { |
---|
198 | my $self = shift; |
---|
199 | my $episode = shift; |
---|
200 | my $suffix = (shift or ''); |
---|
201 | my $firstpass = (shift or 0); |
---|
202 | # Init the commands |
---|
203 | my $ffmpeg = ''; |
---|
204 | my $mythtranscode = ''; |
---|
205 | |
---|
206 | # Generate a cutlist? |
---|
207 | $self->gen_cutlist($episode); |
---|
208 | |
---|
209 | # Set up the fifo dirs? |
---|
210 | if (-e "/tmp/fifodir_$$/vidout" || -e "/tmp/fifodir_$$/audout") { |
---|
211 | die "Possibly stale mythtranscode fifo's in /tmp/fifodir_$$/.\nPlease remove them before running nuvexport.\n\n"; |
---|
212 | } |
---|
213 | |
---|
214 | # Here, we have to fork off a copy of mythtranscode (Do not use --fifosync with ffmpeg or it will hang) |
---|
215 | my $mythtranscode_bin = find_program('mythtranscode'); |
---|
216 | $mythtranscode = "$NICE $mythtranscode_bin --showprogress" |
---|
217 | ." -p '$episode->{'transcoder'}'" |
---|
218 | ." -c '$episode->{'chanid'}'" |
---|
219 | ." -s '".unix_to_myth_time($episode->{'recstartts'})."'" |
---|
220 | ." -f \"/tmp/fifodir_$$/\""; |
---|
221 | $mythtranscode .= ' --honorcutlist' if ($self->{'use_cutlist'}); |
---|
222 | $mythtranscode .= ' --fifosync' if ($self->{'audioonly'} || $firstpass); |
---|
223 | |
---|
224 | my $videofifo = "/tmp/fifodir_$$/vidout"; |
---|
225 | my $videotype = 'rawvideo -pix_fmt yuv420p'; |
---|
226 | my $crop_w; |
---|
227 | my $crop_h; |
---|
228 | my $pad_w; |
---|
229 | my $pad_h; |
---|
230 | my $height; |
---|
231 | my $width; |
---|
232 | |
---|
233 | # Standard encodes |
---|
234 | if (!$self->{'audioonly'}) { |
---|
235 | # Do noise reduction -- ffmpeg's -nr flag doesn't seem to do anything other than prevent denoise from working |
---|
236 | if ($self->{'noise_reduction'}) { |
---|
237 | $ffmpeg .= "$NICE ffmpeg -f rawvideo"; |
---|
238 | $ffmpeg .= ' -s ' . $episode->{'finfo'}{'width'} . 'x' . $episode->{'finfo'}{'height'}; |
---|
239 | $ffmpeg .= ' -r ' . $episode->{'finfo'}{'fps'}; |
---|
240 | $ffmpeg .= " -i /tmp/fifodir_$$/vidout -f yuv4mpegpipe -"; |
---|
241 | $ffmpeg .= ' 2> /dev/null | '; |
---|
242 | $ffmpeg .= "$NICE yuvdenoise"; |
---|
243 | if ($self->{'denoise_vmaj'} < 1.6 || ($self->{'denoise_vmaj'} == 1.6 && $self->{'denoise_vmin'} < 3)) { |
---|
244 | $ffmpeg .= ' -r 16'; |
---|
245 | if ($self->val('fast_denoise')) { |
---|
246 | $ffmpeg .= ' -f'; |
---|
247 | } |
---|
248 | # Deinterlace in yuvdenoise |
---|
249 | if ($self->val('deint_in_yuvdenoise') && $self->val('deinterlace')) { |
---|
250 | $ffmpeg .= " -F"; |
---|
251 | } |
---|
252 | } |
---|
253 | $ffmpeg .= ' 2> /dev/null | '; |
---|
254 | $videofifo = '-'; |
---|
255 | $videotype = 'yuv4mpegpipe'; |
---|
256 | } |
---|
257 | } |
---|
258 | |
---|
259 | # Start the ffmpeg command |
---|
260 | $ffmpeg .= "$NICE ffmpeg"; |
---|
261 | if ($num_cpus > 1) { |
---|
262 | $ffmpeg .= ' -threads '.($num_cpus); |
---|
263 | } |
---|
264 | $ffmpeg .= ' -y -f '.($Config{'byteorder'} == 4321 ? 's16be' : 's16le') |
---|
265 | .' -ar ' . $episode->{'finfo'}{'audio_sample_rate'} |
---|
266 | .' -ac ' . $episode->{'finfo'}{'audio_channels'}; |
---|
267 | if (!$firstpass) { |
---|
268 | $ffmpeg .= " -i /tmp/fifodir_$$/audout"; |
---|
269 | } |
---|
270 | if (!$self->{'audioonly'}) { |
---|
271 | $ffmpeg .= " -f $videotype" |
---|
272 | .' -s ' . $episode->{'finfo'}{'width'} . 'x' . $episode->{'finfo'}{'height'} |
---|
273 | .' -vf aspect=' . $episode->{'finfo'}{'aspect_f'} |
---|
274 | .' -r ' . $episode->{'finfo'}{'fps'} |
---|
275 | ." -i $videofifo"; |
---|
276 | |
---|
277 | $self->{'out_aspect'} ||= $episode->{'finfo'}{'aspect_f'}; |
---|
278 | |
---|
279 | # The output is actually a stretched/anamorphic aspect ratio |
---|
280 | # (like 480x480 for SVCD, which is 4:3) |
---|
281 | if ($self->{'aspect_stretched'}) { |
---|
282 | $width = $self->{'width'}; |
---|
283 | $height = $self->{'height'}; |
---|
284 | $pad_h = 0; |
---|
285 | $pad_w = 0; |
---|
286 | } |
---|
287 | # The output will need letter/pillarboxing |
---|
288 | else { |
---|
289 | # We need to letterbox |
---|
290 | if ($self->{'width'} / $self->{'height'} <= $self->{'out_aspect'}) { |
---|
291 | $width = $self->{'width'}; |
---|
292 | $height = $width / $episode->{'finfo'}{'aspect_f'}; |
---|
293 | $height = int(($height + 2) / 4) * 4; |
---|
294 | $pad_h = int(($self->{'height'} - $height) / 2); |
---|
295 | $pad_w = 0; |
---|
296 | } |
---|
297 | # We need to pillarbox |
---|
298 | else { |
---|
299 | $height = $self->{'height'}; |
---|
300 | $width = $height * $episode->{'finfo'}{'aspect_f'}; |
---|
301 | $width = int(($width + 2) / 4) * 4; |
---|
302 | $pad_w = int(($self->{'width'} - $width) / 2); |
---|
303 | $pad_h = 0; |
---|
304 | } |
---|
305 | } |
---|
306 | |
---|
307 | $ffmpeg .= ' -vf aspect=' . $self->{'out_aspect'} |
---|
308 | .' -r ' . $self->{'out_fps'}; |
---|
309 | |
---|
310 | # Deinterlace in ffmpeg only if the user wants to |
---|
311 | if ($self->val('deinterlace') && !($self->val('noise_reduction') && $self->val('deint_in_yuvdenoise'))) { |
---|
312 | $ffmpeg .= ' -deinterlace'; |
---|
313 | } |
---|
314 | # Crop |
---|
315 | if ($self->{'crop'}) { |
---|
316 | my $t = sprintf('%.0f', ($self->val('crop_top') / 100) * $episode->{'finfo'}{'height'}); |
---|
317 | my $r = sprintf('%.0f', ($self->val('crop_right') / 100) * $episode->{'finfo'}{'width'}); |
---|
318 | my $b = sprintf('%.0f', ($self->val('crop_bottom') / 100) * $episode->{'finfo'}{'height'}); |
---|
319 | my $l = sprintf('%.0f', ($self->val('crop_left') / 100) * $episode->{'finfo'}{'width'}); |
---|
320 | # Keep the crop numbers even |
---|
321 | $t-- if ($t > 0 && $t % 2); |
---|
322 | $r-- if ($r > 0 && $r % 2); |
---|
323 | $b-- if ($b > 0 && $b % 2); |
---|
324 | $l-- if ($l > 0 && $l % 2); |
---|
325 | # crop |
---|
326 | $ffmpeg .= " -croptop $t -cropright $r" |
---|
327 | ." -cropbottom $b -cropleft $l" if ($t || $r || $b || $l); |
---|
328 | } |
---|
329 | |
---|
330 | # Letter/Pillarboxing as appropriate |
---|
331 | if ($pad_h) { |
---|
332 | $ffmpeg .= " -padtop $pad_h -padbottom $pad_h"; |
---|
333 | } |
---|
334 | if ($pad_w) { |
---|
335 | # $ffmpeg .= " -padleft $pad_w -padright $pad_w"; |
---|
336 | $ffmpeg .= " -vf pad ${width}:$height:$pad_w:$pad_w"; |
---|
337 | } |
---|
338 | $ffmpeg .= " -s ${width}x$height"; |
---|
339 | } |
---|
340 | |
---|
341 | # Add any additional settings from the child module |
---|
342 | $ffmpeg .= ' '.$self->{'ffmpeg_xtra'}; |
---|
343 | |
---|
344 | # Output directory set to null means the first pass of a multipass |
---|
345 | if ($firstpass || !$self->{'path'} || $self->{'path'} =~ /^\/dev\/null\b/) { |
---|
346 | $ffmpeg .= ' /dev/null'; |
---|
347 | } |
---|
348 | # Add the output filename |
---|
349 | else { |
---|
350 | $ffmpeg .= ' '.shell_escape($self->get_outfile($episode, $suffix)); |
---|
351 | } |
---|
352 | # ffmpeg pids |
---|
353 | my ($mythtrans_pid, $ffmpeg_pid, $mythtrans_h, $ffmpeg_h); |
---|
354 | |
---|
355 | # Create a directory for mythtranscode's fifo's |
---|
356 | mkdir("/tmp/fifodir_$$/", 0755) or die "Can't create /tmp/fifodir_$$/: $!\n\n"; |
---|
357 | ($mythtrans_pid, $mythtrans_h) = fork_command("$mythtranscode "); |
---|
358 | $children{$mythtrans_pid} = 'mythtranscode' if ($mythtrans_pid); |
---|
359 | fifos_wait("/tmp/fifodir_$$/", $mythtrans_pid, $mythtrans_h); |
---|
360 | push @tmpfiles, "/tmp/fifodir_$$", "/tmp/fifodir_$$/audout", "/tmp/fifodir_$$/vidout"; |
---|
361 | |
---|
362 | # For multipass encodes, we don't need the audio on the first pass |
---|
363 | if ($self->{'audioonly'}) { |
---|
364 | my ($cat_pid, $cat_h) = fork_command("cat /tmp/fifodir_$$/vidout > /dev/null"); |
---|
365 | $children{$cat_pid} = 'video dump' if ($cat_pid); |
---|
366 | } |
---|
367 | elsif ($firstpass) { |
---|
368 | my ($cat_pid, $cat_h) = fork_command("cat /tmp/fifodir_$$/audout > /dev/null"); |
---|
369 | $children{$cat_pid} = 'audio dump' if ($cat_pid); |
---|
370 | } |
---|
371 | # Execute ffmpeg |
---|
372 | print "Starting ffmpeg.\n" unless ($DEBUG); |
---|
373 | ($ffmpeg_pid, $ffmpeg_h) = fork_command("$ffmpeg "); |
---|
374 | $children{$ffmpeg_pid} = 'ffmpeg' if ($ffmpeg_pid); |
---|
375 | |
---|
376 | # Get ready to count the frames that have been processed |
---|
377 | my ($frames, $fps, $start); |
---|
378 | $frames = 0; |
---|
379 | $fps = 0.0; |
---|
380 | $start = time(); |
---|
381 | if (!$episode->{'total_frames'} || $episode->{'total_frames'} < 1) { |
---|
382 | $episode->{'total_frames'} = ($episode->{'last_frame'} && $episode->{'last_frame'} > 0) |
---|
383 | ? $episode->{'last_frame'} - $episode->{'cutlist_frames'} |
---|
384 | : 0; |
---|
385 | } |
---|
386 | # Keep track of any warnings |
---|
387 | my $warnings = ''; |
---|
388 | my $death_timer = 0; |
---|
389 | my $last_death = ''; |
---|
390 | # Wait for child processes to finish |
---|
391 | while ((keys %children) > 0) { |
---|
392 | my ($l, $pct); |
---|
393 | # Show progress |
---|
394 | if ($frames && $episode->{'total_frames'}) { |
---|
395 | $pct = sprintf('%.2f', 100 * $frames / $episode->{'total_frames'}); |
---|
396 | $fps = sprintf('%.2f', ($frames * 1.0) / (time() - $start)); |
---|
397 | } |
---|
398 | else { |
---|
399 | $pct = '~'; |
---|
400 | } |
---|
401 | unless (arg('noprogress')) { |
---|
402 | print "\rprocessed: $frames of $episode->{'total_frames'} frames at $fps fps ($pct\%, eta: ", |
---|
403 | $self->build_eta($frames, $episode->{'total_frames'}, $fps), |
---|
404 | ') '; |
---|
405 | } |
---|
406 | |
---|
407 | # Read from the ffmpeg handle |
---|
408 | while (has_data($ffmpeg_h) and $l = <$ffmpeg_h>) { |
---|
409 | if ($l =~ /frame=\s*(\d+)/) { |
---|
410 | $frames = int($1); |
---|
411 | if ($episode->{'total_frames'} < $frames) { |
---|
412 | $episode->{'total_frames'} = $frames; |
---|
413 | } |
---|
414 | } |
---|
415 | # ffmpeg warnings? |
---|
416 | elsif ($l =~ /^Un(known|supported).+?(codec|format|(?:de|en)coder)/m) { |
---|
417 | $warnings .= $l; |
---|
418 | die "\n\nffmpeg had critical errors:\n\n$warnings"; |
---|
419 | } |
---|
420 | # Segfault? |
---|
421 | elsif ($l =~ /^Segmentation\sfault/m) { |
---|
422 | $warnings .= $l; |
---|
423 | die "\n\nffmpeg had critical errors:\n\n$warnings"; |
---|
424 | } |
---|
425 | # Most likely a misconfigured command line |
---|
426 | elsif ($l =~ /^Unable\s(?:to|for)\s(?:find|parse)/m) { |
---|
427 | $warnings .= $l; |
---|
428 | die "\n\nffmpeg had critical errors:\n\n$warnings"; |
---|
429 | } |
---|
430 | # Another error? |
---|
431 | elsif ($l =~ /\b(?:Error\swhile|Invalid\svalue)\b/m) { |
---|
432 | $warnings .= $l; |
---|
433 | die "\n\nffmpeg had critical errors:\n\n$warnings"; |
---|
434 | } |
---|
435 | # Unrecognized options |
---|
436 | elsif ($l =~ /^ffmpeg:\sunrecognized\soption/m) { |
---|
437 | $warnings .= $l; |
---|
438 | die "\n\nffmpeg had critical errors:\n\n$warnings"; |
---|
439 | } |
---|
440 | } |
---|
441 | # Read from the mythtranscode handle? |
---|
442 | while (has_data($mythtrans_h) and $l = <$mythtrans_h>) { |
---|
443 | if ($l =~ /Processed:\s*(\d+)\s*of\s*(\d+)\s*frames\s*\((\d+)\s*seconds\)/) { |
---|
444 | # ffmpeg doesn't report fame counts for audio streams, so grab it here. |
---|
445 | if ($self->{'audioonly'}) { |
---|
446 | $frames = int($1); |
---|
447 | } |
---|
448 | # mythtranscode is going to be the most accurate total frame count. |
---|
449 | my $total = $2 - $episode->{'cutlist_frames'}; |
---|
450 | if ($episode->{'total_frames'} < $total) { |
---|
451 | $episode->{'total_frames'} = $total; |
---|
452 | } |
---|
453 | } |
---|
454 | # Unrecognized options |
---|
455 | elsif ($l =~ /^Couldn't\sfind\srecording/m) { |
---|
456 | $warnings .= $l; |
---|
457 | die "\n\nmythtranscode had critical errors:\n\n$warnings"; |
---|
458 | } |
---|
459 | } |
---|
460 | # Has the deathtimer been started? Stick around for awhile, but not too long |
---|
461 | if ($death_timer > 0 && time() - $death_timer > 30) { |
---|
462 | $str = "\n\n$last_death died early."; |
---|
463 | if ($warnings) { |
---|
464 | $str .= "See ffmpeg warnings:\n\n$warnings"; |
---|
465 | } |
---|
466 | else { |
---|
467 | $str .= "Please use the --debug option to figure out what went wrong.\n" |
---|
468 | ."http://www.mythtv.org/wiki/index.php/Nuvexport#Debug_Mode\n\n"; |
---|
469 | } |
---|
470 | die $str; |
---|
471 | } |
---|
472 | # The pid? |
---|
473 | $pid = waitpid(-1, &WNOHANG); |
---|
474 | if ($children{$pid}) { |
---|
475 | print "\n$children{$pid} finished.\n" unless ($DEBUG); |
---|
476 | $last_death = $children{$pid}; |
---|
477 | $death_timer = time(); |
---|
478 | delete $children{$pid}; |
---|
479 | } |
---|
480 | # Sleep for 1/10 second so we don't go too fast and annoy the cpu |
---|
481 | usleep(100000); |
---|
482 | } |
---|
483 | # Remove the fifodir? |
---|
484 | unlink "/tmp/fifodir_$$/audout", "/tmp/fifodir_$$/vidout"; |
---|
485 | rmdir "/tmp/fifodir_$$"; |
---|
486 | } |
---|
487 | |
---|
488 | |
---|
489 | # Return true |
---|
490 | 1; |
---|
491 | |
---|
492 | # vim:ts=4:sw=4:ai:et:si:sts=4 |
---|