exiftool-vendored.pl 13.0.1 → 13.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (216) hide show
  1. package/bin/Changes +254 -20
  2. package/bin/MANIFEST +10 -0
  3. package/bin/META.json +1 -1
  4. package/bin/META.yml +1 -1
  5. package/bin/README +3 -3
  6. package/bin/arg_files/exif2xmp.args +4 -0
  7. package/bin/arg_files/xmp2exif.args +2 -1
  8. package/bin/build_geolocation +1 -1
  9. package/bin/exiftool +356 -213
  10. package/bin/lib/File/RandomAccess.pm +1 -1
  11. package/bin/lib/File/RandomAccess.pod +2 -2
  12. package/bin/lib/Image/ExifTool/AAC.pm +1 -1
  13. package/bin/lib/Image/ExifTool/AES.pm +1 -1
  14. package/bin/lib/Image/ExifTool/AFCP.pm +6 -6
  15. package/bin/lib/Image/ExifTool/AIFF.pm +2 -2
  16. package/bin/lib/Image/ExifTool/APE.pm +2 -2
  17. package/bin/lib/Image/ExifTool/APP12.pm +1 -1
  18. package/bin/lib/Image/ExifTool/ASF.pm +2 -2
  19. package/bin/lib/Image/ExifTool/Apple.pm +11 -9
  20. package/bin/lib/Image/ExifTool/Audible.pm +1 -1
  21. package/bin/lib/Image/ExifTool/BMP.pm +1 -1
  22. package/bin/lib/Image/ExifTool/BPG.pm +1 -1
  23. package/bin/lib/Image/ExifTool/BZZ.pm +1 -1
  24. package/bin/lib/Image/ExifTool/BigTIFF.pm +1 -1
  25. package/bin/lib/Image/ExifTool/BuildTagLookup.pm +36 -22
  26. package/bin/lib/Image/ExifTool/CBOR.pm +5 -2
  27. package/bin/lib/Image/ExifTool/Canon.pm +66 -27
  28. package/bin/lib/Image/ExifTool/CanonCustom.pm +1 -1
  29. package/bin/lib/Image/ExifTool/CanonRaw.pm +1 -1
  30. package/bin/lib/Image/ExifTool/CanonVRD.pm +1 -1
  31. package/bin/lib/Image/ExifTool/CaptureOne.pm +1 -1
  32. package/bin/lib/Image/ExifTool/Casio.pm +1 -1
  33. package/bin/lib/Image/ExifTool/Charset.pm +1 -1
  34. package/bin/lib/Image/ExifTool/DICOM.pm +1 -1
  35. package/bin/lib/Image/ExifTool/DJI.pm +196 -30
  36. package/bin/lib/Image/ExifTool/DNG.pm +1 -1
  37. package/bin/lib/Image/ExifTool/DPX.pm +1 -1
  38. package/bin/lib/Image/ExifTool/DV.pm +1 -1
  39. package/bin/lib/Image/ExifTool/DarwinCore.pm +1 -1
  40. package/bin/lib/Image/ExifTool/DjVu.pm +1 -1
  41. package/bin/lib/Image/ExifTool/EXE.pm +138 -33
  42. package/bin/lib/Image/ExifTool/Exif.pm +29 -16
  43. package/bin/lib/Image/ExifTool/FITS.pm +3 -3
  44. package/bin/lib/Image/ExifTool/FLAC.pm +1 -1
  45. package/bin/lib/Image/ExifTool/FLIF.pm +3 -3
  46. package/bin/lib/Image/ExifTool/FLIR.pm +1 -1
  47. package/bin/lib/Image/ExifTool/Fixup.pm +1 -1
  48. package/bin/lib/Image/ExifTool/Flash.pm +1 -1
  49. package/bin/lib/Image/ExifTool/FlashPix.pm +17 -21
  50. package/bin/lib/Image/ExifTool/Font.pm +2 -2
  51. package/bin/lib/Image/ExifTool/FotoStation.pm +1 -1
  52. package/bin/lib/Image/ExifTool/FujiFilm.pm +1 -1
  53. package/bin/lib/Image/ExifTool/GE.pm +1 -1
  54. package/bin/lib/Image/ExifTool/GIF.pm +144 -93
  55. package/bin/lib/Image/ExifTool/GIMP.pm +1 -1
  56. package/bin/lib/Image/ExifTool/GM.pm +1 -1
  57. package/bin/lib/Image/ExifTool/GPS.pm +34 -30
  58. package/bin/lib/Image/ExifTool/GeoTiff.pm +1 -1
  59. package/bin/lib/Image/ExifTool/Geolocation.dat +0 -0
  60. package/bin/lib/Image/ExifTool/Geolocation.pm +19 -9
  61. package/bin/lib/Image/ExifTool/Geotag.pm +46 -12
  62. package/bin/lib/Image/ExifTool/GoPro.pm +120 -8
  63. package/bin/lib/Image/ExifTool/H264.pm +1 -1
  64. package/bin/lib/Image/ExifTool/HP.pm +2 -2
  65. package/bin/lib/Image/ExifTool/HTML.pm +1 -1
  66. package/bin/lib/Image/ExifTool/HtmlDump.pm +1 -1
  67. package/bin/lib/Image/ExifTool/ICC_Profile.pm +81 -2
  68. package/bin/lib/Image/ExifTool/ICO.pm +1 -1
  69. package/bin/lib/Image/ExifTool/ID3.pm +8 -8
  70. package/bin/lib/Image/ExifTool/IPTC.pm +10 -7
  71. package/bin/lib/Image/ExifTool/ISO.pm +1 -1
  72. package/bin/lib/Image/ExifTool/ITC.pm +1 -1
  73. package/bin/lib/Image/ExifTool/Import.pm +5 -4
  74. package/bin/lib/Image/ExifTool/InDesign.pm +2 -2
  75. package/bin/lib/Image/ExifTool/InfiRay.pm +1 -1
  76. package/bin/lib/Image/ExifTool/JPEG.pm +32 -5
  77. package/bin/lib/Image/ExifTool/JPEGDigest.pm +1 -1
  78. package/bin/lib/Image/ExifTool/JSON.pm +1 -1
  79. package/bin/lib/Image/ExifTool/JVC.pm +1 -1
  80. package/bin/lib/Image/ExifTool/Jpeg2000.pm +10 -9
  81. package/bin/lib/Image/ExifTool/Kodak.pm +1 -1
  82. package/bin/lib/Image/ExifTool/KyoceraRaw.pm +1 -1
  83. package/bin/lib/Image/ExifTool/LIF.pm +1 -1
  84. package/bin/lib/Image/ExifTool/LNK.pm +2 -2
  85. package/bin/lib/Image/ExifTool/Lang/cs.pm +1 -1
  86. package/bin/lib/Image/ExifTool/Lang/de.pm +1 -1
  87. package/bin/lib/Image/ExifTool/Lang/en_ca.pm +1 -1
  88. package/bin/lib/Image/ExifTool/Lang/en_gb.pm +1 -1
  89. package/bin/lib/Image/ExifTool/Lang/es.pm +1 -1
  90. package/bin/lib/Image/ExifTool/Lang/fi.pm +1 -1
  91. package/bin/lib/Image/ExifTool/Lang/fr.pm +1 -1
  92. package/bin/lib/Image/ExifTool/Lang/it.pm +1 -1
  93. package/bin/lib/Image/ExifTool/Lang/ja.pm +1 -1
  94. package/bin/lib/Image/ExifTool/Lang/ko.pm +1 -1
  95. package/bin/lib/Image/ExifTool/Lang/nl.pm +1 -1
  96. package/bin/lib/Image/ExifTool/Lang/pl.pm +1 -1
  97. package/bin/lib/Image/ExifTool/Lang/ru.pm +1 -1
  98. package/bin/lib/Image/ExifTool/Lang/sk.pm +1 -1
  99. package/bin/lib/Image/ExifTool/Lang/sv.pm +1 -1
  100. package/bin/lib/Image/ExifTool/Lang/tr.pm +1 -1
  101. package/bin/lib/Image/ExifTool/Lang/zh_cn.pm +1 -1
  102. package/bin/lib/Image/ExifTool/Lang/zh_tw.pm +1 -1
  103. package/bin/lib/Image/ExifTool/Leaf.pm +1 -1
  104. package/bin/lib/Image/ExifTool/LigoGPS.pm +409 -0
  105. package/bin/lib/Image/ExifTool/Lytro.pm +1 -1
  106. package/bin/lib/Image/ExifTool/M2TS.pm +57 -18
  107. package/bin/lib/Image/ExifTool/MIE.pm +15 -6
  108. package/bin/lib/Image/ExifTool/MIEUnits.pod +1 -1
  109. package/bin/lib/Image/ExifTool/MIFF.pm +1 -1
  110. package/bin/lib/Image/ExifTool/MISB.pm +1 -1
  111. package/bin/lib/Image/ExifTool/MNG.pm +1 -1
  112. package/bin/lib/Image/ExifTool/MOI.pm +1 -1
  113. package/bin/lib/Image/ExifTool/MPC.pm +1 -1
  114. package/bin/lib/Image/ExifTool/MPEG.pm +1 -1
  115. package/bin/lib/Image/ExifTool/MPF.pm +1 -1
  116. package/bin/lib/Image/ExifTool/MRC.pm +1 -1
  117. package/bin/lib/Image/ExifTool/MWG.pm +1 -1
  118. package/bin/lib/Image/ExifTool/MXF.pm +3 -3
  119. package/bin/lib/Image/ExifTool/MacOS.pm +3 -2
  120. package/bin/lib/Image/ExifTool/MakerNotes.pm +1 -1
  121. package/bin/lib/Image/ExifTool/Matroska.pm +22 -6
  122. package/bin/lib/Image/ExifTool/Microsoft.pm +2 -2
  123. package/bin/lib/Image/ExifTool/Minolta.pm +1 -1
  124. package/bin/lib/Image/ExifTool/MinoltaRaw.pm +1 -1
  125. package/bin/lib/Image/ExifTool/Motorola.pm +1 -1
  126. package/bin/lib/Image/ExifTool/Nikon.pm +495 -39
  127. package/bin/lib/Image/ExifTool/NikonCapture.pm +1 -1
  128. package/bin/lib/Image/ExifTool/NikonCustom.pm +2 -2
  129. package/bin/lib/Image/ExifTool/NikonSettings.pm +1 -1
  130. package/bin/lib/Image/ExifTool/Nintendo.pm +1 -1
  131. package/bin/lib/Image/ExifTool/OOXML.pm +8 -8
  132. package/bin/lib/Image/ExifTool/Ogg.pm +1 -1
  133. package/bin/lib/Image/ExifTool/Olympus.pm +1 -1
  134. package/bin/lib/Image/ExifTool/OpenEXR.pm +1 -1
  135. package/bin/lib/Image/ExifTool/Opus.pm +1 -1
  136. package/bin/lib/Image/ExifTool/Other.pm +1 -1
  137. package/bin/lib/Image/ExifTool/PCX.pm +1 -1
  138. package/bin/lib/Image/ExifTool/PDF.pm +49 -18
  139. package/bin/lib/Image/ExifTool/PGF.pm +1 -1
  140. package/bin/lib/Image/ExifTool/PICT.pm +1 -1
  141. package/bin/lib/Image/ExifTool/PLIST.pm +4 -4
  142. package/bin/lib/Image/ExifTool/PLUS.pm +1 -1
  143. package/bin/lib/Image/ExifTool/PNG.pm +20 -8
  144. package/bin/lib/Image/ExifTool/PPM.pm +12 -3
  145. package/bin/lib/Image/ExifTool/PSP.pm +1 -1
  146. package/bin/lib/Image/ExifTool/Palm.pm +1 -1
  147. package/bin/lib/Image/ExifTool/Panasonic.pm +27 -3
  148. package/bin/lib/Image/ExifTool/PanasonicRaw.pm +1 -1
  149. package/bin/lib/Image/ExifTool/Parrot.pm +1 -1
  150. package/bin/lib/Image/ExifTool/Pentax.pm +1 -1
  151. package/bin/lib/Image/ExifTool/PhaseOne.pm +4 -4
  152. package/bin/lib/Image/ExifTool/PhotoCD.pm +1 -1
  153. package/bin/lib/Image/ExifTool/PhotoMechanic.pm +1 -1
  154. package/bin/lib/Image/ExifTool/Photoshop.pm +65 -4
  155. package/bin/lib/Image/ExifTool/PostScript.pm +1 -1
  156. package/bin/lib/Image/ExifTool/PrintIM.pm +1 -1
  157. package/bin/lib/Image/ExifTool/Protobuf.pm +270 -0
  158. package/bin/lib/Image/ExifTool/Qualcomm.pm +1 -1
  159. package/bin/lib/Image/ExifTool/QuickTime.pm +326 -88
  160. package/bin/lib/Image/ExifTool/QuickTimeStream.pl +199 -195
  161. package/bin/lib/Image/ExifTool/README +12 -2
  162. package/bin/lib/Image/ExifTool/RIFF.pm +21 -6
  163. package/bin/lib/Image/ExifTool/RSRC.pm +1 -1
  164. package/bin/lib/Image/ExifTool/RTF.pm +2 -2
  165. package/bin/lib/Image/ExifTool/Radiance.pm +1 -1
  166. package/bin/lib/Image/ExifTool/Rawzor.pm +1 -1
  167. package/bin/lib/Image/ExifTool/Real.pm +1 -1
  168. package/bin/lib/Image/ExifTool/Reconyx.pm +1 -1
  169. package/bin/lib/Image/ExifTool/Red.pm +1 -1
  170. package/bin/lib/Image/ExifTool/Ricoh.pm +4 -4
  171. package/bin/lib/Image/ExifTool/Samsung.pm +2 -2
  172. package/bin/lib/Image/ExifTool/Sanyo.pm +1 -1
  173. package/bin/lib/Image/ExifTool/Scalado.pm +1 -1
  174. package/bin/lib/Image/ExifTool/Shift.pl +1 -1
  175. package/bin/lib/Image/ExifTool/Shortcuts.pm +1 -1
  176. package/bin/lib/Image/ExifTool/Sigma.pm +1 -1
  177. package/bin/lib/Image/ExifTool/SigmaRaw.pm +1 -1
  178. package/bin/lib/Image/ExifTool/Sony.pm +5 -4
  179. package/bin/lib/Image/ExifTool/SonyIDC.pm +1 -1
  180. package/bin/lib/Image/ExifTool/Stim.pm +1 -1
  181. package/bin/lib/Image/ExifTool/TagInfoXML.pm +6 -5
  182. package/bin/lib/Image/ExifTool/TagLookup.pm +7023 -6968
  183. package/bin/lib/Image/ExifTool/TagNames.pod +445 -29
  184. package/bin/lib/Image/ExifTool/Text.pm +4 -3
  185. package/bin/lib/Image/ExifTool/Theora.pm +1 -1
  186. package/bin/lib/Image/ExifTool/Torrent.pm +3 -3
  187. package/bin/lib/Image/ExifTool/Unknown.pm +1 -1
  188. package/bin/lib/Image/ExifTool/VCard.pm +3 -3
  189. package/bin/lib/Image/ExifTool/Validate.pm +6 -6
  190. package/bin/lib/Image/ExifTool/Vivo.pm +124 -0
  191. package/bin/lib/Image/ExifTool/Vorbis.pm +1 -1
  192. package/bin/lib/Image/ExifTool/WPG.pm +1 -1
  193. package/bin/lib/Image/ExifTool/WTV.pm +1 -1
  194. package/bin/lib/Image/ExifTool/WriteCanonRaw.pl +1 -1
  195. package/bin/lib/Image/ExifTool/WriteExif.pl +3 -3
  196. package/bin/lib/Image/ExifTool/WriteIPTC.pl +1 -1
  197. package/bin/lib/Image/ExifTool/WritePDF.pl +1 -1
  198. package/bin/lib/Image/ExifTool/WritePNG.pl +1 -1
  199. package/bin/lib/Image/ExifTool/WritePhotoshop.pl +1 -1
  200. package/bin/lib/Image/ExifTool/WritePostScript.pl +1 -1
  201. package/bin/lib/Image/ExifTool/WriteQuickTime.pl +166 -79
  202. package/bin/lib/Image/ExifTool/WriteRIFF.pl +17 -6
  203. package/bin/lib/Image/ExifTool/WriteXMP.pl +3 -3
  204. package/bin/lib/Image/ExifTool/Writer.pl +89 -96
  205. package/bin/lib/Image/ExifTool/XISF.pm +1 -1
  206. package/bin/lib/Image/ExifTool/XMP.pm +28 -13
  207. package/bin/lib/Image/ExifTool/XMP2.pl +103 -1
  208. package/bin/lib/Image/ExifTool/XMPStruct.pl +2 -3
  209. package/bin/lib/Image/ExifTool/ZIP.pm +2 -2
  210. package/bin/lib/Image/ExifTool/ZISRAW.pm +1 -1
  211. package/bin/lib/Image/ExifTool/iWork.pm +1 -1
  212. package/bin/lib/Image/ExifTool.pm +335 -163
  213. package/bin/lib/Image/ExifTool.pod +119 -73
  214. package/bin/perl-Image-ExifTool.spec +1 -1
  215. package/bin/windows_exiftool.txt +96 -51
  216. package/package.json +6 -6
