FlashPix.pm 70 KB

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