| line | stmt | bran | cond | sub | pod | time | code | 
| 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 |  |  |  |  |  |  |  | 
| 22 |  |  |  |  |  |  | package Image::ExifTool::Flash; | 
| 23 |  |  |  |  |  |  |  | 
| 24 | 1 |  |  | 1 |  | 4442 | use strict; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 33 |  | 
| 25 | 1 |  |  | 1 |  | 5 | use vars qw($VERSION); | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 41 |  | 
| 26 | 1 |  |  | 1 |  | 6 | use Image::ExifTool qw(:DataAccess :Utils); | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 204 |  | 
| 27 | 1 |  |  | 1 |  | 475 | use Image::ExifTool::FLAC; | 
|  | 1 |  |  |  |  | 3 |  | 
|  | 1 |  |  |  |  | 3329 |  | 
| 28 |  |  |  |  |  |  |  | 
| 29 |  |  |  |  |  |  | $VERSION = '1.12'; | 
| 30 |  |  |  |  |  |  |  | 
| 31 |  |  |  |  |  |  | sub ProcessMeta($$$;$); | 
| 32 |  |  |  |  |  |  |  | 
| 33 |  |  |  |  |  |  | # Meta packets that we process | 
| 34 |  |  |  |  |  |  | my %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 | 
| 277 |  |  |  |  |  |  | my @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) | 
| 282 |  |  |  |  |  |  | my %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 | 
| 290 |  |  |  |  |  |  | sub ProcessMeta($$$;$) | 
| 291 |  |  |  |  |  |  | { | 
| 292 | 57 |  |  | 57 | 0 | 105 | my ($et, $dirInfo, $tagTablePtr, $single) = @_; | 
| 293 | 57 |  |  |  |  | 87 | my $dataPt = $$dirInfo{DataPt}; | 
| 294 | 57 |  |  |  |  | 75 | my $dataPos = $$dirInfo{DataPos}; | 
| 295 | 57 |  | 33 |  |  | 153 | my $dirLen = $$dirInfo{DirLen} || length($$dataPt); | 
| 296 | 57 |  | 100 |  |  | 125 | my $pos = $$dirInfo{Pos} || 0; | 
| 297 | 57 |  |  |  |  | 83 | my ($type, $val, $rec); | 
| 298 |  |  |  |  |  |  |  | 
| 299 | 57 | 100 |  |  |  | 122 | $et->VerboseDir('Meta') unless $single; | 
| 300 |  |  |  |  |  |  |  | 
| 301 | 57 |  |  |  |  | 85 | Record: for ($rec=0; ; ++$rec) { | 
| 302 | 59 | 100 |  |  |  | 102 | last if $pos >= $dirLen; | 
| 303 | 58 |  |  |  |  | 114 | $type = ord(substr($$dataPt, $pos)); | 
| 304 | 58 |  |  |  |  | 76 | ++$pos; | 
| 305 | 58 | 100 | 100 |  |  | 280 | if ($type == 0x00 or $type == 0x0b) {   # double or date | 
|  |  | 100 | 33 |  |  |  |  | 
|  |  | 100 | 66 |  |  |  |  | 
|  |  | 100 | 66 |  |  |  |  | 
|  |  | 100 | 0 |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 306 | 28 | 50 |  |  |  | 55 | last if $pos + 8 > $dirLen; | 
| 307 | 28 |  |  |  |  | 62 | $val = GetDouble($dataPt, $pos); | 
| 308 | 28 |  |  |  |  | 47 | $pos += 8; | 
| 309 | 28 | 100 |  |  |  | 56 | if ($type == 0x0b) {    # date | 
| 310 | 1 |  |  |  |  | 4 | $val /= 1000;       # convert to seconds | 
| 311 | 1 |  |  |  |  | 4 | my $frac = $val - int($val);    # fractional seconds | 
| 312 |  |  |  |  |  |  | # get time zone | 
| 313 | 1 | 50 |  |  |  | 3 | last if $pos + 2 > $dirLen; | 
| 314 | 1 |  |  |  |  | 5 | my $tz = Get16s($dataPt, $pos); | 
| 315 | 1 |  |  |  |  | 2 | $pos += 2; | 
| 316 |  |  |  |  |  |  | # construct date/time string | 
| 317 | 1 |  |  |  |  | 14 | $val = Image::ExifTool::ConvertUnixTime(int($val)); | 
| 318 | 1 | 50 |  |  |  | 7 | if ($frac) { | 
| 319 | 1 |  |  |  |  | 8 | $frac = sprintf('%.6f', $frac); | 
| 320 | 1 |  |  |  |  | 11 | $frac =~ s/(^0|0+$)//g; | 
| 321 | 1 |  |  |  |  | 4 | $val .= $frac; | 
| 322 |  |  |  |  |  |  | } | 
| 323 |  |  |  |  |  |  | # add timezone | 
| 324 | 1 | 50 |  |  |  | 5 | if ($tz < 0) { | 
| 325 | 1 |  |  |  |  | 3 | $val .= '-'; | 
| 326 | 1 |  |  |  |  | 2 | $tz *= -1; | 
| 327 |  |  |  |  |  |  | } else { | 
| 328 | 0 |  |  |  |  | 0 | $val .= '+'; | 
| 329 |  |  |  |  |  |  | } | 
| 330 | 1 |  |  |  |  | 7 | $val .= sprintf('%.2d:%.2d', int($tz/60), $tz%60); | 
| 331 |  |  |  |  |  |  | } | 
| 332 |  |  |  |  |  |  | } elsif ($type == 0x01) {   # boolean | 
| 333 | 7 | 50 |  |  |  | 23 | last if $pos + 1 > $dirLen; | 
| 334 | 7 |  |  |  |  | 19 | $val = Get8u($dataPt, $pos); | 
| 335 | 7 | 50 |  |  |  | 33 | $val = { 0 => 'No', 1 => 'Yes' }->{$val} if $val < 2; | 
| 336 | 7 |  |  |  |  | 16 | ++$pos; | 
| 337 |  |  |  |  |  |  | } elsif ($type == 0x02) {   # string | 
| 338 | 10 | 50 |  |  |  | 24 | last if $pos + 2 > $dirLen; | 
| 339 | 10 |  |  |  |  | 29 | my $len = Get16u($dataPt, $pos); | 
| 340 | 10 | 50 |  |  |  | 23 | last if $pos + 2 + $len > $dirLen; | 
| 341 | 10 |  |  |  |  | 21 | $val = substr($$dataPt, $pos + 2, $len); | 
| 342 | 10 |  |  |  |  | 17 | $pos += 2 + $len; | 
| 343 |  |  |  |  |  |  | } elsif ($isStruct{$type}) {   # object, mixed array or typed object | 
| 344 | 5 |  |  |  |  | 27 | $et->VPrint(1, "  + [$amfType[$type]]\n"); | 
| 345 | 5 |  |  |  |  | 6 | my $getName; | 
| 346 | 5 |  |  |  |  | 11 | $val = '';  # dummy value | 
| 347 | 5 | 100 |  |  |  | 12 | if ($type == 0x08) {        # mixed array | 
|  |  | 50 |  |  |  |  |  | 
| 348 |  |  |  |  |  |  | # skip last array index for mixed array | 
| 349 | 4 | 50 |  |  |  | 11 | last if $pos + 4 > $dirLen; | 
| 350 | 4 |  |  |  |  | 8 | $pos += 4; | 
| 351 |  |  |  |  |  |  | } elsif ($type == 0x10) {   # typed object | 
| 352 | 0 |  |  |  |  | 0 | $getName = 1; | 
| 353 |  |  |  |  |  |  | } | 
| 354 | 5 |  |  |  |  | 6 | for (;;) { | 
| 355 |  |  |  |  |  |  | # get tag ID (or typed object name) | 
| 356 | 45 | 50 |  |  |  | 104 | last Record if $pos + 2 > $dirLen; | 
| 357 | 45 |  |  |  |  | 102 | my $len = Get16u($dataPt, $pos); | 
| 358 | 45 | 50 |  |  |  | 95 | if ($pos + 2 + $len > $dirLen) { | 
| 359 | 0 |  |  |  |  | 0 | $et->Warn("Truncated $amfType[$type] record"); | 
| 360 | 0 |  |  |  |  | 0 | last Record; | 
| 361 |  |  |  |  |  |  | } | 
| 362 | 45 |  |  |  |  | 97 | my $tag = substr($$dataPt, $pos + 2, $len); | 
| 363 | 45 |  |  |  |  | 66 | $pos += 2 + $len; | 
| 364 |  |  |  |  |  |  | # first string of a typed object is the object name | 
| 365 | 45 | 50 |  |  |  | 81 | if ($getName) { | 
| 366 | 0 |  |  |  |  | 0 | $et->VPrint(1,"  | (object name '${tag}')\n"); | 
| 367 | 0 |  |  |  |  | 0 | undef $getName; | 
| 368 | 0 |  |  |  |  | 0 | next; # (ignore name for now) | 
| 369 |  |  |  |  |  |  | } | 
| 370 | 45 |  |  |  |  | 65 | my $subTablePtr = $tagTablePtr; | 
| 371 | 45 |  |  |  |  | 81 | my $tagInfo = $$subTablePtr{$tag}; | 
| 372 |  |  |  |  |  |  | # switch to subdirectory table if necessary | 
| 373 | 45 | 100 | 100 |  |  | 142 | if ($tagInfo and $$tagInfo{SubDirectory}) { | 
| 374 | 2 |  |  |  |  | 6 | my $subTable = $tagInfo->{SubDirectory}->{TagTable}; | 
| 375 |  |  |  |  |  |  | # descend into Flash SubDirectory | 
| 376 | 2 | 50 |  |  |  | 10 | if ($subTable =~ /^Image::ExifTool::Flash::/) { | 
| 377 | 2 |  |  |  |  | 5 | $tag = $$tagInfo{Name}; # use our name for the tag | 
| 378 | 2 |  |  |  |  | 7 | $subTablePtr = GetTagTable($subTable); | 
| 379 |  |  |  |  |  |  | } | 
| 380 |  |  |  |  |  |  | } | 
| 381 |  |  |  |  |  |  | # get object value | 
| 382 | 45 |  |  |  |  | 127 | my $valPos = $pos + 1; | 
| 383 | 45 |  |  |  |  | 76 | $$dirInfo{Pos} = $pos; | 
| 384 | 45 |  |  |  |  | 62 | my $structName = $$dirInfo{StructName}; | 
| 385 |  |  |  |  |  |  | # add structure name to start of tag name | 
| 386 | 45 | 100 |  |  |  | 96 | $tag = $structName . ucfirst($tag) if defined $structName; | 
| 387 | 45 |  |  |  |  | 69 | $$dirInfo{StructName} = $tag;       # set new structure name | 
| 388 | 45 |  |  |  |  | 224 | my ($t, $v) = ProcessMeta($et, $dirInfo, $subTablePtr, 1); | 
| 389 | 45 |  |  |  |  | 73 | $$dirInfo{StructName} = $structName;# restore original structure name | 
| 390 | 45 |  |  |  |  | 65 | $pos = $$dirInfo{Pos};  # update to new position in packet | 
| 391 |  |  |  |  |  |  | # all done if this value contained tags | 
| 392 | 45 | 50 | 33 |  |  | 135 | last Record unless defined $t and defined $v; | 
| 393 | 45 | 100 |  |  |  | 98 | next if $isStruct{$t};  # already handled tags in sub-structures | 
| 394 | 43 | 100 | 100 |  |  | 120 | next if ref($v) eq 'ARRAY' and not @$v; # ignore empty arrays | 
| 395 | 42 | 100 |  |  |  | 71 | last if $t == 0x09; # (end of object) | 
| 396 | 37 | 100 | 66 |  |  | 142 | if (not $$subTablePtr{$tag} and $tag =~ /^\w+$/) { | 
| 397 | 9 |  |  |  |  | 47 | AddTagToTable($subTablePtr, $tag, { Name => ucfirst($tag) }); | 
| 398 | 9 |  |  |  |  | 86 | $et->VPrint(1, "  | (adding $tag)\n"); | 
| 399 |  |  |  |  |  |  | } | 
| 400 | 37 |  | 33 |  |  | 144 | $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 | 5 |  |  |  |  | 9 | $val = ''; | 
| 412 |  |  |  |  |  |  | } elsif ($type == 0x07) {   # reference | 
| 413 | 0 | 0 |  |  |  | 0 | last if $pos + 2 > $dirLen; | 
| 414 | 0 |  |  |  |  | 0 | $val = Get16u($dataPt, $pos); | 
| 415 | 0 |  |  |  |  | 0 | $pos += 2; | 
| 416 |  |  |  |  |  |  | } elsif ($type == 0x0a) {   # array | 
| 417 | 3 | 50 |  |  |  | 8 | last if $pos + 4 > $dirLen; | 
| 418 | 3 |  |  |  |  | 10 | my $num = Get32u($dataPt, $pos); | 
| 419 | 3 |  |  |  |  | 9 | $$dirInfo{Pos} = $pos + 4; | 
| 420 | 3 |  |  |  |  | 5 | my ($i, @vals); | 
| 421 |  |  |  |  |  |  | # add array index to compound tag name | 
| 422 | 3 |  |  |  |  | 7 | my $structName = $$dirInfo{StructName}; | 
| 423 | 3 |  |  |  |  | 8 | for ($i=0; $i<$num; ++$i) { | 
| 424 | 10 | 50 |  |  |  | 27 | $$dirInfo{StructName} = $structName . $i if defined $structName; | 
| 425 | 10 |  |  |  |  | 47 | my ($t, $v) = ProcessMeta($et, $dirInfo, $tagTablePtr, 1); | 
| 426 | 10 | 50 |  |  |  | 23 | last Record unless defined $v; | 
| 427 |  |  |  |  |  |  | # save value unless contained in a sub-structure | 
| 428 | 10 | 100 |  |  |  | 45 | push @vals, $v unless $isStruct{$t}; | 
| 429 |  |  |  |  |  |  | } | 
| 430 | 3 |  |  |  |  | 14 | $$dirInfo{StructName} = $structName; | 
| 431 | 3 |  |  |  |  | 5 | $pos = $$dirInfo{Pos}; | 
| 432 | 3 |  |  |  |  | 8 | $val = \@vals; | 
| 433 |  |  |  |  |  |  | } elsif ($type == 0x0c or $type == 0x0f) {  # long string or XML | 
| 434 | 0 | 0 |  |  |  | 0 | last if $pos + 4 > $dirLen; | 
| 435 | 0 |  |  |  |  | 0 | my $len = Get32u($dataPt, $pos); | 
| 436 | 0 | 0 |  |  |  | 0 | last if $pos + 4 + $len > $dirLen; | 
| 437 | 0 |  |  |  |  | 0 | $val = substr($$dataPt, $pos + 4, $len); | 
| 438 | 0 |  |  |  |  | 0 | $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 | 0 |  | 0 |  |  | 0 | my $t = $amfType[$type] || sprintf('type 0x%x',$type); | 
| 443 | 0 |  |  |  |  | 0 | $et->Warn("AMF $t record not yet supported"); | 
| 444 | 0 |  |  |  |  | 0 | undef $type;    # (so we don't print another warning) | 
| 445 | 0 |  |  |  |  | 0 | last;           # can't continue | 
| 446 |  |  |  |  |  |  | } | 
| 447 | 58 | 100 |  |  |  | 121 | last if $single;        # all done if extracting single value | 
| 448 | 3 | 100 |  |  |  | 9 | unless ($isStruct{$type}) { | 
| 449 |  |  |  |  |  |  | # only process certain Meta packets | 
| 450 | 2 | 50 | 33 |  |  | 9 | if ($type == 0x02 and not $rec) { | 
| 451 | 2 | 100 |  |  |  | 8 | my $verb = $processMetaPacket{$val} ? 'processing' : 'ignoring'; | 
| 452 | 2 |  |  |  |  | 13 | $et->VPrint(0, "  | ($verb $val information)\n"); | 
| 453 | 2 | 100 |  |  |  | 7 | last unless $processMetaPacket{$val}; | 
| 454 |  |  |  |  |  |  | } else { | 
| 455 |  |  |  |  |  |  | # give verbose indication if we ignore a lone value | 
| 456 | 0 |  | 0 |  |  | 0 | my $t = $amfType[$type] || sprintf('type 0x%x',$type); | 
| 457 | 0 |  |  |  |  | 0 | $et->VPrint(1, "  | (ignored lone $t value '${val}')\n"); | 
| 458 |  |  |  |  |  |  | } | 
| 459 |  |  |  |  |  |  | } | 
| 460 |  |  |  |  |  |  | } | 
| 461 | 57 | 50 | 33 |  |  | 117 | if (not defined $val and defined $type) { | 
| 462 | 0 |  |  |  |  | 0 | $et->Warn(sprintf("Truncated AMF record 0x%x",$type)); | 
| 463 |  |  |  |  |  |  | } | 
| 464 | 57 | 100 |  |  |  | 98 | return 1 unless $single;    # all done | 
| 465 | 55 |  |  |  |  | 79 | $$dirInfo{Pos} = $pos;      # update position | 
| 466 | 55 |  |  |  |  | 134 | 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 | 
| 473 |  |  |  |  |  |  | sub ProcessFLV($$) | 
| 474 |  |  |  |  |  |  | { | 
| 475 | 1 |  |  | 1 | 0 | 5 | my ($et, $dirInfo) = @_; | 
| 476 | 1 |  |  |  |  | 5 | my $verbose = $et->Options('Verbose'); | 
| 477 | 1 |  |  |  |  | 6 | my $raf = $$dirInfo{RAF}; | 
| 478 | 1 |  |  |  |  | 3 | my $buff; | 
| 479 |  |  |  |  |  |  |  | 
| 480 | 1 | 50 |  |  |  | 6 | $raf->Read($buff, 9) == 9 or return 0; | 
| 481 | 1 | 50 |  |  |  | 11 | $buff =~ /^FLV\x01/ or return 0; | 
| 482 | 1 |  |  |  |  | 6 | SetByteOrder('MM'); | 
| 483 | 1 |  |  |  |  | 6 | $et->SetFileType(); | 
| 484 | 1 |  |  |  |  | 12 | my ($flags, $offset) = unpack('x4CN', $buff); | 
| 485 | 1 | 50 | 0 |  |  | 5 | $raf->Seek($offset-9, 1) or return 1 if $offset > 9; | 
| 486 | 1 |  |  |  |  | 3 | $flags &= 0x05; # only look for audio/video | 
| 487 | 1 |  |  |  |  | 2 | my $found = 0; | 
| 488 | 1 |  |  |  |  | 4 | my $tagTablePtr = GetTagTable('Image::ExifTool::Flash::FLV'); | 
| 489 | 1 |  |  |  |  | 2 | for (;;) { | 
| 490 | 4 | 50 |  |  |  | 16 | $raf->Read($buff, 15) == 15 or last; | 
| 491 | 4 |  |  |  |  | 11 | my $len = unpack('x4N', $buff); | 
| 492 | 4 |  |  |  |  | 7 | my $type = $len >> 24; | 
| 493 | 4 |  |  |  |  | 7 | $len &= 0x00ffffff; | 
| 494 | 4 |  |  |  |  | 13 | my $tagInfo = $et->GetTagInfo($tagTablePtr, $type); | 
| 495 | 4 | 50 |  |  |  | 13 | if ($verbose > 1) { | 
| 496 | 0 | 0 |  |  |  | 0 | my $name = $tagInfo ? $$tagInfo{Name} : "type $type"; | 
| 497 | 0 |  |  |  |  | 0 | $et->VPrint(1, "FLV $name packet, len $len\n"); | 
| 498 |  |  |  |  |  |  | } | 
| 499 | 4 |  |  |  |  | 10 | undef $buff; | 
| 500 | 4 | 50 | 33 |  |  | 19 | if ($tagInfo and $$tagInfo{SubDirectory}) { | 
| 501 | 4 |  |  |  |  | 45 | my $mask = $$tagInfo{BitMask}; | 
| 502 | 4 | 100 |  |  |  | 14 | if ($mask) { | 
|  |  | 50 |  |  |  |  |  | 
| 503 |  |  |  |  |  |  | # handle audio or video packet | 
| 504 | 2 | 50 |  |  |  | 11 | unless ($found & $mask) { | 
| 505 | 2 |  |  |  |  | 3 | $found |= $mask; | 
| 506 | 2 |  |  |  |  | 5 | $flags &= ~$mask; | 
| 507 | 2 | 50 | 33 |  |  | 10 | if ($len>=1 and $raf->Read($buff, 1) == 1) { | 
| 508 | 2 |  |  |  |  | 6 | $len -= 1; | 
| 509 |  |  |  |  |  |  | } else { | 
| 510 | 0 |  |  |  |  | 0 | $et->Warn("Bad $$tagInfo{Name} packet"); | 
| 511 | 0 |  |  |  |  | 0 | last; | 
| 512 |  |  |  |  |  |  | } | 
| 513 |  |  |  |  |  |  | } | 
| 514 |  |  |  |  |  |  | } elsif ($raf->Read($buff, $len) == $len) { | 
| 515 | 2 |  |  |  |  | 4 | $len = 0; | 
| 516 |  |  |  |  |  |  | } else { | 
| 517 | 0 |  |  |  |  | 0 | $et->Warn('Truncated Meta packet'); | 
| 518 | 0 |  |  |  |  | 0 | last; | 
| 519 |  |  |  |  |  |  | } | 
| 520 |  |  |  |  |  |  | } | 
| 521 | 4 | 50 |  |  |  | 9 | if (defined $buff) { | 
| 522 | 4 |  |  |  |  | 13 | $et->HandleTag($tagTablePtr, $type, undef, | 
| 523 |  |  |  |  |  |  | DataPt  => \$buff, | 
| 524 |  |  |  |  |  |  | DataPos => $raf->Tell() - length($buff), | 
| 525 |  |  |  |  |  |  | ); | 
| 526 |  |  |  |  |  |  | } | 
| 527 | 4 | 100 |  |  |  | 14 | last unless $flags; | 
| 528 | 3 | 100 | 50 |  |  | 11 | $raf->Seek($len, 1) or last if $len; | 
| 529 |  |  |  |  |  |  | } | 
| 530 | 1 |  |  |  |  | 8 | return 1; | 
| 531 |  |  |  |  |  |  | } | 
| 532 |  |  |  |  |  |  |  | 
| 533 |  |  |  |  |  |  | #------------------------------------------------------------------------------ | 
| 534 |  |  |  |  |  |  | # Found a Flash tag | 
| 535 |  |  |  |  |  |  | # Inputs: 0) ExifTool object ref, 1) tag name, 2) tag value | 
| 536 |  |  |  |  |  |  | sub FoundFlashTag($$$) | 
| 537 |  |  |  |  |  |  | { | 
| 538 | 8 |  |  | 8 | 0 | 30 | my ($et, $tag, $val) = @_; | 
| 539 | 8 |  |  |  |  | 30 | $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) | 
| 549 |  |  |  |  |  |  | sub ReadCompressed($$$$) | 
| 550 |  |  |  |  |  |  | { | 
| 551 | 3 |  |  | 3 | 0 | 8 | my ($raf, $len, $inflate) = ($_[0], $_[2], $_[3]); | 
| 552 | 3 |  |  |  |  | 4 | my $buff; | 
| 553 | 3 | 50 |  |  |  | 19 | unless ($raf->Read($buff, $len)) { | 
| 554 | 0 |  |  |  |  | 0 | $_[3] = 'Error reading file'; | 
| 555 | 0 |  |  |  |  | 0 | return 0; | 
| 556 |  |  |  |  |  |  | } | 
| 557 |  |  |  |  |  |  | # uncompress if necessary | 
| 558 | 3 | 50 |  |  |  | 8 | if ($inflate) { | 
| 559 | 0 | 0 |  |  |  | 0 | unless (ref $inflate) { | 
| 560 | 0 | 0 |  |  |  | 0 | unless (eval { require Compress::Zlib }) { | 
|  | 0 |  |  |  |  | 0 |  | 
| 561 | 0 |  |  |  |  | 0 | $_[3] = 'Install Compress::Zlib to extract compressed information'; | 
| 562 | 0 |  |  |  |  | 0 | return 0; | 
| 563 |  |  |  |  |  |  | } | 
| 564 | 0 |  |  |  |  | 0 | $inflate = Compress::Zlib::inflateInit(); | 
| 565 | 0 | 0 |  |  |  | 0 | unless ($inflate) { | 
| 566 | 0 |  |  |  |  | 0 | $_[3] = 'Error initializing inflate for Flash data'; | 
| 567 | 0 |  |  |  |  | 0 | return 0; | 
| 568 |  |  |  |  |  |  | } | 
| 569 | 0 |  |  |  |  | 0 | $_[3] = $inflate;   # pass inflate object back to caller | 
| 570 |  |  |  |  |  |  | } | 
| 571 | 0 |  |  |  |  | 0 | my $tmp = $buff; | 
| 572 | 0 |  |  |  |  | 0 | $buff = ''; | 
| 573 |  |  |  |  |  |  | # read 64 more bytes at a time and inflate until we get enough uncompressed data | 
| 574 | 0 |  |  |  |  | 0 | for (;;) { | 
| 575 | 0 |  |  |  |  | 0 | my ($dat, $stat) = $inflate->inflate($tmp); | 
| 576 | 0 | 0 | 0 |  |  | 0 | if ($stat == Compress::Zlib::Z_STREAM_END() or | 
| 577 |  |  |  |  |  |  | $stat == Compress::Zlib::Z_OK()) | 
| 578 |  |  |  |  |  |  | { | 
| 579 | 0 |  |  |  |  | 0 | $buff .= $dat;  # add inflated data to buffer | 
| 580 | 0 | 0 | 0 |  |  | 0 | last if length $buff >= $len or $stat == Compress::Zlib::Z_STREAM_END(); | 
| 581 | 0 | 0 |  |  |  | 0 | $raf->Read($tmp,64) or last;    # must read a bit more data | 
| 582 |  |  |  |  |  |  | } else { | 
| 583 | 0 |  |  |  |  | 0 | $buff = ''; | 
| 584 | 0 |  |  |  |  | 0 | last; | 
| 585 |  |  |  |  |  |  | } | 
| 586 |  |  |  |  |  |  | } | 
| 587 | 0 | 0 |  |  |  | 0 | $_[3] = 'Error inflating compressed Flash data' unless length $buff; | 
| 588 |  |  |  |  |  |  | } | 
| 589 | 3 | 50 |  |  |  | 21 | $_[1] = defined $_[1] ? $_[1] . $buff : $buff; | 
| 590 | 3 |  |  |  |  | 11 | 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 | 
| 597 |  |  |  |  |  |  | sub ProcessSWF($$) | 
| 598 |  |  |  |  |  |  | { | 
| 599 | 1 |  |  | 1 | 0 | 5 | my ($et, $dirInfo) = @_; | 
| 600 | 1 |  |  |  |  | 6 | my $raf = $$dirInfo{RAF}; | 
| 601 | 1 |  |  |  |  | 3 | my ($buff, $hasMeta); | 
| 602 |  |  |  |  |  |  |  | 
| 603 | 1 | 50 |  |  |  | 5 | $raf->Read($buff, 8) == 8 or return 0; | 
| 604 | 1 | 50 |  |  |  | 19 | $buff =~ /^(F|C)WS([^\0])/ or return 0; | 
| 605 | 1 | 50 |  |  |  | 8 | my ($compressed, $vers) = ($1 eq 'C' ? 1 : 0, ord($2)); | 
| 606 |  |  |  |  |  |  |  | 
| 607 | 1 |  |  |  |  | 5 | SetByteOrder('II'); | 
| 608 | 1 |  |  |  |  | 7 | $et->SetFileType(); | 
| 609 | 1 |  |  |  |  | 4 | GetTagTable('Image::ExifTool::Flash::Main');  # make sure table is initialized | 
| 610 |  |  |  |  |  |  |  | 
| 611 | 1 |  |  |  |  | 12 | FoundFlashTag($et, FlashVersion => $vers); | 
| 612 | 1 |  |  |  |  | 3 | FoundFlashTag($et, Compressed => $compressed); | 
| 613 |  |  |  |  |  |  |  | 
| 614 |  |  |  |  |  |  | # read the next 64 bytes of the file (and inflate if necessary) | 
| 615 | 1 |  |  |  |  | 2 | $buff = ''; | 
| 616 | 1 | 50 |  |  |  | 5 | unless (ReadCompressed($raf, $buff, 64, $compressed)) { | 
| 617 | 0 | 0 |  |  |  | 0 | $et->Warn($compressed) if $compressed; | 
| 618 | 0 |  |  |  |  | 0 | return 1; | 
| 619 |  |  |  |  |  |  | } | 
| 620 |  |  |  |  |  |  |  | 
| 621 |  |  |  |  |  |  | # unpack elements of bit-packed Flash Rect structure | 
| 622 | 1 |  |  |  |  | 5 | my $nBits = unpack('C', $buff) >> 3;    # bits in x1,x2,y1,y2 elements | 
| 623 | 1 |  |  |  |  | 3 | my $totBits = 5 + $nBits * 4;           # total bits in Rect structure | 
| 624 | 1 |  |  |  |  | 4 | my $nBytes = int(($totBits + 7) / 8);   # byte length of Rect structure | 
| 625 | 1 | 50 |  |  |  | 5 | if (length $buff < $nBytes + 4) { | 
| 626 | 0 |  |  |  |  | 0 | $et->Warn('Truncated Flash file'); | 
| 627 | 0 |  |  |  |  | 0 | return 1; | 
| 628 |  |  |  |  |  |  | } | 
| 629 | 1 |  |  |  |  | 6 | my $bits = unpack("B$totBits", $buff); | 
| 630 |  |  |  |  |  |  | # isolate Rect elements and convert from ASCII bit strings to integers | 
| 631 | 1 |  |  |  |  | 9 | my @vals = unpack('x5' . "a$nBits" x 4, $bits); | 
| 632 |  |  |  |  |  |  | # (do conversion the hard way because oct("0b$val") requires Perl 5.6) | 
| 633 | 1 |  |  |  |  | 3 | map { $_ = unpack('N', pack('B32', '0' x (32 - length $_) . $_)) } @vals; | 
|  | 4 |  |  |  |  | 19 |  | 
| 634 |  |  |  |  |  |  |  | 
| 635 |  |  |  |  |  |  | # calculate and store ImageWidth/Height | 
| 636 | 1 |  |  |  |  | 6 | FoundFlashTag($et, ImageWidth  => ($vals[1] - $vals[0]) / 20); | 
| 637 | 1 |  |  |  |  | 5 | FoundFlashTag($et, ImageHeight => ($vals[3] - $vals[2]) / 20); | 
| 638 |  |  |  |  |  |  |  | 
| 639 |  |  |  |  |  |  | # get frame rate and count | 
| 640 | 1 |  |  |  |  | 5 | @vals = unpack("x${nBytes}v2", $buff); | 
| 641 | 1 |  |  |  |  | 4 | FoundFlashTag($et, FrameRate => $vals[0] / 256); | 
| 642 | 1 |  |  |  |  | 5 | FoundFlashTag($et, FrameCount => $vals[1]); | 
| 643 | 1 | 50 |  |  |  | 6 | FoundFlashTag($et, Duration => $vals[1] * 256 / $vals[0]) if $vals[0]; | 
| 644 |  |  |  |  |  |  |  | 
| 645 |  |  |  |  |  |  | # scan through the tags to find FlashAttributes and XMP | 
| 646 | 1 |  |  |  |  | 4 | $buff = substr($buff, $nBytes + 4); | 
| 647 | 1 |  |  |  |  | 2 | for (;;) { | 
| 648 | 1 |  |  |  |  | 3 | my $buffLen = length $buff; | 
| 649 | 1 | 50 |  |  |  | 4 | last if $buffLen < 2; | 
| 650 | 1 |  |  |  |  | 5 | my $code = Get16u(\$buff, 0); | 
| 651 | 1 |  |  |  |  | 2 | my $pos = 2; | 
| 652 | 1 |  |  |  |  | 3 | my $tag = $code >> 6; | 
| 653 | 1 |  |  |  |  | 2 | my $size = $code & 0x3f; | 
| 654 | 1 |  |  |  |  | 8 | $et->VPrint(1, "SWF tag $tag ($size bytes):\n"); | 
| 655 | 1 | 50 | 33 |  |  | 28 | last unless $tag == 69 or $tag == 77 or $hasMeta; | 
|  |  |  | 33 |  |  |  |  | 
| 656 |  |  |  |  |  |  | # read enough to get a complete short record | 
| 657 | 1 | 50 |  |  |  | 5 | if ($pos + $size > $buffLen) { | 
| 658 |  |  |  |  |  |  | # (read 2 extra bytes if available to get next tag word) | 
| 659 | 1 | 50 |  |  |  | 4 | unless (ReadCompressed($raf, $buff, $size + 2, $compressed)) { | 
| 660 | 0 | 0 |  |  |  | 0 | $et->Warn($compressed) if $compressed; | 
| 661 | 0 |  |  |  |  | 0 | return 1; | 
| 662 |  |  |  |  |  |  | } | 
| 663 | 1 |  |  |  |  | 2 | $buffLen = length $buff; | 
| 664 | 1 | 50 |  |  |  | 4 | last if $pos + $size > $buffLen; | 
| 665 |  |  |  |  |  |  | } | 
| 666 |  |  |  |  |  |  | # read extended record if necessary | 
| 667 | 1 | 50 |  |  |  | 3 | if ($size == 0x3f) { | 
| 668 | 1 | 50 |  |  |  | 4 | last if $pos + 4 > $buffLen; | 
| 669 | 1 |  |  |  |  | 6 | $size = Get32u(\$buff, $pos); | 
| 670 | 1 |  |  |  |  | 2 | $pos += 4; | 
| 671 | 1 | 50 |  |  |  | 4 | last if $size > 1000000; # don't read anything huge | 
| 672 | 1 | 50 |  |  |  | 4 | if ($pos + $size > $buffLen) { | 
| 673 | 1 | 50 |  |  |  | 3 | unless (ReadCompressed($raf, $buff, $size + 2, $compressed)) { | 
| 674 | 0 | 0 |  |  |  | 0 | $et->Warn($compressed) if $compressed; | 
| 675 | 0 |  |  |  |  | 0 | return 1; | 
| 676 |  |  |  |  |  |  | } | 
| 677 | 1 |  |  |  |  | 6 | $buffLen = length $buff; | 
| 678 | 1 | 50 |  |  |  | 5 | last if $pos + $size > $buffLen; | 
| 679 |  |  |  |  |  |  | } | 
| 680 | 1 |  |  |  |  | 6 | $et->VPrint(1, "  [extended size $size bytes]\n"); | 
| 681 |  |  |  |  |  |  | } | 
| 682 | 1 | 50 |  |  |  | 5 | if ($tag == 69) {       # FlashAttributes | 
|  |  | 50 |  |  |  |  |  | 
| 683 | 0 | 0 |  |  |  | 0 | last unless $size; | 
| 684 | 0 |  |  |  |  | 0 | my $flags = Get8u(\$buff, $pos); | 
| 685 | 0 |  |  |  |  | 0 | FoundFlashTag($et, $tag => $flags); | 
| 686 | 0 | 0 |  |  |  | 0 | last unless $flags & 0x10;  # only continue if we have metadata (XMP) | 
| 687 | 0 |  |  |  |  | 0 | $hasMeta = 1; | 
| 688 |  |  |  |  |  |  | } elsif ($tag == 77) {  # Metadata | 
| 689 | 1 |  |  |  |  | 5 | my $val = substr($buff, $pos, $size); | 
| 690 | 1 |  |  |  |  | 7 | FoundFlashTag($et, $tag => $val); | 
| 691 | 1 |  |  |  |  | 3 | last; | 
| 692 |  |  |  |  |  |  | } | 
| 693 | 0 | 0 |  |  |  | 0 | last if $pos + 2 > $buffLen; | 
| 694 | 0 |  |  |  |  | 0 | $buff = substr($buff, $pos);    # remove everything before the next tag | 
| 695 |  |  |  |  |  |  | } | 
| 696 | 1 |  |  |  |  | 6 | return 1; | 
| 697 |  |  |  |  |  |  | } | 
| 698 |  |  |  |  |  |  |  | 
| 699 |  |  |  |  |  |  | 1;  # end | 
| 700 |  |  |  |  |  |  |  | 
| 701 |  |  |  |  |  |  | __END__ |