source: main/trunk/greenstone2/perllib/cpan/Image/ExifTool/Flash.pm@ 34921

Last change on this file since 34921 was 34921, checked in by anupama, 3 years ago

Committing the improvements to EmbeddedMetaPlugin's processing of Keywords vs other metadata fields. Keywords were literally stored as arrays of words rather than phrases in PDFs (at least in Diego's sample PDF), whereas other meta fields like Subjects and Creators stored them as arrays of phrases. To get both to work, Kathy updated EXIF to a newer version, to retrieve the actual EXIF values stored in the PDF. And Kathy and Dr Bainbridge came up with a new option that I added called apply_join_before_split_to_metafields that's a regex which can list the metadata fields to apply the join_before_split to and whcih previously always got applied to all metadata fields. Now it's applied to any *Keywords metafields by default, as that's the metafield we have experience of that behaves differently to the others, as it stores by word instead of phrases. Tested on Diego's sample PDF. Diego has double-checked it to works on his sample PDF too, setting the split char to ; and turning on the join_before_split and leaving apply_join_before_split_to_metafields at its default of .*Keywords. File changes are strings.properties for the tooltip, the plugin introducing the option and working with it and Kathy's EXIF updates affecting cpan/File and cpan/Image.

File size: 27.5 KB
Line 
1#------------------------------------------------------------------------------
2# File: Flash.pm
3#
4# Description: Read Shockwave Flash meta information
5#
6# Revisions: 05/16/2006 - P. Harvey Created
7# 06/07/2007 - PH Added support for FLV (Flash Video) files
8# 10/23/2008 - PH Added support for XMP in FLV and SWF
9#
10# References: 1) http://www.the-labs.com/MacromediaFlash/SWF-Spec/SWFfileformat.html
11# 2) http://sswf.sourceforge.net/SWFalexref.html
12# 3) http://osflash.org/flv/
13# 4) http://www.irisa.fr/texmex/people/dufouil/ffmpegdoxy/flv_8h.html
14# 5) http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart3.pdf (Oct 2008)
15# 6) http://www.adobe.com/devnet/swf/pdf/swf_file_format_spec_v9.pdf
16# 7) http://help.adobe.com/en_US/FlashMediaServer/3.5_Deving/WS5b3ccc516d4fbf351e63e3d11a0773d56e-7ff6.html
17# 8) http://www.adobe.com/devnet/flv/pdf/video_file_format_spec_v10.pdf
18#
19# Notes: I'll add AMF3 support if someone sends me a FLV with AMF3 data
20#------------------------------------------------------------------------------
21
22package Image::ExifTool::Flash;
23
24use strict;
25use vars qw($VERSION);
26use Image::ExifTool qw(:DataAccess :Utils);
27use Image::ExifTool::FLAC;
28
29$VERSION = '1.12';
30
31sub ProcessMeta($$$;$);
32
33# Meta packets that we process
34my %processMetaPacket = ( onMetaData => 1, onXMPData => 1 );
35
36# information extracted from SWF header
37%Image::ExifTool::Flash::Main = (
38 GROUPS => { 2 => 'Video' },
39 VARS => { ALPHA_FIRST => 1 },
40 NOTES => q{
41 The information below is extracted from SWF (Shockwave Flash) files. Tags
42 with string ID's represent information extracted from the file header.
43 },
44 FlashVersion => { },
45 Compressed => { PrintConv => { 0 => 'False', 1 => 'True' } },
46 ImageWidth => { },
47 ImageHeight => { },
48 FrameRate => { },
49 FrameCount => { },
50 Duration => {
51 Notes => 'calculated from FrameRate and FrameCount',
52 PrintConv => 'ConvertDuration($val)',
53 },
54 69 => {
55 Name => 'FlashAttributes',
56 PrintConv => { BITMASK => {
57 0 => 'UseNetwork',
58 3 => 'ActionScript3',
59 4 => 'HasMetadata',
60 } },
61 },
62 77 => {
63 Name => 'XMP',
64 SubDirectory => { TagTable => 'Image::ExifTool::XMP::Main' },
65 },
66);
67
68# packets in Flash Video files
69%Image::ExifTool::Flash::FLV = (
70 NOTES => q{
71 Information is extracted from the following packets in FLV (Flash Video)
72 files.
73 },
74 0x08 => {
75 Name => 'Audio',
76 BitMask => 0x04,
77 SubDirectory => { TagTable => 'Image::ExifTool::Flash::Audio' },
78 },
79 0x09 => {
80 Name => 'Video',
81 BitMask => 0x01,
82 SubDirectory => { TagTable => 'Image::ExifTool::Flash::Video' },
83 },
84 0x12 => {
85 Name => 'Meta',
86 SubDirectory => { TagTable => 'Image::ExifTool::Flash::Meta' },
87 },
88);
89
90# tags in Flash Video packet header
91%Image::ExifTool::Flash::Audio = (
92 PROCESS_PROC => \&Image::ExifTool::FLAC::ProcessBitStream,
93 GROUPS => { 2 => 'Audio' },
94 NOTES => 'Information extracted from the Flash Audio header.',
95 'Bit0-3' => {
96 Name => 'AudioEncoding',
97 PrintConv => {
98 0 => 'PCM-BE (uncompressed)', # PCM-BE according to ref 4
99 1 => 'ADPCM',
100 2 => 'MP3',
101 3 => 'PCM-LE (uncompressed)', #4
102 4 => 'Nellymoser 16kHz Mono', #8
103 5 => 'Nellymoser 8kHz Mono',
104 6 => 'Nellymoser',
105 7 => 'G.711 A-law logarithmic PCM', #8
106 8 => 'G.711 mu-law logarithmic PCM', #8
107 # (9 is reserved, ref 8)
108 10 => 'AAC', #8
109 11 => 'Speex', #8
110 13 => 'MP3 8-Khz', #8
111 15 => 'Device-specific sound', #8
112 },
113 },
114 'Bit4-5' => {
115 Name => 'AudioSampleRate',
116 ValueConv => {
117 0 => 5512,
118 1 => 11025,
119 2 => 22050,
120 3 => 44100,
121 },
122 },
123 'Bit6' => {
124 Name => 'AudioBitsPerSample',
125 ValueConv => '8 * ($val + 1)',
126 },
127 'Bit7' => {
128 Name => 'AudioChannels',
129 ValueConv => '$val + 1',
130 PrintConv => {
131 1 => '1 (mono)',
132 2 => '2 (stereo)',
133 },
134 },
135);
136
137# tags in Flash Video packet header
138%Image::ExifTool::Flash::Video = (
139 PROCESS_PROC => \&Image::ExifTool::FLAC::ProcessBitStream,
140 GROUPS => { 2 => 'Video' },
141 NOTES => 'Information extracted from the Flash Video header.',
142 'Bit4-7' => {
143 Name => 'VideoEncoding',
144 PrintConv => {
145 1 => 'JPEG', #8
146 2 => 'Sorensen H.263',
147 3 => 'Screen Video',
148 4 => 'On2 VP6',
149 5 => 'On2 VP6 Alpha', #3
150 6 => 'Screen Video 2', #3
151 7 => 'H.264', #7 (called "AVC" by ref 8)
152 },
153 },
154);
155
156# tags in Flash META packet (in ActionScript Message Format)
157%Image::ExifTool::Flash::Meta = (
158 PROCESS_PROC => \&ProcessMeta,
159 GROUPS => { 2 => 'Video' },
160 NOTES => q{
161 Below are a few observed FLV Meta tags, but ExifTool will attempt to extract
162 information from any tag found.
163 },
164 'audiocodecid' => { Name => 'AudioCodecID', Groups => { 2 => 'Audio' } },
165 'audiodatarate' => {
166 Name => 'AudioBitrate',
167 Groups => { 2 => 'Audio' },
168 ValueConv => '$val * 1000',
169 PrintConv => 'ConvertBitrate($val)',
170 },
171 'audiodelay' => { Name => 'AudioDelay', Groups => { 2 => 'Audio' } },
172 'audiosamplerate'=>{ Name => 'AudioSampleRate', Groups => { 2 => 'Audio' } },
173 'audiosamplesize'=>{ Name => 'AudioSampleSize', Groups => { 2 => 'Audio' } },
174 'audiosize' => { Name => 'AudioSize', Groups => { 2 => 'Audio' } },
175 'bytelength' => 'ByteLength', # (youtube)
176 'canseekontime' => 'CanSeekOnTime', # (youtube)
177 'canSeekToEnd' => 'CanSeekToEnd',
178 'creationdate' => {
179 # (not an AMF date type in my sample)
180 Name => 'CreateDate',
181 Groups => { 2 => 'Time' },
182 ValueConv => '$val=~s/\s+$//; $val', # trim trailing whitespace
183 },
184 'createdby' => 'CreatedBy', #7
185 'cuePoints' => {
186 Name => 'CuePoint',
187 SubDirectory => { TagTable => 'Image::ExifTool::Flash::CuePoint' },
188 },
189 'datasize' => 'DataSize',
190 'duration' => {
191 Name => 'Duration',
192 PrintConv => 'ConvertDuration($val)',
193 },
194 'filesize' => 'FileSizeBytes',
195 'framerate' => {
196 Name => 'FrameRate',
197 PrintConv => 'int($val * 1000 + 0.5) / 1000',
198 },
199 'hasAudio' => { Name => 'HasAudio', Groups => { 2 => 'Audio' } },
200 'hasCuePoints' => 'HasCuePoints',
201 'hasKeyframes' => 'HasKeyFrames',
202 'hasMetadata' => 'HasMetadata',
203 'hasVideo' => 'HasVideo',
204 'height' => 'ImageHeight',
205 'httphostheader'=> 'HTTPHostHeader', # (youtube)
206 'keyframesTimes'=> 'KeyFramesTimes',
207 'keyframesFilepositions' => 'KeyFramePositions',
208 'lasttimestamp' => 'LastTimeStamp',
209 'lastkeyframetimestamp' => 'LastKeyFrameTime',
210 'metadatacreator'=>'MetadataCreator',
211 'metadatadate' => {
212 Name => 'MetadataDate',
213 Groups => { 2 => 'Time' },
214 PrintConv => '$self->ConvertDateTime($val)',
215 },
216 'purl' => 'URL', # (youtube) (what does P mean?)
217 'pmsg' => 'Message', # (youtube) (what does P mean?)
218 'sourcedata' => 'SourceData', # (youtube)
219 'starttime' => { # (youtube)
220 Name => 'StartTime',
221 PrintConv => 'ConvertDuration($val)',
222 },
223 'stereo' => { Name => 'Stereo', Groups => { 2 => 'Audio' } },
224 'totalduration' => { # (youtube)
225 Name => 'TotalDuration',
226 PrintConv => 'ConvertDuration($val)',
227 },
228 'totaldatarate' => { # (youtube)
229 Name => 'TotalDataRate',
230 ValueConv => '$val * 1000',
231 PrintConv => 'int($val + 0.5)',
232 },
233 'totalduration' => 'TotalDuration',
234 'videocodecid' => 'VideoCodecID',
235 'videodatarate' => {
236 Name => 'VideoBitrate',
237 ValueConv => '$val * 1000',
238 PrintConv => 'ConvertBitrate($val)',
239 },
240 'videosize' => 'VideoSize',
241 'width' => 'ImageWidth',
242 # tags in 'onXMPData' packets
243 'liveXML' => { #5
244 Name => 'XMP',
245 SubDirectory => { TagTable => 'Image::ExifTool::XMP::Main' },
246 },
247);
248
249# tags in Flash META CuePoint structure
250%Image::ExifTool::Flash::CuePoint = (
251 PROCESS_PROC => \&ProcessMeta,
252 GROUPS => { 2 => 'Video' },
253 NOTES => q{
254 These tag names are added to the CuePoint name to generate complete tag
255 names like "CuePoint0Name".
256 },
257 'name' => 'Name',
258 'type' => 'Type',
259 'time' => 'Time',
260 'parameters' => {
261 Name => 'Parameter',
262 SubDirectory => { TagTable => 'Image::ExifTool::Flash::Parameter' },
263 },
264);
265
266# tags in Flash META CuePoint Parameter structure
267%Image::ExifTool::Flash::Parameter = (
268 PROCESS_PROC => \&ProcessMeta,
269 GROUPS => { 2 => 'Video' },
270 NOTES => q{
271 There are no pre-defined parameter tags, but ExifTool will extract any
272 existing parameters, with tag names like "CuePoint0ParameterXxx".
273 },
274);
275
276# name lookup for known AMF data types
277my @amfType = qw(double boolean string object movieClip null undefined reference
278 mixedArray objectEnd array date longString unsupported recordSet
279 XML typedObject AMF3data);
280
281# test for AMF structure types (object, mixed array or typed object)
282my %isStruct = ( 0x03 => 1, 0x08 => 1, 0x10 => 1 );
283
284#------------------------------------------------------------------------------
285# Process Flash Video AMF Meta packet (ref 3)
286# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
287# 3) Set to extract single type/value only
288# Returns: 1 on success, (or type/value if extracting single value)
289# Notes: Updates DataPos in dirInfo if extracting single value
290sub ProcessMeta($$$;$)
291{
292 my ($et, $dirInfo, $tagTablePtr, $single) = @_;
293 my $dataPt = $$dirInfo{DataPt};
294 my $dataPos = $$dirInfo{DataPos};
295 my $dirLen = $$dirInfo{DirLen} || length($$dataPt);
296 my $pos = $$dirInfo{Pos} || 0;
297 my ($type, $val, $rec);
298
299 $et->VerboseDir('Meta') unless $single;
300
301Record: for ($rec=0; ; ++$rec) {
302 last if $pos >= $dirLen;
303 $type = ord(substr($$dataPt, $pos));
304 ++$pos;
305 if ($type == 0x00 or $type == 0x0b) { # double or date
306 last if $pos + 8 > $dirLen;
307 $val = GetDouble($dataPt, $pos);
308 $pos += 8;
309 if ($type == 0x0b) { # date
310 $val /= 1000; # convert to seconds
311 my $frac = $val - int($val); # fractional seconds
312 # get time zone
313 last if $pos + 2 > $dirLen;
314 my $tz = Get16s($dataPt, $pos);
315 $pos += 2;
316 # construct date/time string
317 $val = Image::ExifTool::ConvertUnixTime(int($val));
318 if ($frac) {
319 $frac = sprintf('%.6f', $frac);
320 $frac =~ s/(^0|0+$)//g;
321 $val .= $frac;
322 }
323 # add timezone
324 if ($tz < 0) {
325 $val .= '-';
326 $tz *= -1;
327 } else {
328 $val .= '+';
329 }
330 $val .= sprintf('%.2d:%.2d', int($tz/60), $tz%60);
331 }
332 } elsif ($type == 0x01) { # boolean
333 last if $pos + 1 > $dirLen;
334 $val = Get8u($dataPt, $pos);
335 $val = { 0 => 'No', 1 => 'Yes' }->{$val} if $val < 2;
336 ++$pos;
337 } elsif ($type == 0x02) { # string
338 last if $pos + 2 > $dirLen;
339 my $len = Get16u($dataPt, $pos);
340 last if $pos + 2 + $len > $dirLen;
341 $val = substr($$dataPt, $pos + 2, $len);
342 $pos += 2 + $len;
343 } elsif ($isStruct{$type}) { # object, mixed array or typed object
344 $et->VPrint(1, " + [$amfType[$type]]\n");
345 my $getName;
346 $val = ''; # dummy value
347 if ($type == 0x08) { # mixed array
348 # skip last array index for mixed array
349 last if $pos + 4 > $dirLen;
350 $pos += 4;
351 } elsif ($type == 0x10) { # typed object
352 $getName = 1;
353 }
354 for (;;) {
355 # get tag ID (or typed object name)
356 last Record if $pos + 2 > $dirLen;
357 my $len = Get16u($dataPt, $pos);
358 if ($pos + 2 + $len > $dirLen) {
359 $et->Warn("Truncated $amfType[$type] record");
360 last Record;
361 }
362 my $tag = substr($$dataPt, $pos + 2, $len);
363 $pos += 2 + $len;
364 # first string of a typed object is the object name
365 if ($getName) {
366 $et->VPrint(1," | (object name '${tag}')\n");
367 undef $getName;
368 next; # (ignore name for now)
369 }
370 my $subTablePtr = $tagTablePtr;
371 my $tagInfo = $$subTablePtr{$tag};
372 # switch to subdirectory table if necessary
373 if ($tagInfo and $$tagInfo{SubDirectory}) {
374 my $subTable = $tagInfo->{SubDirectory}->{TagTable};
375 # descend into Flash SubDirectory
376 if ($subTable =~ /^Image::ExifTool::Flash::/) {
377 $tag = $$tagInfo{Name}; # use our name for the tag
378 $subTablePtr = GetTagTable($subTable);
379 }
380 }
381 # get object value
382 my $valPos = $pos + 1;
383 $$dirInfo{Pos} = $pos;
384 my $structName = $$dirInfo{StructName};
385 # add structure name to start of tag name
386 $tag = $structName . ucfirst($tag) if defined $structName;
387 $$dirInfo{StructName} = $tag; # set new structure name
388 my ($t, $v) = ProcessMeta($et, $dirInfo, $subTablePtr, 1);
389 $$dirInfo{StructName} = $structName;# restore original structure name
390 $pos = $$dirInfo{Pos}; # update to new position in packet
391 # all done if this value contained tags
392 last Record unless defined $t and defined $v;
393 next if $isStruct{$t}; # already handled tags in sub-structures
394 next if ref($v) eq 'ARRAY' and not @$v; # ignore empty arrays
395 last if $t == 0x09; # (end of object)
396 if (not $$subTablePtr{$tag} and $tag =~ /^\w+$/) {
397 AddTagToTable($subTablePtr, $tag, { Name => ucfirst($tag) });
398 $et->VPrint(1, " | (adding $tag)\n");
399 }
400 $et->HandleTag($subTablePtr, $tag, $v,
401 DataPt => $dataPt,
402 DataPos => $dataPos,
403 Start => $valPos,
404 Size => $pos - $valPos,
405 Format => $amfType[$t] || sprintf('0x%x',$t),
406 );
407 }
408 # } elsif ($type == 0x04) { # movie clip (not supported)
409 } elsif ($type == 0x05 or $type == 0x06 or $type == 0x09 or $type == 0x0d) {
410 # null, undefined, dirLen of object, or unsupported
411 $val = '';
412 } elsif ($type == 0x07) { # reference
413 last if $pos + 2 > $dirLen;
414 $val = Get16u($dataPt, $pos);
415 $pos += 2;
416 } elsif ($type == 0x0a) { # array
417 last if $pos + 4 > $dirLen;
418 my $num = Get32u($dataPt, $pos);
419 $$dirInfo{Pos} = $pos + 4;
420 my ($i, @vals);
421 # add array index to compound tag name
422 my $structName = $$dirInfo{StructName};
423 for ($i=0; $i<$num; ++$i) {
424 $$dirInfo{StructName} = $structName . $i if defined $structName;
425 my ($t, $v) = ProcessMeta($et, $dirInfo, $tagTablePtr, 1);
426 last Record unless defined $v;
427 # save value unless contained in a sub-structure
428 push @vals, $v unless $isStruct{$t};
429 }
430 $$dirInfo{StructName} = $structName;
431 $pos = $$dirInfo{Pos};
432 $val = \@vals;
433 } elsif ($type == 0x0c or $type == 0x0f) { # long string or XML
434 last if $pos + 4 > $dirLen;
435 my $len = Get32u($dataPt, $pos);
436 last if $pos + 4 + $len > $dirLen;
437 $val = substr($$dataPt, $pos + 4, $len);
438 $pos += 4 + $len;
439 # } elsif ($type == 0x0e) { # record set (not supported)
440 # } elsif ($type == 0x11) { # AMF3 data (can't add support for this without a test sample)
441 } else {
442 my $t = $amfType[$type] || sprintf('type 0x%x',$type);
443 $et->Warn("AMF $t record not yet supported");
444 undef $type; # (so we don't print another warning)
445 last; # can't continue
446 }
447 last if $single; # all done if extracting single value
448 unless ($isStruct{$type}) {
449 # only process certain Meta packets
450 if ($type == 0x02 and not $rec) {
451 my $verb = $processMetaPacket{$val} ? 'processing' : 'ignoring';
452 $et->VPrint(0, " | ($verb $val information)\n");
453 last unless $processMetaPacket{$val};
454 } else {
455 # give verbose indication if we ignore a lone value
456 my $t = $amfType[$type] || sprintf('type 0x%x',$type);
457 $et->VPrint(1, " | (ignored lone $t value '${val}')\n");
458 }
459 }
460 }
461 if (not defined $val and defined $type) {
462 $et->Warn(sprintf("Truncated AMF record 0x%x",$type));
463 }
464 return 1 unless $single; # all done
465 $$dirInfo{Pos} = $pos; # update position
466 return($type,$val); # return single type/value pair
467}
468
469#------------------------------------------------------------------------------
470# Read information frame a Flash Video file
471# Inputs: 0) ExifTool object reference, 1) Directory information reference
472# Returns: 1 on success, 0 if this wasn't a valid Flash Video file
473sub ProcessFLV($$)
474{
475 my ($et, $dirInfo) = @_;
476 my $verbose = $et->Options('Verbose');
477 my $raf = $$dirInfo{RAF};
478 my $buff;
479
480 $raf->Read($buff, 9) == 9 or return 0;
481 $buff =~ /^FLV\x01/ or return 0;
482 SetByteOrder('MM');
483 $et->SetFileType();
484 my ($flags, $offset) = unpack('x4CN', $buff);
485 $raf->Seek($offset-9, 1) or return 1 if $offset > 9;
486 $flags &= 0x05; # only look for audio/video
487 my $found = 0;
488 my $tagTablePtr = GetTagTable('Image::ExifTool::Flash::FLV');
489 for (;;) {
490 $raf->Read($buff, 15) == 15 or last;
491 my $len = unpack('x4N', $buff);
492 my $type = $len >> 24;
493 $len &= 0x00ffffff;
494 my $tagInfo = $et->GetTagInfo($tagTablePtr, $type);
495 if ($verbose > 1) {
496 my $name = $tagInfo ? $$tagInfo{Name} : "type $type";
497 $et->VPrint(1, "FLV $name packet, len $len\n");
498 }
499 undef $buff;
500 if ($tagInfo and $$tagInfo{SubDirectory}) {
501 my $mask = $$tagInfo{BitMask};
502 if ($mask) {
503 # handle audio or video packet
504 unless ($found & $mask) {
505 $found |= $mask;
506 $flags &= ~$mask;
507 if ($len>=1 and $raf->Read($buff, 1) == 1) {
508 $len -= 1;
509 } else {
510 $et->Warn("Bad $$tagInfo{Name} packet");
511 last;
512 }
513 }
514 } elsif ($raf->Read($buff, $len) == $len) {
515 $len = 0;
516 } else {
517 $et->Warn('Truncated Meta packet');
518 last;
519 }
520 }
521 if (defined $buff) {
522 $et->HandleTag($tagTablePtr, $type, undef,
523 DataPt => \$buff,
524 DataPos => $raf->Tell() - length($buff),
525 );
526 }
527 last unless $flags;
528 $raf->Seek($len, 1) or last if $len;
529 }
530 return 1;
531}
532
533#------------------------------------------------------------------------------
534# Found a Flash tag
535# Inputs: 0) ExifTool object ref, 1) tag name, 2) tag value
536sub FoundFlashTag($$$)
537{
538 my ($et, $tag, $val) = @_;
539 $et->HandleTag(\%Image::ExifTool::Flash::Main, $tag, $val);
540}
541
542#------------------------------------------------------------------------------
543# Read data from possibly compressed file
544# Inputs: 0) RAF reference, 1) data buffer, 2) bytes to read, 2) compressed flag
545# Returns: number of bytes read (may be greater than requested bytes if compressed)
546# - concatenates data to current buffer
547# - updates compressed flag with reference to inflate object for future calls
548# (or sets to error message and returns zero on error)
549sub ReadCompressed($$$$)
550{
551 my ($raf, $len, $inflate) = ($_[0], $_[2], $_[3]);
552 my $buff;
553 unless ($raf->Read($buff, $len)) {
554 $_[3] = 'Error reading file';
555 return 0;
556 }
557 # uncompress if necessary
558 if ($inflate) {
559 unless (ref $inflate) {
560 unless (eval { require Compress::Zlib }) {
561 $_[3] = 'Install Compress::Zlib to extract compressed information';
562 return 0;
563 }
564 $inflate = Compress::Zlib::inflateInit();
565 unless ($inflate) {
566 $_[3] = 'Error initializing inflate for Flash data';
567 return 0;
568 }
569 $_[3] = $inflate; # pass inflate object back to caller
570 }
571 my $tmp = $buff;
572 $buff = '';
573 # read 64 more bytes at a time and inflate until we get enough uncompressed data
574 for (;;) {
575 my ($dat, $stat) = $inflate->inflate($tmp);
576 if ($stat == Compress::Zlib::Z_STREAM_END() or
577 $stat == Compress::Zlib::Z_OK())
578 {
579 $buff .= $dat; # add inflated data to buffer
580 last if length $buff >= $len or $stat == Compress::Zlib::Z_STREAM_END();
581 $raf->Read($tmp,64) or last; # must read a bit more data
582 } else {
583 $buff = '';
584 last;
585 }
586 }
587 $_[3] = 'Error inflating compressed Flash data' unless length $buff;
588 }
589 $_[1] = defined $_[1] ? $_[1] . $buff : $buff;
590 return length $buff;
591}
592
593#------------------------------------------------------------------------------
594# Read information frame a Flash file
595# Inputs: 0) ExifTool object reference, 1) Directory information reference
596# Returns: 1 on success, 0 if this wasn't a valid Flash file
597sub ProcessSWF($$)
598{
599 my ($et, $dirInfo) = @_;
600 my $raf = $$dirInfo{RAF};
601 my ($buff, $hasMeta);
602
603 $raf->Read($buff, 8) == 8 or return 0;
604 $buff =~ /^(F|C)WS([^\0])/ or return 0;
605 my ($compressed, $vers) = ($1 eq 'C' ? 1 : 0, ord($2));
606
607 SetByteOrder('II');
608 $et->SetFileType();
609 GetTagTable('Image::ExifTool::Flash::Main'); # make sure table is initialized
610
611 FoundFlashTag($et, FlashVersion => $vers);
612 FoundFlashTag($et, Compressed => $compressed);
613
614 # read the next 64 bytes of the file (and inflate if necessary)
615 $buff = '';
616 unless (ReadCompressed($raf, $buff, 64, $compressed)) {
617 $et->Warn($compressed) if $compressed;
618 return 1;
619 }
620
621 # unpack elements of bit-packed Flash Rect structure
622 my $nBits = unpack('C', $buff) >> 3; # bits in x1,x2,y1,y2 elements
623 my $totBits = 5 + $nBits * 4; # total bits in Rect structure
624 my $nBytes = int(($totBits + 7) / 8); # byte length of Rect structure
625 if (length $buff < $nBytes + 4) {
626 $et->Warn('Truncated Flash file');
627 return 1;
628 }
629 my $bits = unpack("B$totBits", $buff);
630 # isolate Rect elements and convert from ASCII bit strings to integers
631 my @vals = unpack('x5' . "a$nBits" x 4, $bits);
632 # (do conversion the hard way because oct("0b$val") requires Perl 5.6)
633 map { $_ = unpack('N', pack('B32', '0' x (32 - length $_) . $_)) } @vals;
634
635 # calculate and store ImageWidth/Height
636 FoundFlashTag($et, ImageWidth => ($vals[1] - $vals[0]) / 20);
637 FoundFlashTag($et, ImageHeight => ($vals[3] - $vals[2]) / 20);
638
639 # get frame rate and count
640 @vals = unpack("x${nBytes}v2", $buff);
641 FoundFlashTag($et, FrameRate => $vals[0] / 256);
642 FoundFlashTag($et, FrameCount => $vals[1]);
643 FoundFlashTag($et, Duration => $vals[1] * 256 / $vals[0]) if $vals[0];
644
645 # scan through the tags to find FlashAttributes and XMP
646 $buff = substr($buff, $nBytes + 4);
647 for (;;) {
648 my $buffLen = length $buff;
649 last if $buffLen < 2;
650 my $code = Get16u(\$buff, 0);
651 my $pos = 2;
652 my $tag = $code >> 6;
653 my $size = $code & 0x3f;
654 $et->VPrint(1, "SWF tag $tag ($size bytes):\n");
655 last unless $tag == 69 or $tag == 77 or $hasMeta;
656 # read enough to get a complete short record
657 if ($pos + $size > $buffLen) {
658 # (read 2 extra bytes if available to get next tag word)
659 unless (ReadCompressed($raf, $buff, $size + 2, $compressed)) {
660 $et->Warn($compressed) if $compressed;
661 return 1;
662 }
663 $buffLen = length $buff;
664 last if $pos + $size > $buffLen;
665 }
666 # read extended record if necessary
667 if ($size == 0x3f) {
668 last if $pos + 4 > $buffLen;
669 $size = Get32u(\$buff, $pos);
670 $pos += 4;
671 last if $size > 1000000; # don't read anything huge
672 if ($pos + $size > $buffLen) {
673 unless (ReadCompressed($raf, $buff, $size + 2, $compressed)) {
674 $et->Warn($compressed) if $compressed;
675 return 1;
676 }
677 $buffLen = length $buff;
678 last if $pos + $size > $buffLen;
679 }
680 $et->VPrint(1, " [extended size $size bytes]\n");
681 }
682 if ($tag == 69) { # FlashAttributes
683 last unless $size;
684 my $flags = Get8u(\$buff, $pos);
685 FoundFlashTag($et, $tag => $flags);
686 last unless $flags & 0x10; # only continue if we have metadata (XMP)
687 $hasMeta = 1;
688 } elsif ($tag == 77) { # Metadata
689 my $val = substr($buff, $pos, $size);
690 FoundFlashTag($et, $tag => $val);
691 last;
692 }
693 last if $pos + 2 > $buffLen;
694 $buff = substr($buff, $pos); # remove everything before the next tag
695 }
696 return 1;
697}
698
6991; # end
700
701__END__
702
703=head1 NAME
704
705Image::ExifTool::Flash - Read Shockwave Flash meta information
706
707=head1 SYNOPSIS
708
709This module is used by Image::ExifTool
710
711=head1 DESCRIPTION
712
713This module contains definitions required by Image::ExifTool to read SWF
714(Shockwave Flash) and FLV (Flash Video) files.
715
716=head1 NOTES
717
718Flash Video AMF3 support has not yet been added because I haven't yet found
719a FLV file containing AMF3 information. If someone sends me a sample then I
720will add AMF3 support.
721
722=head1 AUTHOR
723
724Copyright 2003-2021, Phil Harvey (philharvey66 at gmail.com)
725
726This library is free software; you can redistribute it and/or modify it
727under the same terms as Perl itself.
728
729=head1 REFERENCES
730
731=over 4
732
733=item L<http://www.the-labs.com/MacromediaFlash/SWF-Spec/SWFfileformat.html>
734
735=item L<http://sswf.sourceforge.net/SWFalexref.html>
736
737=item L<http://osflash.org/flv/>
738
739=item L<http://www.irisa.fr/texmex/people/dufouil/ffmpegdoxy/flv_8h.html>
740
741=item L<http://help.adobe.com/en_US/FlashMediaServer/3.5_Deving/WS5b3ccc516d4fbf351e63e3d11a0773d56e-7ff6.html>
742
743=item L<http://www.adobe.com/devnet/swf/pdf/swf_file_format_spec_v9.pdf>
744
745=item L<http://www.adobe.com/devnet/flv/pdf/video_file_format_spec_v10.pdf>
746
747=back
748
749=head1 SEE ALSO
750
751L<Image::ExifTool::TagNames/Flash Tags>,
752L<Image::ExifTool(3pm)|Image::ExifTool>
753
754=cut
755
Note: See TracBrowser for help on using the repository browser.