- Timestamp:
- 2009-02-20T11:40:23+13:00 (15 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
extensions/gsdl-video/trunk/perllib/plugins/VideoPlugin.pm
r18490 r18556 1 1 ###################################################################### 2 2 # 3 # VideoPlugin.pm -- plugin for processing video largely based on ImagePlug3 # VideoPlugin.pm -- plugin for processing video files 4 4 # A component of the Greenstone digital library software 5 5 # from the New Zealand Digital Library Project at the … … 24 24 ########################################################################### 25 25 26 # -- Largely modeled on how ImagePlugin works 27 # -- Can convert to audio as well as video 28 26 29 package VideoPlugin; 27 30 … … 30 33 no strict 'subs'; 31 34 32 use videoconvert;33 35 use XMLParser; 34 36 use gsprintf; 35 37 36 use BasePlugin; 38 use MultimediaPlugin; 39 use VideoConverter; 37 40 38 41 sub BEGIN { 39 @VideoPlugin::ISA = ('BasePlugin'); 40 41 if (!defined $ENV{'GEXTVIDEO'}) { 42 print STDERR "Warning: Greenstone Video extension not detected.\n"; 43 } 44 45 } 46 47 48 # Customized from BasePlugin. Make 'incremental' the default 49 # to avoid Greenstone hashing on the file (which in the case of video 50 # is HUGE) to generate OID. Also supress 'hash' as option so the user 51 # can't choose it. 52 53 our $oidtype_list = 54 [ { 'name' => "auto", 55 'desc' => "{BasePlugin.OIDtype.auto}" }, 56 { 'name' => "assigned", 57 'desc' => "{import.OIDtype.assigned}" }, 58 { 'name' => "incremental", 59 'desc' => "{import.OIDtype.incremental}" }, 60 { 'name' => "dirname", 61 'desc' => "{import.OIDtype.dirname}" } ]; 42 @VideoPlugin::ISA = ('MultimediaPlugin', 'VideoConverter'); 43 } 62 44 63 45 … … 67 49 'type' => "regexp", 68 50 'deft' => &get_default_process_exp(), 69 'reqd' => "no" },70 { 'name' => "OIDtype",71 'desc' => "{import.OIDtype}",72 'type' => "enum",73 'list' => $oidtype_list,74 'deft' => "incremental",75 'reqd' => "no",76 'modegli' => "2" },77 78 { 'name' => "noscaleup",79 'desc' => "{VideoPlug.noscaleup}",80 'type' => "flag",81 'reqd' => "no" },82 { 'name' => "use_ascii_only_filenames",83 'desc' => "{VideoPlug.use_ascii_only_filenames}",84 'type' => "flag",85 'reqd' => "no" },86 { 'name' => "video_excerpt_duration",87 'desc' => "{VideoPlug.video_excerpt_duration}",88 'type' => "string",89 'deft' => "",90 'reqd' => "no" },91 { 'name' => "extractthumbnail",92 'desc' => "{VideoPlug.extractthumbnail}",93 'type' => "flag",94 'deft' => "0",95 'reqd' => "no" },96 { 'name' => "thumbnailsize",97 'desc' => "{VideoPlug.thumbnailsize}",98 'type' => "int",99 'deft' => "100",100 'range' => "1,",101 'reqd' => "no" },102 { 'name' => "thumbnailtype",103 'desc' => "{VideoPlug.thumbnailtype}",104 'type' => "string",105 'deft' => "jpeg",106 'reqd' => "no" },107 { 'name' => "extractscreenview",108 'desc' => "{VideoPlug.extractscreenview}",109 'type' => "flag",110 'deft' => "0",111 'reqd' => "no" },112 { 'name' => "screenviewsize",113 'desc' => "{VideoPlug.screenviewsize}",114 'type' => "int",115 'deft' => "0",116 'range' => "1,",117 'reqd' => "no" },118 { 'name' => "screenviewtype",119 'desc' => "{VideoPlug.screenviewtype}",120 'type' => "string",121 'deft' => "jpg",122 'reqd' => "no" },123 { 'name' => "converttotype",124 'desc' => "{VideoPlug.converttotype}",125 'type' => "string",126 'deft' => "",127 'reqd' => "no" },128 { 'name' => "converttosize",129 'desc' => "{VideoPlug.converttosize}",130 'type' => "int",131 'deft' => "",132 ## 'deft' => "352",133 'reqd' => "no" },134 { 'name' => "converttobitrate",135 'desc' => "{VideoPlug.converttobitrate}",136 'type' => "string",137 'deft' => "200k",138 'reqd' => "no" },139 { 'name' => "extractkeyframes",140 'desc' => "{VideoPlug.extractkeyframes}",141 'type' => "flag",142 'deft' => "0",143 'reqd' => "no" },144 { 'name' => "enablestreaming",145 'desc' => "{VideoPlug.enablestreaming}",146 'type' => "flag",147 'deft' => "1",148 'reqd' => "no" },149 { 'name' => "streamingsize",150 'desc' => "{VideoPlug.streamingsize}",151 'type' => "int",152 'deft' => "352",153 'reqd' => "no" },154 { 'name' => "streamingbitrate",155 'desc' => "{VideoPlug.streamingbitrate}",156 'type' => "string",157 'deft' => "200k",158 'reqd' => "no" },159 { 'name' => "minimumsize",160 'desc' => "{VideoPlug.minimumsize}",161 'type' => "int",162 'deft' => "100",163 'range' => "1,",164 51 'reqd' => "no" } ]; 165 52 53 166 54 my $options = { 'name' => "VideoPlugin", 167 'desc' => "{VideoPlug .desc}",55 'desc' => "{VideoPlugin.desc}", 168 56 'abstract' => "no", 169 57 'inherits' => "yes", 170 58 'args' => $arguments }; 171 172 173 my ($self);174 59 175 60 sub new { … … 181 66 push(@{$hashArgOptLists->{"OptList"}},$options); 182 67 183 $self = new BasePlugin($pluginlist, $inputargs, $hashArgOptLists); 184 185 # Check that ffmpeg is installed and available on the path (except for Windows 95/98) 186 # This test is "inherited" from ImagePlugin where ImageMagick support 187 # for building cannot be used on these two versions of Windows as they 188 # do not have enough flexablity in the use of stdout and stderr to 189 # support how our code works. It seems reasonable to assume the 190 # same is true for VideoPlugin work using ffmpeg. 191 192 if (($ENV{'GSDLOS'} ne "windows" || Win32::IsWinNT())) { 193 194 my $result = `ffmpeg -h 2>&1`; 195 196 197 if (!defined $result || $result !~ m/^FFmpeg version/) { 198 $self->{'ffmpeg_installed'} = 0; 199 print STDERR $result; 200 } 201 else { 202 $self->{'ffmpeg_installed'} = 1; 203 } 204 205 } 206 207 208 # create XML::Parser object for parsing metadata.xml files 209 my $parser; 210 if ($]<5.008) { 211 # Perl 5.6 212 $parser = new XML::Parser('Style' => 'Stream', 213 'Handlers' => {'Char' => \&Char, 214 'Doctype' => \&Doctype 215 }); 216 } 217 else { 218 # Perl 5.8 219 $parser = new XML::Parser('Style' => 'Stream', 220 'ProtocolEncoding' => 'ISO-8859-1', 221 'Handlers' => {'Char' => \&Char, 222 'Doctype' => \&Doctype 223 }); 224 } 68 new VideoConverter($pluginlist, $inputargs, $hashArgOptLists); 69 my $self = new MultimediaPlugin($pluginlist, $inputargs, $hashArgOptLists); 70 71 if ($self->{'info_only'}) { 72 # don't worry about creating the XML parser as all we want is the 73 # list of plugin options 74 return bless $self, $class; 75 } 76 77 78 # create XML::Parser object for parsing keyframe files (produced by hive) 79 my $parser = new XML::Parser('Style' => 'Stream', 80 'Pkg' => 'VideoPlugin', 81 'PluginObj' => $self, 82 'Namespaces' => 1, # strip out namespaces 83 'Handlers' => {'Char' => \&Char, 84 'XMLDecl' => \&XMLDecl, 85 'Entity' => \&Entity, 86 'Doctype' => \&Doctype, 87 'Default' => \&Default 88 }); 225 89 226 90 $self->{'parser'} = $parser; … … 228 92 229 93 return bless $self, $class; 94 } 95 96 97 98 sub begin { 99 my $self = shift (@_); 100 my ($pluginfo, $base_dir, $processor, $maxdocs) = @_; 101 102 $self->SUPER::begin(@_); 103 $self->VideoConverter::begin(@_); 230 104 } 231 105 … … 236 110 237 111 $self->SUPER::init(@_); 238 ## $self->ImageConverter::init(); # ****** VideoConverter::init ??112 $self->VideoConverter::init(@_); 239 113 } 240 114 … … 246 120 } 247 121 248 # this makes no sense for image 249 sub block_cover_image122 123 sub extract_keyframes 250 124 { 251 my $self =shift (@_); 252 my ($filename) = @_; 253 254 return; 255 } 256 257 125 my $self = shift (@_); 126 my ($doc_obj,$originalfilename,$filename) = @_; 127 128 my $section = $doc_obj->get_top_section(); 129 130 my $output_dir = $self->{'cached_dir'}; 131 my $ivideo_root = $self->{'cached_file_root'}; 132 133 # Generate the keyframes with ffmpeg and hive 134 my ($keyframe_cmd,$okeyframe_filename) = $self->keyframe_cmd($originalfilename || $filename); 135 136 my $keyframe_options = { @{$self->{'ffmpeg_monitor'}}, 137 'message_prefix' => "Keyframe", 138 'message' => "Extracting keyframes" }; 139 140 $self->autorun_cached_general_cmd($keyframe_cmd,$okeyframe_filename,$keyframe_options); 141 $self->parse_shot_xml(); 142 $self->associate_keyframes($doc_obj,$section); 143 } 144 145 146 sub enable_streaming 147 { 148 my $self = shift (@_); 149 my ($doc_obj,$originalfilename,$filename,$convertto_regenerated, 150 $video_width,$video_height) = @_; 151 152 my $section = $doc_obj->get_top_section(); 153 154 my $output_dir = $self->{'cached_dir'}; 155 my $ivideo_root = $self->{'cached_file_root'}; 156 157 # Generate the Flash FLV format for streaming purposes 158 my $streamable_regenerated = 0; 159 160 my $optionally_run_general_cmd = "run_uncached_general_cmd"; 161 if ($self->{'enable_cache'}) { 162 $optionally_run_general_cmd 163 = ($convertto_regenerated) ? "regenerate_general_cmd" : "run_cached_general_cmd"; 164 } 165 166 my $streaming_bitrate = $self->{'streamingbitrate'}; 167 my $streaming_size = $self->{'streamingsize'}; 168 169 my $streaming_quality = "high"; 170 171 my ($stream_cmd,$oflash_filename,$oflash_file) 172 = $self->stream_cmd($originalfilename || $filename, 173 $video_width,$video_height, 174 $streaming_quality, 175 $streaming_bitrate, $streaming_size); 176 177 178 my $streamable_options = { @{$self->{'ffmpeg_monitor'}}, 179 'message_prefix' => "Stream", 180 'message' => "Generating streamable video: $oflash_file" }; 181 182 my $streamable_result; 183 my $streamable_had_error; 184 ($streamable_regenerated,$streamable_result,$streamable_had_error) 185 = $self->$optionally_run_general_cmd($stream_cmd,$oflash_filename,$streamable_options); 186 187 if (!$streamable_had_error) { 188 my ($streamseekable_cmd,$ostreamseekable_filename) = $self->streamseekable_cmd($oflash_filename); 189 190 my $streamseekable_options = { @{$self->{'flvtool2_monitor'}}, 191 'message_prefix' => "Stream Seekable", 192 'message' => "Reprocessing video stream to be seekable by timeline: $oflash_file" }; 193 194 if ($streamable_regenerated) { 195 $self->run_general_cmd($streamseekable_cmd,$streamseekable_options); 196 } 197 198 my $streamable_url = $oflash_file; 199 ## $streamable_url =~ s/ /%20/g; 200 my $streamable_url_safe = $self->url_safe($streamable_url); 201 202 $doc_obj->add_utf8_metadata ($section, "streamablevideo", $streamable_url_safe); 203 $doc_obj->associate_file($oflash_filename,$oflash_file,"video/flash", 204 $section); 205 } 206 207 ## my $video_width = $doc_obj->get_metadata_element($section,"VideoWidth"); 208 ## my $video_height = $doc_obj->get_metadata_element($section,"VideoHeight"); 209 210 # 211 # FlowPlayer.swf height+22 pixels 212 # FlowPlayerBlack.swf height+16 pixels 213 # FlowPlayerThermo.swf height+16 pixels 214 # FlowPlayerWhite.swf height+26 pixels 215 my $flashwidth = $video_width; 216 my $flashheight = $video_height + 22; 217 if ($self->{'extractkeyframes'}) { 218 $flashheight += 100; 219 } 220 $doc_obj->add_metadata ($section, "flashwidth", $flashwidth); 221 $doc_obj->add_metadata ($section, "flashheight", $flashheight); 222 223 my $video_server = $ENV{'GEXT_VIDEO_SERVER'}; 224 my $video_prefix = $ENV{'GEXT_VIDEO_PREFIX'}; 225 my $base_url = "$video_server$video_prefix/collect/[collection]/index/assoc/[assocfilepath]/"; 226 my $base_url_safe = $self->url_safe($base_url); 227 $doc_obj->add_utf8_metadata ($section, "baseurl",$base_url_safe); 228 229 $self->{'oflash_file'} = $oflash_file; 230 $self->{'oflash_filename'} = $oflash_filename; 231 232 return $streamable_regenerated; 233 } 234 235 sub extract_thumbnail 236 { 237 my $self = shift (@_); 238 my ($doc_obj,$filename,$convertto_regenerated,$thumbnailtype, 239 $thumbnail_width, $thumbnail_height) = @_; 240 241 my $section = $doc_obj->get_top_section(); 242 243 my $output_dir = $self->{'cached_dir'}; 244 my $ivideo_root = $self->{'cached_file_root'}; 245 246 my $verbosity = $self->{'verbosity'}; 247 my $outhandle = $self->{'outhandle'}; 248 249 250 # Generate the thumbnail with convert, a la ImagePlug 251 252 my $thumbnailfile = &util::filename_cat($output_dir,"$ivideo_root.$thumbnailtype"); 253 254 255 my $optionally_run_general_cmd = "run_uncached_general_cmd"; 256 if ($self->{'enable_cache'}) { 257 $optionally_run_general_cmd 258 = ($convertto_regenerated) ? "regenerate_general_cmd" : "run_cached_general_cmd"; 259 } 260 261 my ($thumb_cmd ,$othumb_filename) 262 = $self->keyframe_thumbnail_cmd($filename,$thumbnailfile,$thumbnail_width,$thumbnail_height); 263 264 my $thumb_options = { 'verbosity' => $verbosity, 265 'outhandle' => $outhandle, 266 'message_prefix' => "Thumbnail", 267 'message' => "Generating thumbnail" }; 268 269 my ($thumb_regenerated,$thumb_result,$thumb_had_error) 270 = $self->$optionally_run_general_cmd($thumb_cmd,$thumbnailfile,$thumb_options); 271 272 # Add the thumbnail as an associated file ... 273 if (-e "$thumbnailfile") { 274 $doc_obj->associate_file("$thumbnailfile", "thumbnail.$thumbnailtype", 275 "image/$thumbnailtype",$section); 276 $doc_obj->add_metadata ($section, "ThumbType", $thumbnailtype); 277 $doc_obj->add_metadata ($section, "Thumb", "thumbnail.$thumbnailtype"); 278 279 $doc_obj->add_utf8_metadata ($section, "thumbicon", "<img src=\"_httpprefix_/collect/[collection]/index/assoc/[assocfilepath]/[Thumb]\" width=[ThumbWidth] height=[ThumbHeight]>"); 280 ### $doc_obj->add_utf8_metadata ($section, "thumbicon", "<img src=\"_httpprefix_/collect/[collection]/index/assoc/[assocfilepath]/[Thumb]\">"); 281 } 282 283 # Extract Thumnail metadata from convert output 284 if ($thumb_result =~ m/[0-9]+x[0-9]+=>([0-9]+)x([0-9]+)/) { 285 $doc_obj->add_metadata ($section, "ThumbWidth", $1); 286 $doc_obj->add_metadata ($section, "ThumbHeight", $2); 287 } 288 else { 289 # Two reasons for getting to here: 290 # 1.thumbnail was generated by ffmpeg, not imagemagick convert 291 # 2.thumbnail was cached, so imagemagick convert was not run 292 # Either way, the solution is the same: 293 # => run "identify $thumbnailfile" and parse result 294 295 $thumb_result = `identify \"$thumbnailfile\"`; 296 297 if ($thumb_result =~ m/([0-9]+)x([0-9]+)/) { 298 $doc_obj->add_metadata ($section, "ThumbWidth", $1); 299 $doc_obj->add_metadata ($section, "ThumbHeight", $2); 300 } 301 } 302 } 303 304 305 sub extract_keyframes_montage 306 { 307 my $self = shift (@_); 308 my ($doc_obj,$filename,$thumbnailtype) = @_; 309 310 my $section = $doc_obj->get_top_section(); 311 312 my $output_dir = $self->{'cached_dir'}; 313 my $ivideo_root = $self->{'cached_file_root'}; 314 315 316 # Generate the mosaic with 'montage' 317 my $montagefile = &util::filename_cat($output_dir,"$ivideo_root\_montage.$thumbnailtype"); 318 319 my ($montage_cmd,$omontage_filename) 320 = $self->keyframe_montage_cmd($filename,$montagefile); 321 322 my $montage_options = { 'message_prefix' => "Montage", 323 'message' => "Generating montage" }; 324 325 my ($montage_result,$montage_had_error) 326 = $self->run_general_cmd($montage_cmd,$montage_options); 327 328 # Add the montage as an associated file ... 329 if (-e "$montagefile") { 330 $doc_obj->associate_file("$montagefile", "montage.$thumbnailtype", 331 "image/$thumbnailtype",$section); 332 $doc_obj->add_metadata ($section, "MontageType", $thumbnailtype); 333 $doc_obj->add_metadata ($section, "Montage", "montage.$thumbnailtype"); 334 335 $doc_obj->add_utf8_metadata ($section, "montageicon", "<img src=\"_httpprefix_/collect/[collection]/index/assoc/[assocfilepath]/[Montage]\" >"); 336 } 337 } 338 339 340 341 sub extract_screenview 342 { 343 my $self = shift (@_); 344 my ($doc_obj,$filename,$screenviewtype, $screenview_width,$screenview_height) = @_; 345 346 my $section = $doc_obj->get_top_section(); 347 348 my $output_dir = $self->{'cached_dir'}; 349 my $ivideo_root = $self->{'cached_file_root'}; 350 351 352 # make the screenview image 353 354 my $screenviewfilename = &util::filename_cat($output_dir,"$ivideo_root.$screenviewtype"); 355 356 357 my ($screenview_cmd,$oscreenview_filename) 358 = $self->keyframe_thumbnail_cmd($filename,$screenviewfilename,$screenview_width,$screenview_height); 359 360 my $screenview_options = { 'message_prefix' => "Screenview", 361 'message' => "Generating screenview image" }; 362 363 my ($result,$had_error) 364 = $self->run_general_cmd($screenview_cmd,$screenview_options); 365 366 # get screenview dimensions, size and type 367 if ($result =~ m/[0-9]+x[0-9]+=>([0-9]+)x([0-9]+)/) { 368 $doc_obj->add_metadata ($section, "ScreenWidth", $1); 369 $doc_obj->add_metadata ($section, "ScreenHeight", $2); 370 } 371 else { 372 $doc_obj->add_metadata ($section, "ScreenWidth", $screenview_width); 373 $doc_obj->add_metadata ($section, "ScreenHeight", $screenview_height); 374 } 375 376 #add the screenview as an associated file ... 377 if (-e "$screenviewfilename") { 378 $doc_obj->associate_file("$screenviewfilename", "screenview.$screenviewtype", 379 "image/$screenviewtype",$section); 380 $doc_obj->add_metadata ($section, "ScreenType", $screenviewtype); 381 $doc_obj->add_metadata ($section, "Screen", "screenview.$screenviewtype"); 382 383 $doc_obj->add_utf8_metadata ($section, "screenicon", "<img src=\"_httpprefix_/collect/[collection]/index/assoc/[assocfilepath]/[Screen]\" width=[ScreenWidth] height=[ScreenHeight]>"); 384 } else { 385 my $outhandle = $self->{'outhandle'}; 386 print $outhandle "VideoPlugin: couldn't find \"$screenviewfilename\"\n"; 387 } 388 } 258 389 259 390 … … 284 415 my ($video_type, $video_width, $video_height, $video_duration, $video_size, 285 416 $vcodec,$vfps,$atype,$afreq,$achan,$arate) 286 = & videoconvert::identify($filename, $outhandle, $verbosity);417 = &VideoConverter::identify($filename, $outhandle, $verbosity); 287 418 288 419 if ($vfps eq "unknown") { … … 305 436 my $exp_duration = undef; 306 437 307 my $ video_excerpt_duration = $self->{'video_excerpt_duration'};308 309 if ((defined $ video_excerpt_duration) && ($video_excerpt_duration ne "")) {310 $exp_duration = $ video_excerpt_duration;438 my $excerpt_duration = $self->{'excerpt_duration'}; 439 440 if ((defined $excerpt_duration) && ($excerpt_duration ne "")) { 441 $exp_duration = $excerpt_duration; 311 442 my ($hh,$mm,$ss,$ms) = ($exp_duration =~ m/^(\d\d):(\d\d):(\d\d)\.?(\d\d)?/); 312 443 my $excerpt_dur_in_secs = $hh * 3600 + $mm * 60 + $ss; … … 330 461 331 462 my $ascii_only_filenames = $self->{'use_ascii_only_filenames'}; 332 my $videoconvert 333 = new videoconvert($base_dir,$filename,$verbosity,$outhandle,$exp_duration,$ascii_only_filenames); 334 $self->{'videoconvert'} = $videoconvert; 463 $self->init_cache_for_file($filename); 335 464 336 465 my $originalfilename = undef; 337 466 my $type = "unknown"; 338 467 339 my $output_dir = $ videoconvert->{'cached_dir'};340 my $ivideo_root = $ videoconvert->{'file_root'};468 my $output_dir = $self->{'cached_dir'}; 469 my $ivideo_root = $self->{'cached_file_root'}; 341 470 342 471 my $convertto_regenerated = 0; … … 355 484 $filename = &util::filename_cat($output_dir,$file); 356 485 357 my $s_opt = $ videoconvert->optional_frame_scale($converttosize,$video_width,$video_height);486 my $s_opt = $self->optional_frame_scale($converttosize,$video_width,$video_height); 358 487 my $exp_duration = $self->{'exp_duration'}; 359 488 my $t_opt = (defined $exp_duration) ? "-t $exp_duration" : ""; … … 373 502 my $convertto_error; 374 503 375 my $convertto_options = { @{$ videoconvert->{'ffmpeg_monitor'}},504 my $convertto_options = { @{$self->{'ffmpeg_monitor'}}, 376 505 'message_prefix' => "Convert to", 377 506 'message' => "Converting video to $converttotype" }; 378 507 379 508 ($convertto_regenerated,$convertto_result,$convertto_error) 380 = $ videoconvert->run_cached_general_cmd($convertto_command,$filename,$convertto_options);509 = $self->autorun_cached_general_cmd($convertto_command,$filename,$convertto_options); 381 510 382 511 $type = $converttotype; … … 414 543 $file = $file_unicode; 415 544 my $filemeta = $self->filename_to_utf8_metadata($file); 416 my $filemeta_url_safe = $ videoconvert->url_safe($filemeta);545 my $filemeta_url_safe = $self->url_safe($filemeta); 417 546 418 547 $doc_obj->add_utf8_metadata ($section, "Video", $filemeta_url_safe); … … 454 583 455 584 if ($self->{'extractkeyframes'}) { 456 # Generate the keyframes with ffmpeg and hive 457 my ($keyframe_cmd,$okeyframe_filename) = $videoconvert->keyframe_cmd($originalfilename || $filename); 458 459 my $keyframe_options = { @{$videoconvert->{'ffmpeg_monitor'}}, 460 'message_prefix' => "Keyframe", 461 'message' => "Extracting keyframes" }; 462 463 $videoconvert->run_cached_general_cmd($keyframe_cmd,$okeyframe_filename,$keyframe_options); 464 $videoconvert->parse_shot_xml($self); 465 $videoconvert->associate_keyframes($doc_obj,$section,$self); 585 $self->extract_keyframes($doc_obj,$originalfilename,$filename); 466 586 } 467 587 468 588 my $streamable_regenerated = 0; 469 my $optionally_run_general_cmd 470 = ($convertto_regenerated) ? "regenerate_general_cmd" : "run_cached_general_cmd"; 471 472 if ($self->{'enablestreaming'}) { 473 # Generate the Flash FLV format for streaming purposes 474 475 my $streaming_bitrate = $self->{'streamingbitrate'}; 476 my $streaming_size = $self->{'streamingsize'}; 477 478 my $streaming_quality = "high"; 479 480 my ($stream_cmd,$oflash_filename,$oflash_file) 481 = $videoconvert->stream_cmd($originalfilename || $filename, 482 $video_width,$video_height, 483 $streaming_quality, 484 $streaming_bitrate, $streaming_size); 485 486 487 my $streamable_options = { @{$videoconvert->{'ffmpeg_monitor'}}, 488 'message_prefix' => "Stream", 489 'message' => "Generating streamable video: $oflash_file" }; 490 491 my $streamable_result; 492 my $streamable_had_error; 493 ($streamable_regenerated,$streamable_result,$streamable_had_error) 494 = $videoconvert->$optionally_run_general_cmd($stream_cmd,$oflash_filename,$streamable_options); 495 496 if (!$streamable_had_error) { 497 my ($streamseekable_cmd,$ostreamseekable_filename) = $videoconvert->streamseekable_cmd($oflash_filename); 498 499 my $streamseekable_options = { @{$videoconvert->{'flvtool2_monitor'}}, 500 'message_prefix' => "Stream Seekable", 501 'message' => "Reprocessing video stream to be seekable by timeline: $oflash_file" }; 502 503 if ($streamable_regenerated) { 504 $videoconvert->run_general_cmd($streamseekable_cmd,$streamseekable_options); 505 } 506 507 my $streamable_url = $oflash_file; 508 ## $streamable_url =~ s/ /%20/g; 509 my $streamable_url_safe = $videoconvert->url_safe($streamable_url); 510 511 $doc_obj->add_utf8_metadata ($section, "streamablevideo", $streamable_url_safe); 512 $doc_obj->associate_file($oflash_filename,$oflash_file,"video/flash", 513 $section); 514 } 515 516 my $video_width = $doc_obj->get_metadata_element($section,"VideoWidth"); 517 my $video_height = $doc_obj->get_metadata_element($section,"VideoHeight"); 518 519 # 520 # FlowPlayer.swf height+22 pixels 521 # FlowPlayerBlack.swf height+16 pixels 522 # FlowPlayerThermo.swf height+16 pixels 523 # FlowPlayerWhite.swf height+26 pixels 524 my $flashwidth = $video_width; 525 my $flashheight = $video_height + 22; 526 if ($self->{'extractkeyframes'}) { 527 $flashheight += 100; 528 } 529 $doc_obj->add_metadata ($section, "flashwidth", $flashwidth); 530 $doc_obj->add_metadata ($section, "flashheight", $flashheight); 531 532 my $video_server = $ENV{'GEXT_VIDEO_SERVER'}; 533 my $video_prefix = $ENV{'GEXT_VIDEO_PREFIX'}; 534 my $base_url = "$video_server$video_prefix/collect/[collection]/index/assoc/[assocfilepath]/"; 535 my $base_url_safe = $videoconvert->url_safe($base_url); 536 $doc_obj->add_utf8_metadata ($section, "baseurl",$base_url_safe); 537 538 $self->{'oflash_file'} = $oflash_file; 539 $self->{'oflash_filename'} = $oflash_filename; 540 } 541 542 543 # Make the thumbnail image 589 590 if ($self->{'enable_streaming'}) { 591 $streamable_regenerated 592 = $self->enable_streaming($doc_obj,$originalfilename,$filename, 593 $convertto_regenerated, 594 $video_width,$video_height); 595 } 596 597 544 598 my $thumbnailsize = $self->{'thumbnailsize'} || 100; 545 599 my $thumbnailtype = $self->{'thumbnailtype'} || 'jpg'; 546 600 547 my $thumbnailwidth; 548 my $thumbnailheight; 549 550 if ($video_width>$video_height) { 551 my $scale_ratio = $video_height / $video_width; 552 $thumbnailwidth = $thumbnailsize; 553 $thumbnailheight = int($thumbnailsize * $scale_ratio); 554 } 555 else { 556 my $scale_ratio = $video_width / $video_height; 557 $thumbnailwidth = int($thumbnailsize * $scale_ratio); 558 $thumbnailheight = $thumbnailsize; 559 } 560 561 562 my $thumbnailfile = &util::filename_cat($output_dir,"$ivideo_root.$thumbnailtype"); 563 564 565 if ($self->{'extractthumbnail'}) { 566 # Generate the thumbnail with convert, a la ImagePlug 567 my ($thumb_cmd ,$othumb_filename) 568 = $videoconvert->keyframe_thumbnail_cmd($filename,$thumbnailfile,$thumbnailwidth,$thumbnailheight); 569 570 my $thumb_options = { 'verbosity' => $verbosity, 571 'outhandle' => $outhandle, 572 'message_prefix' => "Thumbnail", 573 'message' => "Generating thumbnail" }; 574 575 my ($thumb_regenerated,$thumb_result,$thumb_had_error) 576 = $videoconvert->$optionally_run_general_cmd($thumb_cmd,$thumbnailfile,$thumb_options); 577 578 # Add the thumbnail as an associated file ... 579 if (-e "$thumbnailfile") { 580 $doc_obj->associate_file("$thumbnailfile", "thumbnail.$thumbnailtype", 581 "image/$thumbnailtype",$section); 582 $doc_obj->add_metadata ($section, "ThumbType", $thumbnailtype); 583 $doc_obj->add_metadata ($section, "Thumb", "thumbnail.$thumbnailtype"); 584 585 $doc_obj->add_utf8_metadata ($section, "thumbicon", "<img src=\"_httpprefix_/collect/[collection]/index/assoc/[assocfilepath]/[Thumb]\" width=[ThumbWidth] height=[ThumbHeight]>"); 586 ### $doc_obj->add_utf8_metadata ($section, "thumbicon", "<img src=\"_httpprefix_/collect/[collection]/index/assoc/[assocfilepath]/[Thumb]\">"); 587 } 588 589 # Extract Thumnail metadata from convert output 590 if ($thumb_result =~ m/[0-9]+x[0-9]+=>([0-9]+)x([0-9]+)/) { 591 $doc_obj->add_metadata ($section, "ThumbWidth", $1); 592 $doc_obj->add_metadata ($section, "ThumbHeight", $2); 601 602 if ($self->{'create_thumbnail'} eq "true") { 603 604 my $thumbnail_width; 605 my $thumbnail_height; 606 607 if ($video_width>$video_height) { 608 my $scale_ratio = $video_height / $video_width; 609 $thumbnail_width = $thumbnailsize; 610 $thumbnail_height = int($thumbnailsize * $scale_ratio); 593 611 } 594 612 else { 595 # Two reasons for getting to here: 596 # 1.thumbnail was generated by ffmpeg, not imagemagick convert 597 # 2.thumbnail was cached, so imagemagick convert was not run 598 # Either way, the solution is the same: 599 # => run "identify $thumbnailfile" and parse result 600 601 $thumb_result = `identify \"$thumbnailfile\"`; 602 603 if ($thumb_result =~ m/([0-9]+)x([0-9]+)/) { 604 $doc_obj->add_metadata ($section, "ThumbWidth", $1); 605 $doc_obj->add_metadata ($section, "ThumbHeight", $2); 606 } 607 } 613 my $scale_ratio = $video_width / $video_height; 614 $thumbnail_width = int($thumbnailsize * $scale_ratio); 615 $thumbnail_height = $thumbnailsize; 616 } 617 618 619 $self->extract_thumbnail($doc_obj,$filename,$convertto_regenerated, 620 $thumbnailtype, 621 $thumbnail_width,$thumbnail_height); 608 622 } 609 623 610 624 611 625 if ($self->{'extractkeyframes'}) { 612 613 # Generate the mosaic with 'montage' 614 my $montagefile = &util::filename_cat($output_dir,"$ivideo_root\_montage.$thumbnailtype"); 615 616 my ($montage_cmd,$omontage_filename) 617 = $videoconvert->keyframe_montage_cmd($filename,$montagefile); 618 619 my $montage_options = { 'message_prefix' => "Montage", 620 'message' => "Generating montage" }; 621 622 my ($montage_result,$montage_had_error) 623 = $videoconvert->run_general_cmd($montage_cmd,$montage_options); 624 625 # Add the montage as an associated file ... 626 if (-e "$montagefile") { 627 $doc_obj->associate_file("$montagefile", "montage.$thumbnailtype", 628 "image/$thumbnailtype",$section); 629 $doc_obj->add_metadata ($section, "MontageType", $thumbnailtype); 630 $doc_obj->add_metadata ($section, "Montage", "montage.$thumbnailtype"); 631 632 $doc_obj->add_utf8_metadata ($section, "montageicon", "<img src=\"_httpprefix_/collect/[collection]/index/assoc/[assocfilepath]/[Montage]\" >"); 633 } 634 } 626 $self->extract_keyframes_montage($doc_obj,$filename,$thumbnailtype); 627 } 628 629 my $screenviewsize = $self->{'screenviewsize'}; 630 my $screenviewtype = $self->{'screenviewtype'} || 'jpeg'; 635 631 636 632 # Make a screen-sized version of the picture if requested 637 if ($self->{' extractscreenview'}) {633 if ($self->{'create_screenview'} eq "true") { 638 634 639 635 # To do: if the actual image smaller than the screenview size, 640 636 # we should use the original ! 641 637 642 my $screenviewsize = $self->{'screenviewsize'}; 643 my $screenviewtype = $self->{'screenviewtype'} || 'jpeg'; 644 my $screenviewfilename = &util::filename_cat($output_dir,"$ivideo_root.$screenviewtype"); 645 646 my $screenviewwidth; 647 my $screenviewheight; 638 my $screenview_width; 639 my $screenview_height; 648 640 649 641 if ($video_width>$video_height) { 650 642 my $scale_ratio = $video_height / $video_width; 651 $screenview width = $screenviewsize;652 $screenview height = int($screenviewsize * $scale_ratio);643 $screenview_width = $screenviewsize; 644 $screenview_height = int($screenviewsize * $scale_ratio); 653 645 } 654 646 else { 655 647 my $scale_ratio = $video_width / $video_height; 656 $screenviewwidth = int($screenviewsize * $scale_ratio); 657 $screenviewheight = $screenviewsize; 658 } 659 660 661 # make the screenview image 662 663 my ($screenview_cmd,$oscreenview_filename) 664 = $videoconvert->keyframe_thumbnail_cmd($filename,$screenviewfilename,$screenviewwidth,$screenviewheight); 665 666 my $screenview_options = { 'message_prefix' => "Screenview", 667 'message' => "Generating screenview image" }; 668 669 my ($result,$had_error) 670 = $videoconvert->run_general_cmd($screenview_cmd,$screenview_options); 671 672 # get screenview dimensions, size and type 673 if ($result =~ m/[0-9]+x[0-9]+=>([0-9]+)x([0-9]+)/) { 674 $doc_obj->add_metadata ($section, "ScreenWidth", $1); 675 $doc_obj->add_metadata ($section, "ScreenHeight", $2); 676 } 677 else { 678 $doc_obj->add_metadata ($section, "ScreenWidth", $video_width); 679 $doc_obj->add_metadata ($section, "ScreenHeight", $video_height); 680 } 681 682 #add the screenview as an associated file ... 683 if (-e "$screenviewfilename") { 684 $doc_obj->associate_file("$screenviewfilename", "screenview.$screenviewtype", 685 "image/$screenviewtype",$section); 686 $doc_obj->add_metadata ($section, "ScreenType", $screenviewtype); 687 $doc_obj->add_metadata ($section, "Screen", "screenview.$screenviewtype"); 688 689 $doc_obj->add_utf8_metadata ($section, "screenicon", "<img src=\"_httpprefix_/collect/[collection]/index/assoc/[assocfilepath]/[Screen]\" width=[ScreenWidth] height=[ScreenHeight]>"); 690 } else { 691 print $outhandle "VideoPlugin: couldn't find \"$screenviewfilename\"\n"; 692 } 648 $screenview_width = int($screenviewsize * $scale_ratio); 649 $screenview_height = $screenviewsize; 650 } 651 652 653 $self->extract_screenview($doc_obj,$filename, 654 $screenviewtype, 655 $screenview_width,$screenview_height); 693 656 } 694 657 … … 703 666 my ($pluginfo, $base_dir, $file, $block_hash, $metadata, $processor, $maxdocs, $total_count, $gli) = @_; 704 667 705 my $outhandle = $self->{'outhandle'}; 706 707 # should we move this to read? What about secondary plugins? 708 print STDERR "<Processing n='$file' p='$self->{'plugin_type'}'>\n" if ($gli); 709 print $outhandle "$self->{'plugin_type'} processing $file\n" 710 if $self->{'verbosity'} > 1; 711 712 my ($filename_full_path, $filename_no_path) = &util::get_full_filenames($base_dir, $file); 713 714 # create a new document 715 my $doc_obj = new doc ($filename_full_path, "indexed_doc", $self->{'file_rename_method'}); 716 717 718 my $top_section = $doc_obj->get_top_section(); 719 720 ### <Video Specific> ### 721 $doc_obj->set_OIDtype ($processor->{'OIDtype'}, $processor->{'OIDmetadata'}); 722 ### </Video Specific> ### 723 724 725 $doc_obj->add_utf8_metadata($top_section, "Plugin", "$self->{'plugin_type'}"); 726 $doc_obj->add_utf8_metadata($top_section, "FileSize", (-s $filename_full_path)); 727 728 # sets the UTF8 filename (Source) for display and sets the url ref to URL encoded version 729 # of the UTF8 filename (SourceFile) for generated files 730 $self->set_Source_metadata($doc_obj, $filename_no_path); 731 732 733 # plugin specific stuff - what args do we need here?? 734 unless (defined ($self->process($pluginfo, $base_dir, $file, $metadata, $doc_obj, $gli))) { 735 print STDERR "<ProcessingError n='$file'>\n" if ($gli); 736 return (-1,undef); 737 } 738 739 # include any metadata passed in from previous plugins 740 # note that this metadata is associated with the top level section 741 my $section = $doc_obj->get_top_section(); 742 # can we merge these two methods?? 743 $self->add_associated_files($doc_obj, $filename_full_path); 744 $self->extra_metadata ($doc_obj, $section, $metadata); 745 $self->auto_extract_metadata($doc_obj); 746 747 # if we haven't found any Title so far, assign one 748 # this was shifted to here from inside read() 749 $self->title_fallback($doc_obj,$section,$filename_no_path); 750 751 $self->add_OID($doc_obj); 752 753 754 ### <Video Specific> ### 668 my ($rv,$doc_obj) = $self->SUPER::read_into_doc_obj(@_); 669 670 if ($rv != 1) { 671 return ($rv,$doc_obj); 672 } 673 755 674 756 675 if (($self->{'enablestreaming'}) && ($self->{'extractkeyframes'})) { 676 my $section = $doc_obj->get_top_section(); 757 677 my $oflash_filename = $self->{'oflash_filename'}; 758 my $videoconvert = $self->{'videoconvert'};759 678 my ($streamkeyframes_cmd,$ostreamkeyframes_filename) 760 = $ videoconvert->streamkeyframes_cmd($oflash_filename,$doc_obj,$section);679 = $self->streamkeyframes_cmd($oflash_filename,$doc_obj,$section); 761 680 762 681 my $verbosity = $self->{'verbosity'}; 763 682 764 my $streamkeyframes_options = { @{$ videoconvert->{'flvtool2_monitor'}},683 my $streamkeyframes_options = { @{$self->{'flvtool2_monitor'}}, 765 684 'message_prefix' => "Stream Keyframes", 766 685 'message' => "Reprocessing video stream to add keyframes on timeline" }; 767 #### this used to be commented out!!! 768 $videoconvert->run_general_cmd($streamkeyframes_cmd,$streamkeyframes_options); 769 } 770 ### </Video Specific> ### 771 772 return (1,$doc_obj); 773 } 774 775 sub add_dummy_text { 776 my $self = shift(@_); 777 my ($doc_obj, $section) = @_; 778 779 # add NoText metadata so we can hide this dummy text in format statements 780 $doc_obj->add_metadata($section, "NoText", "1"); 781 $doc_obj->add_text($section, &gsprintf::lookup_string("{BasePlugin.dummy_text}",1)); 782 783 } 784 785 786 787 788 # do plugin specific processing of doc_obj 789 sub process { 790 my $self = shift (@_); 791 # options?? 792 my ($pluginfo, $base_dir, $file, $metadata, $doc_obj, $gli) = @_; 793 794 my $outhandle = $self->{'outhandle'}; 795 796 my ($filename_full_path, $filename_no_path) = &util::get_full_filenames($base_dir, $file); 797 798 799 if ($self->{'ffmpeg_installed'}) { 800 my $utf8_filename_no_path = $self->filepath_to_utf8($filename_no_path); 801 my $url_encoded_filename = &util::rename_file($utf8_filename_no_path, $self->{'file_rename_method'}); 802 803 804 #run convert to get the thumbnail and extract size and type info 805 my $result = $self->run_convert($base_dir, $filename_full_path, 806 $url_encoded_filename, $doc_obj); 807 808 if (!defined $result) { 809 if ($gli) { 810 print STDERR "<ProcessingError n='$file'>\n"; 811 } 812 print $outhandle "VideoPlugin: couldn't process \"$filename_full_path\"\n"; 813 return (-1,undef); # error during processing 814 } 815 } 816 else { 817 if ($gli) { 818 &gsprintf(STDERR, "<Warning p='VideoPlugin' r='{VideoConverter.noconversionavailable}: {VideoConverter.".$self->{'no_image_conversion_reason'}."}'>"); 819 } 820 # all we do is add the original video file as an associated file, and set up srclink etc 821 my $assoc_file = $doc_obj->get_assocfile_from_sourcefile(); 822 my $section = $doc_obj->get_top_section(); 823 824 $doc_obj->associate_file($filename_full_path, $assoc_file, "", $section); 825 826 $doc_obj->add_metadata ($section, "srclink", "<a href=\"_httpprefix_/collect/[collection]/index/assoc/[assocfilepath]/[SourceFile]\">"); 827 $doc_obj->add_metadata ($section, "/srclink", "</a>"); 828 $doc_obj->add_metadata ($section, "srcicon", "<img src=\"_httpprefix_/collect/[collection]/index/assoc/[assocfilepath]/[SourceFile]\" width=\"100\">"); 829 830 } 831 #we have no text - adds dummy text and NoText metadata 832 $self->add_dummy_text($doc_obj, $doc_obj->get_top_section()); 833 834 return 1; 835 686 687 $self->run_general_cmd($streamkeyframes_cmd,$streamkeyframes_options); 688 } 689 690 return ($rv,$doc_obj); 836 691 } 837 692 … … 908 763 909 764 910 911 sub Doctype { 765 sub StartDocument {$_[0]->{'PluginObj'}->xml_start_document(@_);} 766 sub XMLDecl {$_[0]->{'PluginObj'}->xml_xmldecl(@_);} 767 sub Entity {$_[0]->{'PluginObj'}->xml_entity(@_);} 768 sub Doctype {$_[0]->{'PluginObj'}->xml_doctype(@_);} 769 sub StartTag {$_[0]->{'PluginObj'}->xml_start_tag(@_);} 770 sub EndTag {$_[0]->{'PluginObj'}->xml_end_tag(@_);} 771 sub Text {$_[0]->{'PluginObj'}->xml_text(@_);} 772 sub PI {$_[0]->{'PluginObj'}->xml_pi(@_);} 773 sub EndDocument {$_[0]->{'PluginObj'}->xml_end_document(@_);} 774 sub Default {$_[0]->{'PluginObj'}->xml_default(@_);} 775 776 777 # This Char function overrides the one in XML::Parser::Stream to overcome a 778 # problem where $expat->{Text} is treated as the return value, slowing 779 # things down significantly in some cases. 780 sub Char { 781 use bytes; # Necessary to prevent encoding issues with XML::Parser 2.31+ 782 $_[0]->{'Text'} .= $_[1]; 783 return undef; 784 } 785 786 sub xml_start_document { 787 my $self = shift(@_); 912 788 my ($expat, $name, $sysid, $pubid, $internal) = @_; 913 789 914 # my $root_tag = $self->{'root_tag'}; 790 } 791 792 # Called for XML declarations 793 sub xml_xmldecl { 794 my $self = shift(@_); 795 my ($expat, $version, $encoding, $standalone) = @_; 796 } 797 798 # Called for XML entities 799 sub xml_entity { 800 my $self = shift(@_); 801 my ($expat, $name, $val, $sysid, $pubid, $ndata) = @_; 802 } 803 804 805 # Called for DOCTYPE declarations - use die to bail out if this doctype 806 # is not meant for this plugin 807 sub xml_doctype { 808 my $self = shift(@_); 809 my ($expat, $name, $sysid, $pubid, $internal) = @_; 810 811 # This test used to be done in xml_start_document 812 # Moved to here as seems more logical 915 813 916 814 if ($name !~ "seg") { 917 die "Root tag $name does not match expected <seg>"; 918 } 919 } 920 921 sub StartTag { 815 die "VideoPlugin: Root tag $name does not match expected <seg>"; 816 } 817 } 818 819 820 sub xml_start_tag { 821 my $self = shift(@_); 922 822 my ($expat, $element) = @_; 923 823 … … 931 831 #$self->{'flowplayer_thumblist'} = "thumbs: \\["; 932 832 933 my $output_dir = $self->{' videoconvert'}->{'cached_dir'};833 my $output_dir = $self->{'cached_dir'}; 934 834 my $cue_filename = &util::filename_cat($output_dir,"on_cue.xml"); 935 835 … … 945 845 my $avg_frame_num = int(($pre_frame_num+$post_frame_num)/2.0)+1; 946 846 947 my $output_dir = $self->{' videoconvert'}->{'cached_dir'};948 my $ivideo_root = $self->{' videoconvert'}->{'file_root'};847 my $output_dir = $self->{'cached_dir'}; 848 my $ivideo_root = $self->{'cached_file_root'}; 949 849 950 850 my $keyframe_index = $self->{'keyframe_index'}; … … 1016 916 } 1017 917 1018 sub EndTag { 918 sub xml_end_tag { 919 my $self = shift(@_); 1019 920 my ($expat, $element) = @_; 1020 921 … … 1032 933 1033 934 1034 sub Text { 1035 my $text = $_; 1036 } 1037 1038 # This Char function overrides the one in XML::Parser::Stream to overcome a 1039 # problem where $expat->{Text} is treated as the return value, slowing 1040 # things down significantly in some cases. 1041 sub Char { 1042 $_[0]->{'Text'} .= $_[1]; 1043 return undef; 1044 } 935 936 937 938 # Called just before start or end tags with accumulated non-markup text in 939 # the $_ variable. 940 sub xml_text { 941 my $self = shift(@_); 942 my ($expat) = @_; 943 } 944 945 # Called for processing instructions. The $_ variable will contain a copy 946 # of the pi. 947 sub xml_pi { 948 my $self = shift(@_); 949 my ($expat, $target, $data) = @_; 950 } 951 952 # Called at the end of the XML document. 953 sub xml_end_document { 954 my $self = shift(@_); 955 my ($expat) = @_; 956 957 $self->close_document(); 958 } 959 960 961 # Called for any characters not handled by the above functions. 962 sub xml_default { 963 my $self = shift(@_); 964 my ($expat, $text) = @_; 965 } 966 1045 967 1046 968 1;
Note:
See TracChangeset
for help on using the changeset viewer.