source: gs2-extensions/video/trunk/perllib/plugins/VideoPlugin.pm@ 21825

Last change on this file since 21825 was 21825, checked in by max, 12 years ago

Allow to not keep the original file
Two settings for streaming (flv or mp4)

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