@@ -8,7 +8,7 @@
8
8
  # Revisions: Nov. 12/2003 - P. Harvey Created
9
9
  # (See html/history.html for revision history)
10
10
  #
11
- # Legal: Copyright (c) 2003-2024, Phil Harvey (philharvey66 at gmail.com)
11
+ # Legal: Copyright (c) 2003-2025, Phil Harvey (philharvey66 at gmail.com)
12
12
  # This library is free software; you can redistribute it and/or
13
13
  # modify it under the same terms as Perl itself.
14
14
  #------------------------------------------------------------------------------
@@ -29,7 +29,7 @@ use vars qw($VERSION $RELEASE @ISA @EXPORT_OK %EXPORT_TAGS $AUTOLOAD @fileTypes
29
29
  %jpegMarker %specialTags %fileTypeLookup $testLen $exeDir
30
30
  %static_vars $advFmtSelf);
31
31
 
32
- $VERSION = '13.00';
32
+ $VERSION = '13.16';
33
33
  $RELEASE = '';
34
34
  @ISA = qw(Exporter);
35
35
  %EXPORT_TAGS = (
@@ -126,6 +126,7 @@ sub MakeTiffHeader($$$$;$$);
126
126
  # other subroutine definitions
127
127
  sub SplitFileName($);
128
128
  sub EncodeFileName($$;$);
129
+ sub WindowsLongPath($$);
129
130
  sub Open($*$;$);
130
131
  sub Exists($$;$);
131
132
  sub IsDirectory($$);
@@ -153,10 +154,10 @@ sub ReadValue($$$;$$$);
153
154
  Matroska::StdTag MOI MXF DV Flash Flash::FLV Real::Media Real::Audio
154
155
  Real::Metafile Red RIFF AIFF ASF WTV DICOM FITS XISF MIE JSON HTML XMP::SVG
155
156
  Palm Palm::MOBI Palm::EXTH Torrent EXE EXE::PEVersion EXE::PEString
156
- EXE::MachO EXE::PEF EXE::ELF EXE::AR EXE::CHM LNK Font VCard Text
157
- VCard::VCalendar VCard::VNote RSRC Rawzor ZIP ZIP::GZIP ZIP::RAR ZIP::RAR5
158
- RTF OOXML iWork ISO FLIR::AFF FLIR::FPF MacOS MacOS::MDItem
159
- FlashPix::DocTable
157
+ EXE::DebugRSDS EXE::DebugNB10 EXE::Misc EXE::MachO EXE::PEF EXE::ELF EXE::AR
158
+ EXE::CHM LNK Font VCard Text VCard::VCalendar VCard::VNote RSRC Rawzor ZIP
159
+ ZIP::GZIP ZIP::RAR ZIP::RAR5 RTF OOXML iWork ISO FLIR::AFF FLIR::FPF MacOS
160
+ MacOS::MDItem FlashPix::DocTable
160
161
  );
161
162
 
162
163
  # alphabetical list of current Lang modules
