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

Last change on this file since 20492 was 20492, checked in by max, 15 years ago

Don't add the original video file as associated file anymore. Keep the same name for the MP4 streaming video file used for each video clip.

File size: 30.2 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 { 'name' => "streamingHQsize",
124 'desc' => "{VideoPlugin.streamingsize}",
125 'type' => "int",
126 'deft' => "576",
127 'reqd' => "no" },
128 { 'name' => "streamingHQVideoBitrate",
129 'desc' => "{VideoPlugin.streamingbitrate}",
130 'type' => "int",
131 'deft' => "426",
132 'reqd' => "no" },
133 { 'name' => "streamingHQAudioBitrate",
134 'desc' => "{VideoPlugin.streamingbitrate}",
135 'type' => "int",
136 'deft' => "86",
137 'reqd' => "no" },
138
139 { 'name' => "minimumsize",
140 'desc' => "{ImageConverter.minimumsize}",
141 'type' => "int",
142 'deft' => "100",
143 'range' => "1,",
144 'reqd' => "no" },
145
146 ];
147
148my $options = { 'name' => "VideoConverter",
149 'desc' => "{VideoConverter.desc}",
150 'abstract' => "yes",
151 'inherits' => "yes",
152 'args' => $arguments };
153
154sub new {
155 my ($class) = shift (@_);
156 my ($pluginlist,$inputargs,$hashArgOptLists) = @_;
157 push(@$pluginlist, $class);
158
159 push(@{$hashArgOptLists->{"ArgList"}},@{$arguments});
160 push(@{$hashArgOptLists->{"OptList"}},$options);
161
162 my $self = new MultimediaConverter($pluginlist, $inputargs, $hashArgOptLists, 1);
163
164
165 return bless $self, $class;
166
167}
168
169
170
171
172# Discover the characteristics of a video file.
173
174# Equivalent step to that in ImagePlugin that uses ImageMagicks's
175# 'indentify' utility
176
177# Here we use 'ffmpeg' for video but for consistency keep the Perl
178# method name the same as before
179
180
181sub identify {
182 my ($video, $outhandle, $verbosity) = @_;
183
184 # Use the ffmpeg command to get the file specs
185 my $command = "ffmpeg -i \"$video\"";
186
187 print $outhandle " $command\n" if ($verbosity > 2);
188 my $result = '';
189 $result = `$command 2>&1`;
190 print $outhandle " $result\n" if ($verbosity > 4);
191
192 # Read the type, width, and height etc.
193 my $vtype = 'unknown';
194 my $vcodec = 'unknown';
195 my $width = 'unknown';
196 my $height = 'unknown';
197 my $fps = 'unknown';
198
199 my $atype = 'unknown';
200 my $afreq = 'unknown';
201 my $achan = 'unknown';
202 my $arate = 'unknown';
203
204 my $video_safe = quotemeta $video;
205
206 # strip off everything up to filename
207 $result =~ s/^.*\'$video_safe\'://s;
208
209## if ($result =~ m/Video: (.*?) fps/m) {
210 if ($result =~ m/^\s+Stream.*?Video: (.*?)$/m) {
211 my $video_info = $1;
212 $video_info =~ s/\s*\[.*?\]//g;
213
214 my @video_fields = split(/,\s*/,$video_info);
215
216 $vtype = shift @video_fields;
217
218 if ($video_fields[0] !~ m/(\d+)x(\d+)/) {
219 $vcodec = shift @video_fields;
220 }
221 my $video_dim = shift @video_fields;
222
223 ($width,$height) = ($video_dim =~ m/(\d+)x(\d+)/);
224
225# if ($video_info =~ m/([^,]+),(?: ([^,]+),)? (\d+)x(\d+),.*?(\d+\.\d+)/)
226# {
227# $vtype = $1;
228# $vcodec = $2 if defined $2;
229# $width = $3;
230# $height = $4;
231# $fps = $5;
232# }
233 }
234
235 if ($result =~ m/Audio: (\w+), (\d+) Hz, (\w+)(?:, (\d+.*))?/m) {
236 $atype = $1;
237 $afreq = $2;
238 $achan = $3;
239 $arate = $4 if (defined $4);
240 }
241
242 # Read the duration
243 my $duration = "unknown";
244 if ($result =~ m/Duration: (\d+:\d+:\d+\.\d+)/m) {
245 $duration = $1;
246 }
247 print $outhandle " file: $video:\t $vtype, $width, $height, $duration\n"
248 if ($verbosity > 2);
249
250 if ($verbosity >3) {
251 print $outhandle "\t video codec=$vcodec, fps = $fps\n";
252 print $outhandle "\t audio codec=$atype, freq = $afreq Hz, $achan, $arate\n";
253 }
254
255 # Return the specs
256 return ($vtype, $width, $height, $duration, -s $video,
257 $vcodec,$fps,$atype,$afreq,$achan,$arate);
258}
259
260
261sub vob_durations
262{
263 my ($media_base_dir,$title_num,$outhandle) = @_;
264
265 my $filter_re = sprintf("^VTS_%02d_[1-9]\\.VOB\$",$title_num);
266
267 my $duration_info = {};
268
269 if (opendir(VTSIN,$media_base_dir)) {
270 my @vts_title_vobs = grep { $_ =~ m/$filter_re/ } readdir(VTSIN);
271 closedir(VTSIN);
272
273 foreach my $v (@vts_title_vobs) {
274 my $full_v = &util::filename_cat($media_base_dir,$v);
275
276 my ($vtype, $width, $height, $duration, $vsize,
277 $vcodec,$fps,$atype,$afreq,$achan,$arate) = identify($full_v,$outhandle,0);
278
279 my ($vob_num) = ($v =~ m/^VTS_\d\d_(\d)\.VOB$/);
280
281 $duration_info->{$vob_num} = $duration;
282 print STDERR "**** $title_num: $title_num, storing {$vob_num} => $duration\n";
283
284 }
285
286 }
287 else {
288 print $outhandle "Warning: unable to read files in directory $media_base_dir\n";
289 }
290
291 return $duration_info;
292
293}
294
295
296
297sub init_cache_for_file {
298 my $self = shift(@_);
299
300 my ($video_filename) = @_;
301
302 $self->SUPER::init_cache_for_file($video_filename);
303
304
305 my @flvtool2_monitor = ( 'monitor_init' ,"convertutil::monitor_init_unbuffered",
306 'monitor_line' , "VideoConverter::flvtool2_monitor_line",
307 'monitor_deinit' , "convertutil::monitor_deinit_unbuffered" );
308
309 $self->{'flvtool2_monitor'} = \@flvtool2_monitor;
310
311
312}
313
314
315
316sub optional_frame_scale
317{
318 my $self = shift (@_);
319 my ($orig_size,$video_width,$video_height) = @_;
320
321 my $s_opt = "";
322
323 if ($video_width > $video_height) {
324 if ($video_width > $orig_size) {
325 my $scale_factor = $orig_size/$video_width;
326 my $scaled_width = int($video_width * $scale_factor);
327 my $scaled_height = int($video_height * $scale_factor);
328
329 # round to be ensure multiple of 2 (needed by some codecs)
330 $scaled_width = int($scaled_width/2)*2;
331 $scaled_height = int($scaled_height/2)*2;
332
333 $s_opt = "-s ${scaled_width}x${scaled_height}";
334 }
335 # else, video is smaller than requested size, don't scale up
336 }
337 else {
338 if ($video_height > $orig_size) {
339 my $scale_factor = $orig_size/$video_height;
340 my $scaled_width = int($video_width * $scale_factor);
341 my $scaled_height = int($video_height * $scale_factor);
342
343 # round to be ensure multiple of 2 (needed by some codecs)
344 $scaled_width = int($scaled_width/2)*2;
345 $scaled_height = int($scaled_height/2)*2;
346
347 $s_opt = "-s ${scaled_width}x${scaled_height}";
348 }
349 # else, video is smaller than requested size, don't scale up
350
351 }
352
353 return $s_opt;
354}
355
356
357sub keyframe_cmd
358{
359 my $self = shift (@_);
360 my ($ivideo_filename) = @_;
361
362 my $video_ext_dir = &util::filename_cat($ENV{'GSDLHOME'},"ext","video");
363
364 my $output_dir = $self->{'cached_dir'};
365 my $ivideo_root = $self->{'cached_file_root'};
366
367 my $oshot_filename = &util::filename_cat($output_dir,"shots.xml");
368
369 my $exp_duration = $self->{'exp_duration'};
370 my $t_opt = (defined $exp_duration) ? "-t $exp_duration" : "";
371
372 my $main_opts = "-y $t_opt";
373
374 my $hive = &util::filename_cat($video_ext_dir,"lib","vhook","hive.so");
375
376 my $oflash_filename = &util::filename_cat($output_dir,"$ivideo_root\_keyframe.flv");
377
378 my $vhook_opts = "$hive -o $oshot_filename -k $output_dir $ivideo_filename";
379
380 my $ivideo_filename_gsdlenv = $self->gsdlhome_independent($ivideo_filename);
381 my $oflash_filename_gsdlenv = $self->gsdlhome_independent($oflash_filename);
382
383 my $ffmpeg_cmd = "ffkeyframe $main_opts -vhook \"$vhook_opts\" -i \"$ivideo_filename_gsdlenv\" -an -y \"$oflash_filename_gsdlenv\"";
384
385
386 return ($ffmpeg_cmd,$oflash_filename);
387}
388
389
390sub stream_mp4_video_cmd
391{
392 my $self = shift (@_);
393 my ($ivideo_filename,$streaming_HQ_size, $streaming_HQ_VideoBitrate,
394 $streaming_HQ_AudioBitrate) = @_;
395
396
397 my $output_dir = $self->{'cached_dir'};
398 my $ivideo_root = $self->{'cached_file_root'};
399
400 my $omp4_file = "${ivideo_root}_vHQstream.mp4";
401 my $omp4_filename = &util::filename_cat($output_dir,$omp4_file);
402
403
404 #my $exp_duration = $self->{'exp_duration'};
405 #my $t_opt = (defined $exp_duration) ? "-t $exp_duration" : "";
406
407
408
409 my $size;
410 # Looks for empty string as well as fullsize string
411 if(!$streaming_HQ_size || $streaming_HQ_size eq "fullsize") {
412 $size = "--strict-anamorphic";
413 } else {
414 $size = "-w $streaming_HQ_size --loose-anamorphic";
415 }
416
417
418 my $ivideo_filename_gsdlenv = $self->gsdlhome_independent($ivideo_filename);
419 my $omp4_filename_gsdlenv = $self->gsdlhome_independent($omp4_filename);
420
421 my $pre_opts = "--decomb -t 1 -c 1";
422 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";
423
424 my $handbrake_cmd = "HandbrakeCLI.exe -i \"$ivideo_filename_gsdlenv\" $pre_opts -o \"$omp4_filename_gsdlenv\" $post_opts";
425
426
427 return ($handbrake_cmd,$omp4_filename,$omp4_file);
428}
429
430
431
432sub stream_flv_video_cmd
433{
434 my $self = shift (@_);
435 my ($ivideo_filename,$video_width,$video_height,
436 $streaming_quality,
437 $streaming_bitrate,$streaming_size,
438 $opt_streaming_achan, $opt_streaming_arate) = @_;
439
440 my $streaming_achan
441 = (defined $opt_streaming_achan) ? $opt_streaming_achan : 2;
442
443 my $streaming_arate
444 = (defined $opt_streaming_arate) ? $opt_streaming_arate : 22050;
445
446 my $output_dir = $self->{'cached_dir'};
447 my $ivideo_root = $self->{'cached_file_root'};
448
449 my $oflash_file = "${ivideo_root}_vstream.flv";
450 my $oflash_filename = &util::filename_cat($output_dir,$oflash_file);
451
452 my $s_opt = "";
453 my $media_type = $self->{'media_type'};
454 if ($media_type ne "audio") {
455 $s_opt = $self->optional_frame_scale($streaming_size,$video_width,$video_height);
456 }
457
458
459 my $exp_duration = $self->{'exp_duration'};
460 my $t_opt = (defined $exp_duration) ? "-t $exp_duration" : "";
461
462 my $main_opts = "-y $t_opt";
463
464 my $bitrate_opt = "-b $streaming_bitrate";
465 ### my $stream_opts = "-r 25 $s_opt";
466 my $stream_opts .= " $s_opt -ac $streaming_achan -ar $streaming_arate";
467
468 # -flags +ilme+ildct' and maybe '-flags +alt' for interlaced material, and try '-top 0/1'
469
470 my $all_opts = "$main_opts $stream_opts";
471
472 my $ffmpeg_cmd;
473
474 my $ivideo_filename_gsdlenv = $self->gsdlhome_independent($ivideo_filename);
475 my $oflash_filename_gsdlenv = $self->gsdlhome_independent($oflash_filename);
476
477 if ($streaming_quality eq "high") {
478
479 my $pass_log_file = &util::filename_cat($output_dir,"$ivideo_root-logpass.txt");
480 if (-e $pass_log_file) {
481 &util::rm($pass_log_file);
482 }
483
484 my $pass_log_file_gsdlenv = $self->gsdlhome_independent($pass_log_file);
485
486 $all_opts .= " -passlogfile \"$pass_log_file_gsdlenv\"";
487
488 my $ffmpeg_cmd_pass1 = "ffmpeg -pass 1 -i \"$ivideo_filename_gsdlenv\" $all_opts -y \"$oflash_filename_gsdlenv\"";
489
490 my $ffmpeg_cmd_pass2 = "ffmpeg -pass 2 -i \"$ivideo_filename_gsdlenv\" $all_opts $bitrate_opt -y \"$oflash_filename_gsdlenv\"";
491 $ffmpeg_cmd = "( $ffmpeg_cmd_pass1 && $ffmpeg_cmd_pass2 )";
492 }
493 else {
494 # single pass
495
496 $ffmpeg_cmd = "ffmpeg -i \"$ivideo_filename_gsdlenv\" $all_opts -y \"$oflash_filename_gsdlenv\"";
497 }
498
499 return ($ffmpeg_cmd,$oflash_filename,$oflash_file);
500}
501
502
503
504
505sub stream_flv_audio_cmd
506{
507 # AudioConverter also has a routine for doing this
508 # => merge into one!
509 # **************
510
511 my $self = shift (@_);
512 my ($ivideo_filename, $opt_streaming_achan, $opt_streaming_arate) = @_;
513
514 # Convert either audio or video to streamable audio format
515
516 my $streaming_achan
517 = (defined $opt_streaming_achan) ? $opt_streaming_achan : 2;
518
519 my $streaming_arate
520 = (defined $opt_streaming_arate) ? $opt_streaming_arate : 22050;
521
522 my $output_dir = $self->{'cached_dir'};
523 my $ivideo_root = $self->{'cached_file_root'};
524
525 my $ofla_file = "${ivideo_root}_astream.flv";
526 my $ofla_filename = &util::filename_cat($output_dir,$ofla_file);
527
528 my $exp_duration = $self->{'exp_duration'};
529 my $t_opt = (defined $exp_duration) ? "-t $exp_duration" : "";
530
531 my $main_opts = "-vn -y $t_opt";
532
533 my $stream_opts .= " -ac $streaming_achan -ar $streaming_arate";
534
535 my $all_opts = "$main_opts $stream_opts";
536
537 my $ffmpeg_cmd;
538
539 my $ivideo_filename_gsdlenv = $self->gsdlhome_independent($ivideo_filename);
540 my $ofla_filename_gsdlenv = $self->gsdlhome_independent($ofla_filename);
541 $ffmpeg_cmd = "ffmpeg -i \"$ivideo_filename_gsdlenv\" $all_opts -y \"$ofla_filename_gsdlenv\"";
542
543 return ($ffmpeg_cmd,$ofla_filename,$ofla_file);
544}
545
546
547
548
549sub audio_excerpt_cmd
550{
551 my $self = shift (@_);
552 my ($ivoa_filename,$hh,$mm,$ss,$opt_excerpt_len) = @_;
553
554 # ivoa = input video or audio
555
556 my $time_encoded = "$hh:$mm:$ss";
557 my $time_encoded_file = "$hh$mm$ss";
558
559
560 my $output_dir = $self->{'cached_dir'};
561 my $ivoa_root = $self->{'cached_file_root'};
562
563 my $omp3_file = "${ivoa_root}_$time_encoded_file.mp3";
564 my $omp3_filename = &util::filename_cat($output_dir,$omp3_file);
565
566 my $all_opts = "-y -acodec mp3 -ss $time_encoded ";
567
568 if (defined $opt_excerpt_len) {
569 $all_opts .= "-t $opt_excerpt_len ";
570 }
571
572
573 my $ivoa_filename_gsdlenv = $self->gsdlhome_independent($ivoa_filename);
574 my $omp3_filename_gsdlenv = $self->gsdlhome_independent($omp3_filename);
575
576
577 my $ffmpeg_cmd = "ffmpeg -i \"$ivoa_filename_gsdlenv\" $all_opts \"$omp3_filename_gsdlenv\"";
578
579 return ($ffmpeg_cmd,$omp3_filename,$omp3_file);
580}
581
582
583
584sub streamseekable_cmd
585{
586 my $self = shift (@_);
587 my ($oflash_filename) = @_;
588
589 my $output_dir = $self->{'cached_dir'};
590 my $ivideo_root = $self->{'cached_file_root'};
591
592 my $cue_filename = &util::filename_cat($output_dir,"on_cue.xml");
593
594 my $flvtool_cmd = "flvtool2 -vUP \"$oflash_filename\"";
595
596 return ($flvtool_cmd,$oflash_filename);
597}
598
599
600sub streamkeyframes_cmd
601{
602 my $self = shift (@_);
603 my ($oflash_filename,$doc_obj,$section) = @_;
604
605 my $assocfilepath
606 = $doc_obj->get_metadata_element($section,"assocfilepath");
607
608 my $output_dir = $self->{'cached_dir'};
609
610 my $cue_filename = &util::filename_cat($output_dir,"on_cue.xml");
611
612 my $video_server = $ENV{'GEXT_VIDEO_SERVER'};
613 my $video_prefix = $ENV{'GEXT_VIDEO_PREFIX'};
614
615 my $collect = $ENV{'GSDLCOLLECTION'};
616
617 print STDERR "**** Warning: need to remove dependency of GEXT_VIDEO_SERVER and _PREFIX\n";
618
619 my $flvtool_cmd = "flvtool2 -vAUtP \"$cue_filename\" -thumbLocation:$video_server$video_prefix/collect/$collect/index/assoc/$assocfilepath \"$oflash_filename\"";
620
621
622 return ($flvtool_cmd,$oflash_filename);
623}
624
625
626sub streamcuepts_cmd
627{
628 my $self = shift (@_);
629 my ($oflash_filename) = @_;
630
631 my $output_dir = $self->{'cached_dir'};
632
633 my $cue_filename = &util::filename_cat($output_dir,"on_cue.xml");
634
635 my $video_server = $ENV{'GEXT_VIDEO_SERVER'};
636 my $video_prefix = $ENV{'GEXT_VIDEO_PREFIX'};
637
638 my $collect = $ENV{'GSDLCOLLECTION'};
639
640 ## my $thumbloc = "$video_server$video_prefix/collect/$collect";
641
642
643# my $flvtool_cmd = "flvtool2 -vUAtP \"$cue_filename\" -thumbLocation:$thumbloc \"$oflash_filename\"";
644
645# my $flvtool_cmd = "flvtool2 -vUAt \"$cue_filename\" \"$oflash_filename\"";
646
647
648
649# my $flvtool_cmd = "flvtool2 -vUAt \"$cue_filename\" \"$oflash_filename\"";
650
651
652## my $flvtool_cmd = "flvtool2 -vAt \"$cue_filename\" -UP \"$oflash_filename\" \"$output_dir/updated.flv\"";
653
654## my $flvtool_cmd = "flvtool2 -vAtU \"$cue_filename\" \"$oflash_filename\" \"$output_dir/updated.flv\"";
655
656 my $flvtool_cmd = "flvtool2 -vAtUP \"$cue_filename\" \"$oflash_filename\"";
657
658 return ($flvtool_cmd,$oflash_filename);
659}
660
661
662sub keyframe_thumbnail_cmd
663{
664 my $self = shift (@_);
665 my ($ivideo_filename,$thumbnailfile,$thumbnailwidth,$thumbnailheight) = @_;
666
667 my $output_dir = $self->{'cached_dir'};
668 my $ivideo_root = $self->{'cached_file_root'};
669
670 my $key_filename_prefix = &util::filename_cat($output_dir,$ivideo_root);
671
672
673 # Try for 4th keyframe, but fall back to 1st if doesn't exist
674 my $key_filename = "${key_filename_prefix}_0003.jpg";
675 $key_filename = "${key_filename_prefix}_0000.jpg" if (!-e $key_filename);
676
677 my $key_filename_gsdlenv = $self->gsdlhome_independent($key_filename);
678 my $thumbnailfile_gsdlenv = $self->gsdlhome_independent($thumbnailfile);
679
680 my $command;
681
682 if (-e $key_filename) {
683 $command = "convert -interlace plane -verbose -geometry $thumbnailwidth"
684 . "x$thumbnailheight \"$key_filename_gsdlenv\" \"$thumbnailfile_gsdlenv\"";
685 }
686 else {
687 # extractkeyframe has either not been switched on, or else had
688 # a problem when running
689 # => extract a from
690 # my $frame_rate = 1.0 / 60.0;
691
692 my $ivideo_filename_gsdlenv = $self->gsdlhome_independent($ivideo_filename);
693
694 $command = "ffmpeg -i \"$ivideo_filename_gsdlenv\" -ss 12.5 -vframes 1 -f image2 -s ${thumbnailwidth}x${thumbnailheight} -y \"$thumbnailfile_gsdlenv\"";
695
696 # fmpeg -i input.dv -r 1 -f image2 -s 120x96 images%05d.png
697 }
698
699 return ($command,$thumbnailfile);
700}
701
702
703sub keyframe_montage_cmd
704{
705 my $self = shift (@_);
706 my ($ivideo_filename,$montagefile) = @_;
707
708 my $output_dir = $self->{'cached_dir'};
709 my $ivideo_root = $self->{'cached_file_root'};
710
711 my $key_filename_prefix = &util::filename_cat($output_dir,$ivideo_root);
712
713 my $options = "-tile 10 -geometry 75x62+2+2";
714
715 my $command = "montage $options ${key_filename_prefix}_*.jpg \"$montagefile\"";
716
717 return ($command,$montagefile);
718}
719
720
721
722sub parse_shot_xml
723{
724 my ($self) = shift(@_);
725
726 my $outhandle = $self->{'outhandle'};
727 my $output_dir = $self->{'cached_dir'};
728
729 my $shots_filename = &util::filename_cat($output_dir,"shots.xml");
730
731 eval {
732 $self->{'parser'}->parsefile($shots_filename);
733 };
734
735 if ($@) {
736 print $outhandle "VideoConverter: skipping $shots_filename as not conformant to Hive shot syntax\n" if ($self->{'verbosity'} > 1);
737 print $outhandle "\n Perl Error:\n $@\n" if ($self->{'verbosity'}>2);
738 return 0;
739 }
740
741}
742
743sub associate_keyframes_old
744{
745 my ($self) = shift(@_);
746
747 my ($doc_obj,$section) = @_;
748
749 my $output_dir = $self->{'cached_dir'};
750
751 my $count = 1;
752 foreach my $kframe_file (@{$self->{'keyframe_fnames'}}) {
753
754 my $kframe_filename = &util::filename_cat($output_dir,$kframe_file);
755 $doc_obj->associate_file($kframe_filename,"keyframe$count.jpg","image/jpeg",
756 $section);
757 $count++;
758 }
759
760 $doc_obj->add_utf8_metadata($section,"NumKeyframes",scalar(@{$self->{'keyframe_fnames'}}));
761
762
763 # *****
764 # $doc_obj->add_metadata ($section, "thumblist", $self->{'flowplayer_thumblist'});
765
766}
767
768sub associate_keyframes
769{
770 my ($self) = shift(@_);
771
772 my ($doc_obj,$section) = @_;
773
774 my $output_dir = $self->{'cached_dir'};
775 my $timeline = $self->{'keyframe_timeline'};
776
777 my $count = 1;
778
779 foreach my $t (sort { $timeline->{$a}->{'keyframeindex'} <=> $timeline->{$b}->{'keyframeindex'} } keys %$timeline)
780 {
781 my $kframe_file = $timeline->{$t}->{'thumb'};
782 my $timestamp = $timeline->{$t}->{'timestamp'};
783
784 my $kframe_filename = &util::filename_cat($output_dir,$kframe_file);
785 $doc_obj->associate_file($kframe_filename,"keyframe$count.jpg","image/jpeg",
786 $section);
787 $doc_obj->add_utf8_metadata($section,"KeyframeTimestamp",$timestamp);
788
789 $count++;
790 }
791
792 $doc_obj->add_utf8_metadata($section,"NumKeyframes",scalar(@{$self->{'keyframe_fnames'}}));
793
794
795 # *****
796 # $doc_obj->add_metadata ($section, "thumblist", $self->{'flowplayer_thumblist'});
797}
798
799
800
801sub enable_audio_streaming
802{
803 my $self = shift (@_);
804 my ($doc_obj,$originalfilename,$filename,$convertto_regenerated) = @_;
805
806 my $section = $doc_obj->get_top_section();
807
808 my $output_dir = $self->{'cached_dir'};
809 my $ivideo_root = $self->{'cached_file_root'};
810
811 # Generate FLV audio-only format for streaming purposes
812
813 my $optionally_run_general_cmd = "run_uncached_general_cmd";
814 if ($self->{'enable_cache'}) {
815 $optionally_run_general_cmd
816 = ($convertto_regenerated) ? "regenerate_general_cmd" : "run_cached_general_cmd";
817 }
818
819
820 my ($stream_cmd,$ofla_filename,$ofla_file)
821 = $self->stream_flv_audio_cmd($originalfilename || $filename);
822
823
824 my $streamable_options = { @{$self->{'ffmpeg_monitor'}},
825 'message_prefix' => "Stream",
826 'message' => "Generating streamable audio: $ofla_file" };
827
828
829 my ($streamable_regenerated,$streamable_result,$streamable_had_error)
830 = $self->$optionally_run_general_cmd($stream_cmd,$ofla_filename,
831 $streamable_options);
832
833
834 if (!$streamable_had_error) {
835 my ($streamseekable_cmd,$ostreamseekable_filename) = $self->streamseekable_cmd($ofla_filename);
836
837 my $streamseekable_options = { @{$self->{'flvtool2_monitor'}},
838 'message_prefix' => "Stream Seekable",
839 'message' => "Reprocessing audio stream to be seekable by timeline: $ofla_file" };
840
841 if ($streamable_regenerated) {
842 $self->run_general_cmd($streamseekable_cmd,$streamseekable_options);
843 }
844
845 my $streamable_url = $ofla_file;
846 my $streamable_url_safe = $self->url_safe($streamable_url);
847
848 $doc_obj->add_utf8_metadata ($section, "streamableaudio", $streamable_url_safe);
849 $doc_obj->associate_file($ofla_filename,$ofla_file,"audio/flash",
850 $section);
851 }
852
853 $doc_obj->add_metadata ($section, "audioflashwidth", 400);
854 $doc_obj->add_metadata ($section, "audioflashheight", 22 + 100);
855
856 $self->{'ofla_file'} = $ofla_file;
857 $self->{'ofla_filename'} = $ofla_filename;
858
859 return $streamable_regenerated;
860}
861
862
863
864
865
866sub enable_video_streaming
867{
868 my $self = shift (@_);
869 my ($doc_obj,$originalfilename,$filename,$convertto_regenerated,
870 $video_width,$video_height) = @_;
871
872 my $section = $doc_obj->get_top_section();
873
874 my $output_dir = $self->{'cached_dir'};
875 my $ivideo_root = $self->{'cached_file_root'};
876
877 # Generate the Flash FLV format for streaming purposes
878
879 my $optionally_run_general_cmd = "run_uncached_general_cmd";
880 if ($self->{'enable_cache'}) {
881 $optionally_run_general_cmd
882 = ($convertto_regenerated) ? "regenerate_general_cmd" : "run_cached_general_cmd";
883 }
884
885
886 my $streaming_bitrate = $self->{'streamingbitrate'};
887 my $streaming_size = $self->{'streamingsize'};
888
889 my $streaming_quality = "high";
890
891 my ($stream_cmd,$oflash_filename,$oflash_file)
892 = $self->stream_flv_video_cmd($originalfilename || $filename,
893 $video_width,$video_height,
894 $streaming_quality,
895 $streaming_bitrate, $streaming_size);
896
897
898 my $streamable_options = { @{$self->{'ffmpeg_monitor'}},
899 'message_prefix' => "Stream",
900 'message' => "Generating streamable video: $oflash_file" };
901
902
903 my ($streamable_regenerated,$streamable_result,$streamable_had_error)
904 = $self->$optionally_run_general_cmd($stream_cmd,$oflash_filename,$streamable_options);
905
906 if (!$streamable_had_error) {
907 my ($streamseekable_cmd,$ostreamseekable_filename) = $self->streamseekable_cmd($oflash_filename);
908
909 my $streamseekable_options = { @{$self->{'flvtool2_monitor'}},
910 'message_prefix' => "Stream Seekable",
911 'message' => "Reprocessing video stream to be seekable by timeline: $oflash_file" };
912
913 if ($streamable_regenerated) {
914 $self->run_general_cmd($streamseekable_cmd,$streamseekable_options);
915 }
916
917 my $streamable_url = $oflash_file;
918 my $streamable_url_safe = $self->url_safe($streamable_url);
919
920 $doc_obj->add_utf8_metadata ($section, "streamablevideo", $streamable_url_safe);
921 $doc_obj->associate_file($oflash_filename,$oflash_file,"video/flash",
922 $section);
923 }
924
925
926 #
927 # FlowPlayer.swf height+22 pixels
928 # FlowPlayerBlack.swf height+16 pixels
929 # FlowPlayerThermo.swf height+16 pixels
930 # FlowPlayerWhite.swf height+26 pixels
931
932 my $flashwidth = $video_width;
933 my $flashheight = $video_height + 22;
934
935 if ($self->{'extractkeyframes'}) {
936 $flashheight += 100;
937 }
938
939 $doc_obj->add_metadata ($section, "flashwidth", $flashwidth);
940 $doc_obj->add_metadata ($section, "flashheight", $flashheight);
941
942 $self->{'oflash_file'} = $oflash_file;
943 $self->{'oflash_filename'} = $oflash_filename;
944
945 return $streamable_regenerated;
946}
947
948sub enable_h264_streaming
949{
950 my $self = shift (@_);
951 my ($doc_obj,$originalfilename,$filename,$convertto_regenerated,
952 $video_width,$video_height) = @_;
953
954
955 my $section = $doc_obj->get_top_section();
956
957 my $output_dir = $self->{'cached_dir'};
958 my $ivideo_root = $self->{'cached_file_root'};
959
960 # Generate the H264 video file format for streaming purposes using the cache feature if used
961
962 my $optionally_run_general_cmd = "run_uncached_general_cmd";
963 if ($self->{'enable_cache'}) {
964 $optionally_run_general_cmd
965 = ($convertto_regenerated) ? "regenerate_general_cmd" : "run_cached_general_cmd";
966 }
967
968
969 ## my $streaming_bitrate = $self->{'streamingbitrate'};
970 ## my $streaming_size = $self->{'streamingsize'};
971 my $streaming_HQ_size = $self->{'streamingHQsize'};
972 my $streaming_HQ_VideoBitrate = $self->{'streamingHQVideoBitrate'};
973 my $streaming_HQ_AudioBitrate = $self->{'streamingHQAudioBitrate'};
974
975 ## my $streaming_quality = "high";
976
977 my ($stream_cmd,$oflash_filename,$oflash_file)
978 = $self->stream_mp4_video_cmd($originalfilename || $filename,
979 $streaming_HQ_size,
980 $streaming_HQ_VideoBitrate,
981 $streaming_HQ_AudioBitrate);
982
983
984 my $streamable_options = { @{$self->{'handbrake_monitor'}},
985 'message_prefix' => "MP4 Stream",
986 'message' => "Generating streamable MP4 video: $oflash_file" };
987
988
989 my ($streamable_regenerated,$streamable_result,$streamable_had_error)
990 = $self->$optionally_run_general_cmd($stream_cmd,$oflash_filename,$streamable_options);
991
992 if (!$streamable_had_error) {
993# my ($streamseekable_cmd,$ostreamseekable_filename) = $self->streamseekable_cmd($oflash_filename);
994
995# my $streamseekable_options = { @{$self->{'flvtool2_monitor'}},
996# 'message_prefix' => "Stream Seekable",
997# 'message' => "Reprocessing video stream to be seekable by timeline: $oflash_file" };
998
999# if ($streamable_regenerated) {
1000# $self->run_general_cmd($streamseekable_cmd,$streamseekable_options);
1001# }
1002
1003 my $streamable_url = $oflash_file;
1004 my $streamable_url_safe = $self->url_safe($streamable_url);
1005
1006 #add the metadata for the resulting file that should be open inside the flash video player
1007 $doc_obj->add_utf8_metadata ($section, "streamablevideo", $streamable_url_safe);
1008 $doc_obj->associate_file($oflash_filename,"VideoStream.mp4","video/mp4",
1009 $section);
1010 }
1011
1012
1013 #
1014 # FlowPlayer.swf height+22 pixels
1015 # FlowPlayerBlack.swf height+16 pixels
1016 # FlowPlayerThermo.swf height+16 pixels
1017 # FlowPlayerWhite.swf height+26 pixels
1018
1019 my $flashwidth = $video_width;
1020 my $flashheight = $video_height + 22;
1021
1022 if ($self->{'extractkeyframes'}) {
1023 $flashheight += 100;
1024 }
1025
1026 $doc_obj->add_metadata ($section, "flashwidth", $flashwidth);
1027 $doc_obj->add_metadata ($section, "flashheight", $flashheight);
1028
1029 $self->{'oflash_file'} = $oflash_file;
1030 $self->{'oflash_filename'} = $oflash_filename;
1031
1032 return $streamable_regenerated;
1033
1034
1035}
1036
1037sub enable_full_streaming
1038{
1039 my $self = shift (@_);
1040 my ($doc_obj,$originalfilename,$filename,$convertto_regenerated,
1041 $video_width,$video_height) = @_;
1042
1043 my $video_streamable_regenerated
1044 = $self->enable_video_streaming($doc_obj,$originalfilename,$filename,
1045 $convertto_regenerated,
1046 $video_width,$video_height);
1047
1048 my $audio_streamable_regenerated
1049 = $self->enable_audio_streaming($doc_obj,$originalfilename,$filename,
1050 $convertto_regenerated);
1051
1052 return ($video_streamable_regenerated || $audio_streamable_regenerated);
1053}
1054
1055
1056
1057sub flvtool2_monitor_line
1058{
1059 my ($line) = @_;
1060
1061 my $had_error = 0;
1062 my $generate_dot = 1;
1063
1064 if ($line =~ m/\s+\- /) {
1065 # ignore tabulated output printed at end of command
1066 $generate_dot = 0;
1067 }
1068
1069 if ($line =~ m/^Error:/i) {
1070 $had_error = 1;
1071 }
1072
1073 return ($had_error,$generate_dot);
1074}
1075
1076
1077
1078
1079
10801;
Note: See TracBrowser for help on using the repository browser.