source: extensions/gsdl-video/trunk/perllib/plugins/VideoConverter.pm@ 18556

Last change on this file since 18556 was 18556, checked in by davidb, 15 years ago

Restructing of VideoPlugin to be like ImagePlugin (with its supporting ImageConverter plugin). The pattern was then repeated for Audio, so we now have an AudioPlugin and AudioConverter. Where possible code is shared in a Multimedia base class

File size: 18.6 KB
Line 
1###########################################################################
2#
3# VideoConverter - helper plugin that does video (and audio) conversion using ffmpeg
4#
5# A component of the Greenstone digital library software
6# from the New Zealand Digital Library Project at the
7# University of Waikato, New Zealand.
8#
9# Copyright (C) 2008 New Zealand Digital Library Project
10#
11# This program is free software; you can redistribute it and/or modify
12# it under the terms of the GNU General Public License as published by
13# the Free Software Foundation; either version 2 of the License, or
14# (at your option) any later version.
15#
16# This program is distributed in the hope that it will be useful,
17# but WITHOUT ANY WARRANTY; without even the implied warranty of
18# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19# GNU General Public License for more details.
20#
21# You should have received a copy of the GNU General Public License
22# along with this program; if not, write to the Free Software
23# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
24#
25###########################################################################
26package VideoConverter;
27
28use MultimediaConverter;
29
30
31use strict;
32no strict 'refs'; # allow filehandles to be variables and viceversa
33
34use gsprintf 'gsprintf';
35
36BEGIN {
37 @VideoConverter::ISA = ('MultimediaConverter');
38}
39
40
41my @thumb_arguments = (
42 { 'name' => "create_thumbnail",
43 'desc' => "{ImageConverter.create_thumbnail}",
44 'type' => "enum",
45 'list' => [{'name' => "true", 'desc' => "{common.true}"},
46 {'name' => "false", 'desc' => "{common.false}"}],
47 'deft' => "true",
48 'reqd' => "no" },
49 { 'name' => "thumbnailsize",
50 'desc' => "{ImageConverter.thumbnailsize}",
51 'type' => "int",
52 'deft' => "100",
53 'range' => "1,",
54 'reqd' => "no" },
55 { 'name' => "thumbnailtype",
56 'desc' => "{ImageConverter.thumbnailtype}",
57 'type' => "string",
58 'deft' => "jpg",
59 'reqd' => "no" },
60 { 'name' => "noscaleup",
61 'desc' => "{ImageConverter.noscaleup}",
62 'type' => "flag",
63 'reqd' => "no" } );
64
65
66my @screenview_arguments = (
67 { 'name' => "create_screenview",
68 'desc' => "{ImageConverter.create_screenview}",
69 'type' => "enum",
70 'list' => [{'name' => "true", 'desc' => "{common.true}"},
71 {'name' => "false", 'desc' => "{common.false}"}],
72 'deft' => "true",
73 'reqd' => "no" },
74 { 'name' => "screenviewsize",
75 'desc' => "{ImageConverter.screenviewsize}",
76 'type' => "int",
77 'deft' => "352",
78 'range' => "1,",
79 'reqd' => "no" },
80 { 'name' => "screenviewtype",
81 'desc' => "{ImageConverter.screenviewtype}",
82 'type' => "string",
83 'deft' => "jpg",
84 'reqd' => "no" } );
85
86my $arguments = [
87 @thumb_arguments,
88 @screenview_arguments,
89
90 { 'name' => "converttotype",
91 'desc' => "{ImageConverter.converttotype}",
92 'type' => "string",
93 'deft' => "",
94 'reqd' => "no" },
95
96
97 { 'name' => "converttosize",
98 'desc' => "{VideoPlugin.converttosize}",
99 'type' => "int",
100 'deft' => "",
101## 'deft' => "352",
102 'reqd' => "no" },
103 { 'name' => "converttobitrate",
104 'desc' => "{VideoPlugin.converttobitrate}",
105 'type' => "string",
106 'deft' => "200k",
107 'reqd' => "no" },
108 { 'name' => "extractkeyframes",
109 'desc' => "{VideoPlugin.extractkeyframes}",
110 'type' => "flag",
111 'deft' => "0",
112 'reqd' => "no" },
113 { 'name' => "streamingsize",
114 'desc' => "{VideoPlugin.streamingsize}",
115 'type' => "int",
116 'deft' => "352",
117 'reqd' => "no" },
118 { 'name' => "streamingbitrate",
119 'desc' => "{VideoPlugin.streamingbitrate}",
120 'type' => "string",
121 'deft' => "200k",
122 'reqd' => "no" },
123
124 { 'name' => "minimumsize",
125 'desc' => "{ImageConverter.minimumsize}",
126 'type' => "int",
127 'deft' => "100",
128 'range' => "1,",
129 'reqd' => "no" },
130
131 ];
132
133my $options = { 'name' => "VideoConverter",
134 'desc' => "{VideoConverter.desc}",
135 'abstract' => "yes",
136 'inherits' => "yes",
137 'args' => $arguments };
138
139sub new {
140 my ($class) = shift (@_);
141 my ($pluginlist,$inputargs,$hashArgOptLists) = @_;
142 push(@$pluginlist, $class);
143
144 push(@{$hashArgOptLists->{"ArgList"}},@{$arguments});
145 push(@{$hashArgOptLists->{"OptList"}},$options);
146
147 my $self = new MultimediaConverter($pluginlist, $inputargs, $hashArgOptLists, 1);
148
149
150 return bless $self, $class;
151
152}
153
154
155
156
157# Discover the characteristics of a video file.
158
159# Equivalent step to that in ImagePlugin that uses ImageMagicks's
160# 'indentify' utility
161
162# Here we use 'ffmpeg' for video but for consistency keep the Perl
163# method name the same as before
164
165
166sub identify {
167 my ($video, $outhandle, $verbosity) = @_;
168
169 # Use the ffmpeg command to get the file specs
170 my $command = "ffmpeg -i \"$video\"";
171
172 print $outhandle " $command\n" if ($verbosity > 2);
173 my $result = '';
174 $result = `$command 2>&1`;
175 print $outhandle " $result\n" if ($verbosity > 4);
176
177 # Read the type, width, and height etc.
178 my $vtype = 'unknown';
179 my $vcodec = 'unknown';
180 my $width = 'unknown';
181 my $height = 'unknown';
182 my $fps = 'unknown';
183
184 my $atype = 'unknown';
185 my $afreq = 'unknown';
186 my $achan = 'unknown';
187 my $arate = 'unknown';
188
189 my $video_safe = quotemeta $video;
190
191 # strip off everything up to filename
192 $result =~ s/^.*\'$video_safe\'://s;
193
194 if ($result =~ m/Video: (.*?) fps/m) {
195 my $video_info = $1;
196 if ($video_info =~ m/([^,]+),(?: ([^,]+),)? (\d+)x(\d+),.*?(\d+\.\d+)/)
197 {
198 $vtype = $1;
199 $vcodec = $2 if defined $2;
200 $width = $3;
201 $height = $4;
202 $fps = $5;
203 }
204 }
205
206 if ($result =~ m/Audio: (\w+), (\d+) Hz, (\w+)(?:, (\d+.*))?/m) {
207 $atype = $1;
208 $afreq = $2;
209 $achan = $3;
210 $arate = $4 if (defined $4);
211 }
212
213 # Read the duration
214 my $duration = "unknown";
215 if ($result =~ m/Duration: (\d+:\d+:\d+\.\d+)/m) {
216 $duration = $1;
217 }
218 print $outhandle " file: $video:\t $vtype, $width, $height, $duration\n"
219 if ($verbosity > 2);
220
221 if ($verbosity >3) {
222 print $outhandle "\t video codec=$vcodec, fps = $fps\n";
223 print $outhandle "\t audio codec=$atype, freq = $afreq Hz, $achan, $arate\n";
224 }
225
226 # Return the specs
227 return ($vtype, $width, $height, $duration, -s $video,
228 $vcodec,$fps,$atype,$afreq,$achan,$arate);
229}
230
231
232sub vob_durations
233{
234 my ($media_base_dir,$title_num,$outhandle) = @_;
235
236 my $filter_re = sprintf("^VTS_%02d_[1-9]\\.VOB\$",$title_num);
237
238 my $duration_info = {};
239
240 if (opendir(VTSIN,$media_base_dir)) {
241 my @vts_title_vobs = grep { $_ =~ m/$filter_re/ } readdir(VTSIN);
242 closedir(VTSIN);
243
244 foreach my $v (@vts_title_vobs) {
245 my $full_v = &util::filename_cat($media_base_dir,$v);
246
247 my ($vtype, $width, $height, $duration, $vsize,
248 $vcodec,$fps,$atype,$afreq,$achan,$arate) = identify($full_v,$outhandle,0);
249
250 my ($vob_num) = ($v =~ m/^VTS_\d\d_(\d)\.VOB$/);
251
252 $duration_info->{$vob_num} = $duration;
253 print STDERR "**** $title_num: $title_num, storing {$vob_num} => $duration\n";
254
255 }
256
257 }
258 else {
259 print $outhandle "Warning: unable to read files in directory $media_base_dir\n";
260 }
261
262 return $duration_info;
263
264}
265
266
267
268sub init_cache_for_file {
269 my $self = shift(@_);
270
271 my ($video_filename) = @_;
272
273 $self->SUPER::init_cache_for_file($video_filename);
274
275
276 my @flvtool2_monitor = ( 'monitor_init' ,"convertutil::monitor_init_unbuffered",
277 'monitor_line' , "VideoConverter::flvtool2_monitor_line",
278 'monitor_deinit' , "convertutil::monitor_deinit_unbuffered" );
279
280 $self->{'flvtool2_monitor'} = \@flvtool2_monitor;
281
282
283}
284
285
286
287sub optional_frame_scale
288{
289 my $self = shift (@_);
290 my ($orig_size,$video_width,$video_height) = @_;
291
292 my $s_opt = "";
293 if ($video_width > $video_height) {
294 if ($video_width > $orig_size) {
295 my $scale_factor = $orig_size/$video_width;
296 my $scaled_width = int($video_width * $scale_factor);
297 my $scaled_height = int($video_height * $scale_factor);
298
299 # round to be ensure multiple of 2 (needed by some codecs)
300 $scaled_width = int($scaled_width/2)*2;
301 $scaled_height = int($scaled_height/2)*2;
302
303 $s_opt = "-s ${scaled_width}x${scaled_height}";
304 }
305 # else, video is smaller than requested size, don't scale up
306 }
307 else {
308 if ($video_height > $orig_size) {
309 my $scale_factor = $orig_size/$video_height;
310 my $scaled_width = int($video_width * $scale_factor);
311 my $scaled_height = int($video_height * $scale_factor);
312
313 # round to be ensure multiple of 2 (needed by some codecs)
314 $scaled_width = int($scaled_width/2)*2;
315 $scaled_height = int($scaled_height/2)*2;
316
317 $s_opt = "-s ${scaled_width}x${scaled_height}";
318 }
319 # else, video is smaller than requested size, don't scale up
320
321 }
322
323 return $s_opt;
324}
325
326
327sub keyframe_cmd
328{
329 my $self = shift (@_);
330 my ($ivideo_filename) = @_;
331
332 my $video_ext_dir = &util::filename_cat($ENV{'GSDLHOME'},"ext","video");
333
334 my $output_dir = $self->{'cached_dir'};
335 my $ivideo_root = $self->{'cached_file_root'};
336
337 my $oshot_filename = &util::filename_cat($output_dir,"shots.xml");
338
339 my $exp_duration = $self->{'exp_duration'};
340 my $t_opt = (defined $exp_duration) ? "-t $exp_duration" : "";
341
342 my $main_opts = "-y $t_opt";
343
344 my $hive = &util::filename_cat($video_ext_dir,"lib","vhook","hive.so");
345
346 my $oflash_filename = &util::filename_cat($output_dir,"$ivideo_root\_keyframe.flv");
347
348 my $vhook_opts = "$hive -o $oshot_filename -k $output_dir $ivideo_filename";
349
350 my $ivideo_filename_gsdlenv = $self->gsdlhome_independent($ivideo_filename);
351 my $oflash_filename_gsdlenv = $self->gsdlhome_independent($oflash_filename);
352
353 my $ffmpeg_cmd = "ffkeyframe $main_opts -vhook \"$vhook_opts\" -i \"$ivideo_filename_gsdlenv\" -an -y \"$oflash_filename_gsdlenv\"";
354
355
356 return ($ffmpeg_cmd,$oflash_filename);
357}
358
359
360sub stream_cmd
361{
362 my $self = shift (@_);
363 my ($ivideo_filename,$video_width,$video_height,
364 $streaming_quality,
365 $streaming_bitrate,$streaming_size,
366 $opt_streaming_achan, $opt_streaming_arate) = @_;
367
368 my $streaming_achan
369 = (defined $opt_streaming_achan) ? $opt_streaming_achan : 2;
370
371 my $streaming_arate
372 = (defined $opt_streaming_arate) ? $opt_streaming_arate : 22050;
373
374 my $output_dir = $self->{'cached_dir'};
375 my $ivideo_root = $self->{'cached_file_root'};
376
377 my $oflash_file = "${ivideo_root}_stream.flv";
378 my $oflash_filename = &util::filename_cat($output_dir,$oflash_file);
379
380 my $s_opt = $self->optional_frame_scale($streaming_size,$video_width,$video_height);
381
382 my $exp_duration = $self->{'exp_duration'};
383 my $t_opt = (defined $exp_duration) ? "-t $exp_duration" : "";
384
385 my $main_opts = "-y $t_opt";
386
387 my $bitrate_opt = "-b $streaming_bitrate";
388 ### my $stream_opts = "-r 25 $s_opt";
389 my $stream_opts .= " $s_opt -ac $streaming_achan -ar $streaming_arate";
390
391 # -flags +ilme+ildct' and maybe '-flags +alt' for interlaced material, and try '-top 0/1'
392
393 my $all_opts = "$main_opts $stream_opts";
394
395 my $ffmpeg_cmd;
396
397 my $ivideo_filename_gsdlenv = $self->gsdlhome_independent($ivideo_filename);
398 my $oflash_filename_gsdlenv = $self->gsdlhome_independent($oflash_filename);
399
400 if ($streaming_quality eq "high") {
401
402 my $pass_log_file = &util::filename_cat($output_dir,"$ivideo_root-logpass.txt");
403 if (-e $pass_log_file) {
404 &util::rm($pass_log_file);
405 }
406
407 my $pass_log_file_gsdlenv = $self->gsdlhome_independent($pass_log_file);
408
409 $all_opts .= " -passlogfile \"$pass_log_file_gsdlenv\"";
410
411 my $ffmpeg_cmd_pass1 = "ffmpeg -pass 1 -i \"$ivideo_filename_gsdlenv\" $all_opts -y \"$oflash_filename_gsdlenv\"";
412
413 my $ffmpeg_cmd_pass2 = "ffmpeg -pass 2 -i \"$ivideo_filename_gsdlenv\" $all_opts $bitrate_opt -y \"$oflash_filename_gsdlenv\"";
414 $ffmpeg_cmd = "( $ffmpeg_cmd_pass1 ; $ffmpeg_cmd_pass2 )";
415 }
416 else {
417 # single pass
418
419 $ffmpeg_cmd = "ffmpeg -i \"$ivideo_filename_gsdlenv\" $all_opts -y \"$oflash_filename_gsdlenv\"";
420 }
421
422 return ($ffmpeg_cmd,$oflash_filename,$oflash_file);
423}
424
425
426
427sub audio_excerpt_cmd
428{
429 my $self = shift (@_);
430 my ($ivoa_filename,$hh,$mm,$ss,$opt_excerpt_len) = @_;
431
432 # ivoa = input video or audio
433
434 my $time_encoded = "$hh:$mm:$ss";
435 my $time_encoded_file = "$hh$mm$ss";
436
437
438 my $output_dir = $self->{'cached_dir'};
439 my $ivoa_root = $self->{'cached_file_root'};
440
441 my $omp3_file = "${ivoa_root}_$time_encoded_file.mp3";
442 my $omp3_filename = &util::filename_cat($output_dir,$omp3_file);
443
444 my $all_opts = "-y -acodec mp3 -ss $time_encoded ";
445
446 if (defined $opt_excerpt_len) {
447 $all_opts .= "-t $opt_excerpt_len ";
448 }
449
450
451 my $ivoa_filename_gsdlenv = $self->gsdlhome_independent($ivoa_filename);
452 my $omp3_filename_gsdlenv = $self->gsdlhome_independent($omp3_filename);
453
454
455 my $ffmpeg_cmd = "ffmpeg -i \"$ivoa_filename_gsdlenv\" $all_opts \"$omp3_filename_gsdlenv\"";
456
457 return ($ffmpeg_cmd,$omp3_filename,$omp3_file);
458}
459
460
461
462sub streamseekable_cmd
463{
464 my $self = shift (@_);
465 my ($oflash_filename) = @_;
466
467 my $output_dir = $self->{'cached_dir'};
468 my $ivideo_root = $self->{'cached_file_root'};
469
470 my $cue_filename = &util::filename_cat($output_dir,"on_cue.xml");
471
472 my $flvtool_cmd = "flvtool2 -vUP \"$oflash_filename\"";
473
474 return ($flvtool_cmd,$oflash_filename);
475}
476
477
478sub streamkeyframes_cmd
479{
480 my $self = shift (@_);
481 my ($oflash_filename,$doc_obj,$section) = @_;
482
483 my $assocfilepath
484 = $doc_obj->get_metadata_element($section,"assocfilepath");
485
486 my $output_dir = $self->{'cached_dir'};
487
488 my $cue_filename = &util::filename_cat($output_dir,"on_cue.xml");
489
490 my $video_server = $ENV{'GEXT_VIDEO_SERVER'};
491 my $video_prefix = $ENV{'GEXT_VIDEO_PREFIX'};
492
493 my $collect = $ENV{'GSDLCOLLECTION'};
494
495 my $flvtool_cmd = "flvtool2 -vAUtP \"$cue_filename\" -thumbLocation:$video_server$video_prefix/collect/$collect/index/assoc/$assocfilepath \"$oflash_filename\"";
496
497
498 return ($flvtool_cmd,$oflash_filename);
499}
500
501
502sub streamcuepts_cmd
503{
504 my $self = shift (@_);
505 my ($oflash_filename) = @_;
506
507 my $output_dir = $self->{'cached_dir'};
508
509 my $cue_filename = &util::filename_cat($output_dir,"on_cue.xml");
510
511 my $video_server = $ENV{'GEXT_VIDEO_SERVER'};
512 my $video_prefix = $ENV{'GEXT_VIDEO_PREFIX'};
513
514 my $collect = $ENV{'GSDLCOLLECTION'};
515 my $thumbloc = "$video_server$video_prefix/collect/$collect";
516
517
518# my $flvtool_cmd = "flvtool2 -vUAtP \"$cue_filename\" -thumbLocation:$thumbloc \"$oflash_filename\"";
519
520# my $flvtool_cmd = "flvtool2 -vUAt \"$cue_filename\" \"$oflash_filename\"";
521
522
523
524# my $flvtool_cmd = "flvtool2 -vUAt \"$cue_filename\" \"$oflash_filename\"";
525
526
527## my $flvtool_cmd = "flvtool2 -vAt \"$cue_filename\" -UP \"$oflash_filename\" \"$output_dir/updated.flv\"";
528
529## my $flvtool_cmd = "flvtool2 -vAtU \"$cue_filename\" \"$oflash_filename\" \"$output_dir/updated.flv\"";
530
531 my $flvtool_cmd = "flvtool2 -vAtUP \"$cue_filename\" \"$oflash_filename\"";
532
533 return ($flvtool_cmd,$oflash_filename);
534}
535
536
537sub keyframe_thumbnail_cmd
538{
539 my $self = shift (@_);
540 my ($ivideo_filename,$thumbnailfile,$thumbnailwidth,$thumbnailheight) = @_;
541
542 my $output_dir = $self->{'cached_dir'};
543 my $ivideo_root = $self->{'cached_file_root'};
544
545 my $key_filename_prefix = &util::filename_cat($output_dir,$ivideo_root);
546
547
548 # Try for 4th keyframe, but fall back to 1st if doesn't exist
549 my $key_filename = "${key_filename_prefix}_0003.jpg";
550 $key_filename = "${key_filename_prefix}_0000.jpg" if (!-e $key_filename);
551
552 my $key_filename_gsdlenv = $self->gsdlhome_independent($key_filename);
553 my $thumbnailfile_gsdlenv = $self->gsdlhome_independent($thumbnailfile);
554
555 my $command;
556
557 if (-e $key_filename) {
558 $command = "convert -interlace plane -verbose -geometry $thumbnailwidth"
559 . "x$thumbnailheight \"$key_filename_gsdlenv\" \"$thumbnailfile_gsdlenv\"";
560 }
561 else {
562 # extractkeyframe has either not been switched on, or else had
563 # a problem when running
564 # => extract a from
565 # my $frame_rate = 1.0 / 60.0;
566
567 my $ivideo_filename_gsdlenv = $self->gsdlhome_independent($ivideo_filename);
568
569
570
571 $command = "ffmpeg -i \"$ivideo_filename_gsdlenv\" -ss 12.5 -vframes 1 -f image2 -s ${thumbnailwidth}x${thumbnailheight} -y \"$thumbnailfile_gsdlenv\"";
572
573 # fmpeg -i input.dv -r 1 -f image2 -s 120x96 images%05d.png
574 }
575
576 return ($command,$thumbnailfile);
577}
578
579
580sub keyframe_montage_cmd
581{
582 my $self = shift (@_);
583 my ($ivideo_filename,$montagefile) = @_;
584
585 my $output_dir = $self->{'cached_dir'};
586 my $ivideo_root = $self->{'cached_file_root'};
587
588 my $key_filename_prefix = &util::filename_cat($output_dir,$ivideo_root);
589
590 my $options = "-tile 10 -geometry 75x62+2+2";
591
592 my $command = "montage $options ${key_filename_prefix}_*.jpg \"$montagefile\"";
593
594 return ($command,$montagefile);
595}
596
597
598
599sub parse_shot_xml
600{
601 my ($self) = shift(@_);
602
603 my $outhandle = $self->{'outhandle'};
604 my $output_dir = $self->{'cached_dir'};
605
606 my $shots_filename = &util::filename_cat($output_dir,"shots.xml");
607
608 eval {
609 $self->{'parser'}->parsefile($shots_filename);
610 };
611
612 if ($@) {
613 print $outhandle "VideoConverter: skipping $shots_filename as not conformant to Hive shot syntax\n" if ($self->{'verbosity'} > 1);
614 print $outhandle "\n Perl Error:\n $@\n" if ($self->{'verbosity'}>2);
615 return 0;
616 }
617
618}
619
620sub associate_keyframes_old
621{
622 my ($self) = shift(@_);
623
624 my ($doc_obj,$section) = @_;
625
626 my $output_dir = $self->{'cached_dir'};
627
628 my $count = 1;
629 foreach my $kframe_file (@{$self->{'keyframe_fnames'}}) {
630
631 my $kframe_filename = &util::filename_cat($output_dir,$kframe_file);
632 $doc_obj->associate_file($kframe_filename,"keyframe$count.jpg","image/jpeg",
633 $section);
634 $count++;
635 }
636
637 $doc_obj->add_utf8_metadata($section,"NumKeyframes",scalar(@{$self->{'keyframe_fnames'}}));
638
639
640 # *****
641 # $doc_obj->add_metadata ($section, "thumblist", $self->{'flowplayer_thumblist'});
642
643}
644
645sub associate_keyframes
646{
647 my ($self) = shift(@_);
648
649 my ($doc_obj,$section) = @_;
650
651 my $output_dir = $self->{'cached_dir'};
652 my $timeline = $self->{'keyframe_timeline'};
653
654 my $count = 1;
655
656 foreach my $t (sort { $timeline->{$a}->{'keyframeindex'} <=> $timeline->{$b}->{'keyframeindex'} } keys %$timeline)
657 {
658 my $kframe_file = $timeline->{$t}->{'thumb'};
659 my $timestamp = $timeline->{$t}->{'timestamp'};
660
661 my $kframe_filename = &util::filename_cat($output_dir,$kframe_file);
662 $doc_obj->associate_file($kframe_filename,"keyframe$count.jpg","image/jpeg",
663 $section);
664 $doc_obj->add_utf8_metadata($section,"KeyframeTimestamp",$timestamp);
665
666 $count++;
667 }
668
669 $doc_obj->add_utf8_metadata($section,"NumKeyframes",scalar(@{$self->{'keyframe_fnames'}}));
670
671
672 # *****
673 # $doc_obj->add_metadata ($section, "thumblist", $self->{'flowplayer_thumblist'});
674}
675
676
677
678sub flvtool2_monitor_line
679{
680 my ($line) = @_;
681
682 my $had_error = 0;
683 my $generate_dot = 1;
684
685 if ($line =~ m/\s+\- /) {
686 # ignore tabulated output printed at end of command
687 $generate_dot = 0;
688 }
689
690 if ($line =~ m/^Error:/i) {
691 $had_error = 1;
692 }
693
694 return ($had_error,$generate_dot);
695}
696
697
698
699
700
7011;
Note: See TracBrowser for help on using the repository browser.