source: gs2-extensions/video-and-audio/trunk/src/perllib/plugins/VideoPlugin.pm@ 23293

Last change on this file since 23293 was 23293, checked in by max, 13 years ago

Generate thumbnails form original file as we may not always process the video file.

Add direct streaming option.

File size: 31.2 KB
Line 
1######################################################################
2#
3# VideoPlugin.pm -- plugin for processing video files
4# A component of the Greenstone digital library software
5# from the New Zealand Digital Library Project at the
6# University of Waikato, New Zealand.
7#
8# Copyright (C) 1999 New Zealand Digital Library Project
9#
10# This program is free software; you can redistribute it and/or modify
11# it under the terms of the GNU General Public License as published by
12# the Free Software Foundation; either version 2 of the License, or
13# (at your option) any later version.
14#
15# This program is distributed in the hope that it will be useful,
16# but WITHOUT ANY WARRANTY; without even the implied warranty of
17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18# GNU General Public License for more details.
19#
20# You should have received a copy of the GNU General Public License
21# along with this program; if not, write to the Free Software
22# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23#
24###########################################################################
25
26# -- Largely modeled on how ImagePlugin works
27# -- Can convert to audio as well as video
28
29package VideoPlugin;
30
31use strict;
32no strict 'refs'; # allow filehandles to be variables and viceversa
33no strict 'subs';
34
35use XMLParser;
36use gsprintf;
37
38use MultimediaPlugin;
39use VideoConverter;
40use ReadXMLFile;
41
42sub BEGIN {
43 @VideoPlugin::ISA = ('MultimediaPlugin', 'VideoConverter', 'ReadXMLFile');
44}
45
46
47my $arguments =
48 [ { 'name' => "process_exp",
49 'desc' => "{BasePlugin.process_exp}",
50 'type' => "regexp",
51 'deft' => &get_default_process_exp(),
52 'reqd' => "no" } ];
53
54
55my $options = { 'name' => "VideoPlugin",
56 'desc' => "{VideoPlugin.desc}",
57 'abstract' => "no",
58 'inherits' => "yes",
59 'args' => $arguments };
60
61sub new {
62 my ($class) = shift (@_);
63 my ($pluginlist,$inputargs,$hashArgOptLists) = @_;
64 push(@$pluginlist, $class);
65
66 push(@{$hashArgOptLists->{"ArgList"}},@{$arguments});
67 push(@{$hashArgOptLists->{"OptList"}},$options);
68
69 new VideoConverter($pluginlist, $inputargs, $hashArgOptLists);
70 my $self = new MultimediaPlugin($pluginlist, $inputargs, $hashArgOptLists);
71
72 if ($self->{'info_only'}) {
73 # don't worry about creating the XML parser as all we want is the
74 # list of plugin options
75 return bless $self, $class;
76 }
77
78
79 # create XML::Parser object for parsing keyframe files (produced by hive)
80 my $parser = new XML::Parser('Style' => 'Stream',
81 'Pkg' => 'VideoPlugin',
82 'PluginObj' => $self,
83 'Namespaces' => 1, # strip out namespaces
84 'Handlers' => {'Char' => \&Char,
85 'XMLDecl' => \&XMLDecl,
86 'Entity' => \&Entity,
87 'Doctype' => \&Doctype,
88 'Default' => \&Default
89 });
90
91 $self->{'parser'} = $parser;
92
93
94 return bless $self, $class;
95}
96
97
98
99sub begin {
100 my $self = shift (@_);
101 my ($pluginfo, $base_dir, $processor, $maxdocs) = @_;
102
103 $self->SUPER::begin(@_);
104 $self->VideoConverter::begin(@_);
105}
106
107
108sub init {
109 my $self = shift (@_);
110 my ($verbosity, $outhandle, $failhandle) = @_;
111
112 $self->SUPER::init(@_);
113 $self->VideoConverter::init(@_);
114}
115
116
117sub get_default_process_exp {
118 my $self = shift (@_);
119
120 return q^(?i)\.(mpe?g|flv|mov|qt|wmv|vob|avi|mp4|m4v|ts)$^;
121}
122
123
124sub extract_keyframes
125{
126 my $self = shift (@_);
127 my ($doc_obj,$originalfilename,$filename) = @_;
128
129 my $section = $doc_obj->get_top_section();
130
131 my $output_dir = $self->{'cached_dir'};
132 my $ivideo_root = $self->{'cached_file_root'};
133
134 # Generate the keyframes with ffmpeg and hive
135 my $ifilename = $originalfilename || $filename;
136 my ($keyframe_cmd,$okeyframe_filename) = $self->keyframe_cmd($ifilename);
137
138 my $keyframe_options = { @{$self->{'ffmpeg_monitor'}},
139 'message_prefix' => "Keyframe",
140 'message' => "Extracting keyframes" };
141
142 $self->run_cached_general_cmd($keyframe_cmd,
143 $ifilename,$okeyframe_filename,
144 $keyframe_options);
145 $self->parse_shot_xml();
146
147 my $keep_keyframe_timeline = $self->{'keep_keyframe_timeline'};
148 $self->associate_keyframes($doc_obj,$section,$keep_keyframe_timeline);
149
150}
151
152
153
154sub extract_thumbnail
155{
156 my $self = shift (@_);
157 my ($doc_obj,$filename,$convertto_regenerated,$thumbnailtype,
158 $thumbnail_width, $thumbnail_height) = @_;
159
160 my $section = $doc_obj->get_top_section();
161
162 my $output_dir = $self->{'cached_dir'};
163 my $ivideo_root = $self->{'cached_file_root'};
164
165 my $verbosity = $self->{'verbosity'};
166 my $outhandle = $self->{'outhandle'};
167
168
169 # Generate the thumbnail with convert, a la ImagePlugin
170
171 my $thumbnailfile = &util::filename_cat($output_dir,"$ivideo_root-thumbnail.$thumbnailtype");
172
173 my $optionally_run_general_cmd = "run_uncached_general_cmd";
174 if ($self->{'enable_cache'}) {
175 $optionally_run_general_cmd
176 = ($convertto_regenerated) ? "regenerate_general_cmd" : "run_cached_general_cmd";
177 }
178
179### print STDERR "**** creating thumbnail: $thumbnail_width x $thumbnail_height\n";
180 # my $ofilename = $self->get_ovideo_filename($self->{'enable_streaming'});
181 my ($thumb_cmd ,$othumb_filename)
182 = $self->keyframe_thumbnail_cmd($filename,$thumbnailfile,$thumbnail_width,$thumbnail_height);
183
184 my $thumb_options = { 'verbosity' => $verbosity,
185 'outhandle' => $outhandle,
186 'message_prefix' => "Thumbnail",
187 'message' => "Generating thumbnail" };
188
189 my ($thumb_regenerated,$thumb_result,$thumb_had_error)
190 = $self->$optionally_run_general_cmd($thumb_cmd,$filename,$thumbnailfile,$thumb_options);
191
192 # Add the thumbnail as an associated file ...
193 if (-e "$thumbnailfile") {
194 $doc_obj->associate_file("$thumbnailfile", "thumbnail.$thumbnailtype",
195 "image/$thumbnailtype",$section);
196 $doc_obj->add_metadata ($section, "ThumbType", $thumbnailtype);
197 $doc_obj->add_metadata ($section, "Thumb", "thumbnail.$thumbnailtype");
198
199 $doc_obj->add_utf8_metadata ($section, "thumbicon", "<img src=\"_httpprefix_/collect/[collection]/index/assoc/[assocfilepath]/[Thumb]\" width=[ThumbWidth] height=[ThumbHeight]>");
200### $doc_obj->add_utf8_metadata ($section, "thumbicon", "<img src=\"_httpprefix_/collect/[collection]/index/assoc/[assocfilepath]/[Thumb]\">");
201 }
202
203 # Extract Thumnail metadata from convert output
204 if ($thumb_result =~ m/[0-9]+x[0-9]+=>([0-9]+)x([0-9]+)/) {
205 $doc_obj->add_metadata ($section, "ThumbWidth", $1);
206 $doc_obj->add_metadata ($section, "ThumbHeight", $2);
207 }
208 else {
209 # Two reasons for getting to here:
210 # 1.thumbnail was generated by ffmpeg, not imagemagick convert
211 # 2.thumbnail was cached, so imagemagick convert was not run
212 # Either way, the solution is the same:
213 # => run "identify $thumbnailfile" and parse result
214 $thumb_result = `identify \"$thumbnailfile\" 2>&1`;
215
216 if ($thumb_result =~ m/([0-9]+)x([0-9]+)/) {
217 $doc_obj->add_metadata ($section, "ThumbWidth", $1);
218 $doc_obj->add_metadata ($section, "ThumbHeight", $2);
219 }
220 }
221}
222
223
224sub extract_keyframes_montage
225{
226 my $self = shift (@_);
227 my ($doc_obj,$filename,$thumbnailtype) = @_;
228
229 my $section = $doc_obj->get_top_section();
230
231 my $output_dir = $self->{'cached_dir'};
232 my $ivideo_root = $self->{'cached_file_root'};
233
234
235 # Generate the mosaic with 'montage'
236 my $montagefile = &util::filename_cat($output_dir,"$ivideo_root\_montage.$thumbnailtype");
237
238 my ($montage_cmd,$omontage_filename)
239 = $self->keyframe_montage_cmd($filename,$montagefile);
240
241 my $montage_options = { 'message_prefix' => "Montage",
242 'message' => "Generating montage" };
243
244 my ($montage_result,$montage_had_error)
245 = $self->run_general_cmd($montage_cmd,$montage_options);
246
247 # Add the montage as an associated file ...
248 if (-e "$montagefile") {
249 $doc_obj->associate_file("$montagefile", "montage.$thumbnailtype",
250 "image/$thumbnailtype",$section);
251 $doc_obj->add_metadata ($section, "MontageType", $thumbnailtype);
252 $doc_obj->add_metadata ($section, "Montage", "montage.$thumbnailtype");
253
254 $doc_obj->add_utf8_metadata ($section, "montageicon", "<img src=\"_httpprefix_/collect/[collection]/index/assoc/[assocfilepath]/[Montage]\" >");
255 }
256}
257
258
259
260sub extract_screenview
261{
262 my $self = shift (@_);
263 my ($doc_obj,$filename,$convertto_regenerated, $screenviewtype, $screenview_width,$screenview_height) = @_;
264
265 my $section = $doc_obj->get_top_section();
266
267 my $output_dir = $self->{'cached_dir'};
268 my $ivideo_root = $self->{'cached_file_root'};
269
270 my $optionally_run_general_cmd = "run_uncached_general_cmd";
271 if ($self->{'enable_cache'}) {
272 $optionally_run_general_cmd
273 = ($convertto_regenerated) ? "regenerate_general_cmd" : "run_cached_general_cmd";
274 }
275 # make the screenview image
276
277 my $screenviewfilename = &util::filename_cat($output_dir,"$ivideo_root-screenview.$screenviewtype");
278
279
280 my ($screenview_cmd,$oscreenview_filename)
281 = $self->keyframe_thumbnail_cmd($filename,$screenviewfilename,$screenview_width,$screenview_height);
282
283 my $screenview_options = { 'message_prefix' => "Screenview",
284 'message' => "Generating screenview image" };
285
286
287 my ($screenview_regenerated,$screenview_result,$screenview_had_error)
288 = $self->$optionally_run_general_cmd($screenview_cmd,
289 $filename,$screenviewfilename,
290 $screenview_options);
291
292
293 # get screenview dimensions, size and type
294 if ($screenview_result =~ m/[0-9]+x[0-9]+=>([0-9]+)x([0-9]+)/) {
295 $doc_obj->add_metadata ($section, "ScreenWidth", $1);
296 $doc_obj->add_metadata ($section, "ScreenHeight", $2);
297 }
298 else {
299 $doc_obj->add_metadata ($section, "ScreenWidth", $screenview_width);
300 $doc_obj->add_metadata ($section, "ScreenHeight", $screenview_height);
301 }
302
303 #add the screenview as an associated file ...
304 if (-e "$screenviewfilename") {
305 $doc_obj->associate_file("$screenviewfilename", "screenview.$screenviewtype",
306 "image/$screenviewtype",$section);
307 $doc_obj->add_metadata ($section, "ScreenType", $screenviewtype);
308 $doc_obj->add_metadata ($section, "Screen", "screenview.$screenviewtype");
309
310 $doc_obj->add_utf8_metadata ($section, "screenicon", "<img src=\"_httpprefix_/collect/[collection]/index/assoc/[assocfilepath]/[Screen]\" width=[ScreenWidth] height=[ScreenHeight]>");
311 } else {
312 my $outhandle = $self->{'outhandle'};
313 print $outhandle "VideoPlugin: couldn't find \"$screenviewfilename\"\n";
314 }
315}
316
317
318# Create the keyframes, thumbnail and screenview images, and discover
319# the Video's size, width, and height using the ffmpeg utility.
320
321sub run_convert {
322 my $self = shift (@_);
323 my $base_dir = shift (@_);
324 my $filename = shift (@_); # filename with full path
325 my $file = shift (@_); # filename without path
326 my $doc_obj = shift (@_);
327
328 my $section = $doc_obj->get_top_section();
329
330 my $verbosity = $self->{'verbosity'};
331 my $outhandle = $self->{'outhandle'};
332
333 # check the filename is okay
334 return 0 if ($file eq "" || $filename eq "");
335
336 my $minimumsize = $self->{'minimumsize'};
337 if (defined $minimumsize && (-s $filename < $minimumsize)) {
338 print $outhandle "VideoPlugin: \"$filename\" too small, skipping\n"
339 if ($verbosity > 1);
340 }
341
342 my ($video_type, $video_width, $video_height, $video_duration, $durationDisplay, $video_size,
343 $vcodec,$vrate,$vfps,$atype,$afreq,$achan,$arate)
344 = &VideoConverter::identify($filename, $outhandle, $verbosity);
345
346 if ($video_duration =~ m/^(\d\d):(\d\d):(\d\d)\.(\d+)$/) {
347 $video_duration = $1*3600 + $2*60 + $3 + ($4/10.0);
348 }
349
350 if ($vfps eq "unknown") {
351 print $outhandle "Unknown framerate, defaulting to 25 frames per second.\n";
352 $vfps = 25;
353 }
354
355 #my ($dur_hour,$dur_min,$dur_sec)
356 #= ($video_duration =~ m/(\d+):(\d+):(\d+\.\d+)/);
357 #my $total_dur_secs = $dur_hour*3600 + $dur_min*60 + $dur_sec;
358
359 my $total_dur_secs = $video_duration / 1000;
360
361 $self->{'video-fps'} = $vfps;
362 $self->{'num-total-frames'} = $total_dur_secs * $vfps;
363 #print STDERR "****\nTotal_dur_secs= $total_dur_secs vfps= $vfps\n****\n";
364
365 # Convert the video to a new type (if required).
366 my $converttotype = $self->{'converttotype'};
367 my $converttosize = $self->{'converttosize'};
368
369 # shorten duration prcessed for experimentation purposes
370 my $exp_duration = undef;
371## my $exp_duration = "00:00:30";
372
373 my $excerpt_duration = $self->{'excerpt_duration'};
374
375 if ((defined $excerpt_duration) && ($excerpt_duration ne "")) {
376 $exp_duration = $excerpt_duration;
377 my ($hh,$mm,$ss,$ms);
378
379 if ($exp_duration =~ m/^(\d\d):(\d\d):(\d\d)\.?(\d)$/) {
380 $hh = $1;
381 $mm = $2;
382 $ss = $3;
383 $ms = $4;
384 }
385 else {
386 # assume value is in seconds
387 $hh = 0;
388 $mm = 0;
389 $ss = $exp_duration;
390 $ms = 0;
391 }
392
393 my $excerpt_dur_in_secs = $hh * 3600 + $mm * 60 + $ss;
394
395 if ($excerpt_dur_in_secs < $total_dur_secs) {
396 $self->{'num-total-frames'} = $excerpt_dur_in_secs * $vfps; # override calculation for full length duration
397 }
398 else {
399 # clip is already shorter than requested video excerpt duration
400 # set exp_duration back to undefined
401 $exp_duration = undef;
402 }
403 }
404
405
406 if (defined $exp_duration)
407 {
408 print $outhandle "Only encoding first $exp_duration of video.\n";
409 $self->{'exp_duration'} = $exp_duration;
410 }
411
412 my $ascii_only_filenames = $self->{'use_ascii_only_filenames'};
413 $self->init_cache_for_file($filename);
414
415 my $originalfilename = undef;
416 my $type = "unknown";
417
418 my $output_dir = $self->{'cached_dir'};
419 my $ivideo_root = $self->{'cached_file_root'};
420
421 my $convertto_regenerated = 0;
422 if (($converttotype ne "" && $filename =~ m/$converttotype$/i) ||
423 (($converttosize ne "" && $converttosize ne $video_width) || ($converttosize ne "" && $converttosize ne $video_height))) {
424 if ($converttotype eq "") {
425 # in this block because the video width x height different to original
426 # => set (for this call to run_convert only) converttotype
427 # to input file extension
428 ($converttotype) = ($filename =~ m/\.(.*?)$/);
429 }
430
431 $originalfilename = $filename;
432
433 $file = "$ivideo_root.$converttotype";
434 $filename = &util::filename_cat($output_dir,$file);
435
436 my $s_opt = $self->optional_frame_scale($converttosize,$video_width,$video_height);
437 my $exp_duration = $self->{'exp_duration'};
438 my $t_opt = (defined $exp_duration) ? "-t $exp_duration" : "";
439
440 my $main_opts = "-y $t_opt $s_opt";
441
442
443 my $originalfilename_gsdlenv = $self->gsdlhome_independent($originalfilename);
444 my $filename_gsdlenv = $self->gsdlhome_independent($filename);
445
446
447 my $convertto_command = "ffmpeg $main_opts -i \"$originalfilename_gsdlenv\"";
448 $convertto_command .= " -ar 22050" if ($converttotype eq "flv");
449 $convertto_command .= " -y \"$filename_gsdlenv\"";
450
451 my $convertto_result;
452 my $convertto_error;
453
454 my $convertto_options = { @{$self->{'ffmpeg_monitor'}},
455 'message_prefix' => "Convert to",
456 'message' => "Converting video to $converttotype" };
457
458 ($convertto_regenerated,$convertto_result,$convertto_error)
459 = $self->run_cached_general_cmd($convertto_command,
460 $originalfilename,$filename,
461 $convertto_options);
462
463 $type = $converttotype;
464 }
465
466
467 # Add the video metadata
468
469 my $file_unicode = pack("U0C*", map { ord($_) } split(//,$file)); # force explicitly to unicode
470
471 $file_unicode =~ s/\x{2018}|\x{2019}|\x{201C}|\x{201D}//g; # remove smart quotes as cause problem in URL for video server
472 $file_unicode =~ s/\x{2013}/\-/g; # change en-dash to '-' as again causes problems for video server
473
474## print STDERR "**** file metadata = ", &gsprintf::debug_unicode_string($file_unicode), "\n";
475
476
477 # split filename on char if specified
478 if (0) {
479# don't do any more
480# if ($file_unicode =~ m/\-/) {
481
482 my @file_split = split(/\s*\-\s*/,$file_unicode);
483 my $creator = shift @file_split;
484 my $title = shift @file_split;
485
486 $title =~ s/\..*?$//;
487 $title =~ s/^(.)/\u$1/;
488
489 $doc_obj->add_utf8_metadata($section,"Title",$title);
490
491 my @creator_split = split(/\s+and\s+/,$creator);
492 foreach my $c (@creator_split) {
493 $doc_obj->add_utf8_metadata($section,"Creator",$c);
494 }
495 }
496
497 $file = $file_unicode;
498 my $filemeta = $self->filename_to_utf8_metadata($file);
499 my $filemeta_url_safe = $self->url_safe($filemeta);
500
501 $doc_obj->add_utf8_metadata ($section, "Video", $filemeta_url_safe);
502
503 # Also want to set filename as 'Source' metadata to be
504 # consistent with other plugins
505 # $doc_obj->add_utf8_metadata ($section, "Source", $filemeta_url_safe);
506
507
508 if ($video_type ne " ") {
509 $type = $video_type;
510 }
511
512 $doc_obj->add_metadata ($section, "FileFormat", $type);
513 $doc_obj->add_metadata ($section, "FileSize", $video_size);
514
515 $doc_obj->add_metadata ($section, "VideoType", $video_type);
516 $doc_obj->add_metadata ($section, "VideoWidth", $video_width);
517 $doc_obj->add_metadata ($section, "VideoHeight", $video_height);
518 $doc_obj->add_metadata ($section, "VideoDuration", $video_duration);
519 $doc_obj->add_metadata ($section, "VideoSize", $video_size);
520
521 $doc_obj->add_metadata ($section, "VideoCodec", $vcodec);
522 $doc_obj->add_metadata ($section, "VideoFPS", $vfps);
523
524 $doc_obj->add_metadata ($section, "AudioType", $atype);
525 $doc_obj->add_metadata ($section, "AudioFreq", $afreq);
526 $doc_obj->add_metadata ($section, "AudioChannels", $achan);
527 $doc_obj->add_metadata ($section, "AudioRate", $arate);
528
529 $doc_obj->add_utf8_metadata ($section, "srclink",
530 "<a href=\"_httpprefix_/collect/[collection]/index/assoc/[assocfilepath]/[Video]\">");
531 $doc_obj->add_utf8_metadata ($section, "/srclink", "</a>");
532 $doc_obj->add_metadata ($section, "srcicon", "[VideoType]");
533
534
535 # Add the original file as an associated file
536 if ($self->{'keep_original_video'} eq "true") {
537 $doc_obj->associate_file($filename,$file,"video/$type",$section);
538 }
539
540
541 if ($self->{'extract_keyframes'}) {
542 $self->extract_keyframes($doc_obj,$originalfilename,$filename);
543 }
544
545 my $streamable_regenerated = 0;
546
547 # Create a VP6+MP3 streaming ready video file with FFMpeg
548 if ($self->{'enable_streaming'} eq "flv") {
549 $streamable_regenerated
550 = $self->enable_full_streaming($doc_obj,
551 $originalfilename,$filename,
552 $convertto_regenerated,
553 $video_width,$video_height);
554 }
555
556 # Create an H264+ACC streaming ready video file with Handbrake
557 if ($self->{'enable_streaming'} eq "mp4") {
558 $streamable_regenerated
559 = $self->enable_h264_streaming($doc_obj,
560 $originalfilename,$filename,
561 $convertto_regenerated,
562 $video_width,$video_height);
563 }
564
565 # Use the original video file for streaming
566 if ($self->{'enable_streaming'} eq "direct") {
567 $self->enable_direct_streaming($doc_obj,
568 $originalfilename,$filename,
569 $file,$video_width,$video_height);
570 }
571
572 my $thumbnailsize = $self->{'thumbnailsize'} || 100;
573 my $thumbnailtype = $self->{'thumbnailtype'} || 'jpg';
574
575
576 if ($self->{'create_thumbnail'} eq "true") {
577
578 my $thumbnail_width;
579 my $thumbnail_height;
580
581 if ($video_width>$video_height) {
582 my $scale_ratio = $video_height / $video_width;
583 $thumbnail_width = $thumbnailsize;
584 $thumbnail_height = int($thumbnailsize * $scale_ratio);
585 }
586 else {
587 my $scale_ratio = $video_width / $video_height;
588 $thumbnail_width = int($thumbnailsize * $scale_ratio);
589 $thumbnail_height = $thumbnailsize;
590 }
591
592 # for some video formats, extracted size needs to be multiple of 2
593 $thumbnail_width = int($thumbnail_width/2) * 2;
594 $thumbnail_height = int($thumbnail_height/2) * 2;
595
596 $self->extract_thumbnail($doc_obj,$filename,$convertto_regenerated,
597 $thumbnailtype,
598 $thumbnail_width,$thumbnail_height);
599 }
600
601 if ($self->{'extract_keyframes'}) {
602 $self->extract_keyframes_montage($doc_obj,$filename,$thumbnailtype);
603 }
604
605 my $screenviewsize = $self->{'screenviewsize'};
606 my $screenviewtype = $self->{'screenviewtype'} || 'jpeg';
607
608 # Make a screen-sized version of the picture if requested
609 if ($self->{'create_screenview'} eq "true") {
610
611 # To do: if the actual image smaller than the screenview size,
612 # we should use the original !
613
614 my $screenview_width;
615 my $screenview_height;
616
617 if ($video_width>$video_height) {
618 my $scale_ratio = $video_height / $video_width;
619 $screenview_width = $screenviewsize;
620 $screenview_height = int($screenviewsize * $scale_ratio);
621 }
622 else {
623 my $scale_ratio = $video_width / $video_height;
624 $screenview_width = int($screenviewsize * $scale_ratio);
625 $screenview_height = $screenviewsize;
626 }
627
628
629 # for some video formats, extracted size needs to be multiple of 2
630 $screenview_width = int($screenview_width/2) * 2;
631 $screenview_height = int($screenview_height/2) * 2;
632
633 $self->extract_screenview($doc_obj,$filename, $convertto_regenerated,
634 $screenviewtype,
635 $screenview_width,$screenview_height);
636 }
637
638 return $type;
639}
640
641
642
643
644sub read_into_doc_obj {
645 my $self = shift (@_);
646 my ($pluginfo, $base_dir, $file, $block_hash, $metadata, $processor, $maxdocs, $total_count, $gli) = @_;
647
648 $self->{'media_type'} = "video";
649
650 my ($rv,$doc_obj) = $self->SUPER::read_into_doc_obj(@_);
651
652 if ($rv != 1) {
653 return ($rv,$doc_obj);
654 }
655
656 my $enable_streaming
657 = ($self->{'enable_flv_streaming'} || $self->{'enable_mp4_streaming'})
658 ? 1 : 0;
659
660 if (($enable_streaming) && ($self->{'extract_keyframes'})) {
661 my $section = $doc_obj->get_top_section();
662 my $oflash_filename = $self->{'oflash_filename'};
663 my ($streamkeyframes_cmd,$ostreamkeyframes_filename)
664 = $self->streamkeyframes_cmd($oflash_filename,$doc_obj,$section);
665
666 my $verbosity = $self->{'verbosity'};
667
668 my $streamkeyframes_options = { @{$self->{'flvtool2_monitor'}},
669 'message_prefix' => "Stream Keyframes",
670 'message' => "Reprocessing video stream to add keyframes on timeline" };
671
672 $self->run_general_cmd($streamkeyframes_cmd,$streamkeyframes_options);
673 }
674
675 my ($filename_full_path, $filename_no_path) = &util::get_full_filenames($base_dir, $file);
676 my $section = $doc_obj->get_top_section();
677
678 $self->title_fallback($doc_obj,$section,$filename_no_path);
679
680 $self->{'media_type'} = undef;
681
682 return ($rv,$doc_obj);
683}
684
685
686
687
688sub output_distributed_keyframes
689{
690 my ($self) = shift @_;
691 my ($timeline,$num_dist_frames) = @_;
692
693 my $num_total_frames = $self->{'num-total-frames'};
694
695 my $keep_timeline = {};
696
697 my $frame_delta;
698 if ($num_dist_frames eq "all") {
699 $frame_delta = undef;
700 }
701 else {
702 $frame_delta = $num_total_frames/$num_dist_frames;
703 }
704
705 my $closest_to = $frame_delta;
706 my $prev_t = 0;
707
708# print STDERR "*** num total frames = $num_total_frames\n";
709# print STDERR "*** frame delta = $frame_delta\n";
710
711 my $keep_keyframe_num = 1;
712
713 foreach my $curr_t (sort { $a <=> $b } keys %$timeline)
714 {
715# print STDERR "*** curr_t = $curr_t\n";
716
717 my $timeline_rec = undef;
718
719 if (!defined $closest_to) {
720 $timeline_rec = $timeline->{$curr_t};
721 $keep_timeline->{$curr_t} = $timeline_rec;
722 }
723 elsif ($curr_t>$closest_to) {
724 # decide if previous t (prev_t_ or current t (curr_t) is closest
725
726 my $prev_t_dist = $closest_to - $prev_t;
727 my $curr_t_dist = $curr_t - $closest_to;
728
729 if ($curr_t_dist <= $prev_t_dist)
730 {
731 $timeline_rec = $timeline->{$curr_t};
732 $keep_timeline->{$curr_t} = $timeline_rec;
733
734 }
735 else
736 {
737 $timeline_rec = $timeline->{$prev_t};
738 $keep_timeline->{$prev_t} = $timeline_rec;
739 }
740
741 $closest_to += $frame_delta;
742 }
743
744 if (defined $timeline_rec) {
745
746 my $name = $timeline_rec->{'name'};
747 my $timestamp = $timeline_rec->{'timestamp'};
748 my $thumb = $timeline_rec->{'thumb'};
749 my $keyframe_num = $timeline_rec->{'keyframenum'};
750
751
752 print CUEOUT " <metatag event=\"onCuePoint\" overwrite=\"true\">\n";
753 print CUEOUT " <name>$name</name>\n";
754 print CUEOUT " <timestamp>$timestamp</timestamp>\n";
755 print CUEOUT " <parameters>\n";
756 print CUEOUT " <thumb>$thumb</thumb>\n";
757 print CUEOUT " <keyframeNum>$keyframe_num</keyframeNum>\n";
758 print CUEOUT " <keepKeyframeNum>$keep_keyframe_num</keepKeyframeNum>\n";
759 print CUEOUT " </parameters>\n";
760 print CUEOUT " <type>navigation</type>\n";
761 print CUEOUT " </metatag>\n";
762
763# my $testtime = $timestamp+1000;
764# print CUEOUT " <metatag event=\"onCuePoint\" overwrite=\"true\">\n";
765# print CUEOUT " <name>Test $name</name>\n";
766# print CUEOUT " <timestamp>$testtime</timestamp>\n";
767# print CUEOUT " <parameters>\n";
768# print CUEOUT " <thumb>$thumb</thumb>\n";
769# print CUEOUT " <secnum>1</secnum>\n";
770# print CUEOUT " <subsecnum>0</subsecnum>\n";
771# print CUEOUT " </parameters>\n";
772# print CUEOUT " <type>event</type>\n";
773# print CUEOUT " </metatag>\n";
774
775 $keep_keyframe_num++;
776
777 }
778 $prev_t = $curr_t;
779 }
780
781 $self->{'keep_keyframe_timeline'} = $keep_timeline;
782}
783
784
785sub StartDocument {$_[0]->{'PluginObj'}->xml_start_document(@_);}
786sub XMLDecl {$_[0]->{'PluginObj'}->xml_xmldecl(@_);}
787sub Entity {$_[0]->{'PluginObj'}->xml_entity(@_);}
788sub Doctype {$_[0]->{'PluginObj'}->xml_doctype(@_);}
789sub StartTag {$_[0]->{'PluginObj'}->xml_start_tag(@_);}
790sub EndTag {$_[0]->{'PluginObj'}->xml_end_tag(@_);}
791sub Text {$_[0]->{'PluginObj'}->xml_text(@_);}
792sub PI {$_[0]->{'PluginObj'}->xml_pi(@_);}
793sub EndDocument {$_[0]->{'PluginObj'}->xml_end_document(@_);}
794sub Default {$_[0]->{'PluginObj'}->xml_default(@_);}
795
796
797# This Char function overrides the one in XML::Parser::Stream to overcome a
798# problem where $expat->{Text} is treated as the return value, slowing
799# things down significantly in some cases.
800sub Char {
801 use bytes; # Necessary to prevent encoding issues with XML::Parser 2.31+
802 $_[0]->{'Text'} .= $_[1];
803 return undef;
804}
805
806sub xml_start_document {
807 my $self = shift(@_);
808 my ($expat, $name, $sysid, $pubid, $internal) = @_;
809
810}
811
812# Called for XML declarations
813sub xml_xmldecl {
814 my $self = shift(@_);
815 my ($expat, $version, $encoding, $standalone) = @_;
816}
817
818# Called for XML entities
819sub xml_entity {
820 my $self = shift(@_);
821 my ($expat, $name, $val, $sysid, $pubid, $ndata) = @_;
822}
823
824
825# Called for DOCTYPE declarations - use die to bail out if this doctype
826# is not meant for this plugin
827sub xml_doctype {
828 my $self = shift(@_);
829 my ($expat, $name, $sysid, $pubid, $internal) = @_;
830
831 # This test used to be done in xml_start_document
832 # Moved to here as seems more logical
833
834 if ($name !~ "seg") {
835 die "VideoPlugin: Root tag $name does not match expected <seg>";
836 }
837}
838
839
840sub xml_start_tag {
841 my $self = shift(@_);
842 my ($expat, $element) = @_;
843
844 my %attr = %_;
845
846 if ($element eq "seg") {
847 $self->{'keyframe_index'} = 0;
848 $self->{'keyframe_fnames'} = [];
849 $self->{'keyframe_timeline'} = {};
850
851 #$self->{'flowplayer_thumblist'} = "thumbs: \\[";
852
853 my $output_dir = $self->{'cached_dir'};
854 my $cue_filename = &util::filename_cat($output_dir,"on_cue.xml");
855
856 open(CUEOUT,">$cue_filename")
857 || die "Unable to open $cue_filename: $!\n";
858 print CUEOUT "<tags>\n";
859 }
860 elsif ($element eq "trans") {
861 my $trans_type = $attr{'type'};
862 my $pre_frame_num = $attr{'preFNum'};
863 my $post_frame_num = $attr{'postFNum'};
864
865 my $avg_frame_num = int(($pre_frame_num+$post_frame_num)/2.0)+1;
866
867 my $output_dir = $self->{'cached_dir'};
868 my $ivideo_root = $self->{'cached_file_root'};
869
870 my $keyframe_index = $self->{'keyframe_index'};
871
872 my $fps = $self->{'video-fps'};
873
874 if ($keyframe_index==0)
875 {
876 # hive actually generates one extra keyframe at the start,
877 # which is half way between frame 0 and the frist pre_frame
878
879 my $thumb_file = sprintf("%s_%04d.jpg",$ivideo_root,$keyframe_index);
880 my $thumb_filename = &util::filename_cat($output_dir,$thumb_file);
881 push(@{$self->{'keyframe_fnames'}},$thumb_file);
882
883 my $half_frame_num = $pre_frame_num/2.0;
884 my $time_msec = ($half_frame_num / $fps) * 1000;
885
886# print CUEOUT " <metatag event=\"onCuePoint\" overwrite=\"true\">\n";
887# print CUEOUT " <name>Keyframe $keyframe_index</name>\n";
888# print CUEOUT " <timestamp>$time_msec</timestamp>\n";
889# print CUEOUT " <parameters>\n";
890# print CUEOUT " <thumb>$thumb_file</thumb>\n";
891# print CUEOUT " </parameters>\n";
892# print CUEOUT " <type>navigation</type>\n";
893# print CUEOUT " </metatag>\n";
894
895
896 my $timeline_rec = { 'name'=> "Keyframe $keyframe_index",
897 'keyframeindex' => $keyframe_index,
898 'timestamp' => $time_msec,
899 'thumb' => $thumb_file,
900 'keyframenum' => $keyframe_index};
901
902 $self->{'keyframe_timeline'}->{$half_frame_num}=$timeline_rec;
903 }
904
905 $keyframe_index++;
906
907 my $thumb_file = sprintf("%s_%04d.jpg",$ivideo_root,$keyframe_index);
908 my $thumb_filename = &util::filename_cat($output_dir,$thumb_file);
909 push(@{$self->{'keyframe_fnames'}},$thumb_file);
910
911 my $time_msec = (($avg_frame_num) / $fps) * 1000;
912 my $time_sec = (($avg_frame_num)/ $fps);
913
914# print CUEOUT " <metatag event=\"onCuePoint\" overwrite=\"true\">\n";
915# print CUEOUT " <name>Keyframe $keyframe_index</name>\n";
916# print CUEOUT " <timestamp>$time_msec</timestamp>\n";
917# print CUEOUT " <parameters>\n";
918# print CUEOUT " <thumb>$thumb_file</thumb>\n";
919# print CUEOUT " </parameters>\n";
920# print CUEOUT " <type>navigation</type>\n";
921# print CUEOUT " </metatag>\n";
922
923
924 my $timeline_rec = { 'name'=> "Keyframe $keyframe_index",
925 'keyframeindex' => $keyframe_index,
926 'timestamp' => $time_msec,
927 'thumb' => $thumb_file,
928 'keyframenum' => $keyframe_index};
929
930 $self->{'keyframe_timeline'}->{$avg_frame_num}=$timeline_rec;
931
932 # $self->{'flowplayer_thumblist'} .= "\\{ thumbNail: '$thumb_file', time: $time_sec \\},";
933
934 $self->{'keyframe_index'} = $keyframe_index;
935 }
936}
937
938sub xml_end_tag {
939 my $self = shift(@_);
940 my ($expat, $element) = @_;
941
942 if ($element eq "seg") {
943
944 $self->output_distributed_keyframes($self->{'keyframe_timeline'},$self->{'keep_keyframes'});
945
946
947 print CUEOUT "</tags>\n";
948 close(CUEOUT);
949
950 #$self->{'flowplayer_thumblist'} .= "\\]";
951 }
952}
953
954
955
956
957
958# Called just before start or end tags with accumulated non-markup text in
959# the $_ variable.
960sub xml_text {
961 my $self = shift(@_);
962 my ($expat) = @_;
963}
964
965# Called for processing instructions. The $_ variable will contain a copy
966# of the pi.
967sub xml_pi {
968 my $self = shift(@_);
969 my ($expat, $target, $data) = @_;
970}
971
972# Called at the end of the XML document.
973sub xml_end_document {
974 my $self = shift(@_);
975 my ($expat) = @_;
976
977 # ****
978 # $self->close_document();
979}
980
981
982# Called for any characters not handled by the above functions.
983sub xml_default {
984 my $self = shift(@_);
985 my ($expat, $text) = @_;
986}
987
988
9891;
990
991
992
993
994
995
996
997
998
999
1000
Note: See TracBrowser for help on using the repository browser.