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