1 | #------------------------------------------------------------------------------
|
---|
2 | # File: FlashPix.pm
|
---|
3 | #
|
---|
4 | # Description: Read FlashPix meta information
|
---|
5 | #
|
---|
6 | # Revisions: 05/29/2006 - P. Harvey Created
|
---|
7 | #
|
---|
8 | # References: 1) http://www.exif.org/Exif2-2.PDF
|
---|
9 | # 2) http://www.graphcomp.com/info/specs/livepicture/fpx.pdf
|
---|
10 | # 3) http://search.cpan.org/~jdb/libwin32/
|
---|
11 | # 4) http://msdn.microsoft.com/en-us/library/aa380374.aspx
|
---|
12 | #------------------------------------------------------------------------------
|
---|
13 |
|
---|
14 | package Image::ExifTool::FlashPix;
|
---|
15 |
|
---|
16 | use strict;
|
---|
17 | use vars qw($VERSION);
|
---|
18 | use Image::ExifTool qw(:DataAccess :Utils);
|
---|
19 | use Image::ExifTool::Exif;
|
---|
20 | use Image::ExifTool::ASF; # for GetGUID()
|
---|
21 |
|
---|
22 | $VERSION = '1.19';
|
---|
23 |
|
---|
24 | sub ProcessFPX($$);
|
---|
25 | sub ProcessFPXR($$$);
|
---|
26 | sub ProcessProperties($$$);
|
---|
27 | sub ReadFPXValue($$$$$;$$);
|
---|
28 | sub ProcessHyperlinks($$);
|
---|
29 | sub ProcessContents($$$);
|
---|
30 | sub SetDocNum($$;$$$);
|
---|
31 |
|
---|
32 | # sector type constants
|
---|
33 | sub HDR_SIZE () { 512; }
|
---|
34 | sub DIF_SECT () { 0xfffffffc; }
|
---|
35 | sub FAT_SECT () { 0xfffffffd; }
|
---|
36 | sub END_OF_CHAIN () { 0xfffffffe; }
|
---|
37 | sub FREE_SECT () { 0xffffffff; }
|
---|
38 |
|
---|
39 | # format flags
|
---|
40 | sub VT_VECTOR () { 0x1000; }
|
---|
41 | sub VT_ARRAY () { 0x2000; }
|
---|
42 | sub VT_BYREF () { 0x4000; }
|
---|
43 | sub VT_RESERVED () { 0x8000; }
|
---|
44 |
|
---|
45 | # other constants
|
---|
46 | sub VT_VARIANT () { 12; }
|
---|
47 | sub VT_LPSTR () { 30; }
|
---|
48 |
|
---|
49 | # list of OLE format codes (unsupported codes commented out)
|
---|
50 | my %oleFormat = (
|
---|
51 | 0 => undef, # VT_EMPTY
|
---|
52 | 1 => undef, # VT_NULL
|
---|
53 | 2 => 'int16s', # VT_I2
|
---|
54 | 3 => 'int32s', # VT_I4
|
---|
55 | 4 => 'float', # VT_R4
|
---|
56 | 5 => 'double', # VT_R8
|
---|
57 | 6 => undef, # VT_CY
|
---|
58 | 7 => 'VT_DATE', # VT_DATE (double, number of days since Dec 30, 1899)
|
---|
59 | 8 => 'VT_BSTR', # VT_BSTR (int32u count, followed by binary string)
|
---|
60 | # 9 => 'VT_DISPATCH',
|
---|
61 | 10 => 'int32s', # VT_ERROR
|
---|
62 | 11 => 'int16s', # VT_BOOL
|
---|
63 | 12 => 'VT_VARIANT', # VT_VARIANT
|
---|
64 | # 13 => 'VT_UNKNOWN',
|
---|
65 | # 14 => 'VT_DECIMAL',
|
---|
66 | 16 => 'int8s', # VT_I1
|
---|
67 | 17 => 'int8u', # VT_UI1
|
---|
68 | 18 => 'int16u', # VT_UI2
|
---|
69 | 19 => 'int32u', # VT_UI4
|
---|
70 | 20 => 'int64s', # VT_I8
|
---|
71 | 21 => 'int64u', # VT_UI8
|
---|
72 | # 22 => 'VT_INT',
|
---|
73 | # 23 => 'VT_UINT',
|
---|
74 | # 24 => 'VT_VOID',
|
---|
75 | # 25 => 'VT_HRESULT',
|
---|
76 | # 26 => 'VT_PTR',
|
---|
77 | # 27 => 'VT_SAFEARRAY',
|
---|
78 | # 28 => 'VT_CARRAY',
|
---|
79 | # 29 => 'VT_USERDEFINED',
|
---|
80 | 30 => 'VT_LPSTR', # VT_LPSTR (int32u count, followed by string)
|
---|
81 | 31 => 'VT_LPWSTR', # VT_LPWSTR (int32u word count, followed by Unicode string)
|
---|
82 | 64 => 'VT_FILETIME',# VT_FILETIME (int64u, number of nanoseconds since Jan 1, 1601)
|
---|
83 | 65 => 'VT_BLOB', # VT_BLOB
|
---|
84 | # 66 => 'VT_STREAM',
|
---|
85 | # 67 => 'VT_STORAGE',
|
---|
86 | # 68 => 'VT_STREAMED_OBJECT',
|
---|
87 | # 69 => 'VT_STORED_OBJECT',
|
---|
88 | # 70 => 'VT_BLOB_OBJECT',
|
---|
89 | 71 => 'VT_CF', # VT_CF
|
---|
90 | 72 => 'VT_CLSID', # VT_CLSID
|
---|
91 | );
|
---|
92 |
|
---|
93 | # OLE flag codes (high nibble of property type)
|
---|
94 | my %oleFlags = (
|
---|
95 | 0x1000 => 'VT_VECTOR',
|
---|
96 | 0x2000 => 'VT_ARRAY', # not yet supported
|
---|
97 | 0x4000 => 'VT_BYREF', # ditto
|
---|
98 | 0x8000 => 'VT_RESERVED',
|
---|
99 | );
|
---|
100 |
|
---|
101 | # byte sizes for supported VT_* format and flag types
|
---|
102 | my %oleFormatSize = (
|
---|
103 | VT_DATE => 8,
|
---|
104 | VT_BSTR => 4, # (+ string length)
|
---|
105 | VT_VARIANT => 4, # (+ data length)
|
---|
106 | VT_LPSTR => 4, # (+ string length)
|
---|
107 | VT_LPWSTR => 4, # (+ string character length)
|
---|
108 | VT_FILETIME => 8,
|
---|
109 | VT_BLOB => 4, # (+ data length)
|
---|
110 | VT_CF => 4, # (+ data length)
|
---|
111 | VT_CLSID => 16,
|
---|
112 | VT_VECTOR => 4, # (+ vector elements)
|
---|
113 | );
|
---|
114 |
|
---|
115 | # names for each type of directory entry
|
---|
116 | my @dirEntryType = qw(INVALID STORAGE STREAM LOCKBYTES PROPERTY ROOT);
|
---|
117 |
|
---|
118 | # list of code pages used by Microsoft
|
---|
119 | # (ref http://msdn.microsoft.com/en-us/library/dd317756(VS.85).aspx)
|
---|
120 | my %codePage = (
|
---|
121 | 037 => 'IBM EBCDIC US-Canada',
|
---|
122 | 437 => 'DOS United States',
|
---|
123 | 500 => 'IBM EBCDIC International',
|
---|
124 | 708 => 'Arabic (ASMO 708)',
|
---|
125 | 709 => 'Arabic (ASMO-449+, BCON V4)',
|
---|
126 | 710 => 'Arabic - Transparent Arabic',
|
---|
127 | 720 => 'DOS Arabic (Transparent ASMO)',
|
---|
128 | 737 => 'DOS Greek (formerly 437G)',
|
---|
129 | 775 => 'DOS Baltic',
|
---|
130 | 850 => 'DOS Latin 1 (Western European)',
|
---|
131 | 852 => 'DOS Latin 2 (Central European)',
|
---|
132 | 855 => 'DOS Cyrillic (primarily Russian)',
|
---|
133 | 857 => 'DOS Turkish',
|
---|
134 | 858 => 'DOS Multilingual Latin 1 with Euro',
|
---|
135 | 860 => 'DOS Portuguese',
|
---|
136 | 861 => 'DOS Icelandic',
|
---|
137 | 862 => 'DOS Hebrew',
|
---|
138 | 863 => 'DOS French Canadian',
|
---|
139 | 864 => 'DOS Arabic',
|
---|
140 | 865 => 'DOS Nordic',
|
---|
141 | 866 => 'DOS Russian (Cyrillic)',
|
---|
142 | 869 => 'DOS Modern Greek',
|
---|
143 | 870 => 'IBM EBCDIC Multilingual/ROECE (Latin 2)',
|
---|
144 | 874 => 'Windows Thai (same as 28605, ISO 8859-15)',
|
---|
145 | 875 => 'IBM EBCDIC Greek Modern',
|
---|
146 | 932 => 'Windows Japanese (Shift-JIS)',
|
---|
147 | 936 => 'Windows Simplified Chinese (PRC, Singapore)',
|
---|
148 | 949 => 'Windows Korean (Unified Hangul Code)',
|
---|
149 | 950 => 'Windows Traditional Chinese (Taiwan)',
|
---|
150 | 1026 => 'IBM EBCDIC Turkish (Latin 5)',
|
---|
151 | 1047 => 'IBM EBCDIC Latin 1/Open System',
|
---|
152 | 1140 => 'IBM EBCDIC US-Canada with Euro',
|
---|
153 | 1141 => 'IBM EBCDIC Germany with Euro',
|
---|
154 | 1142 => 'IBM EBCDIC Denmark-Norway with Euro',
|
---|
155 | 1143 => 'IBM EBCDIC Finland-Sweden with Euro',
|
---|
156 | 1144 => 'IBM EBCDIC Italy with Euro',
|
---|
157 | 1145 => 'IBM EBCDIC Latin America-Spain with Euro',
|
---|
158 | 1146 => 'IBM EBCDIC United Kingdom with Euro',
|
---|
159 | 1147 => 'IBM EBCDIC France with Euro',
|
---|
160 | 1148 => 'IBM EBCDIC International with Euro',
|
---|
161 | 1149 => 'IBM EBCDIC Icelandic with Euro',
|
---|
162 | 1200 => 'Unicode UTF-16, little endian',
|
---|
163 | 1201 => 'Unicode UTF-16, big endian',
|
---|
164 | 1250 => 'Windows Latin 2 (Central European)',
|
---|
165 | 1251 => 'Windows Cyrillic',
|
---|
166 | 1252 => 'Windows Latin 1 (Western European)',
|
---|
167 | 1253 => 'Windows Greek',
|
---|
168 | 1254 => 'Windows Turkish',
|
---|
169 | 1255 => 'Windows Hebrew',
|
---|
170 | 1256 => 'Windows Arabic',
|
---|
171 | 1257 => 'Windows Baltic',
|
---|
172 | 1258 => 'Windows Vietnamese',
|
---|
173 | 1361 => 'Korean (Johab)',
|
---|
174 | 10000 => 'Mac Roman (Western European)',
|
---|
175 | 10001 => 'Mac Japanese',
|
---|
176 | 10002 => 'Mac Traditional Chinese',
|
---|
177 | 10003 => 'Mac Korean',
|
---|
178 | 10004 => 'Mac Arabic',
|
---|
179 | 10005 => 'Mac Hebrew',
|
---|
180 | 10006 => 'Mac Greek',
|
---|
181 | 10007 => 'Mac Cyrillic',
|
---|
182 | 10008 => 'Mac Simplified Chinese',
|
---|
183 | 10010 => 'Mac Romanian',
|
---|
184 | 10017 => 'Mac Ukrainian',
|
---|
185 | 10021 => 'Mac Thai',
|
---|
186 | 10029 => 'Mac Latin 2 (Central European)',
|
---|
187 | 10079 => 'Mac Icelandic',
|
---|
188 | 10081 => 'Mac Turkish',
|
---|
189 | 10082 => 'Mac Croatian',
|
---|
190 | 12000 => 'Unicode UTF-32, little endian',
|
---|
191 | 12001 => 'Unicode UTF-32, big endian',
|
---|
192 | 20000 => 'CNS Taiwan',
|
---|
193 | 20001 => 'TCA Taiwan',
|
---|
194 | 20002 => 'Eten Taiwan',
|
---|
195 | 20003 => 'IBM5550 Taiwan',
|
---|
196 | 20004 => 'TeleText Taiwan',
|
---|
197 | 20005 => 'Wang Taiwan',
|
---|
198 | 20105 => 'IA5 (IRV International Alphabet No. 5, 7-bit)',
|
---|
199 | 20106 => 'IA5 German (7-bit)',
|
---|
200 | 20107 => 'IA5 Swedish (7-bit)',
|
---|
201 | 20108 => 'IA5 Norwegian (7-bit)',
|
---|
202 | 20127 => 'US-ASCII (7-bit)',
|
---|
203 | 20261 => 'T.61',
|
---|
204 | 20269 => 'ISO 6937 Non-Spacing Accent',
|
---|
205 | 20273 => 'IBM EBCDIC Germany',
|
---|
206 | 20277 => 'IBM EBCDIC Denmark-Norway',
|
---|
207 | 20278 => 'IBM EBCDIC Finland-Sweden',
|
---|
208 | 20280 => 'IBM EBCDIC Italy',
|
---|
209 | 20284 => 'IBM EBCDIC Latin America-Spain',
|
---|
210 | 20285 => 'IBM EBCDIC United Kingdom',
|
---|
211 | 20290 => 'IBM EBCDIC Japanese Katakana Extended',
|
---|
212 | 20297 => 'IBM EBCDIC France',
|
---|
213 | 20420 => 'IBM EBCDIC Arabic',
|
---|
214 | 20423 => 'IBM EBCDIC Greek',
|
---|
215 | 20424 => 'IBM EBCDIC Hebrew',
|
---|
216 | 20833 => 'IBM EBCDIC Korean Extended',
|
---|
217 | 20838 => 'IBM EBCDIC Thai',
|
---|
218 | 20866 => 'Russian/Cyrillic (KOI8-R)',
|
---|
219 | 20871 => 'IBM EBCDIC Icelandic',
|
---|
220 | 20880 => 'IBM EBCDIC Cyrillic Russian',
|
---|
221 | 20905 => 'IBM EBCDIC Turkish',
|
---|
222 | 20924 => 'IBM EBCDIC Latin 1/Open System with Euro',
|
---|
223 | 20932 => 'Japanese (JIS 0208-1990 and 0121-1990)',
|
---|
224 | 20936 => 'Simplified Chinese (GB2312)',
|
---|
225 | 20949 => 'Korean Wansung',
|
---|
226 | 21025 => 'IBM EBCDIC Cyrillic Serbian-Bulgarian',
|
---|
227 | 21027 => 'Extended Alpha Lowercase (deprecated)',
|
---|
228 | 21866 => 'Ukrainian/Cyrillic (KOI8-U)',
|
---|
229 | 28591 => 'ISO 8859-1 Latin 1 (Western European)',
|
---|
230 | 28592 => 'ISO 8859-2 (Central European)',
|
---|
231 | 28593 => 'ISO 8859-3 Latin 3',
|
---|
232 | 28594 => 'ISO 8859-4 Baltic',
|
---|
233 | 28595 => 'ISO 8859-5 Cyrillic',
|
---|
234 | 28596 => 'ISO 8859-6 Arabic',
|
---|
235 | 28597 => 'ISO 8859-7 Greek',
|
---|
236 | 28598 => 'ISO 8859-8 Hebrew (Visual)',
|
---|
237 | 28599 => 'ISO 8859-9 Turkish',
|
---|
238 | 28603 => 'ISO 8859-13 Estonian',
|
---|
239 | 28605 => 'ISO 8859-15 Latin 9',
|
---|
240 | 29001 => 'Europa 3',
|
---|
241 | 38598 => 'ISO 8859-8 Hebrew (Logical)',
|
---|
242 | 50220 => 'ISO 2022 Japanese with no halfwidth Katakana (JIS)',
|
---|
243 | 50221 => 'ISO 2022 Japanese with halfwidth Katakana (JIS-Allow 1 byte Kana)',
|
---|
244 | 50222 => 'ISO 2022 Japanese JIS X 0201-1989 (JIS-Allow 1 byte Kana - SO/SI)',
|
---|
245 | 50225 => 'ISO 2022 Korean',
|
---|
246 | 50227 => 'ISO 2022 Simplified Chinese',
|
---|
247 | 50229 => 'ISO 2022 Traditional Chinese',
|
---|
248 | 50930 => 'EBCDIC Japanese (Katakana) Extended',
|
---|
249 | 50931 => 'EBCDIC US-Canada and Japanese',
|
---|
250 | 50933 => 'EBCDIC Korean Extended and Korean',
|
---|
251 | 50935 => 'EBCDIC Simplified Chinese Extended and Simplified Chinese',
|
---|
252 | 50936 => 'EBCDIC Simplified Chinese',
|
---|
253 | 50937 => 'EBCDIC US-Canada and Traditional Chinese',
|
---|
254 | 50939 => 'EBCDIC Japanese (Latin) Extended and Japanese',
|
---|
255 | 51932 => 'EUC Japanese',
|
---|
256 | 51936 => 'EUC Simplified Chinese',
|
---|
257 | 51949 => 'EUC Korean',
|
---|
258 | 51950 => 'EUC Traditional Chinese',
|
---|
259 | 52936 => 'HZ-GB2312 Simplified Chinese',
|
---|
260 | 54936 => 'Windows XP and later: GB18030 Simplified Chinese (4 byte)',
|
---|
261 | 57002 => 'ISCII Devanagari',
|
---|
262 | 57003 => 'ISCII Bengali',
|
---|
263 | 57004 => 'ISCII Tamil',
|
---|
264 | 57005 => 'ISCII Telugu',
|
---|
265 | 57006 => 'ISCII Assamese',
|
---|
266 | 57007 => 'ISCII Oriya',
|
---|
267 | 57008 => 'ISCII Kannada',
|
---|
268 | 57009 => 'ISCII Malayalam',
|
---|
269 | 57010 => 'ISCII Gujarati',
|
---|
270 | 57011 => 'ISCII Punjabi',
|
---|
271 | 65000 => 'Unicode (UTF-7)',
|
---|
272 | 65001 => 'Unicode (UTF-8)',
|
---|
273 | );
|
---|
274 |
|
---|
275 | # test for file extensions which may be variants of the FPX format
|
---|
276 | # (have seen one password-protected DOCX file that is FPX-like, so assume
|
---|
277 | # that all the rest could be as well)
|
---|
278 | my %fpxFileType = (
|
---|
279 | DOC => 1, DOCX => 1, DOCM => 1,
|
---|
280 | DOT => 1, DOTX => 1, DOTM => 1,
|
---|
281 | POT => 1, POTX => 1, POTM => 1,
|
---|
282 | PPS => 1, PPSX => 1, PPSM => 1,
|
---|
283 | PPT => 1, PPTX => 1, PPTM => 1, THMX => 1,
|
---|
284 | XLA => 1, XLAM => 1,
|
---|
285 | XLS => 1, XLSX => 1, XLSM => 1, XLSB => 1,
|
---|
286 | XLT => 1, XLTX => 1, XLTM => 1,
|
---|
287 | # non MSOffice types
|
---|
288 | FLA => 1, VSD => 1,
|
---|
289 | );
|
---|
290 |
|
---|
291 | %Image::ExifTool::FlashPix::Main = (
|
---|
292 | PROCESS_PROC => \&ProcessFPXR,
|
---|
293 | GROUPS => { 2 => 'Image' },
|
---|
294 | NOTES => q{
|
---|
295 | The FlashPix file format, introduced in 1996, was developed by Kodak,
|
---|
296 | Hewlett-Packard and Microsoft. Internally the FPX file structure mimics
|
---|
297 | that of an old DOS disk with fixed-sized "sectors" (usually 512 bytes) and a
|
---|
298 | "file allocation table" (FAT). No wonder this image format never became
|
---|
299 | popular. However, some of the structures used in FlashPix streams are part
|
---|
300 | of the EXIF specification, and are still being used in the APP2 FPXR segment
|
---|
301 | of JPEG images by some Kodak and Hewlett-Packard digital cameras.
|
---|
302 |
|
---|
303 | ExifTool extracts FlashPix information from both FPX images and the APP2
|
---|
304 | FPXR segment of JPEG images. As well, FlashPix information is extracted
|
---|
305 | from DOC, PPT, XLS (Microsoft Word, PowerPoint and Excel) documents, VSD
|
---|
306 | (Microsoft Visio) drawings, and FLA (Macromedia/Adobe Flash project) files
|
---|
307 | since these are based on the same file format as FlashPix (the Windows
|
---|
308 | Compound Binary File format). See
|
---|
309 | L<http://graphcomp.com/info/specs/livepicture/fpx.pdf> for the FlashPix
|
---|
310 | specification.
|
---|
311 | },
|
---|
312 | "\x05SummaryInformation" => {
|
---|
313 | Name => 'SummaryInfo',
|
---|
314 | SubDirectory => {
|
---|
315 | TagTable => 'Image::ExifTool::FlashPix::SummaryInfo',
|
---|
316 | },
|
---|
317 | },
|
---|
318 | "\x05DocumentSummaryInformation" => {
|
---|
319 | Name => 'DocumentInfo',
|
---|
320 | Multi => 1, # flag to process UserDefined information after this
|
---|
321 | SubDirectory => {
|
---|
322 | TagTable => 'Image::ExifTool::FlashPix::DocumentInfo',
|
---|
323 | },
|
---|
324 | },
|
---|
325 | "\x01CompObj" => {
|
---|
326 | Name => 'CompObj',
|
---|
327 | SubDirectory => {
|
---|
328 | TagTable => 'Image::ExifTool::FlashPix::CompObj',
|
---|
329 | DirStart => 0x1c, # skip stream header
|
---|
330 | },
|
---|
331 | },
|
---|
332 | "\x05Image Info" => {
|
---|
333 | Name => 'ImageInfo',
|
---|
334 | SubDirectory => {
|
---|
335 | TagTable => 'Image::ExifTool::FlashPix::ImageInfo',
|
---|
336 | },
|
---|
337 | },
|
---|
338 | "\x05Image Contents" => {
|
---|
339 | Name => 'Image',
|
---|
340 | SubDirectory => {
|
---|
341 | TagTable => 'Image::ExifTool::FlashPix::Image',
|
---|
342 | },
|
---|
343 | },
|
---|
344 | "Contents" => {
|
---|
345 | Name => 'Contents',
|
---|
346 | Notes => 'found in FLA files; may contain XMP',
|
---|
347 | SubDirectory => {
|
---|
348 | TagTable => 'Image::ExifTool::XMP::Main',
|
---|
349 | ProcessProc => \&ProcessContents,
|
---|
350 | },
|
---|
351 | },
|
---|
352 | "ICC Profile 0001" => {
|
---|
353 | Name => 'ICC_Profile',
|
---|
354 | SubDirectory => {
|
---|
355 | TagTable => 'Image::ExifTool::ICC_Profile::Main',
|
---|
356 | DirStart => 0x1c, # skip stream header
|
---|
357 | },
|
---|
358 | },
|
---|
359 | "\x05Extension List" => {
|
---|
360 | Name => 'Extensions',
|
---|
361 | SubDirectory => {
|
---|
362 | TagTable => 'Image::ExifTool::FlashPix::Extensions',
|
---|
363 | },
|
---|
364 | },
|
---|
365 | 'Subimage 0000 Header' => {
|
---|
366 | Name => 'SubimageHdr',
|
---|
367 | SubDirectory => {
|
---|
368 | TagTable => 'Image::ExifTool::FlashPix::SubimageHdr',
|
---|
369 | DirStart => 0x1c, # skip stream header
|
---|
370 | },
|
---|
371 | },
|
---|
372 | # 'Subimage 0000 Data'
|
---|
373 | "\x05Data Object" => { # plus instance number (ie. " 000000")
|
---|
374 | Name => 'DataObject',
|
---|
375 | SubDirectory => {
|
---|
376 | TagTable => 'Image::ExifTool::FlashPix::DataObject',
|
---|
377 | },
|
---|
378 | },
|
---|
379 | # "\x05Data Object Store" => { # plus instance number (ie. " 000000")
|
---|
380 | "\x05Transform" => { # plus instance number (ie. " 000000")
|
---|
381 | Name => 'Transform',
|
---|
382 | SubDirectory => {
|
---|
383 | TagTable => 'Image::ExifTool::FlashPix::Transform',
|
---|
384 | },
|
---|
385 | },
|
---|
386 | "\x05Operation" => { # plus instance number (ie. " 000000")
|
---|
387 | Name => 'Operation',
|
---|
388 | SubDirectory => {
|
---|
389 | TagTable => 'Image::ExifTool::FlashPix::Operation',
|
---|
390 | },
|
---|
391 | },
|
---|
392 | "\x05Global Info" => {
|
---|
393 | Name => 'GlobalInfo',
|
---|
394 | SubDirectory => {
|
---|
395 | TagTable => 'Image::ExifTool::FlashPix::GlobalInfo',
|
---|
396 | },
|
---|
397 | },
|
---|
398 | "\x05Screen Nail" => { # plus class ID (ie. "_bd0100609719a180")
|
---|
399 | Name => 'ScreenNail',
|
---|
400 | Groups => { 2 => 'Other' },
|
---|
401 | # strip off stream header
|
---|
402 | ValueConv => 'length($val) > 0x1c and $val = substr($val, 0x1c); \$val',
|
---|
403 | },
|
---|
404 | "\x05Audio Info" => {
|
---|
405 | Name => 'AudioInfo',
|
---|
406 | SubDirectory => {
|
---|
407 | TagTable => 'Image::ExifTool::FlashPix::AudioInfo',
|
---|
408 | },
|
---|
409 | },
|
---|
410 | 'Audio Stream' => { # plus instance number (ie. " 000000")
|
---|
411 | Name => 'AudioStream',
|
---|
412 | Groups => { 2 => 'Audio' },
|
---|
413 | # strip off stream header
|
---|
414 | ValueConv => 'length($val) > 0x1c and $val = substr($val, 0x1c); \$val',
|
---|
415 | },
|
---|
416 | 'Current User' => { #PH
|
---|
417 | Name => 'CurrentUser',
|
---|
418 | # not sure what the rest of this data is, but extract ASCII name from it - PH
|
---|
419 | ValueConv => q{
|
---|
420 | return undef if length $val < 12;
|
---|
421 | my ($size,$pos) = unpack('x4VV', $val);
|
---|
422 | my $len = $size - $pos - 4;
|
---|
423 | return undef if $len < 0 or length $val < $size + 8;
|
---|
424 | return substr($val, 8 + $pos, $len);
|
---|
425 | },
|
---|
426 | },
|
---|
427 | );
|
---|
428 |
|
---|
429 | # Summary Information properties
|
---|
430 | %Image::ExifTool::FlashPix::SummaryInfo = (
|
---|
431 | PROCESS_PROC => \&ProcessProperties,
|
---|
432 | GROUPS => { 2 => 'Document' },
|
---|
433 | NOTES => q{
|
---|
434 | The Dictionary, CodePage and LocalIndicator tags are common to all FlashPix
|
---|
435 | property tables, even though they are only listed in the SummaryInfo table.
|
---|
436 | },
|
---|
437 | 0x00 => { Name => 'Dictionary', Groups => { 2 => 'Other' }, Binary => 1 },
|
---|
438 | 0x01 => {
|
---|
439 | Name => 'CodePage',
|
---|
440 | Groups => { 2 => 'Other' },
|
---|
441 | PrintConv => \%codePage,
|
---|
442 | },
|
---|
443 | 0x02 => 'Title',
|
---|
444 | 0x03 => 'Subject',
|
---|
445 | 0x04 => { Name => 'Author', Groups => { 2 => 'Author' } },
|
---|
446 | 0x05 => 'Keywords',
|
---|
447 | 0x06 => 'Comments',
|
---|
448 | 0x07 => 'Template',
|
---|
449 | 0x08 => { Name => 'LastModifiedBy', Groups => { 2 => 'Author' } },
|
---|
450 | 0x09 => 'RevisionNumber',
|
---|
451 | 0x0a => { Name => 'TotalEditTime', PrintConv => 'ConvertTimeSpan($val)' }, # (in sec)
|
---|
452 | 0x0b => { Name => 'LastPrinted', Groups => { 2 => 'Time' } },
|
---|
453 | 0x0c => {
|
---|
454 | Name => 'CreateDate',
|
---|
455 | Groups => { 2 => 'Time' },
|
---|
456 | PrintConv => '$self->ConvertDateTime($val)',
|
---|
457 | },
|
---|
458 | 0x0d => {
|
---|
459 | Name => 'ModifyDate',
|
---|
460 | Groups => { 2 => 'Time' },
|
---|
461 | PrintConv => '$self->ConvertDateTime($val)',
|
---|
462 | },
|
---|
463 | 0x0e => 'Pages',
|
---|
464 | 0x0f => 'Words',
|
---|
465 | 0x10 => 'Characters',
|
---|
466 | 0x11 => { Name => 'ThumbnailClip', Binary => 1 },
|
---|
467 | 0x12 => {
|
---|
468 | Name => 'Software',
|
---|
469 | RawConv => '$$self{Software} = $val', # (use to determine file type)
|
---|
470 | },
|
---|
471 | 0x13 => {
|
---|
472 | Name => 'Security',
|
---|
473 | # see http://msdn.microsoft.com/en-us/library/aa379255(VS.85).aspx
|
---|
474 | PrintConv => {
|
---|
475 | 0 => 'None',
|
---|
476 | 1 => 'Password protected',
|
---|
477 | 2 => 'Read-only recommended',
|
---|
478 | 4 => 'Read-only enforced',
|
---|
479 | 8 => 'Locked for annotations',
|
---|
480 | },
|
---|
481 | },
|
---|
482 | 0x80000000 => { Name => 'LocaleIndicator', Groups => { 2 => 'Other' } },
|
---|
483 | );
|
---|
484 |
|
---|
485 | # Document Summary Information properties (ref 4)
|
---|
486 | %Image::ExifTool::FlashPix::DocumentInfo = (
|
---|
487 | PROCESS_PROC => \&ProcessProperties,
|
---|
488 | GROUPS => { 2 => 'Document' },
|
---|
489 | NOTES => q{
|
---|
490 | The DocumentSummaryInformation property set includes a UserDefined property
|
---|
491 | set for which only the Hyperlinks and HyperlinkBase tags are pre-defined.
|
---|
492 | However, ExifTool will also extract any other information found in the
|
---|
493 | UserDefined properties.
|
---|
494 | },
|
---|
495 | 0x02 => 'Category',
|
---|
496 | 0x03 => 'PresentationTarget',
|
---|
497 | 0x04 => 'Bytes',
|
---|
498 | 0x05 => 'Lines',
|
---|
499 | 0x06 => 'Paragraphs',
|
---|
500 | 0x07 => 'Slides',
|
---|
501 | 0x08 => 'Notes',
|
---|
502 | 0x09 => 'HiddenSlides',
|
---|
503 | 0x0a => 'MMClips',
|
---|
504 | 0x0b => {
|
---|
505 | Name => 'ScaleCrop',
|
---|
506 | PrintConv => { 0 => 'No', 1 => 'Yes' },
|
---|
507 | },
|
---|
508 | 0x0c => 'HeadingPairs',
|
---|
509 | 0x0d => 'TitleOfParts',
|
---|
510 | 0x0e => 'Manager',
|
---|
511 | 0x0f => 'Company',
|
---|
512 | 0x10 => {
|
---|
513 | Name => 'LinksUpToDate',
|
---|
514 | PrintConv => { 0 => 'No', 1 => 'Yes' },
|
---|
515 | },
|
---|
516 | 0x11 => 'CharCountWithSpaces',
|
---|
517 | # 0x12 ?
|
---|
518 | 0x13 => { #PH (unconfirmed)
|
---|
519 | Name => 'SharedDoc',
|
---|
520 | PrintConv => { 0 => 'No', 1 => 'Yes' },
|
---|
521 | },
|
---|
522 | # 0x14 ?
|
---|
523 | # 0x15 ?
|
---|
524 | 0x16 => {
|
---|
525 | Name => 'HyperlinksChanged',
|
---|
526 | PrintConv => { 0 => 'No', 1 => 'Yes' },
|
---|
527 | },
|
---|
528 | 0x17 => { #PH (unconfirmed handling of lower 16 bits)
|
---|
529 | Name => 'AppVersion',
|
---|
530 | ValueConv => 'sprintf("%d.%.4d",$val >> 16, $val & 0xffff)',
|
---|
531 | },
|
---|
532 | '_PID_LINKBASE' => {
|
---|
533 | Name => 'HyperlinkBase',
|
---|
534 | ValueConv => '$self->Decode($val, "UCS2","II")',
|
---|
535 | },
|
---|
536 | '_PID_HLINKS' => {
|
---|
537 | Name => 'Hyperlinks',
|
---|
538 | RawConv => \&ProcessHyperlinks,
|
---|
539 | },
|
---|
540 | );
|
---|
541 |
|
---|
542 | # Image Information properties
|
---|
543 | %Image::ExifTool::FlashPix::ImageInfo = (
|
---|
544 | PROCESS_PROC => \&ProcessProperties,
|
---|
545 | GROUPS => { 2 => 'Image' },
|
---|
546 | 0x21000000 => {
|
---|
547 | Name => 'FileSource',
|
---|
548 | PrintConv => {
|
---|
549 | 1 => 'Film Scanner',
|
---|
550 | 2 => 'Reflection Print Scanner',
|
---|
551 | 3 => 'Digital Camera',
|
---|
552 | 4 => 'Video Capture',
|
---|
553 | 5 => 'Computer Graphics',
|
---|
554 | },
|
---|
555 | },
|
---|
556 | 0x21000001 => {
|
---|
557 | Name => 'SceneType',
|
---|
558 | PrintConv => {
|
---|
559 | 1 => 'Original Scene',
|
---|
560 | 2 => 'Second Generation Scene',
|
---|
561 | 3 => 'Digital Scene Generation',
|
---|
562 | },
|
---|
563 | },
|
---|
564 | 0x21000002 => 'CreationPathVector',
|
---|
565 | 0x21000003 => 'SoftwareRelease',
|
---|
566 | 0x21000004 => 'UserDefinedID',
|
---|
567 | 0x21000005 => 'SharpnessApproximation',
|
---|
568 | 0x22000000 => { Name => 'Copyright', Groups => { 2 => 'Author' } },
|
---|
569 | 0x22000001 => { Name => 'OriginalImageBroker', Groups => { 2 => 'Author' } },
|
---|
570 | 0x22000002 => { Name => 'DigitalImageBroker', Groups => { 2 => 'Author' } },
|
---|
571 | 0x22000003 => { Name => 'Authorship', Groups => { 2 => 'Author' } },
|
---|
572 | 0x22000004 => { Name => 'IntellectualPropertyNotes', Groups => { 2 => 'Author' } },
|
---|
573 | 0x23000000 => {
|
---|
574 | Name => 'TestTarget',
|
---|
575 | PrintConv => {
|
---|
576 | 1 => 'Color Chart',
|
---|
577 | 2 => 'Gray Card',
|
---|
578 | 3 => 'Grayscale',
|
---|
579 | 4 => 'Resolution Chart',
|
---|
580 | 5 => 'Inch Scale',
|
---|
581 | 6 => 'Centimeter Scale',
|
---|
582 | 7 => 'Millimeter Scale',
|
---|
583 | 8 => 'Micrometer Scale',
|
---|
584 | },
|
---|
585 | },
|
---|
586 | 0x23000002 => 'GroupCaption',
|
---|
587 | 0x23000003 => 'CaptionText',
|
---|
588 | 0x23000004 => 'People',
|
---|
589 | 0x23000007 => 'Things',
|
---|
590 | 0x2300000A => {
|
---|
591 | Name => 'DateTimeOriginal',
|
---|
592 | Description => 'Date/Time Original',
|
---|
593 | Groups => { 2 => 'Time' },
|
---|
594 | PrintConv => '$self->ConvertDateTime($val)',
|
---|
595 | },
|
---|
596 | 0x2300000B => 'Events',
|
---|
597 | 0x2300000C => 'Places',
|
---|
598 | 0x2300000F => 'ContentDescriptionNotes',
|
---|
599 | 0x24000000 => { Name => 'Make', Groups => { 2 => 'Camera' } },
|
---|
600 | 0x24000001 => {
|
---|
601 | Name => 'Model',
|
---|
602 | Description => 'Camera Model Name',
|
---|
603 | Groups => { 2 => 'Camera' },
|
---|
604 | },
|
---|
605 | 0x24000002 => { Name => 'SerialNumber', Groups => { 2 => 'Camera' } },
|
---|
606 | 0x25000000 => {
|
---|
607 | Name => 'CreateDate',
|
---|
608 | Groups => { 2 => 'Time' },
|
---|
609 | PrintConv => '$self->ConvertDateTime($val)',
|
---|
610 | },
|
---|
611 | 0x25000001 => {
|
---|
612 | Name => 'ExposureTime',
|
---|
613 | PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)',
|
---|
614 | },
|
---|
615 | 0x25000002 => {
|
---|
616 | Name => 'FNumber',
|
---|
617 | PrintConv => 'sprintf("%.1f",$val)',
|
---|
618 | },
|
---|
619 | 0x25000003 => {
|
---|
620 | Name => 'ExposureProgram',
|
---|
621 | Groups => { 2 => 'Camera' },
|
---|
622 | # use PrintConv of corresponding EXIF tag
|
---|
623 | PrintConv => $Image::ExifTool::Exif::Main{0x8822}->{PrintConv},
|
---|
624 | },
|
---|
625 | 0x25000004 => 'BrightnessValue',
|
---|
626 | 0x25000005 => 'ExposureCompensation',
|
---|
627 | 0x25000006 => {
|
---|
628 | Name => 'SubjectDistance',
|
---|
629 | Groups => { 2 => 'Camera' },
|
---|
630 | PrintConv => 'sprintf("%.3f m", $val)',
|
---|
631 | },
|
---|
632 | 0x25000007 => {
|
---|
633 | Name => 'MeteringMode',
|
---|
634 | Groups => { 2 => 'Camera' },
|
---|
635 | PrintConv => $Image::ExifTool::Exif::Main{0x9207}->{PrintConv},
|
---|
636 | },
|
---|
637 | 0x25000008 => {
|
---|
638 | Name => 'LightSource',
|
---|
639 | Groups => { 2 => 'Camera' },
|
---|
640 | PrintConv => $Image::ExifTool::Exif::Main{0x9208}->{PrintConv},
|
---|
641 | },
|
---|
642 | 0x25000009 => {
|
---|
643 | Name => 'FocalLength',
|
---|
644 | Groups => { 2 => 'Camera' },
|
---|
645 | PrintConv => 'sprintf("%.1f mm",$val)',
|
---|
646 | },
|
---|
647 | 0x2500000A => {
|
---|
648 | Name => 'MaxApertureValue',
|
---|
649 | Groups => { 2 => 'Camera' },
|
---|
650 | ValueConv => '2 ** ($val / 2)',
|
---|
651 | PrintConv => 'sprintf("%.1f",$val)',
|
---|
652 | },
|
---|
653 | 0x2500000B => {
|
---|
654 | Name => 'Flash',
|
---|
655 | Groups => { 2 => 'Camera' },
|
---|
656 | PrintConv => {
|
---|
657 | 1 => 'No Flash',
|
---|
658 | 2 => 'Flash Fired',
|
---|
659 | },
|
---|
660 | },
|
---|
661 | 0x2500000C => {
|
---|
662 | Name => 'FlashEnergy',
|
---|
663 | Groups => { 2 => 'Camera' },
|
---|
664 | },
|
---|
665 | 0x2500000D => {
|
---|
666 | Name => 'FlashReturn',
|
---|
667 | Groups => { 2 => 'Camera' },
|
---|
668 | PrintConv => {
|
---|
669 | 1 => 'Subject Outside Flash Range',
|
---|
670 | 2 => 'Subject Inside Flash Range',
|
---|
671 | },
|
---|
672 | },
|
---|
673 | 0x2500000E => {
|
---|
674 | Name => 'BackLight',
|
---|
675 | PrintConv => {
|
---|
676 | 1 => 'Front Lit',
|
---|
677 | 2 => 'Back Lit 1',
|
---|
678 | 3 => 'Back Lit 2',
|
---|
679 | },
|
---|
680 | },
|
---|
681 | 0x2500000F => { Name => 'SubjectLocation', Groups => { 2 => 'Camera' } },
|
---|
682 | 0x25000010 => 'ExposureIndex',
|
---|
683 | 0x25000011 => {
|
---|
684 | Name => 'SpecialEffectsOpticalFilter',
|
---|
685 | PrintConv => {
|
---|
686 | 1 => 'None',
|
---|
687 | 2 => 'Colored',
|
---|
688 | 3 => 'Diffusion',
|
---|
689 | 4 => 'Multi-image',
|
---|
690 | 5 => 'Polarizing',
|
---|
691 | 6 => 'Split-field',
|
---|
692 | 7 => 'Star',
|
---|
693 | },
|
---|
694 | },
|
---|
695 | 0x25000012 => 'PerPictureNotes',
|
---|
696 | 0x26000000 => {
|
---|
697 | Name => 'SensingMethod',
|
---|
698 | Groups => { 2 => 'Camera' },
|
---|
699 | PrintConv => $Image::ExifTool::Exif::Main{0x9217}->{PrintConv},
|
---|
700 | },
|
---|
701 | 0x26000001 => { Name => 'FocalPlaneXResolution', Groups => { 2 => 'Camera' } },
|
---|
702 | 0x26000002 => { Name => 'FocalPlaneYResolution', Groups => { 2 => 'Camera' } },
|
---|
703 | 0x26000003 => {
|
---|
704 | Name => 'FocalPlaneResolutionUnit',
|
---|
705 | Groups => { 2 => 'Camera' },
|
---|
706 | PrintConv => $Image::ExifTool::Exif::Main{0xa210}->{PrintConv},
|
---|
707 | },
|
---|
708 | 0x26000004 => 'SpatialFrequencyResponse',
|
---|
709 | 0x26000005 => 'CFAPattern',
|
---|
710 | 0x27000001 => {
|
---|
711 | Name => 'FilmCategory',
|
---|
712 | PrintConv => {
|
---|
713 | 1 => 'Negative B&W',
|
---|
714 | 2 => 'Negative Color',
|
---|
715 | 3 => 'Reversal B&W',
|
---|
716 | 4 => 'Reversal Color',
|
---|
717 | 5 => 'Chromagenic',
|
---|
718 | 6 => 'Internegative B&W',
|
---|
719 | 7 => 'Internegative Color',
|
---|
720 | },
|
---|
721 | },
|
---|
722 | 0x26000007 => 'ISO',
|
---|
723 | 0x26000008 => 'Opto-ElectricConvFactor',
|
---|
724 | 0x27000000 => 'FilmBrand',
|
---|
725 | 0x27000001 => 'FilmCategory',
|
---|
726 | 0x27000002 => 'FilmSize',
|
---|
727 | 0x27000003 => 'FilmRollNumber',
|
---|
728 | 0x27000004 => 'FilmFrameNumber',
|
---|
729 | 0x29000000 => 'OriginalScannedImageSize',
|
---|
730 | 0x29000001 => 'OriginalDocumentSize',
|
---|
731 | 0x29000002 => {
|
---|
732 | Name => 'OriginalMedium',
|
---|
733 | PrintConv => {
|
---|
734 | 1 => 'Continuous Tone Image',
|
---|
735 | 2 => 'Halftone Image',
|
---|
736 | 3 => 'Line Art',
|
---|
737 | },
|
---|
738 | },
|
---|
739 | 0x29000003 => {
|
---|
740 | Name => 'TypeOfOriginal',
|
---|
741 | PrintConv => {
|
---|
742 | 1 => 'B&W Print',
|
---|
743 | 2 => 'Color Print',
|
---|
744 | 3 => 'B&W Document',
|
---|
745 | 4 => 'Color Document',
|
---|
746 | },
|
---|
747 | },
|
---|
748 | 0x28000000 => 'ScannerMake',
|
---|
749 | 0x28000001 => 'ScannerModel',
|
---|
750 | 0x28000002 => 'ScannerSerialNumber',
|
---|
751 | 0x28000003 => 'ScanSoftware',
|
---|
752 | 0x28000004 => { Name => 'ScanSoftwareRevisionDate', Groups => { 2 => 'Time' } },
|
---|
753 | 0x28000005 => 'ServiceOrganizationName',
|
---|
754 | 0x28000006 => 'ScanOperatorID',
|
---|
755 | 0x28000008 => {
|
---|
756 | Name => 'ScanDate',
|
---|
757 | Groups => { 2 => 'Time' },
|
---|
758 | PrintConv => '$self->ConvertDateTime($val)',
|
---|
759 | },
|
---|
760 | 0x28000009 => {
|
---|
761 | Name => 'ModifyDate',
|
---|
762 | Groups => { 2 => 'Time' },
|
---|
763 | PrintConv => '$self->ConvertDateTime($val)',
|
---|
764 | },
|
---|
765 | 0x2800000A => 'ScannerPixelSize',
|
---|
766 | );
|
---|
767 |
|
---|
768 | # Image Contents properties
|
---|
769 | %Image::ExifTool::FlashPix::Image = (
|
---|
770 | PROCESS_PROC => \&ProcessProperties,
|
---|
771 | GROUPS => { 2 => 'Image' },
|
---|
772 | # VARS storage is used as a hash lookup for tagID's which aren't constant.
|
---|
773 | # The key is a mask for significant bits of the tagID, and the value
|
---|
774 | # is a lookup for tagID's for which this mask is valid.
|
---|
775 | VARS => {
|
---|
776 | # ID's are different for each subimage
|
---|
777 | 0xff00ffff => {
|
---|
778 | 0x02000000=>1, 0x02000001=>1, 0x02000002=>1, 0x02000003=>1,
|
---|
779 | 0x02000004=>1, 0x02000005=>1, 0x02000006=>1, 0x02000007=>1,
|
---|
780 | 0x03000001=>1,
|
---|
781 | },
|
---|
782 | },
|
---|
783 | 0x01000000 => 'NumberOfResolutions',
|
---|
784 | 0x01000002 => 'ImageWidth', # width of highest resolution image
|
---|
785 | 0x01000003 => 'ImageHeight',
|
---|
786 | 0x01000004 => 'DefaultDisplayHeight',
|
---|
787 | 0x01000005 => 'DefaultDisplayWidth',
|
---|
788 | 0x01000006 => {
|
---|
789 | Name => 'DisplayUnits',
|
---|
790 | PrintConv => {
|
---|
791 | 0 => 'inches',
|
---|
792 | 1 => 'meters',
|
---|
793 | 2 => 'cm',
|
---|
794 | 3 => 'mm',
|
---|
795 | },
|
---|
796 | },
|
---|
797 | 0x02000000 => 'SubimageWidth',
|
---|
798 | 0x02000001 => 'SubimageHeight',
|
---|
799 | 0x02000002 => {
|
---|
800 | Name => 'SubimageColor',
|
---|
801 | # decode only component count and color space of first component
|
---|
802 | ValueConv => 'sprintf("%.2x %.4x", unpack("x4vx4v",$val))',
|
---|
803 | PrintConv => {
|
---|
804 | '01 0000' => 'Opacity Only',
|
---|
805 | '01 8000' => 'Opacity Only (uncalibrated)',
|
---|
806 | '01 0001' => 'Monochrome',
|
---|
807 | '01 8001' => 'Monochrome (uncalibrated)',
|
---|
808 | '03 0002' => 'YCbCr',
|
---|
809 | '03 8002' => 'YCbCr (uncalibrated)',
|
---|
810 | '03 0003' => 'RGB',
|
---|
811 | '03 8003' => 'RGB (uncalibrated)',
|
---|
812 | '04 0002' => 'YCbCr with Opacity',
|
---|
813 | '04 8002' => 'YCbCr with Opacity (uncalibrated)',
|
---|
814 | '04 0003' => 'RGB with Opacity',
|
---|
815 | '04 8003' => 'RGB with Opacity (uncalibrated)',
|
---|
816 | },
|
---|
817 | },
|
---|
818 | 0x02000003 => {
|
---|
819 | Name => 'SubimageNumericalFormat',
|
---|
820 | PrintConv => {
|
---|
821 | 17 => '8-bit, Unsigned',
|
---|
822 | 18 => '16-bit, Unsigned',
|
---|
823 | 19 => '32-bit, Unsigned',
|
---|
824 | },
|
---|
825 | },
|
---|
826 | 0x02000004 => {
|
---|
827 | Name => 'DecimationMethod',
|
---|
828 | PrintConv => {
|
---|
829 | 0 => 'None (Full-sized Image)',
|
---|
830 | 8 => '8-point Prefilter',
|
---|
831 | },
|
---|
832 | },
|
---|
833 | 0x02000005 => 'DecimationPrefilterWidth',
|
---|
834 | 0x02000007 => 'SubimageICC_Profile',
|
---|
835 | 0x03000001 => { Name => 'JPEGTables', Binary => 1 },
|
---|
836 | 0x03000002 => 'MaxJPEGTableIndex',
|
---|
837 | );
|
---|
838 |
|
---|
839 | # Extension List properties
|
---|
840 | %Image::ExifTool::FlashPix::Extensions = (
|
---|
841 | PROCESS_PROC => \&ProcessProperties,
|
---|
842 | GROUPS => { 2 => 'Other' },
|
---|
843 | VARS => {
|
---|
844 | # ID's are different for each extension type
|
---|
845 | 0x0000ffff => {
|
---|
846 | 0x0001=>1, 0x0002=>1, 0x0003=>1, 0x0004=>1,
|
---|
847 | 0x0005=>1, 0x0006=>1, 0x0007=>1, 0x1000=>1,
|
---|
848 | 0x2000=>1, 0x2001=>1, 0x3000=>1, 0x4000=>1,
|
---|
849 | },
|
---|
850 | 0x0000f00f => { 0x3001=>1, 0x3002=>1 },
|
---|
851 | },
|
---|
852 | 0x10000000 => 'UsedExtensionNumbers',
|
---|
853 | 0x0001 => 'ExtensionName',
|
---|
854 | 0x0002 => 'ExtensionClassID',
|
---|
855 | 0x0003 => {
|
---|
856 | Name => 'ExtensionPersistence',
|
---|
857 | PrintConv => {
|
---|
858 | 0 => 'Always Valid',
|
---|
859 | 1 => 'Invalidated By Modification',
|
---|
860 | 2 => 'Potentially Invalidated By Modification',
|
---|
861 | },
|
---|
862 | },
|
---|
863 | 0x0004 => { Name => 'ExtensionCreateDate', Groups => { 2 => 'Time' } },
|
---|
864 | 0x0005 => { Name => 'ExtensionModifyDate', Groups => { 2 => 'Time' } },
|
---|
865 | 0x0006 => 'CreatingApplication',
|
---|
866 | 0x0007 => 'ExtensionDescription',
|
---|
867 | 0x1000 => 'Storage-StreamPathname',
|
---|
868 | 0x2000 => 'FlashPixStreamPathname',
|
---|
869 | 0x2001 => 'FlashPixStreamFieldOffset',
|
---|
870 | 0x3000 => 'PropertySetPathname',
|
---|
871 | 0x3001 => 'PropertySetIDCodes',
|
---|
872 | 0x3002 => 'PropertyVectorElements',
|
---|
873 | 0x4000 => 'SubimageResolutions',
|
---|
874 | );
|
---|
875 |
|
---|
876 | # Subimage Header tags
|
---|
877 | %Image::ExifTool::FlashPix::SubimageHdr = (
|
---|
878 | PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
---|
879 | FORMAT => 'int32u',
|
---|
880 | # 0 => 'HeaderLength',
|
---|
881 | 1 => 'SubimageWidth',
|
---|
882 | 2 => 'SubimageHeight',
|
---|
883 | 3 => 'SubimageTileCount',
|
---|
884 | 4 => 'SubimageTileWidth',
|
---|
885 | 5 => 'SubimageTileHeight',
|
---|
886 | 6 => 'NumChannels',
|
---|
887 | # 7 => 'TileHeaderOffset',
|
---|
888 | # 8 => 'TileHeaderLength',
|
---|
889 | # ... followed by tile header table
|
---|
890 | );
|
---|
891 |
|
---|
892 | # Data Object properties
|
---|
893 | %Image::ExifTool::FlashPix::DataObject = (
|
---|
894 | PROCESS_PROC => \&ProcessProperties,
|
---|
895 | GROUPS => { 2 => 'Other' },
|
---|
896 | 0x00010000 => 'DataObjectID',
|
---|
897 | 0x00010002 => 'LockedPropertyList',
|
---|
898 | 0x00010003 => 'DataObjectTitle',
|
---|
899 | 0x00010004 => 'LastModifier',
|
---|
900 | 0x00010005 => 'RevisionNumber',
|
---|
901 | 0x00010006 => { Name => 'DataCreateDate', Groups => { 2 => 'Time' } },
|
---|
902 | 0x00010007 => { Name => 'DataModifyDate', Groups => { 2 => 'Time' } },
|
---|
903 | 0x00010008 => 'CreatingApplication',
|
---|
904 | 0x00010100 => {
|
---|
905 | Name => 'DataObjectStatus',
|
---|
906 | PrintConv => q{
|
---|
907 | ($val & 0x0000ffff ? 'Exists' : 'Does Not Exist') .
|
---|
908 | ', ' . ($val & 0xffff0000 ? 'Not ' : '') . 'Purgeable'
|
---|
909 | },
|
---|
910 | },
|
---|
911 | 0x00010101 => {
|
---|
912 | Name => 'CreatingTransform',
|
---|
913 | PrintConv => '$val ? $val : "Source Image"',
|
---|
914 | },
|
---|
915 | 0x00010102 => 'UsingTransforms',
|
---|
916 | 0x10000000 => 'CachedImageHeight',
|
---|
917 | 0x10000001 => 'CachedImageWidth',
|
---|
918 | );
|
---|
919 |
|
---|
920 | # Transform properties
|
---|
921 | %Image::ExifTool::FlashPix::Transform = (
|
---|
922 | PROCESS_PROC => \&ProcessProperties,
|
---|
923 | GROUPS => { 2 => 'Other' },
|
---|
924 | 0x00010000 => 'TransformNodeID',
|
---|
925 | 0x00010001 => 'OperationClassID',
|
---|
926 | 0x00010002 => 'LockedPropertyList',
|
---|
927 | 0x00010003 => 'TransformTitle',
|
---|
928 | 0x00010004 => 'LastModifier',
|
---|
929 | 0x00010005 => 'RevisionNumber',
|
---|
930 | 0x00010006 => { Name => 'TransformCreateDate', Groups => { 2 => 'Time' } },
|
---|
931 | 0x00010007 => { Name => 'TransformModifyDate', Groups => { 2 => 'Time' } },
|
---|
932 | 0x00010008 => 'CreatingApplication',
|
---|
933 | 0x00010100 => 'InputDataObjectList',
|
---|
934 | 0x00010101 => 'OutputDataObjectList',
|
---|
935 | 0x00010102 => 'OperationNumber',
|
---|
936 | 0x10000000 => 'ResultAspectRatio',
|
---|
937 | 0x10000001 => 'RectangleOfInterest',
|
---|
938 | 0x10000002 => 'Filtering',
|
---|
939 | 0x10000003 => 'SpatialOrientation',
|
---|
940 | 0x10000004 => 'ColorTwistMatrix',
|
---|
941 | 0x10000005 => 'ContrastAdjustment',
|
---|
942 | );
|
---|
943 |
|
---|
944 | # Operation properties
|
---|
945 | %Image::ExifTool::FlashPix::Operation = (
|
---|
946 | PROCESS_PROC => \&ProcessProperties,
|
---|
947 | 0x00010000 => 'OperationID',
|
---|
948 | );
|
---|
949 |
|
---|
950 | # Global Info properties
|
---|
951 | %Image::ExifTool::FlashPix::GlobalInfo = (
|
---|
952 | PROCESS_PROC => \&ProcessProperties,
|
---|
953 | 0x00010002 => 'LockedPropertyList',
|
---|
954 | 0x00010003 => 'TransformedImageTitle',
|
---|
955 | 0x00010004 => 'LastModifier',
|
---|
956 | 0x00010100 => 'VisibleOutputs',
|
---|
957 | 0x00010101 => 'MaximumImageIndex',
|
---|
958 | 0x00010102 => 'MaximumTransformIndex',
|
---|
959 | 0x00010103 => 'MaximumOperationIndex',
|
---|
960 | );
|
---|
961 |
|
---|
962 | # Audio Info properties
|
---|
963 | %Image::ExifTool::FlashPix::AudioInfo = (
|
---|
964 | PROCESS_PROC => \&ProcessProperties,
|
---|
965 | GROUPS => { 2 => 'Audio' },
|
---|
966 | );
|
---|
967 |
|
---|
968 | # MacroMedia flash contents
|
---|
969 | %Image::ExifTool::FlashPix::Contents = (
|
---|
970 | PROCESS_PROC => \&ProcessProperties,
|
---|
971 | GROUPS => { 2 => 'Image' },
|
---|
972 | );
|
---|
973 |
|
---|
974 | # CompObj tags
|
---|
975 | %Image::ExifTool::FlashPix::CompObj = (
|
---|
976 | PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
---|
977 | GROUPS => { 2 => 'Other' },
|
---|
978 | FORMAT => 'int32u',
|
---|
979 | 0 => { Name => 'CompObjUserTypeLen' },
|
---|
980 | 1 => {
|
---|
981 | Name => 'CompObjUserType',
|
---|
982 | Format => 'string[$val{0}]',
|
---|
983 | RawConv => '$$self{CompObjUserType} = $val', # (use to determine file type)
|
---|
984 | },
|
---|
985 | );
|
---|
986 |
|
---|
987 | # composite FlashPix tags
|
---|
988 | %Image::ExifTool::FlashPix::Composite = (
|
---|
989 | GROUPS => { 2 => 'Image' },
|
---|
990 | PreviewImage => {
|
---|
991 | # extract JPEG preview from ScreenNail if possible
|
---|
992 | Require => {
|
---|
993 | 0 => 'ScreenNail',
|
---|
994 | },
|
---|
995 | Binary => 1,
|
---|
996 | RawConv => q{
|
---|
997 | return undef unless $val[0] =~ /\xff\xd8\xff/g;
|
---|
998 | return substr($val[0], pos($val[0])-3);
|
---|
999 | },
|
---|
1000 | },
|
---|
1001 | );
|
---|
1002 |
|
---|
1003 | # add our composite tags
|
---|
1004 | Image::ExifTool::AddCompositeTags('Image::ExifTool::FlashPix');
|
---|
1005 |
|
---|
1006 | #------------------------------------------------------------------------------
|
---|
1007 | # Process hyperlinks from PID_HYPERLINKS array
|
---|
1008 | # (ref http://msdn.microsoft.com/archive/default.asp?url=/archive/en-us/dnaro97ta/html/msdn_hyper97.asp)
|
---|
1009 | # Inputs: 0) value, 1) ExifTool ref
|
---|
1010 | # Returns: list of hyperlinks
|
---|
1011 | sub ProcessHyperlinks($$)
|
---|
1012 | {
|
---|
1013 | my ($val, $exifTool) = @_;
|
---|
1014 |
|
---|
1015 | # process as an array of VT_VARIANT's
|
---|
1016 | my $dirEnd = length $val;
|
---|
1017 | return undef if $dirEnd < 4;
|
---|
1018 | my $num = Get32u(\$val, 0);
|
---|
1019 | my $valPos = 4;
|
---|
1020 | my ($i, @vals);
|
---|
1021 | for ($i=0; $i<$num; ++$i) {
|
---|
1022 | # read VT_BLOB entries as an array of VT_VARIANT's
|
---|
1023 | my $value = ReadFPXValue($exifTool, \$val, $valPos, VT_VARIANT, $dirEnd);
|
---|
1024 | last unless defined $value;
|
---|
1025 | push @vals, $value;
|
---|
1026 | }
|
---|
1027 | # filter values to extract only the links
|
---|
1028 | my @links;
|
---|
1029 | for ($i=0; $i<@vals; $i+=6) {
|
---|
1030 | push @links, $vals[$i+4]; # get address
|
---|
1031 | $links[-1] .= '#' . $vals[$i+5] if length $vals[$i+5]; # add subaddress
|
---|
1032 | }
|
---|
1033 | return \@links;
|
---|
1034 | }
|
---|
1035 |
|
---|
1036 | #------------------------------------------------------------------------------
|
---|
1037 | # Read FlashPix value
|
---|
1038 | # Inputs: 0) ExifTool ref, 1) data ref, 2) value offset, 3) FPX format number,
|
---|
1039 | # 4) end offset, 5) flag for no padding, 6) code page
|
---|
1040 | # Returns: converted value (or list of values in list context) and updates
|
---|
1041 | # value offset to end of value if successful, or returns undef on error
|
---|
1042 | sub ReadFPXValue($$$$$;$$)
|
---|
1043 | {
|
---|
1044 | my ($exifTool, $dataPt, $valPos, $type, $dirEnd, $noPad, $codePage) = @_;
|
---|
1045 | my @vals;
|
---|
1046 |
|
---|
1047 | my $format = $oleFormat{$type & 0x0fff};
|
---|
1048 | while ($format) {
|
---|
1049 | my $count = 1;
|
---|
1050 | # handle VT_VECTOR types
|
---|
1051 | my $flags = $type & 0xf000;
|
---|
1052 | if ($flags) {
|
---|
1053 | if ($flags == VT_VECTOR) {
|
---|
1054 | $noPad = 1; # values don't seem to be padded inside vectors
|
---|
1055 | my $size = $oleFormatSize{VT_VECTOR};
|
---|
1056 | last if $valPos + $size > $dirEnd;
|
---|
1057 | $count = Get32u($dataPt, $valPos);
|
---|
1058 | push @vals, '' if $count == 0; # allow zero-element vector
|
---|
1059 | $valPos += 4;
|
---|
1060 | } else {
|
---|
1061 | # can't yet handle this property flag
|
---|
1062 | last;
|
---|
1063 | }
|
---|
1064 | }
|
---|
1065 | unless ($format =~ /^VT_/) {
|
---|
1066 | my $size = Image::ExifTool::FormatSize($format) * $count;
|
---|
1067 | last if $valPos + $size > $dirEnd;
|
---|
1068 | @vals = ReadValue($dataPt, $valPos, $format, $count, $size);
|
---|
1069 | # update position to end of value plus padding
|
---|
1070 | $valPos += ($count * $size + 3) & 0xfffffffc;
|
---|
1071 | last;
|
---|
1072 | }
|
---|
1073 | my $size = $oleFormatSize{$format};
|
---|
1074 | my ($item, $val);
|
---|
1075 | for ($item=0; $item<$count; ++$item) {
|
---|
1076 | last if $valPos + $size > $dirEnd;
|
---|
1077 | if ($format eq 'VT_VARIANT') {
|
---|
1078 | my $subType = Get32u($dataPt, $valPos);
|
---|
1079 | $valPos += $size;
|
---|
1080 | $val = ReadFPXValue($exifTool, $dataPt, $valPos, $subType, $dirEnd, $noPad, $codePage);
|
---|
1081 | last unless defined $val;
|
---|
1082 | push @vals, $val;
|
---|
1083 | next; # avoid adding $size to $valPos again
|
---|
1084 | } elsif ($format eq 'VT_FILETIME') {
|
---|
1085 | # get time in seconds
|
---|
1086 | $val = 1e-7 * Image::ExifTool::Get64u($dataPt, $valPos);
|
---|
1087 | # print as date/time if value is greater than one year (PH hack)
|
---|
1088 | if ($val > 365 * 24 * 3600) {
|
---|
1089 | # shift from Jan 1, 1601 to Jan 1, 1970
|
---|
1090 | $val -= 134774 * 24 * 3600 if $val != 0;
|
---|
1091 | $val = Image::ExifTool::ConvertUnixTime($val);
|
---|
1092 | }
|
---|
1093 | } elsif ($format eq 'VT_DATE') {
|
---|
1094 | $val = Image::ExifTool::GetDouble($dataPt, $valPos);
|
---|
1095 | # shift zero from Dec 30, 1899 to Jan 1, 1970 and convert to secs
|
---|
1096 | $val = ($val - 25569) * 24 * 3600 if $val != 0;
|
---|
1097 | $val = Image::ExifTool::ConvertUnixTime($val);
|
---|
1098 | } elsif ($format =~ /STR$/) {
|
---|
1099 | my $len = Get32u($dataPt, $valPos);
|
---|
1100 | $len *= 2 if $format eq 'VT_LPWSTR'; # convert to byte count
|
---|
1101 | last if $valPos + $len + 4 > $dirEnd;
|
---|
1102 | $val = substr($$dataPt, $valPos + 4, $len);
|
---|
1103 | if ($format eq 'VT_LPWSTR') {
|
---|
1104 | # convert wide string from Unicode
|
---|
1105 | $val = $exifTool->Decode($val, 'UCS2');
|
---|
1106 | } elsif ($codePage) {
|
---|
1107 | my $charset = $Image::ExifTool::charsetName{"cp$codePage"};
|
---|
1108 | if ($charset) {
|
---|
1109 | $val = $exifTool->Decode($val, $charset);
|
---|
1110 | } elsif ($codePage eq 1200) { # UTF-16, little endian
|
---|
1111 | $val = $exifTool->Decode(undef, 'UCS2', 'II');
|
---|
1112 | }
|
---|
1113 | }
|
---|
1114 | $val =~ s/\0.*//s; # truncate at null terminator
|
---|
1115 | # update position for string length
|
---|
1116 | # (the spec states that strings should be padded to align
|
---|
1117 | # on even 32-bit boundaries, but this isn't always the case)
|
---|
1118 | $valPos += $noPad ? $len : ($len + 3) & 0xfffffffc;
|
---|
1119 | } elsif ($format eq 'VT_BLOB' or $format eq 'VT_CF') {
|
---|
1120 | my $len = Get32u($dataPt, $valPos);
|
---|
1121 | last if $valPos + $len + 4 > $dirEnd;
|
---|
1122 | $val = substr($$dataPt, $valPos + 4, $len);
|
---|
1123 | # update position for data length plus padding
|
---|
1124 | # (does this padding disappear in arrays too?)
|
---|
1125 | $valPos += ($len + 3) & 0xfffffffc;
|
---|
1126 | } elsif ($format eq 'VT_CLSID') {
|
---|
1127 | $val = Image::ExifTool::ASF::GetGUID(substr($$dataPt, $valPos, $size));
|
---|
1128 | }
|
---|
1129 | $valPos += $size; # update value pointer to end of value
|
---|
1130 | push @vals, $val;
|
---|
1131 | }
|
---|
1132 | # join VT_ values with commas unless we want an array
|
---|
1133 | @vals = ( join $exifTool->Options('ListSep'), @vals ) if @vals > 1 and not wantarray;
|
---|
1134 | last; # didn't really want to loop
|
---|
1135 | }
|
---|
1136 | $_[2] = $valPos; # return updated value position
|
---|
1137 |
|
---|
1138 | if (wantarray) {
|
---|
1139 | return @vals;
|
---|
1140 | } elsif (@vals > 1) {
|
---|
1141 | return join(' ', @vals);
|
---|
1142 | } else {
|
---|
1143 | return $vals[0];
|
---|
1144 | }
|
---|
1145 | }
|
---|
1146 |
|
---|
1147 | #------------------------------------------------------------------------------
|
---|
1148 | # Scan for XMP in FLA Contents (ref PH)
|
---|
1149 | # Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
|
---|
1150 | # Returns: 1 on success
|
---|
1151 | # Notes: FLA format is proprietary and I couldn't find any documentation,
|
---|
1152 | # so this routine is entirely based on observations from sample files
|
---|
1153 | sub ProcessContents($$$)
|
---|
1154 | {
|
---|
1155 | my ($exifTool, $dirInfo, $tagTablePtr) = @_;
|
---|
1156 | my $dataPt = $$dirInfo{DataPt};
|
---|
1157 | my $isFLA;
|
---|
1158 |
|
---|
1159 | # all of my FLA samples contain "Contents" data, an no other FPX-like samples have
|
---|
1160 | # this, but check the data for a familiar pattern to be sure this is FLA: the
|
---|
1161 | # Contents of all of my FLA samples start with two bytes (0x29,0x38,0x3f,0x43 or 0x47,
|
---|
1162 | # then 0x01) followed by a number of zero bytes (from 0x18 to 0x26 of them, related
|
---|
1163 | # somehow to the value of the first byte), followed by the string "DocumentPage"
|
---|
1164 | $isFLA = 1 if $$dataPt =~ /^..\0+\xff\xff\x01\0\x0d\0CDocumentPage/;
|
---|
1165 |
|
---|
1166 | # do a brute-force scan of the "Contents" for UTF-16 XMP
|
---|
1167 | # (this may always be little-endian, but allow for either endianness)
|
---|
1168 | if ($$dataPt =~ /<\0\?\0x\0p\0a\0c\0k\0e\0t\0 \0b\0e\0g\0i\0n\0=\0['"](\0\xff\xfe|\xfe\xff)/g) {
|
---|
1169 | $$dirInfo{DirStart} = pos($$dataPt) - 36;
|
---|
1170 | if ($$dataPt =~ /<\0\?\0x\0p\0a\0c\0k\0e\0t\0 \0e\0n\0d\0=\0['"]\0[wr]\0['"]\0\?\0>\0?/g) {
|
---|
1171 | $$dirInfo{DirLen} = pos($$dataPt) - $$dirInfo{DirStart};
|
---|
1172 | Image::ExifTool::XMP::ProcessXMP($exifTool, $dirInfo, $tagTablePtr);
|
---|
1173 | # override format if not already FLA but XMP-dc:Format indicates it is
|
---|
1174 | $isFLA = 1 if $$exifTool{FILE_TYPE} ne 'FLA' and $$exifTool{VALUE}{Format} and
|
---|
1175 | $$exifTool{VALUE}{Format} eq 'application/vnd.adobe.fla';
|
---|
1176 | }
|
---|
1177 | }
|
---|
1178 | $exifTool->OverrideFileType('FLA') if $isFLA;
|
---|
1179 | return 1;
|
---|
1180 | }
|
---|
1181 |
|
---|
1182 | #------------------------------------------------------------------------------
|
---|
1183 | # Check FPX byte order mark (BOM) and set byte order appropriately
|
---|
1184 | # Inputs: 0) data ref, 1) offset to BOM
|
---|
1185 | # Returns: true on success
|
---|
1186 | sub CheckBOM($$)
|
---|
1187 | {
|
---|
1188 | my ($dataPt, $pos) = @_;
|
---|
1189 | my $bom = Get16u($dataPt, $pos);
|
---|
1190 | return 1 if $bom == 0xfffe;
|
---|
1191 | return 0 unless $bom == 0xfeff;
|
---|
1192 | ToggleByteOrder();
|
---|
1193 | return 1;
|
---|
1194 | }
|
---|
1195 |
|
---|
1196 | #------------------------------------------------------------------------------
|
---|
1197 | # Process FlashPix properties
|
---|
1198 | # Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
|
---|
1199 | # Returns: 1 on success
|
---|
1200 | sub ProcessProperties($$$)
|
---|
1201 | {
|
---|
1202 | my ($exifTool, $dirInfo, $tagTablePtr) = @_;
|
---|
1203 | my $dataPt = $$dirInfo{DataPt};
|
---|
1204 | my $pos = $$dirInfo{DirStart} || 0;
|
---|
1205 | my $dirLen = $$dirInfo{DirLen} || length($$dataPt) - $pos;
|
---|
1206 | my $dirEnd = $pos + $dirLen;
|
---|
1207 | my $verbose = $exifTool->Options('Verbose');
|
---|
1208 | my $n;
|
---|
1209 |
|
---|
1210 | if ($dirLen < 48) {
|
---|
1211 | $exifTool->Warn('Truncated FPX properties');
|
---|
1212 | return 0;
|
---|
1213 | }
|
---|
1214 | # check and set our byte order if necessary
|
---|
1215 | unless (CheckBOM($dataPt, $pos)) {
|
---|
1216 | $exifTool->Warn('Bad FPX property byte order mark');
|
---|
1217 | return 0;
|
---|
1218 | }
|
---|
1219 | # get position of start of section
|
---|
1220 | $pos = Get32u($dataPt, $pos + 44);
|
---|
1221 | if ($pos < 48) {
|
---|
1222 | $exifTool->Warn('Bad FPX property section offset');
|
---|
1223 | return 0;
|
---|
1224 | }
|
---|
1225 | for ($n=0; $n<2; ++$n) {
|
---|
1226 | my %dictionary; # dictionary to translate user-defined properties
|
---|
1227 | my $codePage;
|
---|
1228 | last if $pos + 8 > $dirEnd;
|
---|
1229 | # read property section header
|
---|
1230 | my $size = Get32u($dataPt, $pos);
|
---|
1231 | last unless $size;
|
---|
1232 | my $numEntries = Get32u($dataPt, $pos + 4);
|
---|
1233 | $verbose and $exifTool->VerboseDir('Property Info', $numEntries, $size);
|
---|
1234 | if ($pos + 8 + 8 * $numEntries > $dirEnd) {
|
---|
1235 | $exifTool->Warn('Truncated property list');
|
---|
1236 | last;
|
---|
1237 | }
|
---|
1238 | my $index;
|
---|
1239 | for ($index=0; $index<$numEntries; ++$index) {
|
---|
1240 | my $entry = $pos + 8 + 8 * $index;
|
---|
1241 | my $tag = Get32u($dataPt, $entry);
|
---|
1242 | my $offset = Get32u($dataPt, $entry + 4);
|
---|
1243 | my $valStart = $pos + 4 + $offset;
|
---|
1244 | last if $valStart >= $dirEnd;
|
---|
1245 | my $valPos = $valStart;
|
---|
1246 | my $type = Get32u($dataPt, $pos + $offset);
|
---|
1247 | if ($tag == 0) {
|
---|
1248 | # read dictionary to get tag name lookup for this property set
|
---|
1249 | my $i;
|
---|
1250 | for ($i=0; $i<$type; ++$i) {
|
---|
1251 | last if $valPos + 8 > $dirEnd;
|
---|
1252 | $tag = Get32u($dataPt, $valPos);
|
---|
1253 | my $len = Get32u($dataPt, $valPos + 4);
|
---|
1254 | $valPos += 8 + $len;
|
---|
1255 | last if $valPos > $dirEnd;
|
---|
1256 | my $name = substr($$dataPt, $valPos - $len, $len);
|
---|
1257 | $name =~ s/\0.*//s;
|
---|
1258 | next unless length $name;
|
---|
1259 | $dictionary{$tag} = $name;
|
---|
1260 | next if $$tagTablePtr{$name};
|
---|
1261 | $tag = $name;
|
---|
1262 | $name =~ s/(^| )([a-z])/\U$2/g; # start with uppercase
|
---|
1263 | $name =~ tr/-_a-zA-Z0-9//dc; # remove illegal characters
|
---|
1264 | next unless length $name;
|
---|
1265 | $exifTool->VPrint(0, "$$exifTool{INDENT}\[adding $name]\n") if $verbose;
|
---|
1266 | Image::ExifTool::AddTagToTable($tagTablePtr, $tag, { Name => $name });
|
---|
1267 | }
|
---|
1268 | next;
|
---|
1269 | }
|
---|
1270 | # use tag name from dictionary if available
|
---|
1271 | my ($custom, $val);
|
---|
1272 | if (defined $dictionary{$tag}) {
|
---|
1273 | $tag = $dictionary{$tag};
|
---|
1274 | $custom = 1;
|
---|
1275 | }
|
---|
1276 | my @vals = ReadFPXValue($exifTool, $dataPt, $valPos, $type, $dirEnd, undef, $codePage);
|
---|
1277 | @vals or $exifTool->Warn('Error reading property value');
|
---|
1278 | $val = @vals > 1 ? \@vals : $vals[0];
|
---|
1279 | my $format = $type & 0x0fff;
|
---|
1280 | my $flags = $type & 0xf000;
|
---|
1281 | my $formStr = $oleFormat{$format} || "Type $format";
|
---|
1282 | $formStr .= '|' . ($oleFlags{$flags} || sprintf("0x%x",$flags)) if $flags;
|
---|
1283 | my $tagInfo;
|
---|
1284 | # check for common tag ID's: Dictionary, CodePage and LocaleIndicator
|
---|
1285 | # (must be done before masking because masked tags may overlap these ID's)
|
---|
1286 | if (not $custom and ($tag == 1 or $tag == 0x80000000)) {
|
---|
1287 | # get tagInfo from SummaryInfo table
|
---|
1288 | my $summaryTable = GetTagTable('Image::ExifTool::FlashPix::SummaryInfo');
|
---|
1289 | $tagInfo = $exifTool->GetTagInfo($summaryTable, $tag);
|
---|
1290 | if ($tag == 1) {
|
---|
1291 | $val += 0x10000 if $val < 0; # (may be incorrectly stored as int16s)
|
---|
1292 | $codePage = $val; # save code page for translating values
|
---|
1293 | }
|
---|
1294 | } elsif ($$tagTablePtr{$tag}) {
|
---|
1295 | $tagInfo = $exifTool->GetTagInfo($tagTablePtr, $tag);
|
---|
1296 | } elsif ($$tagTablePtr{VARS} and not $custom) {
|
---|
1297 | # mask off insignificant bits of tag ID if necessary
|
---|
1298 | my $masked = $$tagTablePtr{VARS};
|
---|
1299 | my $mask;
|
---|
1300 | foreach $mask (keys %$masked) {
|
---|
1301 | if ($masked->{$mask}->{$tag & $mask}) {
|
---|
1302 | $tagInfo = $exifTool->GetTagInfo($tagTablePtr, $tag & $mask);
|
---|
1303 | last;
|
---|
1304 | }
|
---|
1305 | }
|
---|
1306 | }
|
---|
1307 | $exifTool->HandleTag($tagTablePtr, $tag, $val,
|
---|
1308 | DataPt => $dataPt,
|
---|
1309 | Start => $valStart,
|
---|
1310 | Size => $valPos - $valStart,
|
---|
1311 | Format => $formStr,
|
---|
1312 | Index => $index,
|
---|
1313 | TagInfo => $tagInfo,
|
---|
1314 | Extra => ", type=$type",
|
---|
1315 | );
|
---|
1316 | }
|
---|
1317 | # issue warning if we hit end of property section prematurely
|
---|
1318 | $exifTool->Warn('Truncated property data') if $index < $numEntries;
|
---|
1319 | last unless $$dirInfo{Multi};
|
---|
1320 | $pos += $size;
|
---|
1321 | }
|
---|
1322 |
|
---|
1323 | return 1;
|
---|
1324 | }
|
---|
1325 |
|
---|
1326 | #------------------------------------------------------------------------------
|
---|
1327 | # Load chain of sectors from file
|
---|
1328 | # Inputs: 0) RAF ref, 1) first sector number, 2) FAT ref, 3) sector size, 4) header size
|
---|
1329 | sub LoadChain($$$$$)
|
---|
1330 | {
|
---|
1331 | my ($raf, $sect, $fatPt, $sectSize, $hdrSize) = @_;
|
---|
1332 | return undef unless $raf;
|
---|
1333 | my $chain = '';
|
---|
1334 | my ($buff, %loadedSect);
|
---|
1335 | for (;;) {
|
---|
1336 | last if $sect >= END_OF_CHAIN;
|
---|
1337 | return undef if $loadedSect{$sect}; # avoid infinite loop
|
---|
1338 | $loadedSect{$sect} = 1;
|
---|
1339 | my $offset = $sect * $sectSize + $hdrSize;
|
---|
1340 | return undef unless ($offset <= 0x7fffffff or $$raf{LargeFileSupport}) and
|
---|
1341 | $raf->Seek($offset, 0) and
|
---|
1342 | $raf->Read($buff, $sectSize) == $sectSize;
|
---|
1343 | $chain .= $buff;
|
---|
1344 | # step to next sector in chain
|
---|
1345 | return undef if $sect * 4 > length($$fatPt) - 4;
|
---|
1346 | $sect = Get32u($fatPt, $sect * 4);
|
---|
1347 | }
|
---|
1348 | return $chain;
|
---|
1349 | }
|
---|
1350 |
|
---|
1351 | #------------------------------------------------------------------------------
|
---|
1352 | # Extract information from a JPEG APP2 FPXR segment
|
---|
1353 | # Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
|
---|
1354 | # Returns: 1 on success
|
---|
1355 | sub ProcessFPXR($$$)
|
---|
1356 | {
|
---|
1357 | my ($exifTool, $dirInfo, $tagTablePtr) = @_;
|
---|
1358 | my $dataPt = $$dirInfo{DataPt};
|
---|
1359 | my $dirStart = $$dirInfo{DirStart};
|
---|
1360 | my $dirLen = $$dirInfo{DirLen};
|
---|
1361 | my $verbose = $exifTool->Options('Verbose');
|
---|
1362 |
|
---|
1363 | if ($dirLen < 13) {
|
---|
1364 | $exifTool->Warn('FPXR segment to small');
|
---|
1365 | return 0;
|
---|
1366 | }
|
---|
1367 |
|
---|
1368 | # get version and segment type (version is 0 in all my samples)
|
---|
1369 | my ($vers, $type) = unpack('x5C2', $$dataPt);
|
---|
1370 |
|
---|
1371 | if ($type == 1) { # a "Contents List" segment
|
---|
1372 |
|
---|
1373 | $vers != 0 and $exifTool->Warn("Untested FPXR version $vers");
|
---|
1374 | if ($$exifTool{FPXR}) {
|
---|
1375 | $exifTool->Warn('Multiple FPXR contents lists');
|
---|
1376 | delete $$exifTool{FPXR};
|
---|
1377 | }
|
---|
1378 | my $numEntries = unpack('x7n', $$dataPt);
|
---|
1379 | my @contents;
|
---|
1380 | $verbose and $exifTool->VerboseDir('Contents List', $numEntries);
|
---|
1381 | my $pos = 9;
|
---|
1382 | my $entry;
|
---|
1383 | for ($entry = 0; $entry < $numEntries; ++$entry) {
|
---|
1384 | if ($pos + 4 > $dirLen) {
|
---|
1385 | $exifTool->Warn('Truncated FPXR contents');
|
---|
1386 | return 0;
|
---|
1387 | }
|
---|
1388 | my ($size, $default) = unpack("x${pos}Na", $$dataPt);
|
---|
1389 | pos($$dataPt) = $pos + 5;
|
---|
1390 | # according to the spec, this string is little-endian
|
---|
1391 | # (very odd, since the size word is big-endian),
|
---|
1392 | # and the first char must be '/'
|
---|
1393 | unless ($$dataPt =~ m{\G(/\0(..)*?)\0\0}sg) {
|
---|
1394 | $exifTool->Warn('Invalid FPXR stream name');
|
---|
1395 | return 0;
|
---|
1396 | }
|
---|
1397 | # convert stream pathname to ascii
|
---|
1398 | my $name = Image::ExifTool::Decode(undef, $1, 'UCS2', 'II', 'Latin');
|
---|
1399 | if ($verbose) {
|
---|
1400 | my $psize = ($size == 0xffffffff) ? 'storage' : "$size bytes";
|
---|
1401 | $exifTool->VPrint(0," | $entry) Name: '$name' [$psize]\n");
|
---|
1402 | }
|
---|
1403 | # remove directory specification
|
---|
1404 | $name =~ s{.*/}{}s;
|
---|
1405 | # read storage class ID if necessary
|
---|
1406 | my $classID;
|
---|
1407 | if ($size == 0xffffffff) {
|
---|
1408 | unless ($$dataPt =~ m{(.{16})}sg) {
|
---|
1409 | $exifTool->Warn('Truncated FPXR storage class ID');
|
---|
1410 | return 0;
|
---|
1411 | }
|
---|
1412 | # unpack class ID in case we want to use it sometime
|
---|
1413 | $classID = Image::ExifTool::ASF::GetGUID($1);
|
---|
1414 | }
|
---|
1415 | # update position in list
|
---|
1416 | $pos = pos($$dataPt);
|
---|
1417 | # add to our contents list
|
---|
1418 | push @contents, {
|
---|
1419 | Name => $name,
|
---|
1420 | Size => $size,
|
---|
1421 | Default => $default,
|
---|
1422 | ClassID => $classID,
|
---|
1423 | };
|
---|
1424 | }
|
---|
1425 | # save contents list as $exifTool member variable
|
---|
1426 | # (must do this last so we don't save list on error)
|
---|
1427 | $$exifTool{FPXR} = \@contents;
|
---|
1428 |
|
---|
1429 | } elsif ($type == 2) { # a "Stream Data" segment
|
---|
1430 |
|
---|
1431 | # get the contents list index and stream data offset
|
---|
1432 | my ($index, $offset) = unpack('x7nN', $$dataPt);
|
---|
1433 | my $fpxr = $$exifTool{FPXR};
|
---|
1434 | if ($fpxr and $$fpxr[$index]) {
|
---|
1435 | my $obj = $$fpxr[$index];
|
---|
1436 | # extract stream data (after 13-byte header)
|
---|
1437 | if (not defined $$obj{Stream}) {
|
---|
1438 | # ignore offset for first segment of this type
|
---|
1439 | # (in my sample images, this isn't always zero as one would expect)
|
---|
1440 | $$obj{Stream} = substr($$dataPt, $dirStart+13);
|
---|
1441 | } else {
|
---|
1442 | # add data to the stream at the proper offset
|
---|
1443 | my $pad = $offset - length($$obj{Stream});
|
---|
1444 | if ($pad >= 0) {
|
---|
1445 | if ($pad) {
|
---|
1446 | if ($pad > 0x10000) {
|
---|
1447 | $exifTool->Warn("Bad FPXR stream offset ($offset)");
|
---|
1448 | } else {
|
---|
1449 | # pad with default value to specified offset
|
---|
1450 | $exifTool->Warn("Padding FPXR stream with $pad default bytes",1);
|
---|
1451 | $$obj{Stream} .= ($$obj{Default} x $pad);
|
---|
1452 | }
|
---|
1453 | }
|
---|
1454 | # concatenate data with this stream
|
---|
1455 | $$obj{Stream} .= substr($$dataPt, $dirStart+13);
|
---|
1456 | } else {
|
---|
1457 | $exifTool->Warn("Duplicate FPXR stream data at offset $offset");
|
---|
1458 | substr($$obj{Stream}, $offset, -$pad) = substr($$dataPt, $dirStart+13);
|
---|
1459 | }
|
---|
1460 | }
|
---|
1461 | # save value for this tag if stream is complete
|
---|
1462 | my $len = length $$obj{Stream};
|
---|
1463 | if ($len >= $$obj{Size}) {
|
---|
1464 | if ($verbose) {
|
---|
1465 | $exifTool->VPrint(0, " + [FPXR stream, Contents index $index, $len bytes]\n");
|
---|
1466 | }
|
---|
1467 | if ($len > $$obj{Size}) {
|
---|
1468 | $exifTool->Warn('Extra data in FPXR segment (truncated)');
|
---|
1469 | $$obj{Stream} = substr($$obj{Stream}, 0, $$obj{Size});
|
---|
1470 | }
|
---|
1471 | my $tag = $$obj{Name};
|
---|
1472 | my $tagInfo;
|
---|
1473 | unless ($$tagTablePtr{$tag}) {
|
---|
1474 | # remove instance number or class ID from tag if necessary
|
---|
1475 | $tagInfo = $exifTool->GetTagInfo($tagTablePtr, $1) if
|
---|
1476 | ($tag =~ /(.*) \d{6}$/s and $$tagTablePtr{$1}) or
|
---|
1477 | ($tag =~ /(.*)_[0-9a-f]{16}$/s and $$tagTablePtr{$1});
|
---|
1478 | }
|
---|
1479 | # save the data for this tag
|
---|
1480 | $exifTool->HandleTag($tagTablePtr, $tag, $$obj{Stream},
|
---|
1481 | DataPt => \$$obj{Stream},
|
---|
1482 | TagInfo => $tagInfo,
|
---|
1483 | );
|
---|
1484 | delete $$obj{Stream}; # done with this stream
|
---|
1485 | }
|
---|
1486 | # hack for improperly stored FujiFilm PreviewImage (stored with no contents list)
|
---|
1487 | } elsif ($index == 512 and $dirLen > 60 and ($$exifTool{FujiPreview} or
|
---|
1488 | ($dirLen > 64 and substr($$dataPt, $dirStart+60, 4) eq "\xff\xd8\xff\xdb")))
|
---|
1489 | {
|
---|
1490 | # recombine PreviewImage, skipping unknown 60 byte header
|
---|
1491 | if ($$exifTool{FujiPreview}) {
|
---|
1492 | $$exifTool{FujiPreview} .= substr($$dataPt, $dirStart+60);
|
---|
1493 | } else {
|
---|
1494 | $$exifTool{FujiPreview} = substr($$dataPt, $dirStart+60);
|
---|
1495 | }
|
---|
1496 | } else {
|
---|
1497 | # (Kodak uses index 255 for a free segment in images from some cameras)
|
---|
1498 | $exifTool->Warn("Unlisted FPXR segment (index $index)") if $index != 255;
|
---|
1499 | }
|
---|
1500 |
|
---|
1501 | } elsif ($type ne 3) { # not a "Reserved" segment
|
---|
1502 |
|
---|
1503 | $exifTool->Warn("Unknown FPXR segment (type $type)");
|
---|
1504 |
|
---|
1505 | }
|
---|
1506 |
|
---|
1507 | # clean up if this was the last FPXR segment
|
---|
1508 | if ($$dirInfo{LastFPXR}) {
|
---|
1509 | if ($$exifTool{FPXR}) {
|
---|
1510 | my $obj;
|
---|
1511 | my $i = 0;
|
---|
1512 | foreach $obj (@{$$exifTool{FPXR}}) {
|
---|
1513 | $exifTool->Warn("Missing stream for FPXR object $i") if defined $$obj{Stream};
|
---|
1514 | ++$i;
|
---|
1515 | }
|
---|
1516 | delete $$exifTool{FPXR}; # delete our temporary variables
|
---|
1517 | }
|
---|
1518 | if ($$exifTool{FujiPreview}) {
|
---|
1519 | $exifTool->FoundTag('PreviewImage', $$exifTool{FujiPreview});
|
---|
1520 | delete $$exifTool{FujiPreview};
|
---|
1521 | }
|
---|
1522 | }
|
---|
1523 | return 1;
|
---|
1524 | }
|
---|
1525 |
|
---|
1526 | #------------------------------------------------------------------------------
|
---|
1527 | # Set document number for objects
|
---|
1528 | # Inputs: 0) object hierarchy hash ref, 1) object index, 2) doc number list ref,
|
---|
1529 | # 3) doc numbers used at each level, 4) flag set for metadata levels
|
---|
1530 | sub SetDocNum($$;$$$)
|
---|
1531 | {
|
---|
1532 | my ($hier, $index, $doc, $used, $meta) = @_;
|
---|
1533 | my $obj = $$hier{$index} or return;
|
---|
1534 | return if exists $$obj{DocNum};
|
---|
1535 | $$obj{DocNum} = $doc;
|
---|
1536 | SetDocNum($hier, $$obj{Left}, $doc, $used, $meta) if $$obj{Left};
|
---|
1537 | SetDocNum($hier, $$obj{Right}, $doc, $used, $meta) if $$obj{Right};
|
---|
1538 | if (defined $$obj{Child}) {
|
---|
1539 | $used or $used = [ ];
|
---|
1540 | my @subDoc;
|
---|
1541 | push @subDoc, @$doc if $doc;
|
---|
1542 | # we must dive down 2 levels for each sub-document, so use the
|
---|
1543 | # $meta flag to add a sub-document level only for every 2nd generation
|
---|
1544 | if ($meta) {
|
---|
1545 | my $subNum = ($$used[scalar @subDoc] || 0);
|
---|
1546 | $$used[scalar @subDoc] = $subNum;
|
---|
1547 | push @subDoc, $subNum;
|
---|
1548 | } elsif (@subDoc) {
|
---|
1549 | $subDoc[-1] = ++$$used[$#subDoc];
|
---|
1550 | }
|
---|
1551 | SetDocNum($hier, $$obj{Child}, \@subDoc, $used, not $meta)
|
---|
1552 | }
|
---|
1553 | }
|
---|
1554 |
|
---|
1555 | #------------------------------------------------------------------------------
|
---|
1556 | # Extract information from a FlashPix (FPX) file
|
---|
1557 | # Inputs: 0) ExifTool object ref, 1) dirInfo ref
|
---|
1558 | # Returns: 1 on success, 0 if this wasn't a valid FPX-format file
|
---|
1559 | sub ProcessFPX($$)
|
---|
1560 | {
|
---|
1561 | my ($exifTool, $dirInfo) = @_;
|
---|
1562 | my $raf = $$dirInfo{RAF};
|
---|
1563 | my ($buff, $out, %dumpParms, $oldIndent, $miniStreamBuff);
|
---|
1564 | my ($tag, %hier, %objIndex);
|
---|
1565 |
|
---|
1566 | # read header
|
---|
1567 | return 0 unless $raf->Read($buff,HDR_SIZE) == HDR_SIZE;
|
---|
1568 | # check signature
|
---|
1569 | return 0 unless $buff =~ /^\xd0\xcf\x11\xe0\xa1\xb1\x1a\xe1/;
|
---|
1570 |
|
---|
1571 | # set FileType initially based on file extension (we may override this later)
|
---|
1572 | my $fileType = $exifTool->{FILE_EXT};
|
---|
1573 | $fileType = 'FPX' unless $fileType and $fpxFileType{$fileType};
|
---|
1574 | $exifTool->SetFileType($fileType);
|
---|
1575 | SetByteOrder(substr($buff, 0x1c, 2) eq "\xff\xfe" ? 'MM' : 'II');
|
---|
1576 | my $tagTablePtr = GetTagTable('Image::ExifTool::FlashPix::Main');
|
---|
1577 | my $verbose = $exifTool->Options('Verbose');
|
---|
1578 | # copy LargeFileSupport option to RAF for use in LoadChain
|
---|
1579 | $$raf{LargeFileSupport} = $exifTool->Options('LargeFileSupport');
|
---|
1580 |
|
---|
1581 | my $sectSize = 1 << Get16u(\$buff, 0x1e);
|
---|
1582 | my $miniSize = 1 << Get16u(\$buff, 0x20);
|
---|
1583 | my $fatCount = Get32u(\$buff, 0x2c); # number of FAT sectors
|
---|
1584 | my $dirStart = Get32u(\$buff, 0x30); # first directory sector
|
---|
1585 | my $miniCutoff = Get32u(\$buff, 0x38); # minimum size for big-FAT streams
|
---|
1586 | my $miniStart = Get32u(\$buff, 0x3c); # first sector of mini-FAT
|
---|
1587 | my $miniCount = Get32u(\$buff, 0x40); # number of mini-FAT sectors
|
---|
1588 | my $difStart = Get32u(\$buff, 0x44); # first sector of DIF chain
|
---|
1589 | my $difCount = Get32u(\$buff, 0x48); # number of DIF sectors
|
---|
1590 |
|
---|
1591 | if ($verbose) {
|
---|
1592 | $out = $exifTool->Options('TextOut');
|
---|
1593 | $dumpParms{Out} = $out;
|
---|
1594 | $dumpParms{MaxLen} = 96 if $verbose == 3;
|
---|
1595 | print $out " Sector size=$sectSize\n FAT: Count=$fatCount\n";
|
---|
1596 | print $out " DIR: Start=$dirStart\n";
|
---|
1597 | print $out " MiniFAT: Mini-sector size=$miniSize Start=$miniStart Count=$miniCount Cutoff=$miniCutoff\n";
|
---|
1598 | print $out " DIF FAT: Start=$difStart Count=$difCount\n";
|
---|
1599 | }
|
---|
1600 | #
|
---|
1601 | # load the FAT
|
---|
1602 | #
|
---|
1603 | my $pos = 0x4c;
|
---|
1604 | my $endPos = length($buff);
|
---|
1605 | my $fat = '';
|
---|
1606 | my $fatCountCheck = 0;
|
---|
1607 | for (;;) {
|
---|
1608 | while ($pos <= $endPos - 4) {
|
---|
1609 | my $sect = Get32u(\$buff, $pos);
|
---|
1610 | $pos += 4;
|
---|
1611 | next if $sect == FREE_SECT;
|
---|
1612 | my $offset = $sect * $sectSize + HDR_SIZE;
|
---|
1613 | my $fatSect;
|
---|
1614 | unless ($raf->Seek($offset, 0) and
|
---|
1615 | $raf->Read($fatSect, $sectSize) == $sectSize)
|
---|
1616 | {
|
---|
1617 | $exifTool->Error("Error reading FAT from sector $sect");
|
---|
1618 | return 1;
|
---|
1619 | }
|
---|
1620 | $fat .= $fatSect;
|
---|
1621 | ++$fatCountCheck;
|
---|
1622 | }
|
---|
1623 | last if $difStart >= END_OF_CHAIN;
|
---|
1624 | # read next DIF (Dual Indirect FAT) sector
|
---|
1625 | my $offset = $difStart * $sectSize + HDR_SIZE;
|
---|
1626 | unless ($raf->Seek($offset, 0) and $raf->Read($buff, $sectSize) == $sectSize) {
|
---|
1627 | $exifTool->Error("Error reading DIF sector $difStart");
|
---|
1628 | return 1;
|
---|
1629 | }
|
---|
1630 | # set end of sector information in this DIF
|
---|
1631 | $pos = 0;
|
---|
1632 | $endPos = $sectSize - 4;
|
---|
1633 | # next time around we want to read next DIF in chain
|
---|
1634 | $difStart = Get32u(\$buff, $endPos);
|
---|
1635 | }
|
---|
1636 | if ($fatCountCheck != $fatCount) {
|
---|
1637 | $exifTool->Warn("Bad number of FAT sectors (expected $fatCount but found $fatCountCheck)");
|
---|
1638 | }
|
---|
1639 | #
|
---|
1640 | # load the mini-FAT and the directory
|
---|
1641 | #
|
---|
1642 | my $miniFat = LoadChain($raf, $miniStart, \$fat, $sectSize, HDR_SIZE);
|
---|
1643 | my $dir = LoadChain($raf, $dirStart, \$fat, $sectSize, HDR_SIZE);
|
---|
1644 | unless (defined $miniFat and defined $dir) {
|
---|
1645 | $exifTool->Error('Error reading mini-FAT or directory stream');
|
---|
1646 | return 1;
|
---|
1647 | }
|
---|
1648 | if ($verbose) {
|
---|
1649 | print $out " FAT [",length($fat)," bytes]:\n";
|
---|
1650 | Image::ExifTool::HexDump(\$fat, undef, %dumpParms) if $verbose > 2;
|
---|
1651 | print $out " Mini-FAT [",length($miniFat)," bytes]:\n";
|
---|
1652 | Image::ExifTool::HexDump(\$miniFat, undef, %dumpParms) if $verbose > 2;
|
---|
1653 | print $out " Directory [",length($dir)," bytes]:\n";
|
---|
1654 | Image::ExifTool::HexDump(\$dir, undef, %dumpParms) if $verbose > 2;
|
---|
1655 | }
|
---|
1656 | #
|
---|
1657 | # process the directory
|
---|
1658 | #
|
---|
1659 | if ($verbose) {
|
---|
1660 | $oldIndent = $exifTool->{INDENT};
|
---|
1661 | $exifTool->{INDENT} .= '| ';
|
---|
1662 | $exifTool->VerboseDir('FPX', undef, length $dir);
|
---|
1663 | }
|
---|
1664 | my $miniStream;
|
---|
1665 | $endPos = length($dir);
|
---|
1666 | my $index = 0;
|
---|
1667 |
|
---|
1668 | for ($pos=0; $pos<=$endPos-128; $pos+=128, ++$index) {
|
---|
1669 |
|
---|
1670 | # get directory entry type
|
---|
1671 | # (0=invalid, 1=storage, 2=stream, 3=lockbytes, 4=property, 5=root)
|
---|
1672 | my $type = Get8u(\$dir, $pos + 0x42);
|
---|
1673 | next if $type == 0; # skip invalid entries
|
---|
1674 | if ($type > 5) {
|
---|
1675 | $exifTool->Warn("Invalid directory entry type $type");
|
---|
1676 | last; # rest of directory is probably garbage
|
---|
1677 | }
|
---|
1678 | # get entry name (note: this is supposed to be length in 2-byte
|
---|
1679 | # characters but this isn't what is done in my sample FPX file, so
|
---|
1680 | # be very tolerant of this count -- it's null terminated anyway)
|
---|
1681 | my $len = Get16u(\$dir, $pos + 0x40);
|
---|
1682 | $len > 32 and $len = 32;
|
---|
1683 | $tag = Image::ExifTool::Decode(undef, substr($dir,$pos,$len*2), 'UCS2', 'II', 'Latin');
|
---|
1684 | $tag =~ s/\0.*//s; # truncate at null (in case length was wrong)
|
---|
1685 |
|
---|
1686 | my $sect = Get32u(\$dir, $pos + 0x74); # start sector number
|
---|
1687 | my $size = Get32u(\$dir, $pos + 0x78); # stream length
|
---|
1688 |
|
---|
1689 | # load Ministream (referenced from first directory entry)
|
---|
1690 | unless ($miniStream) {
|
---|
1691 | $miniStreamBuff = LoadChain($raf, $sect, \$fat, $sectSize, HDR_SIZE);
|
---|
1692 | unless (defined $miniStreamBuff) {
|
---|
1693 | $exifTool->Warn('Error loading Mini-FAT stream');
|
---|
1694 | last;
|
---|
1695 | }
|
---|
1696 | $miniStream = new File::RandomAccess(\$miniStreamBuff);
|
---|
1697 | }
|
---|
1698 |
|
---|
1699 | my $tagInfo;
|
---|
1700 | if ($$tagTablePtr{$tag}) {
|
---|
1701 | $tagInfo = $exifTool->GetTagInfo($tagTablePtr, $tag);
|
---|
1702 | } else {
|
---|
1703 | # remove instance number or class ID from tag if necessary
|
---|
1704 | $tagInfo = $exifTool->GetTagInfo($tagTablePtr, $1) if
|
---|
1705 | ($tag =~ /(.*) \d{6}$/s and $$tagTablePtr{$1}) or
|
---|
1706 | ($tag =~ /(.*)_[0-9a-f]{16}$/s and $$tagTablePtr{$1});
|
---|
1707 | }
|
---|
1708 |
|
---|
1709 | my $lSib = Get32u(\$dir, $pos + 0x44); # left sibling
|
---|
1710 | my $rSib = Get32u(\$dir, $pos + 0x48); # right sibling
|
---|
1711 | my $chld = Get32u(\$dir, $pos + 0x4c); # child directory
|
---|
1712 |
|
---|
1713 | # save information about object hierachy
|
---|
1714 | my ($obj, $sub);
|
---|
1715 | $obj = $hier{$index} or $obj = $hier{$index} = { };
|
---|
1716 | $$obj{Left} = $lSib unless $lSib == FREE_SECT;
|
---|
1717 | $$obj{Right} = $rSib unless $rSib == FREE_SECT;
|
---|
1718 | unless ($chld == FREE_SECT) {
|
---|
1719 | $$obj{Child} = $chld;
|
---|
1720 | $sub = $hier{$chld} or $sub = $hier{$chld} = { };
|
---|
1721 | $$sub{Parent} = $index;
|
---|
1722 | }
|
---|
1723 |
|
---|
1724 | next unless $tagInfo or $verbose;
|
---|
1725 |
|
---|
1726 | # load the data for stream types
|
---|
1727 | my $extra = '';
|
---|
1728 | my $typeStr = $dirEntryType[$type] || $type;
|
---|
1729 | if ($typeStr eq 'STREAM') {
|
---|
1730 | if ($size >= $miniCutoff) {
|
---|
1731 | # stream is in the main FAT
|
---|
1732 | $buff = LoadChain($raf, $sect, \$fat, $sectSize, HDR_SIZE);
|
---|
1733 | } elsif ($size) {
|
---|
1734 | # stream is in the mini-FAT
|
---|
1735 | $buff = LoadChain($miniStream, $sect, \$miniFat, $miniSize, 0);
|
---|
1736 | } else {
|
---|
1737 | $buff = ''; # an empty stream
|
---|
1738 | }
|
---|
1739 | unless (defined $buff) {
|
---|
1740 | my $name = $tagInfo ? $$tagInfo{Name} : 'unknown';
|
---|
1741 | $exifTool->Warn("Error reading $name stream");
|
---|
1742 | $buff = '';
|
---|
1743 | }
|
---|
1744 | } elsif ($typeStr eq 'ROOT') {
|
---|
1745 | $buff = $miniStreamBuff;
|
---|
1746 | $extra .= ' (Ministream)';
|
---|
1747 | } else {
|
---|
1748 | $buff = '';
|
---|
1749 | undef $size;
|
---|
1750 | }
|
---|
1751 | if ($verbose) {
|
---|
1752 | my $flags = Get8u(\$dir, $pos + 0x43); # 0=red, 1=black
|
---|
1753 | my $col = { 0 => 'Red', 1 => 'Black' }->{$flags} || $flags;
|
---|
1754 | $extra .= " Type=$typeStr Flags=$col";
|
---|
1755 | $extra .= " Left=$lSib" unless $lSib == FREE_SECT;
|
---|
1756 | $extra .= " Right=$rSib" unless $rSib == FREE_SECT;
|
---|
1757 | $extra .= " Child=$chld" unless $chld == FREE_SECT;
|
---|
1758 | $exifTool->VerboseInfo($tag, $tagInfo,
|
---|
1759 | Index => $index,
|
---|
1760 | Value => $buff,
|
---|
1761 | DataPt => \$buff,
|
---|
1762 | Extra => $extra,
|
---|
1763 | Size => $size,
|
---|
1764 | );
|
---|
1765 | }
|
---|
1766 | if ($tagInfo and $buff) {
|
---|
1767 | my $num = $$exifTool{NUM_FOUND};
|
---|
1768 | my $subdir = $$tagInfo{SubDirectory};
|
---|
1769 | if ($subdir) {
|
---|
1770 | my %dirInfo = (
|
---|
1771 | DataPt => \$buff,
|
---|
1772 | DirStart => $$subdir{DirStart},
|
---|
1773 | DirLen => length $buff,
|
---|
1774 | Multi => $$tagInfo{Multi},
|
---|
1775 | );
|
---|
1776 | my $subTablePtr = GetTagTable($$subdir{TagTable});
|
---|
1777 | $exifTool->ProcessDirectory(\%dirInfo, $subTablePtr, $$subdir{ProcessProc});
|
---|
1778 | } else {
|
---|
1779 | $exifTool->FoundTag($tagInfo, $buff);
|
---|
1780 | }
|
---|
1781 | # save object index number for all found tags
|
---|
1782 | my $num2 = $$exifTool{NUM_FOUND};
|
---|
1783 | $objIndex{++$num} = $index while $num < $num2;
|
---|
1784 | }
|
---|
1785 | }
|
---|
1786 | # set document numbers for tags extracted from embedded documents
|
---|
1787 | unless ($$exifTool{DOC_NUM}) {
|
---|
1788 | # initialize document number for all objects, beginning at root (index 0)
|
---|
1789 | SetDocNum(\%hier, 0);
|
---|
1790 | # set family 3 group name for all tags in embedded documents
|
---|
1791 | my $order = $$exifTool{FILE_ORDER};
|
---|
1792 | my (@pri, $copy, $member);
|
---|
1793 | foreach $tag (keys %$order) {
|
---|
1794 | my $num = $$order{$tag};
|
---|
1795 | next unless defined $num and $objIndex{$num};
|
---|
1796 | my $obj = $hier{$objIndex{$num}} or next;
|
---|
1797 | my $docNums = $$obj{DocNum};
|
---|
1798 | next unless $docNums and @$docNums;
|
---|
1799 | $$exifTool{TAG_EXTRA}{$tag}{G3} = join '-', @$docNums;
|
---|
1800 | push @pri, $tag unless $tag =~ / /; # save keys for priority sub-doc tags
|
---|
1801 | }
|
---|
1802 | # swap priority sub-document tags with main document tags if they exist
|
---|
1803 | foreach $tag (@pri) {
|
---|
1804 | for ($copy=1; ;++$copy) {
|
---|
1805 | my $key = "$tag ($copy)";
|
---|
1806 | last unless defined $$exifTool{VALUE}{$key};
|
---|
1807 | my $extra = $$exifTool{TAG_EXTRA}{$key};
|
---|
1808 | next if $extra and $$extra{G3}; # not Main if family 3 group is set
|
---|
1809 | foreach $member ('PRIORITY','VALUE','FILE_ORDER','TAG_INFO','TAG_EXTRA') {
|
---|
1810 | my $pHash = $$exifTool{$member};
|
---|
1811 | my $t = $$pHash{$tag};
|
---|
1812 | $$pHash{$tag} = $$pHash{$key};
|
---|
1813 | $$pHash{$key} = $t;
|
---|
1814 | }
|
---|
1815 | last;
|
---|
1816 | }
|
---|
1817 | }
|
---|
1818 | }
|
---|
1819 | $exifTool->{INDENT} = $oldIndent if $verbose;
|
---|
1820 | # try to better identify the file type
|
---|
1821 | if ($$exifTool{VALUE}{FileType} eq 'FPX') {
|
---|
1822 | my $val = $$exifTool{CompObjUserType} || $$exifTool{Software};
|
---|
1823 | if ($val) {
|
---|
1824 | my %type = ( Word => 'DOC', PowerPoint => 'PPT', Excel => 'XLS' );
|
---|
1825 | my $pat;
|
---|
1826 | foreach $pat (sort keys %type) {
|
---|
1827 | next unless $val =~ /$pat/;
|
---|
1828 | $exifTool->OverrideFileType($type{$pat});
|
---|
1829 | last;
|
---|
1830 | }
|
---|
1831 | }
|
---|
1832 | }
|
---|
1833 | return 1;
|
---|
1834 | }
|
---|
1835 |
|
---|
1836 | 1; # end
|
---|
1837 |
|
---|
1838 | __END__
|
---|
1839 |
|
---|
1840 | =head1 NAME
|
---|
1841 |
|
---|
1842 | Image::ExifTool::FlashPix - Read FlashPix meta information
|
---|
1843 |
|
---|
1844 | =head1 SYNOPSIS
|
---|
1845 |
|
---|
1846 | This module is used by Image::ExifTool
|
---|
1847 |
|
---|
1848 | =head1 DESCRIPTION
|
---|
1849 |
|
---|
1850 | This module contains routines required by Image::ExifTool to extract
|
---|
1851 | FlashPix meta information from FPX images, and from the APP2 FPXR segment of
|
---|
1852 | JPEG images.
|
---|
1853 |
|
---|
1854 | =head1 AUTHOR
|
---|
1855 |
|
---|
1856 | Copyright 2003-2011, Phil Harvey (phil at owl.phy.queensu.ca)
|
---|
1857 |
|
---|
1858 | This library is free software; you can redistribute it and/or modify it
|
---|
1859 | under the same terms as Perl itself.
|
---|
1860 |
|
---|
1861 | =head1 REFERENCES
|
---|
1862 |
|
---|
1863 | =over 4
|
---|
1864 |
|
---|
1865 | =item L<http://www.exif.org/Exif2-2.PDF>
|
---|
1866 |
|
---|
1867 | =item L<http://www.graphcomp.com/info/specs/livepicture/fpx.pdf>
|
---|
1868 |
|
---|
1869 | =item L<http://search.cpan.org/~jdb/libwin32/>
|
---|
1870 |
|
---|
1871 | =item L<http://msdn.microsoft.com/en-us/library/aa380374.aspx>
|
---|
1872 |
|
---|
1873 | =back
|
---|
1874 |
|
---|
1875 | =head1 SEE ALSO
|
---|
1876 |
|
---|
1877 | L<Image::ExifTool::TagNames/FlashPix Tags>,
|
---|
1878 | L<Image::ExifTool::TagNames/OOXML Tags>,
|
---|
1879 | L<Image::ExifTool(3pm)|Image::ExifTool>
|
---|
1880 |
|
---|
1881 | =cut
|
---|
1882 |
|
---|