@@ -1081,6 +1082,7 @@ my %xmpShorthandOpt = ( 0 => 'None', 1 => 'Shorthand', 2 => ['Shorthand','OneDes
1081
1082
  # +-----------------------------------------------------+
1082
1083
  # (Note: All options must exist in this lookup, even if undefined,
1083
1084
  # to facilitate case-insensitive options. 'Group#' is handled specially)
1085
+ # (item 3 is a flag indicating the option is undocumented)
1084
1086
  my @availableOptions = (
1085
1087
  [ 'Binary', undef, 'flag to extract binary values even if tag not specified' ],
1086
1088
  [ 'ByteOrder', undef, 'default byte order when creating EXIF information' ],
@@ -1098,7 +1100,12 @@ my @availableOptions = (
1098
1100
  [ 'Compress', undef, 'flag to write new values as compressed if possible' ],
1099
1101
  [ 'CoordFormat', undef, 'GPS lat/long coordinate format' ],
1100
1102
  [ 'DateFormat', undef, 'format for date/time' ],
1103
+ [ 'Debug', undef, 'enable debugging output', 1 ], # (undocumented)
1101
1104
  [ 'Duplicates', 1, 'flag to save duplicate tag values' ],
1105
+ # ("require Encode" hangs on my Windows 10 virtual machine running under MacOS if
1106
+ # the current working directory has a long path name. This problem hasn't been
1107
+ # seen on other Windows systems, so I'm leaving this option undocumented for now)
1108
+ [ 'EncodeHangs', undef, 'flag set to avoid using Encode if it hangs on your system', 1 ], # (undocumented)
1102
1109
  [ 'Escape', undef, 'escape special characters' ],
1103
1110
  [ 'Exclude', undef, 'tags to exclude' ],
1104
1111
  [ 'ExtendedXMP', 1, 'strategy for reading extended XMP' ],
@@ -1119,6 +1126,7 @@ my @availableOptions = (
1119
1126
  [ 'GeoMinSats', undef, 'geotag minimum satellites' ],
1120
1127
  [ 'GeoSpeedRef', undef, 'geotag GPSSpeedRef' ],
1121
1128
  [ 'GlobalTimeShift', undef, 'apply time shift to all extracted date/time values' ],
1129
+ [ 'GPSQuadrant', undef, 'quadrant for GPS if not otherwise known' ],
1122
1130
  [ 'Group#', undef, 'return tags for specified groups in family #' ],
1123
1131
  [ 'HexTagIDs', 0, 'use hex tag ID\'s in family 7 group names' ],
1124
1132
  [ 'HtmlDump', 0, 'HTML dump (0-3, higher # = bigger limit)' ],
@@ -1130,11 +1138,12 @@ my @availableOptions = (
1130
1138
  [ 'Lang', $defaultLang, 'localized language for descriptions etc' ],
1131
1139
  [ 'LargeFileSupport', 1, 'flag indicating support of 64-bit file offsets' ],
1132
1140
  [ 'LimitLongValues', 60, 'length limit for long values' ],
1133
- [ 'List', undef, '[deprecated, use ListSplit and ListJoin instead]' ],
1141
+ [ 'List', undef, '[deprecated, use ListSplit and ListJoin instead]', 1 ],
1134
1142
  [ 'ListItem', undef, 'used to return a specific item from lists' ],
1135
1143
  [ 'ListJoin', ', ', 'join lists together with this separator' ],
1136
- [ 'ListSep', ', ', '[deprecated, use ListSplit and ListJoin instead]' ],
1144
+ [ 'ListSep', ', ', '[deprecated, use ListSplit and ListJoin instead]', 1 ],
1137
1145
  [ 'ListSplit', undef, 'regex for splitting list-type tag values when writing' ],
1146
+ # LigoGPSScale - undocumented scale for unfuzzing LIGO GPS: 1,2,3 for standard scales (1 default), or scale value
1138
1147
  [ 'MakerNotes', undef, 'extract maker notes as a block' ],
1139
1148
  [ 'MDItemTags', undef, 'extract MacOS metadata item tags' ],
1140
1149
  [ 'MissingTagValue', undef, 'value for missing tags when expanded in expressions' ],
@@ -1150,6 +1159,7 @@ my @availableOptions = (
1150
1159
  [ 'QuickTimeUTC', undef, 'assume that QuickTime date/time tags are stored as UTC' ],
1151
1160
  [ 'RequestAll', undef, 'extract all tags that must be specifically requested' ],
1152
1161
  [ 'RequestTags', undef, 'extra tags to request (on top of those in the tag list)' ],
1162
+ [ 'SaveBin', undef, 'save binary values of tags' ],
1153
1163
  [ 'SaveFormat', undef, 'save family 6 tag TIFF format' ],
1154
1164
  [ 'SavePath', undef, 'save family 5 location path' ],
1155
1165
  [ 'ScanForXMP', undef, 'flag to scan for XMP information in all files' ],
@@ -1165,11 +1175,12 @@ my @availableOptions = (
1165
1175
  [ 'UserParam', { }, 'user parameters for additional user-defined tag values' ],
1166
1176
  [ 'Validate', undef, 'perform additional validation' ],
1167
1177
  [ 'Verbose', 0, 'print verbose messages (0-5, higher # = more verbose)' ],
1178
+ [ 'WindowsLongPath', 1, 'enable support for long pathnames (enables WindowsWideFile)' ],
1168
1179
  [ 'WindowsWideFile', undef, 'force the use of Windows wide-character file routines' ], # (see forum15208)
1169
1180
  [ 'WriteMode', 'wcg', 'enable all write modes by default' ],
1170
1181
  [ 'XAttrTags', undef, 'extract MacOS extended attribute tags' ],
1171
1182
  [ 'XMPAutoConv', 1, 'automatic conversion of unknown XMP tag values' ],
1172
- [ 'XMPShorthand', 0, '[deprecated, use Compact=Shorthand instead]' ],
1183
+ [ 'XMPShorthand', 0, '[deprecated, use Compact=Shorthand instead]', 1 ],
1173
1184
  );
1174
1185
 
1175
1186
  # default family 0 group priority for writing
@@ -1256,7 +1267,9 @@ my %systemTagsNotes = (
1256
1267
  Use the -a or L<Duplicates|../ExifTool.html#Duplicates> option to see all warnings if more than one
1257
1268
  occurred. Minor warnings may be ignored with the -m or L<IgnoreMinorErrors|../ExifTool.html#IgnoreMinorErrors>
1258
1269
  option. Minor warnings with a capital "M" in the "[Minor]" designation
1259
- indicate that the processing is affected by ignoring the warning
1270
+ indicate that the processing is affected by ignoring the warning. Multiple
1271
+ identical warnings are indicated by a count after the warning message, eg.
1272
+ "[x2]" if the same warning occurred twice
1260
1273
  },
1261
1274
  },
1262
1275
  Comment => {
@@ -2488,6 +2501,7 @@ sub Options($$;@)
2488
2501
  }
2489
2502
  } elsif ($param =~ /^(IgnoreTags|IgnoreGroups)$/) {
2490
2503
  if (defined $newVal) {
2504
+ ref $newVal eq 'HASH' and $$options{$param} = $newVal, next;
2491
2505
  # parse list from delimited string if necessary
2492
2506
  my @ignoreList = (ref $newVal eq 'ARRAY') ? @$newVal : ($newVal =~ /[-\w?*:#]+/g);
2493
2507
  ExpandShortcuts(\@ignoreList) if $param eq 'IgnoreTags';
@@ -2574,7 +2588,7 @@ sub Options($$;@)
2574
2588
  warn("Can't set $param to undef\n");
2575
2589
  }
2576
2590
  } elsif (lc $param eq 'geodir') {
2577
- $Image::ExifTool::Geolocation::geoDir = $newVal; # (undocumented)
2591
+ $Image::ExifTool::Geolocation::geoDir = $newVal;
2578
2592
  } else {
2579
2593
  if ($param eq 'Escape') {
2580
2594
  # set ESCAPE_PROC
@@ -2652,7 +2666,7 @@ sub ExtractInfo($;@)
2652
2666
  my $fast = $$options{FastScan} || 0;
2653
2667
  my $req = $$self{REQ_TAG_LOOKUP};
2654
2668
  my $reqAll = $$options{RequestAll} || 0;
2655
- my (%saveOptions, $reEntry, $rsize, $zid, $type, @startTime, $saveOrder, $isDir);
2669
+ my (%saveOptions, $reEntry, $rsize, $zid, $type, @startTime, $saveOrder, $isDir, $i);
2656
2670
 
2657
2671
  # check for internal ReEntry option to allow recursive calls to ExtractInfo
2658
2672
  if (ref $_[1] eq 'HASH' and $_[1]{ReEntry} and
@@ -2709,7 +2723,7 @@ sub ExtractInfo($;@)
2709
2723
  if ($$req{processingtime} or $reqAll) {
2710
2724
  eval { require Time::HiRes; @startTime = Time::HiRes::gettimeofday() };
2711
2725
  if (not @startTime and $$req{processingtime}) {
2712
- $self->WarnOnce('Install Time::HiRes to generate ProcessingTime');
2726
+ $self->Warn('Install Time::HiRes to generate ProcessingTime');
2713
2727
  }
2714
2728
  }
2715
2729
 
@@ -2720,12 +2734,12 @@ sub ExtractInfo($;@)
2720
2734
  if (require Digest::SHA) {
2721
2735
  $$self{ImageDataHash} = Digest::SHA->new($1);
2722
2736
  } else {
2723
- $self->WarnOnce("Install Digest::SHA to calculate image data SHA$1");
2737
+ $self->Warn("Install Digest::SHA to calculate image data SHA$1");
2724
2738
  }
2725
2739
  } elsif (require Digest::MD5) {
2726
2740
  $$self{ImageDataHash} = Digest::MD5->new;
2727
2741
  } else {
2728
- $self->WarnOnce('Install Digest::MD5 to calculate image data MD5');
2742
+ $self->Warn('Install Digest::MD5 to calculate image data MD5');
2729
2743
  }
2730
2744
  }
2731
2745
  ++$$self{FILE_SEQUENCE}; # count files read
@@ -2754,12 +2768,18 @@ sub ExtractInfo($;@)
2754
2768
  if ($$req{filepath} or
2755
2769
  ($reqAll and not $$self{EXCL_TAG_LOOKUP}{filepath}))
2756
2770
  {
2771
+ my $path;
2757
2772
  local $SIG{'__WARN__'} = \&SetWarning;
2758
- if (eval { require Cwd }) {
2759
- my $path = eval { Cwd::abs_path($filename) };
2760
- $self->FoundTag('FilePath', $path) if defined $path;
2773
+ if ($^O eq 'MSWin32' and $$options{WindowsLongPath}) {
2774
+ $path = $self->WindowsLongPath($filename);
2775
+ } elsif (eval { require Cwd }) {
2776
+ $path = eval { Cwd::abs_path($filename) };
2777
+ }
2778
+ if (defined $path) {
2779
+ $path =~ tr/\\/\// if $^O eq 'MSWin32'; # return forward slashes
2780
+ $self->FoundTag('FilePath', $path);
2761
2781
  } elsif ($$req{filepath}) {
2762
- $self->WarnOnce('The Perl Cwd module must be installed to use FilePath');
2782
+ $self->Warn('The Perl Cwd module must be installed to use FilePath');
2763
2783
  }
2764
2784
  }
2765
2785
  # get size of resource fork on Mac OS
@@ -3098,6 +3118,15 @@ sub ExtractInfo($;@)
3098
3118
  # and as such it can't be used in user-defined Composite tags
3099
3119
  @startTime and $self->FoundTag('ProcessingTime', Time::HiRes::tv_interval(\@startTime));
3100
3120
 
3121
+ # add numbers to warnings with multiple occurrences
3122
+ if (%{$$self{WAS_WARNED}}) {
3123
+ my ($tag, $val) = ( 'Warning', $$self{VALUE} );
3124
+ for ($i=1; $$val{$tag}; ++$i) {
3125
+ my $n = $$self{WAS_WARNED}{$$val{$tag}};
3126
+ $$val{$tag} .= " [x$n]" if $n and $n > 1;
3127
+ $tag = "Warning ($i)";
3128
+ }
3129
+ }
3101
3130
  # restore original options
3102
3131
  %saveOptions and $$self{OPTIONS} = \%saveOptions;
3103
3132
 
@@ -3328,8 +3357,8 @@ sub GetRequestedTags($)
3328
3357
  # Inputs: 0) ExifTool object reference
3329
3358
  # 1) tag key or tag name with optional group names (case sensitive)
3330
3359
  # (or flattened tagInfo for getting field values, not part of public API)
3331
- # 2) [optional] Value type: PrintConv, ValueConv, Both, Raw or Rational, the default
3332
- # is PrintConv or ValueConv, depending on the PrintConv option setting
3360
+ # 2) [optional] Value type: PrintConv, ValueConv, Both, Raw, Bin or Rational, the
3361
+ # default is PrintConv or ValueConv, depending on the PrintConv option setting
3333
3362
  # 3) raw field value (not part of public API)
3334
3363
  # Returns: Scalar context: tag value or undefined
3335
3364
  # List context: list of values or empty list
@@ -3358,7 +3387,8 @@ sub GetValue($$;$)
3358
3387
  }
3359
3388
  # figure out what conversions to do
3360
3389
  if ($type) {
3361
- return $$self{RATIONAL}{$tag} if $type eq 'Rational';
3390
+ return $$self{TAG_EXTRA}{$tag}{Rational} if $type eq 'Rational';
3391
+ return $$self{TAG_EXTRA}{$tag}{BinVal} if $type eq 'Bin';
3362
3392
  } else {
3363
3393
  $type = $$self{OPTIONS}{PrintConv} ? 'PrintConv' : 'ValueConv';
3364
3394
  }
@@ -3702,9 +3732,10 @@ sub GetGroup($$;$)
3702
3732
  $tag = $$tagInfo{Name};
3703
3733
  # set flag so we don't get extra information for an extracted tag
3704
3734
  $byTagInfo = 1;
3735
+ $ex = { };
3705
3736
  } else {
3706
3737
  $tagInfo = $$self{TAG_INFO}{$tag} || { };
3707
- $ex = $$self{TAG_EXTRA}{$tag};
3738
+ $ex = $$self{TAG_EXTRA}{$tag} || { };
3708
3739
  }
3709
3740
  my $groups = $$tagInfo{Groups};
3710
3741
  # fill in default groups unless already done
@@ -3723,32 +3754,30 @@ sub GetGroup($$;$)
3723
3754
  if (defined $family and $family ne '-1') {
3724
3755
  if ($family =~ /[^\d]/) {
3725
3756
  @families = ($family =~ /\d+/g);
3726
- return(($ex && $$ex{G0}) || $$groups{0}) unless @families;
3757
+ return($$ex{G0} || $$groups{0}) unless @families;
3727
3758
  $simplify = 1 unless $family =~ /^:/;
3728
3759
  undef $family;
3729
3760
  foreach (0..2) { $groups[$_] = $$groups{$_}; }
3730
3761
  $noID = 1 if @families == 1 and $families[0] != 7;
3731
3762
  } else {
3732
- return(($ex && $$ex{"G$family"}) || $$groups{$family}) if $family == 0 or $family == 2;
3763
+ return($$ex{"G$family"} || $$groups{$family}) if $family == 0 or $family == 2;
3733
3764
  $groups[1] = $$groups{1};
3734
3765
  }
3735
3766
  } else {
3736
- return(($ex && $$ex{G0}) || $$groups{0}) unless wantarray;
3767
+ return($$ex{G0} || $$groups{0}) unless wantarray;
3737
3768
  foreach (0..2) { $groups[$_] = $$groups{$_}; }
3738
3769
  }
3739
3770
  $groups[3] = 'Main';
3740
- $groups[4] = ($tag =~ /\((\d+)\)$/) ? "Copy$1" : '';
3771
+ $groups[4] = ($tag =~ /\((\d+)\)$/ and $1 ne '0') ? "Copy$1" : '';
3741
3772
  # handle dynamic group names if necessary
3742
3773
  unless ($byTagInfo) {
3743
- if ($ex) {
3744
- $groups[0] = $$ex{G0} if $$ex{G0};
3745
- $groups[1] = $$ex{G1} =~ /^\+(.*)/ ? "$groups[1]$1" : $$ex{G1} if $$ex{G1};
3746
- $groups[3] = 'Doc' . $$ex{G3} if $$ex{G3};
3747
- $groups[5] = $$ex{G5} || $groups[1] if defined $$ex{G5};
3748
- if (defined $$ex{G6}) {
3749
- $groups[5] = '' unless defined $groups[5]; # (can't leave a hole in the array)
3750
- $groups[6] = $$ex{G6};
3751
- }
3774
+ $groups[0] = $$ex{G0} if $$ex{G0};
3775
+ $groups[1] = $$ex{G1} =~ /^\+(.*)/ ? "$groups[1]$1" : $$ex{G1} if $$ex{G1};
3776
+ $groups[3] = 'Doc' . $$ex{G3} if $$ex{G3};
3777
+ $groups[5] = $$ex{G5} || $groups[1] if defined $$ex{G5};
3778
+ if (defined $$ex{G6}) {
3779
+ $groups[5] = '' unless defined $groups[5]; # (can't leave a hole in the array)
3780
+ $groups[6] = $$ex{G6};
3752
3781
  }
3753
3782
  if ($$ex{G8}) {
3754
3783
  $groups[7] = '';
@@ -3921,12 +3950,9 @@ COMPOSITE_TAG:
3921
3950
  $key = "$reqTag ($i)";
3922
3951
  }
3923
3952
  @keys = $self->GroupMatches($reqGroup, \@keys) if defined $reqGroup;
3924
- if (@keys) {
3925
- my $ex = $$self{TAG_EXTRA};
3926
- # loop through tags in reverse order of precedence so the higher
3927
- # priority tag will win in the case of duplicates within a doc
3928
- $$cacheTag[$$ex{$_} ? $$ex{$_}{G3} || 0 : 0] = $_ foreach reverse @keys;
3929
- }
3953
+ # loop through tags in reverse order of precedence so the higher
3954
+ # priority tag will win in the case of duplicates within a doc
3955
+ $$cacheTag[$$self{TAG_EXTRA}{$_}{G3} || 0] = $_ foreach reverse @keys;
3930
3956
  }
3931
3957
  # (set $reqTag to a bogus key if not found)
3932
3958
  $reqTag = $$cacheTag[$doc] || "$reqTag (0)";
@@ -4140,7 +4166,7 @@ sub GetFileType(;$$)
4140
4166
  $desc = $$fileType[1];
4141
4167
  }
4142
4168
  } else {
4143
- $desc = $fileDescription{$file};
4169
+ $desc = $fileDescription{$file} || $file;
4144
4170
  }
4145
4171
  $desc .= ", $subType" if $subType;
4146
4172
  return $desc;
@@ -4218,9 +4244,7 @@ sub Init($)
4218
4244
  local $_;
4219
4245
  my $self = shift;
4220
4246
  # delete all DataMember variables (lower-case names)
4221
- foreach (keys %$self) {
4222
- /[a-z]/ and delete $$self{$_};
4223
- }
4247
+ delete $$self{$_} foreach grep /[a-z]/, keys %$self;
4224
4248
  undef %static_vars; # clear all static variables
4225
4249
  delete $$self{FOUND_TAGS}; # list of found tags
4226
4250
  delete $$self{EXIF_DATA}; # the EXIF data block
@@ -4235,7 +4259,6 @@ sub Init($)
4235
4259
  $$self{FILE_ORDER} = { }; # * hash of tag order in file ('*' = based on tag key)
4236
4260
  $$self{VALUE} = { }; # * hash of raw tag values
4237
4261
  $$self{BOTH} = { }; # * hash for Value/PrintConv values of Require'd tags
4238
- $$self{RATIONAL} = { }; # * hash of original rational components
4239
4262
  $$self{TAG_INFO} = { }; # * hash of tag information
4240
4263
  $$self{TAG_EXTRA} = { }; # * hash of extra tag information (dynamic group names)
4241
4264
  $$self{PRIORITY} = { }; # * priority of current tags
@@ -4243,7 +4266,7 @@ sub Init($)
4243
4266
  $$self{PROCESSED} = { }; # hash of processed directory start positions
4244
4267
  $$self{DIR_COUNT} = { }; # count various types of directories
4245
4268
  $$self{DUPL_TAG} = { }; # last-used index for duplicate-tag keys
4246
- $$self{WARNED_ONCE}= { }; # WarnOnce() warnings already issued
4269
+ $$self{WAS_WARNED} = { }; # number of times each warning was issued
4247
4270
  $$self{WRITTEN} = { }; # list of tags written (selected tags only)
4248
4271
  $$self{FORCE_WRITE}= { }; # ForceWrite lookup (set from ForceWrite tag)
4249
4272
  $$self{FOUND_DIR} = { }; # hash of directory names found in file
@@ -4401,6 +4424,7 @@ sub DoneExtract($)
4401
4424
  local $SIG{'__WARN__'} = \&SetWarning;
4402
4425
  undef $evalWarning;
4403
4426
  $$opts{GeolocMulti} = $$opts{Duplicates};
4427
+ $self->VPrint(0, "Geolocation arguments: '${arg}'\n");
4404
4428
  my ($cities, $dist) = Image::ExifTool::Geolocation::Geolocate($arg, $opts);
4405
4429
  delete $$opts{GeolocMulti};
4406
4430
  if ($cities and (@$cities < 2 or $dist or not $self->Warn('Multiple Geolocation cities are possible',2))) {
@@ -4468,13 +4492,9 @@ sub DoneExtract($)
4468
4492
  my $err = $$altExifTool{VALUE}{Error};
4469
4493
  $err and $self->Warn(qq{$err "$fileName"});
4470
4494
  # set family 8 group name for all tags
4471
- foreach (keys %{$$altExifTool{VALUE}}) {
4472
- my $ex = $$altExifTool{TAG_EXTRA}{$_};
4473
- $ex or $ex = $$altExifTool{TAG_EXTRA}{$_} = { };
4474
- $$ex{G8} = $g8;
4475
- }
4495
+ $$altExifTool{TAG_EXTRA}{$_}{G8} = $g8 foreach keys %{$$altExifTool{VALUE}};
4476
4496
  # prepare our sorted list of found tags
4477
- $$altExifTool{FoundTags} = [ reverse sort keys %{$$altExifTool{VALUE}} ];
4497
+ $$altExifTool{FoundTags} = $altExifTool->SetFoundTags();
4478
4498
  $$altExifTool{DID_EXTRACT} = 1;
4479
4499
  }
4480
4500
  # if necessary, build composite tags that rely on tags from alternate files
@@ -4614,36 +4634,124 @@ sub SplitFileName($)
4614
4634
 
4615
4635
  #------------------------------------------------------------------------------
4616
4636
  # Encode file name for calls to system i/o routines
4617
- # Inputs: 0) ExifTool ref, 1) file name in CharSetFileName, 2) flag to force conversion
4637
+ # Inputs: 0) ExifTool ref, 1) file name in CharsetFileName encoding, 2) flag to force conversion
4618
4638
  # Returns: true if Windows Unicode routines should be used (in which case
4619
4639
  # the file name will be encoded as a null-terminated UTF-16LE string)
4620
4640
  sub EncodeFileName($$;$)
4621
4641
  {
4622
4642
  my ($self, $file, $force) = @_;
4643
+ return 0 if $file eq '-'; # special case for stdin pipe
4623
4644
  my $enc = $$self{OPTIONS}{CharsetFileName};
4624
- $force = 1 if $$self{OPTIONS}{WindowsWideFile};
4625
- if ($enc) {
4626
- if ($file =~ /[\x80-\xff]/ or $force) {
4627
- # encode for use in Windows Unicode functions if necessary
4628
- if ($^O eq 'MSWin32') {
4629
- local $SIG{'__WARN__'} = \&SetWarning;
4630
- if (eval { require Win32API::File }) {
4631
- # recode as UTF-16LE and add null terminator
4632
- $_[1] = $self->Decode($file, $enc, undef, 'UTF16', 'II') . "\0\0";
4633
- return 1;
4634
- }
4635
- $self->WarnOnce('Install Win32API::File for Windows Unicode file support');
4645
+ my $hasSpecialChars;
4646
+ if ($file =~ /[\x80-\xff]/) {
4647
+ $hasSpecialChars = 1;
4648
+ if (not $enc and $^O eq 'MSWin32') {
4649
+ if (IsUTF8(\$file) < 0) {
4650
+ $self->Warn('FileName encoding must be specified') if not defined $enc;
4651
+ return 0;
4636
4652
  } else {
4637
- # recode as UTF-8 for other platforms if necessary
4638
- $_[1] = $self->Decode($file, $enc, undef, 'UTF8') unless $enc eq 'UTF8';
4653
+ $enc = 'UTF8'; # assume UTF8
4639
4654
  }
4640
4655
  }
4641
- } elsif ($^O eq 'MSWin32' and $file =~ /[\x80-\xff]/ and not defined $enc) {
4642
- $self->WarnOnce('FileName encoding not specified') if IsUTF8(\$file) < 0;
4656
+ }
4657
+ if ($hasSpecialChars or $force or $$self{OPTIONS}{WindowsLongPath} or $$self{OPTIONS}{WindowsWideFile}) {
4658
+ $enc or $enc = 'UTF8';
4659
+ if ($^O eq 'MSWin32') {
4660
+ local $SIG{'__WARN__'} = \&SetWarning;
4661
+ if (eval { require Win32API::File }) {
4662
+ $file = $self->WindowsLongPath($file) if $$self{OPTIONS}{WindowsLongPath};
4663
+ # recode as UTF-16LE and add null terminator
4664
+ $_[1] = $self->Decode($file, $enc, undef, 'UTF16', 'II') . "\0\0";
4665
+ return 1;
4666
+ }
4667
+ $self->Warn('Install Win32API::File for Windows wide/long file name support');
4668
+ } elsif ($enc ne 'UTF8') {
4669
+ # recode as UTF-8 for other platforms if necessary
4670
+ $_[1] = $self->Decode($file, $enc, undef, 'UTF8');
4671
+ }
4643
4672
  }
4644
4673
  return 0;
4645
4674
  }
4646
4675
 
4676
+ #------------------------------------------------------------------------------
4677
+ # Rebuild a path as an absolute long path to be usable in Windows system calls
4678
+ # Inputs: 0) ExifTool ref, 1) path string (CharsetFileName)
4679
+ # Returns: normalized long path (CharsetFileName)
4680
+ # Note: this should only be called for Windows systems
4681
+ # References:
4682
+ # - https://learn.microsoft.com/en-us/dotnet/standard/io/file-path-formats
4683
+ # - https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation
4684
+ # GetFullPathName supported by Windows XP and later. It handles:
4685
+ # full path names EG: c:\foto\sub\abc.jpg
4686
+ # relative EG: .\abc.jpg, ..\abc.jpg
4687
+ # full UNC paths EG: \\server\share\abc.jpg
4688
+ # relative UNC paths EG: .\abc.jpg, ..\abc.jpg
4689
+ # Dos device paths EG: \\.\c:\fotoabc.jpg
4690
+ # relative path on other drives EG: z:abc.jpg (working dir on z: z:\foto called from c:\foto)
4691
+ # Wide chars EG: Chars that need UTF8.
4692
+ my $k32GetFullPathName;
4693
+ sub WindowsLongPath($$)
4694
+ {
4695
+ my ($self, $path) = @_;
4696
+ my $debug = $$self{OPTIONS}{Debug};
4697
+ my $out = $$self{OPTIONS}{TextOut};
4698
+ my $suffix = '';
4699
+ my $longPath;
4700
+
4701
+ # remove common suffixes to make cache more effective
4702
+ if ($path =~ s/(_original|_exiftool_tmp|:Zone\.Identifier)$//) {
4703
+ $suffix = $1;
4704
+ if (not length $path or $path =~ m([:./\\]$)) {
4705
+ # don't remove suffix if it could be the whole file name
4706
+ $path .= $suffix;
4707
+ $suffix = '';
4708
+ }
4709
+ }
4710
+ return $$self{LONG_PATH_OUT}.$suffix if defined $$self{LONG_PATH_IN} and $$self{LONG_PATH_IN} eq $path;
4711
+
4712
+ $debug and print $out "WindowsLongPath input : $path$suffix\n";
4713
+
4714
+ for (;;) { # (cheap goto)
4715
+ ($longPath = $path) =~ tr(/)(\\); # convert slashes to backslashes
4716
+ last if $longPath =~ /^\\\\\?\\/; # already a device path in the format we want
4717
+
4718
+ unless ($k32GetFullPathName) { # need to import (once) GetFullPathNameW
4719
+ last if defined $k32GetFullPathName;
4720
+ unless (eval { require Win32::API }) {
4721
+ $self->Warn('Install Win32::API to use WindowsLongPath option');
4722
+ last;
4723
+ }
4724
+ $k32GetFullPathName = Win32::API->new('KERNEL32', 'GetFullPathNameW', 'PNPP', 'I');
4725
+ unless ($k32GetFullPathName) {
4726
+ $k32GetFullPathName = 0;
4727
+ $self->Warn('Error loading Win32::API GetFullPathNameW');
4728
+ last;
4729
+ }
4730
+ }
4731
+ my $enc = $$self{OPTIONS}{CharsetFileName} || 'UTF8';
4732
+ my $encPath = $self->Decode($longPath, $enc, undef, 'UTF16', 'II');# need to encode to UTF16
4733
+ my $lenReq = $k32GetFullPathName->Call($encPath,0,0,0) + 1; # first pass gets length required, +1 for safety (null?)
4734
+ my $fullPath = "\0" x $lenReq x 2; # create buffer to hold full path
4735
+ $k32GetFullPathName->Call($encPath, $lenReq, $fullPath, 0); # fullPath is UTF16 now
4736
+ $longPath = $self->Decode($fullPath, 'UTF16', 'II', $enc);
4737
+
4738
+ last if length($longPath) <= 247 - length($suffix);
4739
+
4740
+ if ($longPath =~ /^\\\\/) {
4741
+ $longPath = '\\\\?\\UNC' . substr($longPath, 1);
4742
+ } else {
4743
+ $longPath = '\\\\?\\' . $longPath;
4744
+ }
4745
+ last;
4746
+ }
4747
+ # this may be called repeatedly for the same file file (exists, stat, open),
4748
+ # so cache the last return value (without any of the suffixes that we use)
4749
+ $$self{LONG_PATH_IN} = $path;
4750
+ $$self{LONG_PATH_OUT} = $longPath;
4751
+ $debug and print $out "WindowsLongPath return: $longPath$suffix\n";
4752
+ return $longPath . $suffix;
4753
+ }
4754
+
4647
4755
  #------------------------------------------------------------------------------
4648
4756
  # Modified perl open() routine to properly handle special characters in file names
4649
4757
  # Inputs: 0) ExifTool ref, 1) filehandle, 2) filename,
@@ -4773,16 +4881,16 @@ sub CreateDirectory($$)
4773
4881
  my $d2 = $dir; # (must make a copy in case EncodeFileName recodes it)
4774
4882
  if ($self->EncodeFileName($d2)) {
4775
4883
  # handle Windows Unicode directory names
4776
- unless (eval { require Win32::API }) {
4777
- $err = 'Install Win32::API to create directories with Unicode names';
4778
- last;
4779
- }
4780
4884
  unless (defined $k32CreateDir) {
4885
+ unless (eval { require Win32::API }) {
4886
+ $err = 'Install Win32::API to create directories with Unicode names';
4887
+ last;
4888
+ }
4781
4889
  $k32CreateDir = Win32::API->new('KERNEL32', 'CreateDirectoryW', 'PP', 'I');
4782
4890
  unless ($k32CreateDir) {
4783
4891
  $k32CreateDir = 0;
4784
4892
  # give this error once, then just "Error creating" for subsequent attempts
4785
- return 'Error accessing Win32::API::CreateDirectoryW';
4893
+ return 'Error loading Win32::API CreateDirectoryW';
4786
4894
  }
4787
4895
  }
4788
4896
  $success = $k32CreateDir->Call($d2, 0) if $k32CreateDir;
@@ -4823,9 +4931,9 @@ sub GetFileTime($$)
4823
4931
  # on Windows, try to work around incorrect file times when daylight saving time is in effect
4824
4932
  if ($^O eq 'MSWin32') {
4825
4933
  if (not eval { require Win32::API }) {
4826
- $self->WarnOnce('Install Win32::API for proper handling of Windows file times', 1);
4934
+ $self->Warn('Install Win32::API for proper handling of Windows file times', 1);
4827
4935
  } elsif (not eval { require Win32API::File }) {
4828
- $self->WarnOnce('Install Win32API::File for proper handling of Windows file times', 1);
4936
+ $self->Warn('Install Win32API::File for proper handling of Windows file times', 1);
4829
4937
  } else {
4830
4938
  # get Win32 handle, needed for GetFileTime
4831
4939
  my $win32Handle = eval { Win32API::File::GetOsFHandle($file) };
@@ -4840,13 +4948,13 @@ sub GetFileTime($$)
4840
4948
  return () if defined $k32GetFileTime;
4841
4949
  $k32GetFileTime = Win32::API->new('KERNEL32', 'GetFileTime', 'NPPP', 'I');
4842
4950
  unless ($k32GetFileTime) {
4843
- $self->Warn('Error calling Win32::API::GetFileTime');
4951
+ $self->Warn('Error loading Win32::API GetFileTime');
4844
4952
  $k32GetFileTime = 0;
4845
4953
  return ();
4846
4954
  }
4847
4955
  }
4848
4956
  unless ($k32GetFileTime->Call($win32Handle, $ctime, $atime, $mtime)) {
4849
- $self->Warn("Win32::API::GetFileTime returned " . Win32::GetLastError());
4957
+ $self->Warn("Win32::API GetFileTime returned " . Win32::GetLastError());
4850
4958
  return ();
4851
4959
  }
4852
4960
  # convert FILETIME structs to Unix seconds
@@ -4910,11 +5018,13 @@ sub ParseArguments($;@)
4910
5018
  next if defined $$self{RAF};
4911
5019
  # convert image data from UTF-8 to character stream if necessary
4912
5020
  # (patches RHEL 3 UTF8 LANG problem)
4913
- if (ref $arg eq 'SCALAR' and $] >= 5.006 and
4914
- (eval { require Encode; Encode::is_utf8($$arg) } or $@))
5021
+ if (ref $arg eq 'SCALAR' and $] >= 5.006 and ($$self{OPTIONS}{EncodeHangs} or
5022
+ eval { require Encode; Encode::is_utf8($$arg) } or $@))
4915
5023
  {
5024
+ local $SIG{'__WARN__'} = \&SetWarning;
4916
5025
  # repack by hand if Encode isn't available
4917
- my $buff = $@ ? pack('C*',unpack($] < 5.010000 ? 'U0C*' : 'C0C*',$$arg)) : Encode::encode('utf8',$$arg);
5026
+ my $buff = ($$self{OPTIONS}{EncodeHangs} or $@) ? pack('C*',unpack($] < 5.010000 ?
5027
+ 'U0C*' : 'C0C*', $$arg)) : Encode::encode('utf8', $$arg);
4918
5028
  $arg = \$buff;
4919
5029
  }
4920
5030
  $$self{RAF} = File::RandomAccess->new($arg);
@@ -4995,7 +5105,7 @@ sub IsSameID($$)
4995
5105
 
4996
5106
  #------------------------------------------------------------------------------
4997
5107
  # Get list of tags in specified group
4998
- # Inputs: 0) ExifTool ref, 1) group spec, 2) tag key or reference to list of tag keys
5108
+ # Inputs: 0) ExifTool ref, 1) group spec (case insensitive), 2) tag key or reference to list of tag keys
4999
5109
  # Returns: list of matching tags in list context, or first match in scalar context
5000
5110
  # Notes: Group spec may contain multiple groups separated by colons, each
5001
5111
  # possibly with a leading family number
@@ -5399,28 +5509,21 @@ sub AddCleanup($)
5399
5509
  sub Warn($$;$)
5400
5510
  {
5401
5511
  my ($self, $str, $ignorable) = @_;
5402
- my $noWarn = $self->Options('NoWarning');
5512
+ my $noWarn = $$self{OPTIONS}{NoWarning};
5403
5513
  if ($ignorable) {
5404
5514
  return 0 if $$self{OPTIONS}{IgnoreMinorErrors};
5405
5515
  return 0 if $ignorable eq '3' and $$self{OPTIONS}{Validate};
5406
5516
  return 1 if defined $noWarn and eval { $str =~ /$noWarn/ };
5407
5517
  $str = $ignorable eq '2' ? "[Minor] $str" : "[minor] $str";
5408
5518
  }
5409
- $self->FoundTag('Warning', $str) unless defined $noWarn and eval { $str =~ /$noWarn/ };
5410
- return 1;
5411
- }
5412
-
5413
- #------------------------------------------------------------------------------
5414
- # Add warning tag only once per processed file
5415
- # Inputs: 0) ExifTool object reference, 1) warning message, 2) true if minor
5416
- # Returns: true if warning tag was added
5417
- sub WarnOnce($$;$)
5418
- {
5419
- my ($self, $str, $ignorable) = @_;
5420
- return 0 if $ignorable and $$self{OPTIONS}{IgnoreMinorErrors};
5421
- unless ($$self{WARNED_ONCE}{$str}) {
5422
- $self->Warn($str, $ignorable);
5423
- $$self{WARNED_ONCE}{$str} = 1;
5519
+ unless (defined $noWarn and eval { $str =~ /$noWarn/ }) {
5520
+ # add each warning only once but count number of occurrences
5521
+ if ($$self{WAS_WARNED}{$str}) {
5522
+ ++$$self{WAS_WARNED}{$str};
5523
+ } else {
5524
+ $self->FoundTag('Warning', $str);
5525
+ $$self{WAS_WARNED}{$str} = 1;
5526
+ }
5424
5527
  }
5425
5528
  return 1;
5426
5529
  }
@@ -6151,7 +6254,7 @@ sub Decode($$$;$$$)
6151
6254
  }
6152
6255
 
6153
6256
  #------------------------------------------------------------------------------
6154
- # Encode string with specified encoding
6257
+ # Encode string (in Charset encoding) to specified encoding
6155
6258
  # Inputs: 0) ExifTool object ref, 1) string, 2) destination character set name,
6156
6259
  # 3) optional destination byte order (2-byte and 4-byte fixed-width sets only)
6157
6260
  # Returns: string in specified encoding
@@ -6322,10 +6425,12 @@ sub Filter($$$)
6322
6425
  # Return printable value
6323
6426
  # Inputs: 0) ExifTool object reference
6324
6427
  # 1) value to print, 2) line length limit (undef defaults to 60, 0=unlimited)
6428
+ # Returns: Printable string
6325
6429
  sub Printable($;$)
6326
6430
  {
6327
6431
  my ($self, $outStr, $maxLen) = @_;
6328
6432
  return '(undef)' unless defined $outStr;
6433
+ ref $outStr eq 'SCALAR' and return '(Binary data '.length($$outStr).' bytes)';
6329
6434
  $outStr =~ tr/\x01-\x1f\x7f-\xff/./;
6330
6435
  $outStr =~ s/\x00//g;
6331
6436
  my $verbose = $$self{OPTIONS}{Verbose};
@@ -6358,9 +6463,45 @@ sub ConvertDateTime($$)
6358
6463
  my $fmt = $$self{OPTIONS}{DateFormat};
6359
6464
  my $shift = $$self{OPTIONS}{GlobalTimeShift};
6360
6465
  if ($shift) {
6361
- my $dir = ($shift =~ s/^([-+])// and $1 eq '-') ? -1 : 1;
6362
6466
  my $offset = $$self{GLOBAL_TIME_OFFSET};
6363
- $offset or $offset = $$self{GLOBAL_TIME_OFFSET} = { };
6467
+ my ($g, $t, $dir, @matches);
6468
+ if ($shift =~ s/^((\d?[A-Z][-\w]*\w:)*)([A-Z][-\w]*\w)([-+])//i) {
6469
+ ($g, $t, $dir) = ($1, $3, ($4 eq '-' ? -1 : 1));
6470
+ } else {
6471
+ $dir = ($shift =~ s/^([-+])// and $1 eq '-') ? -1 : 1;
6472
+ }
6473
+ unless ($offset) {
6474
+ $offset = $$self{GLOBAL_TIME_OFFSET} = { };
6475
+ # (see forum16692 for a discussion about why this code was added)
6476
+ if ($t) {
6477
+ # determine initial shift from specified tag
6478
+ @matches = sort grep(/^$t( \(|$)/i, keys %{$$self{VALUE}});
6479
+ if ($g and @matches) {
6480
+ $g =~ s/:$//;
6481
+ @matches = $self->GroupMatches($g, \@matches);
6482
+ }
6483
+ }
6484
+ if (not @matches and $$self{TAGS_FROM_FILE} and $$self{OPTIONS}{RequestTags}) {
6485
+ # determine initial shift from first requested date/time tag
6486
+ my @reqDate = grep /date/i, @{$$self{OPTIONS}{RequestTags}};
6487
+ while (@reqDate) {
6488
+ $t = shift @reqDate;
6489
+ @matches = sort grep(/^$t( \(|$)/i, keys %{$$self{VALUE}});
6490
+ my $ti = $$self{TAG_INFO};
6491
+ for (; @matches; shift @matches) {
6492
+ # select the first tag that calls this routine in its PrintConv
6493
+ next unless $$ti{$matches[0]}{PrintConv};
6494
+ next unless $$ti{$matches[0]}{PrintConv} =~ /ConvertDateTime/;
6495
+ undef @reqDate;
6496
+ last;
6497
+ }
6498
+ }
6499
+ }
6500
+ if (@matches) {
6501
+ my $val = $self->GetValue($matches[0], 'ValueConv');
6502
+ ShiftTime($val, $shift, $dir, $offset) if defined $val;
6503
+ }
6504
+ }
6364
6505
  ShiftTime($date, $shift, $dir, $offset);
6365
6506
  }
6366
6507
  # only convert date if a format was specified and the date is recognizable
@@ -6412,11 +6553,13 @@ sub ConvertDateTime($$)
6412
6553
  $fmt =~ s/(^|[^%])((%%)*)%-?\.?\d*f/$1$2$frac/g;
6413
6554
  }
6414
6555
  # parse %z and %s ourself (to handle time zones properly)
6415
- if ($fmt =~ /%[sz]/) {
6556
+ if ($fmt =~ /%:?[sz]/) {
6416
6557
  # use system time zone unless otherwise specified
6417
6558
  $tz = TimeZoneString(\@a, TimeLocal(@a)) if not $tz and eval { require Time::Local };
6418
6559
  # remove colon, setting to UTC if time zone is not numeric
6419
- $tz = ($tz and $tz=~/^([-+]\d{2}):(\d{2})$/) ? "$1$2" : '+0000';
6560
+ $tz = '+00:00' unless $tz and $tz=~/^[-+]\d{2}:\d{2}$/;
6561
+ $fmt =~ s/(^|[^%])((%%)*)%:z/$1$2$tz/g; # convert '%:z' format codes
6562
+ $tz =~ s/://;
6420
6563
  $fmt =~ s/(^|[^%])((%%)*)%z/$1$2$tz/g; # convert '%z' format codes
6421
6564
  if ($fmt =~ /%s/ and eval { require Time::Local }) {
6422
6565
  # calculate seconds since the Epoch, UTC
@@ -6753,6 +6896,8 @@ sub IdentifyTrailer($;$)
6753
6896
  $type = 'Insta360';
6754
6897
  } elsif ($buff =~ m(\0{6}/NIKON APP$)) {
6755
6898
  $type = 'NikonApp';
6899
+ } elsif ($buff =~ /\xff{4}\x1b\*9HWfu\x84\x93\xa2\xb1$/) {
6900
+ $type = 'Vivo';
6756
6901
  }
6757
6902
  last;
6758
6903
  }
@@ -6765,7 +6910,8 @@ sub IdentifyTrailer($;$)
6765
6910
  # Inputs: 0) ExifTool object ref, 1) DirInfo ref:
6766
6911
  # - requires RAF and DirName
6767
6912
  # - OutFile is a scalar reference for writing
6768
- # - scans from current file position if ScanForAFCP is set
6913
+ # - scans from current file position for each trailer if ScanForTrailer is set
6914
+ # (current file position is just after JPEG EOF for a JPEG image)
6769
6915
  # Returns: 1 if trailer was processed or couldn't be processed (or written OK)
6770
6916
  # 0 if trailer was recognized but offsets need fixing (or write error)
6771
6917
  # - DirName, DirLen, DataPos, Offset, Fixup and OutFile are updated
@@ -6811,7 +6957,7 @@ sub ProcessTrailers($$)
6811
6957
  # read or write this trailer
6812
6958
  # (proc takes Offset as positive offset from end of trailer to end of file,
6813
6959
  # and returns DataPos and DirLen, and Fixup if applicable, and updates
6814
- # OutFile when writing)
6960
+ # OutFile when writing. Returns < 0 if we must scan for this trailer)
6815
6961
  no strict 'refs';
6816
6962
  my $result = &$proc($self, $dirInfo);
6817
6963
  use strict 'refs';
@@ -7221,7 +7367,7 @@ sub ProcessJPEG($$;$)
7221
7367
  # and scan for AFCP if necessary
7222
7368
  my $fromEnd = 0;
7223
7369
  if ($trailInfo) {
7224
- $$trailInfo{ScanForAFCP} = 1; # scan now if necessary
7370
+ $$trailInfo{ScanForTrailer} = 1; # scan now if necessary
7225
7371
  $self->ProcessTrailers($trailInfo);
7226
7372
  # save offset from end of file to start of first trailer
7227
7373
  $fromEnd = $$trailInfo{Offset};
@@ -7322,7 +7468,7 @@ sub ProcessJPEG($$;$)
7322
7468
  }
7323
7469
  # handle all other markers
7324
7470
  my $dumpType = '';
7325
- my ($desc, $tip, $xtra);
7471
+ my ($desc, $tip, $xtra, $useJpegMain);
7326
7472
  $length = length $$segDataPt;
7327
7473
  $appBytes += $length + 4 if ($marker & 0xf0) == 0xe0; # total size of APP segments
7328
7474
  if ($verbose) {
@@ -7421,6 +7567,19 @@ sub ProcessJPEG($$;$)
7421
7567
  $$self{SkipData} = \@skipData if @skipData;
7422
7568
  # extract the EXIF information (it is in standard TIFF format)
7423
7569
  $self->ProcessTIFF(\%dirInfo) or $self->Warn('Malformed APP1 EXIF segment');
7570
+ # scan for Vivo HiddenData if necessary
7571
+ if ($$self{Make} eq 'vivo' and
7572
+ # (stored as UserComment by some models)
7573
+ not ($$self{VALUE}{UserComment} and $$self{VALUE}{UserComment} =~ /^filter:/) and
7574
+ $$dataPt =~ /(filter: .*?; \n)\0/sg)
7575
+ {
7576
+ if ($htmlDump) {
7577
+ my $n = length($1) + 1;
7578
+ $self->HDump($segPos+pos($$dataPt)-$n, $n, '[Vivo HiddenData]', undef, 0x08);
7579
+ }
7580
+ my $tbl = GetTagTable('Image::ExifTool::Vivo::Main');
7581
+ $self->HandleTag($tbl, HiddenData => $1);
7582
+ }
7424
7583
  # avoid looking for preview unless necessary because it really slows
7425
7584
  # us down -- only look for it if we found pointer, and preview is
7426
7585
  # outside EXIF, and PreviewImage is specifically requested
@@ -7457,13 +7616,13 @@ sub ProcessJPEG($$;$)
7457
7616
  my ($size, $off) = unpack('x67N2', $$segDataPt);
7458
7617
  my $guid = substr($$segDataPt, 35, 32);
7459
7618
  if ($guid =~ /[^A-Za-z0-9]/) { # (technically, should be uppercase)
7460
- $self->WarnOnce($tip = 'Invalid extended XMP GUID');
7619
+ $self->Warn($tip = 'Invalid extended XMP GUID');
7461
7620
  } else {
7462
7621
  my $extXMP = $extendedXMP{$guid};
7463
7622
  if (not $extXMP) {
7464
7623
  $extXMP = $extendedXMP{$guid} = { };
7465
7624
  } elsif ($size != $$extXMP{Size}) {
7466
- $self->WarnOnce('Inconsistent extended XMP size');
7625
+ $self->Warn('Inconsistent extended XMP size');
7467
7626
  }
7468
7627
  $$extXMP{Size} = $size;
7469
7628
  $$extXMP{$off} = substr($$segDataPt, 75);
@@ -7472,7 +7631,7 @@ sub ProcessJPEG($$;$)
7472
7631
  # (delay processing extended XMP until after reading all segments)
7473
7632
  }
7474
7633
  } else {
7475
- $self->WarnOnce($tip = 'Invalid extended XMP segment');
7634
+ $self->Warn($tip = 'Invalid extended XMP segment');
7476
7635
  }
7477
7636
  } elsif ($$segDataPt =~ /^QVCI\0/) {
7478
7637
  $dumpType = 'QVCI';
@@ -7495,7 +7654,7 @@ sub ProcessJPEG($$;$)
7495
7654
  }
7496
7655
  if (defined $flirCount) {
7497
7656
  if (defined $flirChunk[$chunkNum]) {
7498
- $self->WarnOnce('Duplicate FLIR chunk number(s)');
7657
+ $self->Warn('Duplicate FLIR chunk number(s)');
7499
7658
  $flirChunk[$chunkNum] .= substr($$segDataPt, 8);
7500
7659
  } else {
7501
7660
  $flirChunk[$chunkNum] = substr($$segDataPt, 8);
@@ -7515,7 +7674,7 @@ sub ProcessJPEG($$;$)
7515
7674
  undef $flirCount; # prevent reprocessing
7516
7675
  }
7517
7676
  } else {
7518
- $self->WarnOnce('Invalid or extraneous FLIR chunk(s)');
7677
+ $self->Warn('Invalid or extraneous FLIR chunk(s)');
7519
7678
  }
7520
7679
  } elsif ($$segDataPt =~ /^PARROT\0(II\x2a\0|MM\0\x2a)/) {
7521
7680
  # (don't know if this could span multiple segments)
@@ -7542,7 +7701,7 @@ sub ProcessJPEG($$;$)
7542
7701
  $self->Warn("Ignored APP1 segment length $length (unknown header)");
7543
7702
  }
7544
7703
  }
7545
- } elsif ($marker == 0xe2) { # APP2 (ICC Profile, FPXR, MPF, InfiRay, PreviewImage)
7704
+ } elsif ($marker == 0xe2) { # APP2 (ICC Profile, FPXR, MPF, InfiRay, URN, PreviewImage)
7546
7705
  if ($$segDataPt =~ /^ICC_PROFILE\0/ and $length >= 14) {
7547
7706
  $dumpType = 'ICC_Profile';
7548
7707
  # must concatenate profile chunks (note: handle the case where
@@ -7560,7 +7719,7 @@ sub ProcessJPEG($$;$)
7560
7719
  }
7561
7720
  if (defined $iccChunkCount) {
7562
7721
  if (defined $iccChunk[$chunkNum]) {
7563
- $self->WarnOnce('Duplicate ICC_Profile chunk number(s)');
7722
+ $self->Warn('Duplicate ICC_Profile chunk number(s)');
7564
7723
  $iccChunk[$chunkNum] .= substr($$segDataPt, 14);
7565
7724
  } else {
7566
7725
  $iccChunk[$chunkNum] = substr($$segDataPt, 14);
@@ -7583,7 +7742,7 @@ sub ProcessJPEG($$;$)
7583
7742
  undef $iccChunkCount; # prevent reprocessing
7584
7743
  }
7585
7744
  } else {
7586
- $self->WarnOnce('Invalid or extraneous ICC_Profile chunk(s)');
7745
+ $self->Warn('Invalid or extraneous ICC_Profile chunk(s)');
7587
7746
  }
7588
7747
  } elsif ($$segDataPt =~ /^FPXR\0/) {
7589
7748
  next if $fast > 1; # skip processing for very fast
@@ -7615,6 +7774,9 @@ sub ProcessJPEG($$;$)
7615
7774
  # Digilife DDC-690/Rollei="BGTH"
7616
7775
  $dumpType = 'Preview Image';
7617
7776
  $preview = substr($$segDataPt, length($1));
7777
+ } elsif ($$segDataPt =~ /^urn:/) { # (found in Apple HDR images)
7778
+ $dumpType = 'URN';
7779
+ $useJpegMain = 1;
7618
7780
  } elsif ($preview) {
7619
7781
  $dumpType = 'Preview Image';
7620
7782
  $preview .= $$segDataPt;
@@ -7860,6 +8022,10 @@ sub ProcessJPEG($$;$)
7860
8022
  SetByteOrder('II');
7861
8023
  my $tagTablePtr = GetTagTable('Image::ExifTool::InfiRay::Isothermal');
7862
8024
  $self->ProcessDirectory(\%dirInfo, $tagTablePtr);
8025
+ } elsif ($$segDataPt =~ /^SEAL\0/) {
8026
+ $dumpType = 'SEAL';
8027
+ DirStart(\%dirInfo, 5);
8028
+ $self->ProcessDirectory(\%dirInfo, GetTagTable("Image::ExifTool::XMP::SEAL"));
7863
8029
  }
7864
8030
  } elsif ($marker == 0xe9) { # APP9 (InfiRay, Media Jukebox)
7865
8031
  if ($$segDataPt =~ /^Media Jukebox\0/ and $length > 22) {
@@ -7875,18 +8041,19 @@ sub ProcessJPEG($$;$)
7875
8041
  SetByteOrder('II');
7876
8042
  my $tagTablePtr = GetTagTable('Image::ExifTool::InfiRay::Sensor');
7877
8043
  $self->ProcessDirectory(\%dirInfo, $tagTablePtr);
8044
+ } elsif ($$segDataPt =~ /^SEAL\0/) {
8045
+ $dumpType = 'SEAL';
8046
+ DirStart(\%dirInfo, 5);
8047
+ $self->ProcessDirectory(\%dirInfo, GetTagTable("Image::ExifTool::XMP::SEAL"));
7878
8048
  }
7879
- } elsif ($marker == 0xea) { # APP10 (PhotoStudio Unicode comments)
8049
+ } elsif ($marker == 0xea) { # APP10 (PhotoStudio Unicode comments, HDR gain curve)
7880
8050
  if ($$segDataPt =~ /^UNICODE\0/) {
7881
8051
  $dumpType = 'PhotoStudio';
7882
8052
  my $comment = $self->Decode(substr($$segDataPt,8), 'UCS2', 'MM');
7883
8053
  $self->FoundTag('Comment', $comment);
7884
- } elsif ($$segDataPt =~ /^AROT\0/ and $length > 10) {
7885
- # iPhone "AROT" segment containing integrated intensity per 16 scan lines
7886
- # (with number of elements N = ImageHeight / 16 - 1, ref PH/NealKrawetz)
7887
- # "Absolute ROTational difference between two frames"
7888
- # (see https://www.hackerfactor.com/blog/index.php?/archives/822-Apple-Rot.html)
7889
- $xtra = 'segment (N=' . unpack('x6N', $$segDataPt) . ')';
8054
+ } elsif ($$segDataPt =~ /^AROT\0\0.{4}/s) {
8055
+ $dumpType = 'AROT', # (HDR gain curve? PH guess)
8056
+ $useJpegMain = 1;
7890
8057
  }
7891
8058
  } elsif ($marker == 0xeb) { # APP11 (JPEG-HDR, JUMBF)
7892
8059
  if ($$segDataPt =~ /^HDR_RI /) {
@@ -7919,7 +8086,7 @@ sub ProcessJPEG($$;$)
7919
8086
  my $type = substr($$segDataPt, 12, 4);
7920
8087
  # a Microsoft bug writes $len and $type incorrectly as little-endian
7921
8088
  if ($type eq 'bmuj') {
7922
- $self->WarnOnce('Wrong byte order in C2PA APP11 JUMBF header');
8089
+ $self->Warn('Wrong byte order in C2PA APP11 JUMBF header');
7923
8090
  $type = 'jumb';
7924
8091
  $len = unpack('x8V', $$segDataPt);
7925
8092
  # fix the header
@@ -8053,6 +8220,10 @@ sub ProcessJPEG($$;$)
8053
8220
  $desc = "[JPEG $markerName]"; # (other known JPEG segments)
8054
8221
  }
8055
8222
  if (defined $dumpType) {
8223
+ if ($useJpegMain) {
8224
+ my $tagTablePtr = GetTagTable('Image::ExifTool::JPEG::Main');
8225
+ $self->HandleTag($tagTablePtr, $markerName, $$segDataPt);
8226
+ }
8056
8227
  if (not $dumpType and ($$options{Unknown} or $$options{Validate})) {
8057
8228
  my $str = ($$segDataPt =~ /^([\x20-\x7e]{1,20})\0/) ? " '${1}'" : '';
8058
8229
  $xtra = 'segment' unless $xtra;
@@ -8223,7 +8394,11 @@ sub DoProcessTIFF($$;$)
8223
8394
  # save a copy of the EXIF data
8224
8395
  my $dirStart = $$dirInfo{DirStart} || 0;
8225
8396
  my $dirLen = $$dirInfo{DirLen} || (length($$dataPt) - $dirStart);
8226
- $$self{EXIF_DATA} = substr($$dataPt, $dirStart, $dirLen);
8397
+ if ($dirLen > 0 or not $outfile) {
8398
+ $$self{EXIF_DATA} = substr($$dataPt, $dirStart, $dirLen);
8399
+ } else {
8400
+ delete $$self{EXIF_DATA}; # create from scratch;
8401
+ }
8227
8402
  $self->VerboseDir('TIFF') if $$self{OPTIONS}{Verbose} and length($$self{INDENT}) > 2;
8228
8403
  } elsif ($outfile) {
8229
8404
  delete $$self{EXIF_DATA}; # create from scratch
@@ -8382,7 +8557,7 @@ sub DoProcessTIFF($$;$)
8382
8557
  if ($raf) {
8383
8558
  my $trailInfo = IdentifyTrailer($raf);
8384
8559
  if ($trailInfo) {
8385
- $$trailInfo{ScanForAFCP} = 1; # scan to find AFCP if necessary
8560
+ $$trailInfo{ScanForTrailer} = 1; # scan to find AFCP if necessary
8386
8561
  $self->ProcessTrailers($trailInfo);
8387
8562
  }
8388
8563
  # dump any other known trailer (eg. A100 RAW Data)
@@ -8487,7 +8662,7 @@ sub DoProcessTIFF($$;$)
8487
8662
  last unless $trailInfo;
8488
8663
  my $tbuf = '';
8489
8664
  $$trailInfo{OutFile} = \$tbuf; # rewrite trailer(s)
8490
- $$trailInfo{ScanForAFCP} = 1; # scan for AFCP if necessary
8665
+ $$trailInfo{ScanForTrailer} = 1; # scan for AFCP if necessary
8491
8666
  # rewrite all trailers to buffer
8492
8667
  unless ($self->ProcessTrailers($trailInfo)) {
8493
8668
  undef $trailInfo;
@@ -8592,23 +8767,18 @@ sub GetTagTable($)
8592
8767
  # try to load module for this table
8593
8768
  if ($tableName =~ /(.*)::/) {
8594
8769
  my $module = $1;
8595
- if (eval "require $module") {
8770
+ if (not eval "require $module") {
8771
+ $@ and warn $@;
8772
+ } elsif (not %$tableName) {
8596
8773
  # load additional modules if required
8597
- if (not %$tableName) {
8598
- if ($module eq 'Image::ExifTool::XMP') {
8599
- require 'Image/ExifTool/XMP2.pl';
8600
- } elsif ($tableName eq 'Image::ExifTool::QuickTime::Stream') {
8601
- require 'Image/ExifTool/QuickTimeStream.pl';
8602
- }
8774
+ if ($module eq 'Image::ExifTool::XMP') {
8775
+ require 'Image/ExifTool/XMP2.pl';
8776
+ } elsif ($tableName eq 'Image::ExifTool::QuickTime::Stream') {
8777
+ require 'Image/ExifTool/QuickTimeStream.pl';
8603
8778
  }
8604
- } else {
8605
- $@ and warn $@;
8606
8779
  }
8607
8780
  }
8608
- unless (%$tableName) {
8609
- warn "Can't find table $tableName\n";
8610
- return undef;
8611
- }
8781
+ %$tableName or warn("Can't find table $tableName\n"), return undef;
8612
8782
  }
8613
8783
  no strict 'refs';
8614
8784
  $table = \%$tableName;
@@ -8797,6 +8967,7 @@ sub GetTagInfo($$$;$$$)
8797
8967
  my ($valPt, $format, $count);
8798
8968
 
8799
8969
  my @infoArray = GetTagInfoList($tagTablePtr, $tagID);
8970
+ my $options = $$self{OPTIONS};
8800
8971
  # evaluate condition
8801
8972
  my $tagInfo;
8802
8973
  foreach $tagInfo (@infoArray) {
@@ -8814,10 +8985,11 @@ sub GetTagInfo($$$;$$$)
8814
8985
  next;
8815
8986
  }
8816
8987
  }
8817
- # don't return Unknown tags unless that option is set (also see forum13716)
8818
- if ($$tagInfo{Unknown} and not $$self{OPTIONS}{Unknown} and not
8819
- ($$self{OPTIONS}{Verbose} or $$self{HTML_DUMP} or
8820
- ($$self{OPTIONS}{Validate} and not $$tagInfo{AddedUnknown})))
8988
+ # don't return Unknown tags unless that option is set or we are writing (also see forum13716)
8989
+ if ($$tagInfo{Unknown} and not $$options{Unknown} and
8990
+ (not $$self{IsWriting} or $$tagInfo{AddedUnknown}) and not
8991
+ ($$options{Verbose} or $$self{HTML_DUMP} or
8992
+ ($$options{Validate} and not $$tagInfo{AddedUnknown})))
8821
8993
  {
8822
8994
  return undef;
8823
8995
  }
@@ -8825,7 +8997,7 @@ sub GetTagInfo($$$;$$$)
8825
8997
  return $tagInfo;
8826
8998
  }
8827
8999
  # generate information for unknown tags (numerical only) if required
8828
- if (not $tagInfo and ($$self{OPTIONS}{Unknown} or $$self{OPTIONS}{Verbose}) and
9000
+ if (not $tagInfo and ($$options{Unknown} or $$options{Verbose} or $$self{HTML_DUMP}) and
8829
9001
  $tagID =~ /^\d+$/ and not $$self{NO_UNKNOWN})
8830
9002
  {
8831
9003
  my $printConv;
@@ -8924,7 +9096,7 @@ sub HandleTag($$$$;%)
8924
9096
  my $pfmt = $parms{Format};
8925
9097
  my $tagInfo = $parms{TagInfo} || $self->GetTagInfo($tagTablePtr, $tag, \$val, $pfmt, $parms{Count});
8926
9098
  my $dataPt = $parms{DataPt};
8927
- my ($subdir, $format, $noTagInfo, $rational);
9099
+ my ($subdir, $format, $noTagInfo, $rational, $binVal);
8928
9100
 
8929
9101
  if ($tagInfo) {
8930
9102
  $subdir = $$tagInfo{SubDirectory};
@@ -8948,6 +9120,7 @@ sub HandleTag($$$$;%)
8948
9120
  } else {
8949
9121
  $val = substr($$dataPt, $start, $size);
8950
9122
  }
9123
+ $binVal = substr($$dataPt, $start, $size) if $$self{OPTIONS}{SaveBin};
8951
9124
  } else {
8952
9125
  $self->Warn("Error extracting value for $$tagInfo{Name}");
8953
9126
  return undef;
@@ -9009,6 +9182,7 @@ sub HandleTag($$$$;%)
9009
9182
  Base => $parms{Base},
9010
9183
  Multi => $$subdir{Multi},
9011
9184
  TagInfo => $tagInfo,
9185
+ IgnoreProp => $$subdir{IgnoreProp},
9012
9186
  RAF => $parms{RAF},
9013
9187
  );
9014
9188
  my $oldOrder = GetByteOrder();
@@ -9030,8 +9204,11 @@ sub HandleTag($$$$;%)
9030
9204
  return undef unless $$tagInfo{Writable};
9031
9205
  }
9032
9206
  my $key = $self->FoundTag($tagInfo, $val);
9033
- # save original components of rational numbers
9034
- $$self{RATIONAL}{$key} = $rational if defined $rational and defined $key;
9207
+ if (defined $key) {
9208
+ # save original components of rational numbers and original binary value
9209
+ $$self{TAG_EXTRA}{$key}{Rational} = $rational if defined $rational;
9210
+ $$self{TAG_EXTRA}{$key}{BinVal} = $binVal if defined $binVal;
9211
+ }
9035
9212
  return $key;
9036
9213
  }
9037
9214
  return undef;
@@ -9142,9 +9319,7 @@ sub FoundTag($$$;@)
9142
9319
  # a Warning tag because they may be added by ValueConv, which could be confusing)
9143
9320
  my $oldPriority = $$self{PRIORITY}{$tag};
9144
9321
  unless ($oldPriority) {
9145
- if ($$self{DOC_NUM} or not $$self{TAG_EXTRA}{$tag} or $tag eq 'Warning' or
9146
- not $$self{TAG_EXTRA}{$tag}{G3})
9147
- {
9322
+ if ($$self{DOC_NUM} or $tag eq 'Warning' or not $$self{TAG_EXTRA}{$tag}{G3}) {
9148
9323
  $oldPriority = 1;
9149
9324
  } else {
9150
9325
  $oldPriority = 0; # don't promote sub-document tag over main document
@@ -9162,8 +9337,7 @@ sub FoundTag($$$;@)
9162
9337
  } else {
9163
9338
  $priority = 1; # the normal default
9164
9339
  }
9165
- if ($priority >= $oldPriority and (not $$self{DOC_NUM} or
9166
- ($$self{TAG_EXTRA}{$tag} and $$self{TAG_EXTRA}{$tag}{G3} and
9340
+ if ($priority >= $oldPriority and (not $$self{DOC_NUM} or ($$self{TAG_EXTRA}{$tag}{G3} and
9167
9341
  $$self{DOC_NUM} eq $$self{TAG_EXTRA}{$tag}{G3})) and not $noListDel)
9168
9342
  {
9169
9343
  # move existing tag out of the way since this tag is higher priority
@@ -9172,12 +9346,8 @@ sub FoundTag($$$;@)
9172
9346
  $$valueHash{$nextTag} = $$valueHash{$tag};
9173
9347
  $$self{FILE_ORDER}{$nextTag} = $$self{FILE_ORDER}{$tag};
9174
9348
  my $oldInfo = $$self{TAG_INFO}{$nextTag} = $$self{TAG_INFO}{$tag};
9175
- foreach ('TAG_EXTRA','RATIONAL') {
9176
- if ($$self{$_}{$tag}) {
9177
- $$self{$_}{$nextTag} = $$self{$_}{$tag};
9178
- delete $$self{$_}{$tag};
9179
- }
9180
- }
9349
+ $$self{TAG_EXTRA}{$nextTag} = $$self{TAG_EXTRA}{$tag};
9350
+ $$self{TAG_EXTRA}{$tag} = { };
9181
9351
  delete $$self{BOTH}{$tag};
9182
9352
  # update tag key for list if necessary
9183
9353
  $$self{LIST_TAGS}{$oldInfo} = $nextTag if $$self{LIST_TAGS}{$oldInfo};
@@ -9202,6 +9372,7 @@ sub FoundTag($$$;@)
9202
9372
  $$valueHash{$tag} = $value;
9203
9373
  $$self{FILE_ORDER}{$tag} = ++$$self{NUM_FOUND};
9204
9374
  $$self{TAG_INFO}{$tag} = $tagInfo;
9375
+ $$self{TAG_EXTRA}{$tag} = { } unless $$self{TAG_EXTRA}{$tag};
9205
9376
  # set dynamic groups 0, 1 and 3 if necessary
9206
9377
  $$self{TAG_EXTRA}{$tag}{G0} = $grps[0] if $grps[0];
9207
9378
  $$self{TAG_EXTRA}{$tag}{G1} = $grps[1] if $grps[1];
@@ -9260,7 +9431,6 @@ sub DeleteTag($$)
9260
9431
  delete $$self{TAG_INFO}{$tag};
9261
9432
  delete $$self{TAG_EXTRA}{$tag};
9262
9433
  delete $$self{PRIORITY}{$tag};
9263
- delete $$self{RATIONAL}{$tag};
9264
9434
  delete $$self{BOTH}{$tag};
9265
9435
  }
9266
9436
 
@@ -9496,7 +9666,7 @@ sub ProcessBinaryData($$$)
9496
9666
  $increment = $formatSize{$defaultFormat};
9497
9667
  }
9498
9668
  # prepare list of tag numbers to extract
9499
- my (@tags, $topIndex);
9669
+ my (@tags, $topIndex, $binVal);
9500
9670
  if ($unknown > 1 and defined $$tagTablePtr{FIRST_ENTRY}) {
9501
9671
  # don't create a stupid number of tags if data is huge
9502
9672
  my $sizeLimit = $size < 65536 ? $size : 65536;
@@ -9636,6 +9806,7 @@ sub ProcessBinaryData($$$)
9636
9806
  $val = $self->Decode($val, 'UCS2') if $format eq 'ustring' or $format eq 'ustr32';
9637
9807
  $val =~ s/\0.*//s unless $format eq 'undef'; # truncate at null
9638
9808
  }
9809
+ $binVal = substr($$dataPt, $entry+$dirStart, $count) if $$self{OPTIONS}{SaveBin};
9639
9810
  $wasVar = 1;
9640
9811
  # save variable size data if required for writing
9641
9812
  if ($$dirInfo{VarFormatData}) {
@@ -9757,7 +9928,8 @@ sub ProcessBinaryData($$$)
9757
9928
  my $key = $self->FoundTag($tagInfo,$val);
9758
9929
  $$self{BASE} = $oldBase if defined $oldBase;
9759
9930
  if ($key) {
9760
- $$self{RATIONAL}{$key} = $rational if defined $rational;
9931
+ $$self{TAG_EXTRA}{$key}{Rational} = $rational if defined $rational;
9932
+ $$self{TAG_EXTRA}{$key}{BinVal} = $binVal if defined $binVal;
9761
9933
  } else {
9762
9934
  # don't increment nextIndex if we didn't extract a tag
9763
9935
  $nextIndex = $saveNextIndex if defined $saveNextIndex;