source: gs2-extensions/video/trunk/perllib/plugins/VideoConverter.pm@ 21824

Last change on this file since 21824 was 21824, checked in by max, 14 years ago

Allow to keep original video file
Uses MediaInfo instead of ffmpeg to extract technical metadata
Uses the original file name to create compressed filename
Optimisations for speed and use of HandBrake 0.9.4 Syntax

File size: 36.5 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' => "keep_original_video",
50 'desc' => "Copy the original video file in the collection as an associated file.",
51 'type' => "enum",
52 'list' => [{'name' => "true", 'desc' => "Include the original video file in the collection"},
53 {'name' => "false", 'desc' => "Do not copy the original video file in the collection"}],
54 'deft' => "false",
55 'reqd' => "no" },
56 { 'name' => "thumbnailsize",
57 'desc' => "{ImageConverter.thumbnailsize}",
58 'type' => "int",
59 'deft' => "100",
60 'range' => "1,",
61 'reqd' => "no" },
62 { 'name' => "thumbnailtype",
63 'desc' => "{ImageConverter.thumbnailtype}",
64 'type' => "string",
65 'deft' => "jpg",
66 'reqd' => "no" },
67 { 'name' => "noscaleup",
68 'desc' => "{ImageConverter.noscaleup}",
69 'type' => "flag",
70 'reqd' => "no" } );
71
72
73my @screenview_arguments = (
74 { 'name' => "create_screenview",
75 'desc' => "{ImageConverter.create_screenview}",
76 'type' => "enum",
77 'list' => [{'name' => "true", 'desc' => "{common.true}"},
78 {'name' => "false", 'desc' => "{common.false}"}],
79 'deft' => "true",
80 'reqd' => "no" },
81 { 'name' => "screenviewsize",
82 'desc' => "{ImageConverter.screenviewsize}",
83 'type' => "int",
84 'deft' => "576",
85 'range' => "1,",
86 'reqd' => "no" },
87 { 'name' => "screenviewtype",
88 'desc' => "{ImageConverter.screenviewtype}",
89 'type' => "string",
90 'deft' => "jpg",
91 'reqd' => "no" } );
92
93my $arguments = [
94 @thumb_arguments,
95 @screenview_arguments,
96
97 { 'name' => "converttotype",
98 'desc' => "{ImageConverter.converttotype}",
99 'type' => "string",
100 'deft' => "",
101 'reqd' => "no" },
102
103
104 { 'name' => "converttosize",
105 'desc' => "{VideoPlugin.converttosize}",
106 'type' => "int",
107 'deft' => "",
108## 'deft' => "352",
109 'reqd' => "no" },
110 { 'name' => "converttobitrate",
111 'desc' => "{VideoPlugin.converttobitrate}",
112 'type' => "string",
113 'deft' => "200k",
114 'reqd' => "no" },
115 { 'name' => "extract_keyframes",
116 'desc' => "{VideoPlugin.extractkeyframes}",
117 'type' => "flag",
118 'deft' => "0",
119 'reqd' => "no" },
120 { 'name' => "keep_keyframes",
121 'desc' => "{VideoPlugin.keep_keyframes}",
122 'type' => "string",
123 'deft' => "all",
124 'reqd' => "no" },
125 { 'name' => "streamingsize",
126 'desc' => "{VideoPlugin.streamingsize}",
127 'type' => "int",
128 'deft' => "352",
129 'reqd' => "no" },
130 { 'name' => "streamingbitrate",
131 'desc' => "{VideoPlugin.streamingbitrate}",
132 'type' => "string",
133 'deft' => "200k",
134 'reqd' => "no" },
135 { 'name' => "streamingHQsize",
136 'desc' => "{VideoPlugin.streamingsize}",
137 'type' => "int",
138 'deft' => "576",
139 'reqd' => "no" },
140 { 'name' => "streamingHQVideoBitrate",
141 'desc' => "{VideoPlugin.streamingbitrate}",
142 'type' => "int",
143 'deft' => "432",
144 'reqd' => "no" },
145 { 'name' => "streamingHQAudioBitrate",
146 'desc' => "{VideoPlugin.streamingbitrate}",
147 'type' => "int",
148 'deft' => "80",
149 'reqd' => "no" },
150
151 { 'name' => "minimumsize",
152 'desc' => "{ImageConverter.minimumsize}",
153 'type' => "int",
154 'deft' => "100",
155 'range' => "1,",
156 'reqd' => "no" },
157
158 ];
159
160my $options = { 'name' => "VideoConverter",
161 'desc' => "{VideoConverter.desc}",
162 'abstract' => "yes",
163 'inherits' => "yes",
164 'args' => $arguments };
165
166sub new {
167 my ($class) = shift (@_);
168 my ($pluginlist,$inputargs,$hashArgOptLists) = @_;
169 push(@$pluginlist, $class);
170
171 push(@{$hashArgOptLists->{"ArgList"}},@{$arguments});
172 push(@{$hashArgOptLists->{"OptList"}},$options);
173
174 my $self = new MultimediaConverter($pluginlist, $inputargs, $hashArgOptLists, 1);
175
176
177 return bless $self, $class;
178
179}
180
181
182
183
184# Discover the characteristics of a video file.
185
186# Equivalent step to that in ImagePlugin that uses ImageMagicks's
187# 'indentify' utility
188
189# Here we use 'MediaInfo' for video but for consistency keep the Perl
190# method name the same as before
191
192sub mediaInfo_Inform_Cmd {
193 my ($video, $parameter, $outhandle, $verbosity) = @_;
194
195 # Use MediaInfo CLI to get the file spec according to the requested parameter
196 my $command = "mediainfo --Inform=\"$parameter\" \"$video\"";
197
198 print $outhandle " $command\n" if ($verbosity > 2);
199 my $result = '';
200 $result = `$command 2>&1`;
201 chomp $result;
202 print $outhandle " $result\n" if ($verbosity > 4);
203
204 # Return the asked spec
205 return ($result);
206}
207
208# Here we use mediaInfo to get all the possible metadata in XML form that could then be parsed
209#the result will vary depending on the file type and how it was encoded
210sub mediaInfo_XML_Cmd {
211 my ($video, $outhandle, $verbosity) = @_;
212
213 # Use MediaInfo CLI to get the file spec according to the requested parameter
214 my $command = "mediainfo --Output=XML \"$video\"";
215
216 print $outhandle " $command\n" if ($verbosity > 2);
217 my $result = '';
218 $result = `$command 2>&1`;
219 print $outhandle " $result\n" if ($verbosity > 4);
220
221 # Return the asked spec
222 return ($result);
223}
224
225#Here we parse the video specs contained in the XML text in order to create a metadata entry for each field
226sub mediaInfo_parse_XML {
227 my ($xmlTxt, $outhandle, $verbosity) = @_;
228
229 my @parts = split(/<track\s+type=\"(.*?)\">/is,$xmlTxt);
230
231 shift @parts; # Skip preamble
232 my $metadata_table = {};
233
234 while (@parts) {
235 my $type = shift(@parts);
236 my $vals = shift(@parts);
237 my @fieldlist=();
238 while ($vals =~ s/<(.*?)>(.*?)<\/\1>//) {
239 my $metaname = $1;
240 my $metaval = $2;
241 my $fullmetaname = "mi.$type\^$metaname";
242 $metadata_table->{$fullmetaname}= $metaval;
243 push(@fieldlist,$fullmetaname);
244 }
245
246 $metadata_table->{"mi.${type}Fields"}= join(",",@fieldlist);
247
248 }
249
250 return $metadata_table;
251
252}
253
254sub identify {
255 my ($video, $outhandle, $verbosity) = @_;
256
257 # Use the ffmpeg command to get the file specs
258 my $command = "ffmpeg -i \"$video\"";
259
260 print $outhandle " $command\n" if ($verbosity > 2);
261 my $result = '';
262 $result = `$command 2>&1`;
263 print $outhandle " $result\n" if ($verbosity > 4);
264
265 # Read the type, width, and height etc.
266 # This could be done in a more efficient way by only calling the application mediainfo once and
267 # giving all the parameters then parsing the results, however on most operating system
268 # once the application is called once it is then cached for subsequent calls.
269 my $vtype = mediaInfo_Inform_Cmd($video,"General;%Format%",$outhandle,0);
270 my $duration = mediaInfo_Inform_Cmd($video,"General;%Duration%",$outhandle,0);
271 my $durationDisplay = mediaInfo_Inform_Cmd($video,"General;%Duration/String1%",$outhandle,0);
272 my $filesize = mediaInfo_Inform_Cmd($video,"General;%FileSize/String%",$outhandle,0);
273 my $vcodec = mediaInfo_Inform_Cmd($video,"Video;%Format%",$outhandle,0);
274 my $vrate = mediaInfo_Inform_Cmd($video,"Video;%BitRate%",$outhandle,0);
275 my $width = mediaInfo_Inform_Cmd($video,"Video;%Width%",$outhandle,0);
276 my $height = mediaInfo_Inform_Cmd($video,"Video;%Height%",$outhandle,0);
277 my $fps = mediaInfo_Inform_Cmd($video,"Video;%FrameRate%",$outhandle,0);
278 my $atype = mediaInfo_Inform_Cmd($video,"Audio;%Format%",$outhandle,0);
279 my $afreq = mediaInfo_Inform_Cmd($video,"Audio;%SamplingRate/String%",$outhandle,0);
280 my $achan = mediaInfo_Inform_Cmd($video,"Audio;%Channel(s)%",$outhandle,0);
281 my $arate = mediaInfo_Inform_Cmd($video,"Audio;%BitRate%",$outhandle,0);
282
283 my $xmlTxt = mediaInfo_XML_Cmd ($video,$outhandle,0);
284
285 my $metadata_table = mediaInfo_parse_XML($xmlTxt,$outhandle,0);
286
287 # Return the specs
288 return ($vtype,$width,$height,$duration,$durationDisplay,$filesize,
289 $vcodec,$vrate,$fps,$atype,$afreq,$achan,$arate,$metadata_table);
290}
291
292
293sub identify2 {
294 my ($video, $outhandle, $verbosity) = @_;
295
296 # Use the ffmpeg command to get the file specs
297 my $command = "ffmpeg -i \"$video\"";
298
299 print $outhandle " $command\n" if ($verbosity > 2);
300 my $result = '';
301 $result = `$command 2>&1`;
302 print $outhandle " $result\n" if ($verbosity > 4);
303
304 # Read the type, width, and height etc.
305 my $vtype = 'unknown';
306 my $vcodec = 'unknown';
307 my $width = 'unknown';
308 my $height = 'unknown';
309 my $fps = 'unknown';
310
311 my $atype = 'unknown';
312 my $afreq = 'unknown';
313 my $achan = 'unknown';
314 my $arate = 'unknown';
315
316 my $video_safe = quotemeta $video;
317
318 # strip off everything up to filename
319 $result =~ s/^.*\'$video_safe\'://s;
320
321## if ($result =~ m/Video: (.*?) fps/m) {
322 if ($result =~ m/^\s+Stream.*?Video: (.*?)$/m) {
323 my $video_info = $1;
324 $video_info =~ s/\s*\[.*?\]//g;
325
326 my @video_fields = split(/,\s*/,$video_info);
327
328 $vtype = shift @video_fields;
329
330 if ($video_fields[0] !~ m/(\d+)x(\d+)/) {
331 $vcodec = shift @video_fields;
332 }
333 my $video_dim = shift @video_fields;
334
335 ($width,$height) = ($video_dim =~ m/(\d+)x(\d+)/);
336
337 if ($video_fields[0] =~ m/(\d+)\s+tbr$/) {
338 $fps = $1;
339 }
340
341# if ($video_info =~ m/([^,]+),(?: ([^,]+),)? (\d+)x(\d+),.*?(\d+\.\d+)/)
342# {
343# $vtype = $1;
344# $vcodec = $2 if defined $2;
345# $width = $3;
346# $height = $4;
347# $fps = $5;
348# }
349 }
350
351 if ($result =~ m/Audio: (\w+), (\d+) Hz, (\w+)(?:, (\d+.*))?/m) {
352 $atype = $1;
353 $afreq = $2;
354 $achan = $3;
355 $arate = $4 if (defined $4);
356 }
357
358 # Read the duration
359 my $duration = "unknown";
360 if ($result =~ m/Duration: (\d+:\d+:\d+\.\d+)/m) {
361 $duration = $1;
362 }
363 print $outhandle " file: $video:\t $vtype, $width, $height, $duration\n"
364 if ($verbosity > 2);
365
366 if ($verbosity >3) {
367 print $outhandle "\t video codec=$vcodec, fps = $fps\n";
368 print $outhandle "\t audio codec=$atype, freq = $afreq Hz, $achan, $arate\n";
369 }
370
371 # Return the specs
372 return ($vtype, $width, $height, $duration, -s $video,
373 $vcodec,$fps,$atype,$afreq,$achan,$arate);
374}
375
376
377sub vob_durations
378{
379 my ($media_base_dir,$title_num,$outhandle) = @_;
380
381 my $filter_re = sprintf("^VTS_%02d_[1-9]\\.VOB\$",$title_num);
382
383 my $duration_info = {};
384
385 if (opendir(VTSIN,$media_base_dir)) {
386 my @vts_title_vobs = grep { $_ =~ m/$filter_re/ } readdir(VTSIN);
387 closedir(VTSIN);
388
389 foreach my $v (@vts_title_vobs) {
390 my $full_v = &util::filename_cat($media_base_dir,$v);
391
392 my ($vtype, $width, $height, $duration, $durationDisplay, $vsize,
393 $vcodec,$vrate,$fps,$atype,$afreq,$achan,$arate) = identify($full_v,$outhandle,0);
394
395 my ($vob_num) = ($v =~ m/^VTS_\d\d_(\d)\.VOB$/);
396
397 $duration_info->{$vob_num} = $duration;
398 print STDERR "**** $title_num: $title_num, storing {$vob_num} => $duration\n";
399
400 }
401
402 }
403 else {
404 print $outhandle "Warning: unable to read files in directory $media_base_dir\n";
405 }
406
407 return $duration_info;
408
409}
410
411
412
413sub init_cache_for_file {
414 my $self = shift(@_);
415
416 my ($video_filename) = @_;
417
418 $self->SUPER::init_cache_for_file($video_filename);
419
420
421 my @flvtool2_monitor = ( 'monitor_init' ,"convertutil::monitor_init_unbuffered",
422 'monitor_line' , "VideoConverter::flvtool2_monitor_line",
423 'monitor_deinit' , "convertutil::monitor_deinit_unbuffered" );
424
425 $self->{'flvtool2_monitor'} = \@flvtool2_monitor;
426
427
428}
429
430
431
432sub optional_frame_scale
433{
434 my $self = shift (@_);
435 my ($orig_size,$video_width,$video_height) = @_;
436
437 my $s_opt = "";
438
439 if ($video_width > $video_height) {
440 if ($video_width > $orig_size) {
441 my $scale_factor = $orig_size/$video_width;
442 my $scaled_width = int($video_width * $scale_factor);
443 my $scaled_height = int($video_height * $scale_factor);
444
445 # round to be ensure multiple of 2 (needed by some codecs)
446 $scaled_width = int($scaled_width/2)*2;
447 $scaled_height = int($scaled_height/2)*2;
448
449 $s_opt = "-s ${scaled_width}x${scaled_height}";
450 }
451 # else, video is smaller than requested size, don't scale up
452 }
453 else {
454 if ($video_height > $orig_size) {
455 my $scale_factor = $orig_size/$video_height;
456 my $scaled_width = int($video_width * $scale_factor);
457 my $scaled_height = int($video_height * $scale_factor);
458
459 # round to be ensure multiple of 2 (needed by some codecs)
460 $scaled_width = int($scaled_width/2)*2;
461 $scaled_height = int($scaled_height/2)*2;
462
463 $s_opt = "-s ${scaled_width}x${scaled_height}";
464 }
465 # else, video is smaller than requested size, don't scale up
466
467 }
468
469 return $s_opt;
470}
471
472
473sub keyframe_cmd
474{
475 my $self = shift (@_);
476 my ($ivideo_filename) = @_;
477
478 my $video_ext_dir = &util::filename_cat($ENV{'GSDLHOME'},"ext","video");
479
480 my $output_dir = $self->{'cached_dir'};
481 my $ivideo_root = $self->{'cached_file_root'};
482
483 my $oshot_filename = &util::filename_cat($output_dir,"shots.xml");
484
485 my $exp_duration = $self->{'exp_duration'};
486 my $t_opt = (defined $exp_duration) ? "-t $exp_duration" : "";
487
488 my $main_opts = "-y $t_opt";
489
490 my $hive = &util::filename_cat($video_ext_dir,"lib","vhook","hive.so");
491
492 my $oflash_filename = &util::filename_cat($output_dir,"$ivideo_root\_keyframe.flv");
493
494 my $vhook_opts = "$hive -o $oshot_filename -k $output_dir $ivideo_filename";
495
496 my $ivideo_filename_gsdlenv = $self->gsdlhome_independent($ivideo_filename);
497 my $oflash_filename_gsdlenv = $self->gsdlhome_independent($oflash_filename);
498
499 my $ffmpeg_cmd = "ffkeyframe $main_opts -vhook \"$vhook_opts\" -i \"$ivideo_filename_gsdlenv\" -an -y \"$oflash_filename_gsdlenv\"";
500
501
502 return ($ffmpeg_cmd,$oflash_filename);
503}
504
505sub get_ovideo_file
506{
507 my $self = shift (@_);
508 my ($stream_type) = @_;
509
510 my $ivideo_root = $self->{'cached_file_root'};
511 my $ofile;
512
513 if ($stream_type eq "flv")
514 {
515 $ofile = "${ivideo_root}_vstream.flv";
516 }
517 else
518 {
519 $ofile = "${ivideo_root}_vHQstream.mp4";
520 }
521
522 return $ofile;
523}
524
525sub get_ovideo_filename
526{
527 my $self = shift (@_);
528 my ($stream_type) = @_;
529
530 my $output_dir = $self->{'cached_dir'};
531
532 my $ofile = $self->get_ovideo_file($stream_type);
533
534 my $ofilename = &util::filename_cat($output_dir,$ofile);
535
536 return $ofilename;
537}
538
539sub stream_mp4_video_cmd
540{
541 my $self = shift (@_);
542 my ($ivideo_filename,$streaming_HQ_size, $streaming_HQ_VideoBitrate,
543 $streaming_HQ_AudioBitrate) = @_;
544
545
546 my $output_dir = $self->{'cached_dir'};
547 my $ivideo_root = $self->{'cached_file_root'};
548
549 my $omp4_file = "${ivideo_root}_vHQstream.mp4";
550 my $omp4_filename = &util::filename_cat($output_dir,$omp4_file);
551
552
553 #my $exp_duration = $self->{'exp_duration'};
554 #my $t_opt = (defined $exp_duration) ? "-t $exp_duration" : "";
555
556
557
558 my $size;
559 # Looks for empty string as well as fullsize string
560 if(!$streaming_HQ_size || $streaming_HQ_size eq "fullsize") {
561 $size = "--strict-anamorphic";
562 } else {
563 $size = "-w $streaming_HQ_size --loose-anamorphic";
564 }
565
566
567 my $ivideo_filename_gsdlenv = $self->gsdlhome_independent($ivideo_filename);
568 my $omp4_filename_gsdlenv = $self->gsdlhome_independent($omp4_filename);
569
570 #my $pre_opts = "--decomb -t 1 -c 1";
571 #my $post_opts = "-f mp4 $size -O -e x264 -b $streaming_HQ_VideoBitrate -a 1 -E faac -6 dpl2 -B $streaming_HQ_AudioBitrate -D 0.0 -x ref=2:bframes=2:me-umh -v 1";
572
573 my $pre_opts = "-t 1 -c 1";
574 my $post_opts = "-f mp4 -O $size --decomb -e x264 -b $streaming_HQ_VideoBitrate -a 1 -E faac -6 dpl2 -R Auto -B $streaming_HQ_AudioBitrate -D 0.0 -x ref=2:bframes=2:subq=6:mixed-refs=0:weightb=0:8x8dct=0:trellis=0";
575
576 my $handbrake_cmd = "HandbrakeCLI.exe -i \"$ivideo_filename_gsdlenv\" $pre_opts -o \"$omp4_filename_gsdlenv\" $post_opts";
577
578
579 return ($handbrake_cmd,$omp4_filename,$omp4_file);
580}
581
582
583
584sub stream_flv_video_cmd
585{
586 my $self = shift (@_);
587 my ($ivideo_filename,$video_width,$video_height,
588 $streaming_quality,
589 $streaming_bitrate,$streaming_size,
590 $opt_streaming_achan, $opt_streaming_arate) = @_;
591
592 my $streaming_achan
593 = (defined $opt_streaming_achan) ? $opt_streaming_achan : 2;
594
595 my $streaming_arate
596 = (defined $opt_streaming_arate) ? $opt_streaming_arate : 22050;
597
598 my $output_dir = $self->{'cached_dir'};
599 my $ivideo_root = $self->{'cached_file_root'};
600
601 my $oflash_file = "${ivideo_root}_vstream.flv";
602 my $oflash_filename = &util::filename_cat($output_dir,$oflash_file);
603
604 my $s_opt = "";
605 my $media_type = $self->{'media_type'};
606 if ($media_type ne "audio") {
607 $s_opt = $self->optional_frame_scale($streaming_size,$video_width,$video_height);
608 }
609
610
611 my $exp_duration = $self->{'exp_duration'};
612 my $t_opt = (defined $exp_duration) ? "-t $exp_duration" : "";
613
614 my $main_opts = "-y $t_opt";
615
616 my $bitrate_opt = "-b $streaming_bitrate";
617 ### my $stream_opts = "-r 25 $s_opt";
618 my $stream_opts .= " $s_opt -ac $streaming_achan -ar $streaming_arate";
619
620 # -flags +ilme+ildct' and maybe '-flags +alt' for interlaced material, and try '-top 0/1'
621
622 my $all_opts = "$main_opts $stream_opts";
623
624 my $ffmpeg_cmd;
625
626 my $ivideo_filename_gsdlenv = $self->gsdlhome_independent($ivideo_filename);
627 my $oflash_filename_gsdlenv = $self->gsdlhome_independent($oflash_filename);
628
629 if ($streaming_quality eq "high") {
630
631 my $pass_log_file = &util::filename_cat($output_dir,"$ivideo_root-logpass.txt");
632 if (-e $pass_log_file) {
633 &util::rm($pass_log_file);
634 }
635
636 my $pass_log_file_gsdlenv = $self->gsdlhome_independent($pass_log_file);
637
638 $all_opts .= " -passlogfile \"$pass_log_file_gsdlenv\"";
639
640 my $ffmpeg_cmd_pass1 = "ffmpeg -pass 1 -i \"$ivideo_filename_gsdlenv\" $all_opts -y \"$oflash_filename_gsdlenv\"";
641
642 my $ffmpeg_cmd_pass2 = "ffmpeg -pass 2 -i \"$ivideo_filename_gsdlenv\" $all_opts $bitrate_opt -y \"$oflash_filename_gsdlenv\"";
643 $ffmpeg_cmd = "( $ffmpeg_cmd_pass1 && $ffmpeg_cmd_pass2 )";
644 }
645 else {
646 # single pass
647
648 $ffmpeg_cmd = "ffmpeg -i \"$ivideo_filename_gsdlenv\" $all_opts -y \"$oflash_filename_gsdlenv\"";
649 }
650
651 return ($ffmpeg_cmd,$oflash_filename,$oflash_file);
652}
653
654
655
656
657sub stream_flv_audio_cmd
658{
659 # AudioConverter also has a routine for doing this
660 # => merge into one!
661 # **************
662
663 my $self = shift (@_);
664 my ($ivideo_filename, $opt_streaming_achan, $opt_streaming_arate) = @_;
665
666 # Convert either audio or video to streamable audio format
667
668 my $streaming_achan
669 = (defined $opt_streaming_achan) ? $opt_streaming_achan : 2;
670
671 my $streaming_arate
672 = (defined $opt_streaming_arate) ? $opt_streaming_arate : 22050;
673
674 my $output_dir = $self->{'cached_dir'};
675 my $ivideo_root = $self->{'cached_file_root'};
676
677 my $ofla_file = "${ivideo_root}_astream.flv";
678 my $ofla_filename = &util::filename_cat($output_dir,$ofla_file);
679
680 my $exp_duration = $self->{'exp_duration'};
681 my $t_opt = (defined $exp_duration) ? "-t $exp_duration" : "";
682
683 my $main_opts = "-vn -y $t_opt";
684
685 my $stream_opts .= " -ac $streaming_achan -ar $streaming_arate";
686
687 my $all_opts = "$main_opts $stream_opts";
688
689 my $ffmpeg_cmd;
690
691 my $ivideo_filename_gsdlenv = $self->gsdlhome_independent($ivideo_filename);
692 my $ofla_filename_gsdlenv = $self->gsdlhome_independent($ofla_filename);
693 $ffmpeg_cmd = "ffmpeg -i \"$ivideo_filename_gsdlenv\" $all_opts -y \"$ofla_filename_gsdlenv\"";
694
695 return ($ffmpeg_cmd,$ofla_filename,$ofla_file);
696}
697
698
699
700
701sub audio_excerpt_cmd
702{
703 my $self = shift (@_);
704 my ($ivoa_filename,$hh,$mm,$ss,$opt_excerpt_len) = @_;
705
706 # ivoa = input video or audio
707
708 my $time_encoded = "$hh:$mm:$ss";
709 my $time_encoded_file = "$hh$mm$ss";
710
711
712 my $output_dir = $self->{'cached_dir'};
713 my $ivoa_root = $self->{'cached_file_root'};
714
715 my $omp3_file = "${ivoa_root}_$time_encoded_file.mp3";
716 my $omp3_filename = &util::filename_cat($output_dir,$omp3_file);
717
718 my $all_opts = "-y -acodec mp3 -ss $time_encoded ";
719
720 if (defined $opt_excerpt_len) {
721 $all_opts .= "-t $opt_excerpt_len ";
722 }
723
724
725 my $ivoa_filename_gsdlenv = $self->gsdlhome_independent($ivoa_filename);
726 my $omp3_filename_gsdlenv = $self->gsdlhome_independent($omp3_filename);
727
728
729 my $ffmpeg_cmd = "ffmpeg -i \"$ivoa_filename_gsdlenv\" $all_opts \"$omp3_filename_gsdlenv\"";
730
731 return ($ffmpeg_cmd,$omp3_filename,$omp3_file);
732}
733
734
735
736sub streamseekable_cmd
737{
738 my $self = shift (@_);
739 my ($oflash_filename) = @_;
740
741 my $output_dir = $self->{'cached_dir'};
742 my $ivideo_root = $self->{'cached_file_root'};
743
744 ## my $cue_filename = &util::filename_cat($output_dir,"on_cue.xml");
745
746 my $flvtool_cmd = "flvtool2 -vUP \"$oflash_filename\"";
747
748 return ($flvtool_cmd,$oflash_filename);
749}
750
751
752sub streamkeyframes_cmd
753{
754 my $self = shift (@_);
755 my ($oflash_filename,$doc_obj,$section) = @_;
756
757 my $assocfilepath
758 = $doc_obj->get_metadata_element($section,"assocfilepath");
759
760 my $output_dir = $self->{'cached_dir'};
761
762 my $cue_filename = &util::filename_cat($output_dir,"on_cue.xml");
763
764 my $flvtool_cmd = "flvtool2 -vAUtP \"$cue_filename\" \"$oflash_filename\"";
765
766 return ($flvtool_cmd,$oflash_filename);
767}
768
769
770sub streamkeyframes_cmd_old
771{
772 my $self = shift (@_);
773 my ($oflash_filename,$doc_obj,$section) = @_;
774
775 my $assocfilepath
776 = $doc_obj->get_metadata_element($section,"assocfilepath");
777
778 my $output_dir = $self->{'cached_dir'};
779
780 my $cue_filename = &util::filename_cat($output_dir,"on_cue.xml");
781
782 my $video_server = $ENV{'GEXT_VIDEO_SERVER'};
783 my $video_prefix = $ENV{'GEXT_VIDEO_PREFIX'};
784
785 my $collect = $ENV{'GSDLCOLLECTION'};
786
787 print STDERR "**** Warning: need to remove dependency of GEXT_VIDEO_SERVER and _PREFIX\n";
788
789 my $flvtool_cmd = "flvtool2 -vAUtP \"$cue_filename\" -thumbLocation:$video_server$video_prefix/collect/$collect/index/assoc/$assocfilepath \"$oflash_filename\"";
790
791
792 return ($flvtool_cmd,$oflash_filename);
793}
794
795
796sub streamcuepts_cmd
797{
798 my $self = shift (@_);
799 my ($oflash_filename) = @_;
800
801 my $output_dir = $self->{'cached_dir'};
802
803 my $cue_filename = &util::filename_cat($output_dir,"on_cue.xml");
804
805 ##my $video_server = $ENV{'GEXT_VIDEO_SERVER'};
806 ##my $video_prefix = $ENV{'GEXT_VIDEO_PREFIX'};
807
808 ## my $collect = $ENV{'GSDLCOLLECTION'};
809
810 ## my $thumbloc = "$video_server$video_prefix/collect/$collect";
811
812
813# my $flvtool_cmd = "flvtool2 -vUAtP \"$cue_filename\" -thumbLocation:$thumbloc \"$oflash_filename\"";
814
815# my $flvtool_cmd = "flvtool2 -vUAt \"$cue_filename\" \"$oflash_filename\"";
816
817
818
819# my $flvtool_cmd = "flvtool2 -vUAt \"$cue_filename\" \"$oflash_filename\"";
820
821
822## my $flvtool_cmd = "flvtool2 -vAt \"$cue_filename\" -UP \"$oflash_filename\" \"$output_dir/updated.flv\"";
823
824## my $flvtool_cmd = "flvtool2 -vAtU \"$cue_filename\" \"$oflash_filename\" \"$output_dir/updated.flv\"";
825
826 my $flvtool_cmd = "flvtool2 -vAtUP \"$cue_filename\" \"$oflash_filename\"";
827
828 return ($flvtool_cmd,$oflash_filename);
829}
830
831
832sub keyframe_thumbnail_cmd
833{
834 my $self = shift (@_);
835 my ($ivideo_filename,$thumbnailfile,$thumbnailwidth,$thumbnailheight) = @_;
836
837 my $output_dir = $self->{'cached_dir'};
838 my $ivideo_root = $self->{'cached_file_root'};
839
840 my $key_filename_prefix = &util::filename_cat($output_dir,$ivideo_root);
841
842
843 # Try for 4th keyframe, but fall back to 1st if doesn't exist
844 my $key_filename = "${key_filename_prefix}_0003.jpg";
845 $key_filename = "${key_filename_prefix}_0000.jpg" if (!-e $key_filename);
846
847 my $key_filename_gsdlenv = $self->gsdlhome_independent($key_filename);
848 my $thumbnailfile_gsdlenv = $self->gsdlhome_independent($thumbnailfile);
849
850 my $command;
851
852 if (-e $key_filename) {
853 $command = "convert -interlace plane -verbose -geometry $thumbnailwidth"
854 . "x$thumbnailheight \"$key_filename_gsdlenv\" \"$thumbnailfile_gsdlenv\"";
855 }
856 else {
857 # extract_keyframe has either not been switched on, or else had
858 # a problem when running
859 # => extract a from
860 # my $frame_rate = 1.0 / 60.0;
861
862 my $ivideo_filename_gsdlenv = $self->gsdlhome_independent($ivideo_filename);
863
864 $command = "ffmpeg -i \"$ivideo_filename_gsdlenv\" -ss 3.5 -vframes 1 -f image2 -s ${thumbnailwidth}x${thumbnailheight} -y \"$thumbnailfile_gsdlenv\"";
865
866 # fmpeg -i input.dv -r 1 -f image2 -s 120x96 images%05d.png
867 }
868
869 return ($command,$thumbnailfile);
870}
871
872
873sub keyframe_montage_cmd
874{
875 my $self = shift (@_);
876 my ($ivideo_filename,$montagefile) = @_;
877
878 my $output_dir = $self->{'cached_dir'};
879 my $ivideo_root = $self->{'cached_file_root'};
880
881 my $key_filename_prefix = &util::filename_cat($output_dir,$ivideo_root);
882
883 my $options = "-tile 10 -geometry 75x62+2+2";
884
885 my $command = "montage $options ${key_filename_prefix}_*.jpg \"$montagefile\"";
886
887 return ($command,$montagefile);
888}
889
890
891
892sub parse_shot_xml
893{
894 my ($self) = shift(@_);
895
896 my $outhandle = $self->{'outhandle'};
897 my $output_dir = $self->{'cached_dir'};
898
899 my $shots_filename = &util::filename_cat($output_dir,"shots.xml");
900
901 eval {
902 $self->{'parser'}->parsefile($shots_filename);
903 };
904
905 if ($@) {
906 print $outhandle "VideoConverter: skipping $shots_filename as not conformant to Hive shot syntax\n" if ($self->{'verbosity'} > 1);
907 print $outhandle "\n Perl Error:\n $@\n" if ($self->{'verbosity'}>2);
908 return 0;
909 }
910
911}
912
913sub associate_keyframes_old
914{
915 my ($self) = shift(@_);
916
917 my ($doc_obj,$section) = @_;
918
919 my $output_dir = $self->{'cached_dir'};
920
921 my $count = 1;
922 foreach my $kframe_file (@{$self->{'keyframe_fnames'}}) {
923
924 my $kframe_filename = &util::filename_cat($output_dir,$kframe_file);
925 $doc_obj->associate_file($kframe_filename,"keyframe$count.jpg","image/jpeg",
926 $section);
927 $count++;
928 }
929
930 $doc_obj->add_utf8_metadata($section,"NumKeyframes",scalar(@{$self->{'keyframe_fnames'}}));
931
932
933 # *****
934 # $doc_obj->add_metadata ($section, "thumblist", $self->{'flowplayer_thumblist'});
935
936}
937
938sub associate_keyframes
939{
940 my ($self) = shift(@_);
941
942 my ($doc_obj,$topsection,$timeline) = @_;
943
944 my $output_dir = $self->{'cached_dir'};
945
946 my $count = 1;
947
948 foreach my $t (sort { $timeline->{$a}->{'keyframeindex'} <=> $timeline->{$b}->{'keyframeindex'} } keys %$timeline)
949 {
950 my $kframe_file = $timeline->{$t}->{'thumb'};
951 my $timestamp = $timeline->{$t}->{'timestamp'};
952
953 # create next sub-section to video "document"
954 my $endchild = $doc_obj->insert_section($doc_obj->get_end_child($topsection));
955 $doc_obj->add_utf8_metadata($endchild,"Title","Timestamp $timestamp");
956 $doc_obj->add_utf8_metadata($endchild,"FrameNum",$t);
957
958 my $kframe_filename = &util::filename_cat($output_dir,$kframe_file);
959 $doc_obj->associate_file($kframe_filename,"keyframe$count.jpg",
960 "image/jpeg",
961 $endchild);
962
963 $doc_obj->add_utf8_metadata($endchild,"assockeyframe","keyframe$count.jpg");
964
965 $doc_obj->add_utf8_metadata($topsection,"KeyframeTimestamp",$timestamp);
966 $doc_obj->add_utf8_metadata($topsection,"KeyframeFrameNum",$t);
967
968
969
970 $count++;
971 }
972
973 #### $doc_obj->add_utf8_metadata($topsection,"NumKeyframes",scalar(@{$self->{'keyframe_fnames'}}));
974
975 $doc_obj->add_utf8_metadata($topsection,"NumKeyframes",scalar(keys %$timeline));
976
977
978 # *****
979 # $doc_obj->add_metadata ($topsection, "thumblist", $self->{'flowplayer_thumblist'});
980}
981
982
983
984sub enable_audio_streaming
985{
986 my $self = shift (@_);
987 my ($doc_obj,$originalfilename,$filename,$convertto_regenerated) = @_;
988
989 my $section = $doc_obj->get_top_section();
990
991 my $output_dir = $self->{'cached_dir'};
992 my $ivideo_root = $self->{'cached_file_root'};
993
994 # Generate FLV audio-only format for streaming purposes
995
996 my $optionally_run_general_cmd = "run_uncached_general_cmd";
997 if ($self->{'enable_cache'}) {
998 $optionally_run_general_cmd
999 = ($convertto_regenerated) ? "regenerate_general_cmd" : "run_cached_general_cmd";
1000 }
1001
1002
1003 my ($stream_cmd,$ofla_filename,$ofla_file)
1004 = $self->stream_flv_audio_cmd($originalfilename || $filename);
1005
1006
1007 my $streamable_options = { @{$self->{'ffmpeg_monitor'}},
1008 'message_prefix' => "Stream",
1009 'message' => "Generating streamable audio: $ofla_file" };
1010
1011
1012 my ($streamable_regenerated,$streamable_result,$streamable_had_error)
1013 = $self->$optionally_run_general_cmd($stream_cmd,$ofla_filename,
1014 $streamable_options);
1015
1016
1017 if (!$streamable_had_error) {
1018 my ($streamseekable_cmd,$ostreamseekable_filename) = $self->streamseekable_cmd($ofla_filename);
1019
1020 my $streamseekable_options = { @{$self->{'flvtool2_monitor'}},
1021 'message_prefix' => "Stream Seekable",
1022 'message' => "Reprocessing audio stream to be seekable by timeline: $ofla_file" };
1023
1024 if ($streamable_regenerated) {
1025 $self->run_general_cmd($streamseekable_cmd,$streamseekable_options);
1026 }
1027
1028 my $streamable_url = $ofla_file;
1029 my $streamable_url_safe = $self->url_safe($streamable_url);
1030
1031 $doc_obj->add_utf8_metadata ($section, "streamableaudio", $streamable_url_safe);
1032 $doc_obj->associate_file($ofla_filename,$ofla_file,"audio/flash",
1033 $section);
1034 }
1035
1036 $doc_obj->add_metadata ($section, "audioflashwidth", 400);
1037 $doc_obj->add_metadata ($section, "audioflashheight", 22 + 100);
1038
1039 $self->{'ofla_file'} = $ofla_file;
1040 $self->{'ofla_filename'} = $ofla_filename;
1041
1042 return $streamable_regenerated;
1043}
1044
1045
1046
1047
1048
1049sub enable_video_streaming
1050{
1051 my $self = shift (@_);
1052 my ($doc_obj,$originalfilename,$filename,$convertto_regenerated,
1053 $video_width,$video_height) = @_;
1054
1055 my $section = $doc_obj->get_top_section();
1056
1057 my $output_dir = $self->{'cached_dir'};
1058 my $ivideo_root = $self->{'cached_file_root'};
1059
1060 # Generate the Flash FLV format for streaming purposes
1061
1062 my $optionally_run_general_cmd = "run_uncached_general_cmd";
1063 if ($self->{'enable_cache'}) {
1064 $optionally_run_general_cmd
1065 = ($convertto_regenerated) ? "regenerate_general_cmd" : "run_cached_general_cmd";
1066 }
1067
1068
1069 my $streaming_bitrate = $self->{'streamingbitrate'};
1070 my $streaming_size = $self->{'streamingsize'};
1071
1072 my $streaming_quality = "high";
1073
1074 my ($stream_cmd,$oflash_filename,$oflash_file)
1075 = $self->stream_flv_video_cmd($originalfilename || $filename,
1076 $video_width,$video_height,
1077 $streaming_quality,
1078 $streaming_bitrate, $streaming_size);
1079
1080
1081 my $streamable_options = { @{$self->{'ffmpeg_monitor'}},
1082 'message_prefix' => "Stream",
1083 'message' => "Generating streamable video: $oflash_file" };
1084
1085
1086 my ($streamable_regenerated,$streamable_result,$streamable_had_error)
1087 = $self->$optionally_run_general_cmd($stream_cmd,$oflash_filename,$streamable_options);
1088
1089 if (!$streamable_had_error) {
1090 my ($streamseekable_cmd,$ostreamseekable_filename) = $self->streamseekable_cmd($oflash_filename);
1091
1092 my $streamseekable_options = { @{$self->{'flvtool2_monitor'}},
1093 'message_prefix' => "Stream Seekable",
1094 'message' => "Reprocessing video stream to be seekable by timeline: $oflash_file" };
1095
1096 if ($streamable_regenerated) {
1097 $self->run_general_cmd($streamseekable_cmd,$streamseekable_options);
1098 }
1099
1100 my $streamable_url = $oflash_file;
1101 my $streamable_url_safe = $self->url_safe($streamable_url);
1102
1103 $doc_obj->add_utf8_metadata ($section, "streamablevideo", $streamable_url_safe);
1104 $doc_obj->associate_file($oflash_filename,$oflash_file,"video/flash",
1105 $section);
1106 }
1107
1108
1109 #
1110 # FlowPlayer.swf height+22 pixels
1111 # FlowPlayerBlack.swf height+16 pixels
1112 # FlowPlayerThermo.swf height+16 pixels
1113 # FlowPlayerWhite.swf height+26 pixels
1114
1115 my $flashwidth = $video_width;
1116 my $flashheight = $video_height + 22;
1117
1118 if ($self->{'extract_keyframes'}) {
1119 $flashheight += 100;
1120 }
1121
1122 $doc_obj->add_metadata ($section, "flashwidth", $flashwidth);
1123 $doc_obj->add_metadata ($section, "flashheight", $flashheight);
1124
1125 $self->{'oflash_file'} = $oflash_file;
1126 $self->{'oflash_filename'} = $oflash_filename;
1127
1128 return $streamable_regenerated;
1129}
1130
1131sub enable_h264_streaming
1132{
1133 my $self = shift (@_);
1134 my ($doc_obj,$originalfilename,$filename,$convertto_regenerated,
1135 $video_width,$video_height) = @_;
1136
1137
1138 my $section = $doc_obj->get_top_section();
1139
1140 my $output_dir = $self->{'cached_dir'};
1141 my $ivideo_root = $self->{'cached_file_root'};
1142
1143 # Generate the H264 video file format for streaming purposes using the cache feature if used
1144
1145 my $optionally_run_general_cmd = "run_uncached_general_cmd";
1146 if ($self->{'enable_cache'}) {
1147 $optionally_run_general_cmd
1148 = ($convertto_regenerated) ? "regenerate_general_cmd" : "run_cached_general_cmd";
1149 }
1150
1151 my $streaming_HQ_size = $self->{'streamingHQsize'};
1152 my $streaming_HQ_VideoBitrate = $self->{'streamingHQVideoBitrate'};
1153 my $streaming_HQ_AudioBitrate = $self->{'streamingHQAudioBitrate'};
1154
1155 my ($stream_cmd,$oflash_filename,$oflash_file)
1156 = $self->stream_mp4_video_cmd($originalfilename || $filename,
1157 $streaming_HQ_size,
1158 $streaming_HQ_VideoBitrate,
1159 $streaming_HQ_AudioBitrate);
1160
1161
1162 my $streamable_options = { @{$self->{'handbrake_monitor'}},
1163 'message_prefix' => "MP4 Stream",
1164 'message' => "Generating streamable MP4 video: $oflash_file" };
1165
1166
1167 my ($streamable_regenerated,$streamable_result,$streamable_had_error)
1168 = $self->$optionally_run_general_cmd($stream_cmd,$oflash_filename,$streamable_options);
1169
1170 if (!$streamable_had_error) {
1171
1172
1173 my $streamable_url = $oflash_file;
1174 my $streamable_url_safe = $self->url_safe($streamable_url);
1175
1176 my $outhandle = $self->{'outhandle'};
1177
1178 my ($vtype, $width, $height, $duration, $durationDisplay, $vsize,
1179 $vcodec,$vrate,$fps,$atype,$afreq,$achan,$arate,$metadata_table) = identify($oflash_filename,$outhandle,0);
1180
1181 $doc_obj->add_utf8_metadata ($section, "tv.HQStreamingVideoFormat", $vtype);
1182 $doc_obj->add_utf8_metadata ($section, "tv.HQStreamingVideoFileSize", $vsize);
1183 $doc_obj->add_utf8_metadata ($section, "tv.HQStreamingWidth", $width);
1184 $doc_obj->add_utf8_metadata ($section, "tv.HQStreamingHeight", $height);
1185 $doc_obj->add_utf8_metadata ($section, "tv.HQStreamingDuration", $durationDisplay);
1186 $doc_obj->add_utf8_metadata ($section, "tv.HQStreamingFPS", $fps);
1187 $doc_obj->add_utf8_metadata ($section, "tv.HQStreamingVideoCodec", $vcodec);
1188 $doc_obj->add_utf8_metadata ($section, "tv.HQStreamingVideoBitrate", $vrate);
1189 $doc_obj->add_utf8_metadata ($section, "tv.HQStreamingAudioCodec", $atype);
1190 $doc_obj->add_utf8_metadata ($section, "tv.HQStreamingAudioBitrate", $arate);
1191 $doc_obj->add_utf8_metadata ($section, "tv.HQStreamingFileBitrate", $vrate + $arate);
1192
1193 foreach my $metaname(keys %$metadata_table)
1194 {
1195 my $metaval = $metadata_table->{$metaname};
1196
1197 $doc_obj->add_utf8_metadata ($section, $metaname, $metaval);
1198 }
1199
1200 #add the metadata for the resulting file that should be open inside the flash video player
1201 $doc_obj->add_utf8_metadata ($section, "streamablevideo", $streamable_url_safe);
1202 $doc_obj->associate_file($oflash_filename,$oflash_file,"video/mp4",
1203 $section);
1204 }
1205
1206
1207
1208
1209 my $flashwidth = $video_width;
1210 my $flashheight = $video_height + 22;
1211
1212 if ($self->{'extract_keyframes'}) {
1213 $flashheight += 100;
1214 }
1215
1216 $doc_obj->add_metadata ($section, "flashwidth", $flashwidth);
1217 $doc_obj->add_metadata ($section, "flashheight", $flashheight);
1218
1219 $self->{'oflash_file'} = $oflash_file;
1220 $self->{'oflash_filename'} = $oflash_filename;
1221
1222 return $streamable_regenerated;
1223
1224
1225}
1226
1227sub enable_full_streaming
1228{
1229 my $self = shift (@_);
1230 my ($doc_obj,$originalfilename,$filename,$convertto_regenerated,
1231 $video_width,$video_height) = @_;
1232
1233 my $video_streamable_regenerated
1234 = $self->enable_video_streaming($doc_obj,$originalfilename,$filename,
1235 $convertto_regenerated,
1236 $video_width,$video_height);
1237
1238 my $audio_streamable_regenerated
1239 = $self->enable_audio_streaming($doc_obj,$originalfilename,$filename,
1240 $convertto_regenerated);
1241
1242 return ($video_streamable_regenerated || $audio_streamable_regenerated);
1243}
1244
1245
1246
1247sub flvtool2_monitor_line
1248{
1249 my ($line) = @_;
1250
1251 my $had_error = 0;
1252 my $generate_dot = 1;
1253
1254 if ($line =~ m/\s+\- /) {
1255 # ignore tabulated output printed at end of command
1256 $generate_dot = 0;
1257 }
1258
1259 if ($line =~ m/^Error:/i) {
1260 $had_error = 1;
1261 }
1262
1263 return ($had_error,$generate_dot);
1264}
1265
1266
1267
1268
1269
12701;
Note: See TracBrowser for help on using the repository browser.