exiftool_vendored 12.98.0 → 13.02.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/bin/Changes +73 -1
  3. data/bin/META.json +1 -1
  4. data/bin/META.yml +1 -1
  5. data/bin/README +2 -2
  6. data/bin/arg_files/exif2xmp.args +4 -0
  7. data/bin/arg_files/xmp2exif.args +4 -0
  8. data/bin/config_files/example.config +2 -1
  9. data/bin/exiftool +297 -66
  10. data/bin/lib/Image/ExifTool/APP12.pm +3 -2
  11. data/bin/lib/Image/ExifTool/CBOR.pm +4 -1
  12. data/bin/lib/Image/ExifTool/Canon.pm +36 -26
  13. data/bin/lib/Image/ExifTool/Exif.pm +8 -9
  14. data/bin/lib/Image/ExifTool/FlashPix.pm +5 -9
  15. data/bin/lib/Image/ExifTool/Geolocation.dat +0 -0
  16. data/bin/lib/Image/ExifTool/Geotag.pm +6 -5
  17. data/bin/lib/Image/ExifTool/GoPro.pm +2 -2
  18. data/bin/lib/Image/ExifTool/Import.pm +7 -3
  19. data/bin/lib/Image/ExifTool/JSON.pm +3 -4
  20. data/bin/lib/Image/ExifTool/Jpeg2000.pm +2 -2
  21. data/bin/lib/Image/ExifTool/LNK.pm +1 -1
  22. data/bin/lib/Image/ExifTool/Lytro.pm +2 -2
  23. data/bin/lib/Image/ExifTool/M2TS.pm +2 -2
  24. data/bin/lib/Image/ExifTool/MIE.pm +9 -3
  25. data/bin/lib/Image/ExifTool/MacOS.pm +2 -1
  26. data/bin/lib/Image/ExifTool/Nikon.pm +5 -2
  27. data/bin/lib/Image/ExifTool/PDF.pm +7 -3
  28. data/bin/lib/Image/ExifTool/PhaseOne.pm +2 -1
  29. data/bin/lib/Image/ExifTool/QuickTime.pm +11 -1
  30. data/bin/lib/Image/ExifTool/QuickTimeStream.pl +88 -9
  31. data/bin/lib/Image/ExifTool/TagLookup.pm +14 -9
  32. data/bin/lib/Image/ExifTool/TagNames.pod +37 -20
  33. data/bin/lib/Image/ExifTool/Text.pm +3 -2
  34. data/bin/lib/Image/ExifTool/Validate.pm +2 -2
  35. data/bin/lib/Image/ExifTool/WriteXMP.pl +16 -4
  36. data/bin/lib/Image/ExifTool/Writer.pl +42 -61
  37. data/bin/lib/Image/ExifTool/XMP.pm +13 -3
  38. data/bin/lib/Image/ExifTool/XMPStruct.pl +16 -9
  39. data/bin/lib/Image/ExifTool.pm +190 -77
  40. data/bin/lib/Image/ExifTool.pod +73 -36
  41. data/bin/perl-Image-ExifTool.spec +1 -1
  42. data/lib/exiftool_vendored/version.rb +1 -1
  43. metadata +2 -2
@@ -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 = '12.98';
32
+ $VERSION = '13.02';
33
33
  $RELEASE = '';
34
34
  @ISA = qw(Exporter);
35
35
  %EXPORT_TAGS = (
@@ -37,7 +37,7 @@ $RELEASE = '';
37
37
  Public => [qw(
38
38
  ImageInfo AvailableOptions GetTagName GetShortcuts GetAllTags
39
39
  GetWritableTags GetAllGroups GetDeleteGroups GetFileType CanWrite
40
- CanCreate AddUserDefinedTags
40
+ CanCreate AddUserDefinedTags OrderedKeys
41
41
  )],
42
42
  # exports not part of the public API, but used by ExifTool modules:
43
43
  DataAccess => [qw(
@@ -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($$);
@@ -740,7 +741,7 @@ my %fileDescription = (
740
741
  OGV => 'video/ogg',
741
742
  ONP => 'application/on1',
742
743
  ORF => 'image/x-olympus-orf',
743
- OTF => 'application/x-font-otf',
744
+ OTF => 'application/font-otf',
744
745
  PAGES=> 'application/x-iwork-pages-sffpages',
745
746
  PBM => 'image/x-portable-bitmap',
746
747
  PCD => 'image/x-photo-cd',
@@ -798,8 +799,8 @@ my %fileDescription = (
798
799
  THMX => 'application/vnd.ms-officetheme',
799
800
  TIFF => 'image/tiff',
800
801
  Torrent => 'application/x-bittorrent',
801
- TTC => 'application/x-font-ttf',
802
- TTF => 'application/x-font-ttf',
802
+ TTC => 'application/font-ttf',
803
+ TTF => 'application/font-ttf',
803
804
  TXT => 'text/plain',
804
805
  VCard=> 'text/vcard',
805
806
  VRD => 'application/octet-stream', #PH (NC)
@@ -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' ],
@@ -1130,10 +1137,10 @@ my @availableOptions = (
1130
1137
  [ 'Lang', $defaultLang, 'localized language for descriptions etc' ],
1131
1138
  [ 'LargeFileSupport', 1, 'flag indicating support of 64-bit file offsets' ],
1132
1139
  [ 'LimitLongValues', 60, 'length limit for long values' ],
1133
- [ 'List', undef, '[deprecated, use ListSplit and ListJoin instead]' ],
1140
+ [ 'List', undef, '[deprecated, use ListSplit and ListJoin instead]', 1 ],
1134
1141
  [ 'ListItem', undef, 'used to return a specific item from lists' ],
1135
1142
  [ 'ListJoin', ', ', 'join lists together with this separator' ],
1136
- [ 'ListSep', ', ', '[deprecated, use ListSplit and ListJoin instead]' ],
1143
+ [ 'ListSep', ', ', '[deprecated, use ListSplit and ListJoin instead]', 1 ],
1137
1144
  [ 'ListSplit', undef, 'regex for splitting list-type tag values when writing' ],
1138
1145
  [ 'MakerNotes', undef, 'extract maker notes as a block' ],
1139
1146
  [ 'MDItemTags', undef, 'extract MacOS metadata item tags' ],
@@ -1150,6 +1157,7 @@ my @availableOptions = (
1150
1157
  [ 'QuickTimeUTC', undef, 'assume that QuickTime date/time tags are stored as UTC' ],
1151
1158
  [ 'RequestAll', undef, 'extract all tags that must be specifically requested' ],
1152
1159
  [ 'RequestTags', undef, 'extra tags to request (on top of those in the tag list)' ],
1160
+ [ 'SaveBin', undef, 'save binary values of tags' ],
1153
1161
  [ 'SaveFormat', undef, 'save family 6 tag TIFF format' ],
1154
1162
  [ 'SavePath', undef, 'save family 5 location path' ],
1155
1163
  [ 'ScanForXMP', undef, 'flag to scan for XMP information in all files' ],
@@ -1165,11 +1173,12 @@ my @availableOptions = (
1165
1173
  [ 'UserParam', { }, 'user parameters for additional user-defined tag values' ],
1166
1174
  [ 'Validate', undef, 'perform additional validation' ],
1167
1175
  [ 'Verbose', 0, 'print verbose messages (0-5, higher # = more verbose)' ],
1176
+ [ 'WindowsLongPath', undef, 'enable support for long pathnames (enables WindowsWideFile)' ],
1168
1177
  [ 'WindowsWideFile', undef, 'force the use of Windows wide-character file routines' ], # (see forum15208)
1169
1178
  [ 'WriteMode', 'wcg', 'enable all write modes by default' ],
1170
1179
  [ 'XAttrTags', undef, 'extract MacOS extended attribute tags' ],
1171
1180
  [ 'XMPAutoConv', 1, 'automatic conversion of unknown XMP tag values' ],
1172
- [ 'XMPShorthand', 0, '[deprecated, use Compact=Shorthand instead]' ],
1181
+ [ 'XMPShorthand', 0, '[deprecated, use Compact=Shorthand instead]', 1 ],
1173
1182
  );
1174
1183
 
1175
1184
  # default family 0 group priority for writing
@@ -2292,6 +2301,7 @@ sub new
2292
2301
  $$self{PATH} = [ ]; # (this too)
2293
2302
  $$self{DEL_GROUP} = { }; # lookup for groups to delete when writing
2294
2303
  $$self{SAVE_COUNT} = 0; # count calls to SaveNewValues()
2304
+ $$self{NV_COUNT} = 0; # count of NEW_VALUE entries
2295
2305
  $$self{FILE_SEQUENCE} = 0; # sequence number for files when reading
2296
2306
  $$self{FILES_WRITTEN} = 0; # count of files successfully written
2297
2307
  $$self{INDENT2} = ''; # indentation of verbose messages from SetNewValue
@@ -2517,6 +2527,8 @@ sub Options($$;@)
2517
2527
  # set Compact and XMPShorthand options, preserving backward compatibility
2518
2528
  my ($p, %compact);
2519
2529
  foreach $p ('Compact','XMPShorthand') {
2530
+ # (allow setting from a HASH (undocumented)
2531
+ ref $newVal eq 'HASH' and %compact = %{$newVal}, next;
2520
2532
  my $val = $param eq $p ? $newVal : $$options{Compact}{$p};
2521
2533
  if (defined $val) {
2522
2534
  my @v = ($val =~ /\w+/g);
@@ -3325,8 +3337,8 @@ sub GetRequestedTags($)
3325
3337
  # Inputs: 0) ExifTool object reference
3326
3338
  # 1) tag key or tag name with optional group names (case sensitive)
3327
3339
  # (or flattened tagInfo for getting field values, not part of public API)
3328
- # 2) [optional] Value type: PrintConv, ValueConv, Both, Raw or Rational, the default
3329
- # is PrintConv or ValueConv, depending on the PrintConv option setting
3340
+ # 2) [optional] Value type: PrintConv, ValueConv, Both, Raw, Bin or Rational, the
3341
+ # default is PrintConv or ValueConv, depending on the PrintConv option setting
3330
3342
  # 3) raw field value (not part of public API)
3331
3343
  # Returns: Scalar context: tag value or undefined
3332
3344
  # List context: list of values or empty list
@@ -3355,7 +3367,8 @@ sub GetValue($$;$)
3355
3367
  }
3356
3368
  # figure out what conversions to do
3357
3369
  if ($type) {
3358
- return $$self{RATIONAL}{$tag} if $type eq 'Rational';
3370
+ return $$self{TAG_EXTRA}{$tag}{Rational} if $type eq 'Rational';
3371
+ return $$self{TAG_EXTRA}{$tag}{BinVal} if $type eq 'Bin';
3359
3372
  } else {
3360
3373
  $type = $$self{OPTIONS}{PrintConv} ? 'PrintConv' : 'ValueConv';
3361
3374
  }
@@ -3699,9 +3712,10 @@ sub GetGroup($$;$)
3699
3712
  $tag = $$tagInfo{Name};
3700
3713
  # set flag so we don't get extra information for an extracted tag
3701
3714
  $byTagInfo = 1;
3715
+ $ex = { };
3702
3716
  } else {
3703
3717
  $tagInfo = $$self{TAG_INFO}{$tag} || { };
3704
- $ex = $$self{TAG_EXTRA}{$tag};
3718
+ $ex = $$self{TAG_EXTRA}{$tag} || { };
3705
3719
  }
3706
3720
  my $groups = $$tagInfo{Groups};
3707
3721
  # fill in default groups unless already done
@@ -3720,32 +3734,30 @@ sub GetGroup($$;$)
3720
3734
  if (defined $family and $family ne '-1') {
3721
3735
  if ($family =~ /[^\d]/) {
3722
3736
  @families = ($family =~ /\d+/g);
3723
- return(($ex && $$ex{G0}) || $$groups{0}) unless @families;
3737
+ return($$ex{G0} || $$groups{0}) unless @families;
3724
3738
  $simplify = 1 unless $family =~ /^:/;
3725
3739
  undef $family;
3726
3740
  foreach (0..2) { $groups[$_] = $$groups{$_}; }
3727
3741
  $noID = 1 if @families == 1 and $families[0] != 7;
3728
3742
  } else {
3729
- return(($ex && $$ex{"G$family"}) || $$groups{$family}) if $family == 0 or $family == 2;
3743
+ return($$ex{"G$family"} || $$groups{$family}) if $family == 0 or $family == 2;
3730
3744
  $groups[1] = $$groups{1};
3731
3745
  }
3732
3746
  } else {
3733
- return(($ex && $$ex{G0}) || $$groups{0}) unless wantarray;
3747
+ return($$ex{G0} || $$groups{0}) unless wantarray;
3734
3748
  foreach (0..2) { $groups[$_] = $$groups{$_}; }
3735
3749
  }
3736
3750
  $groups[3] = 'Main';
3737
- $groups[4] = ($tag =~ /\((\d+)\)$/) ? "Copy$1" : '';
3751
+ $groups[4] = ($tag =~ /\((\d+)\)$/ and $1 ne '0') ? "Copy$1" : '';
3738
3752
  # handle dynamic group names if necessary
3739
3753
  unless ($byTagInfo) {
3740
- if ($ex) {
3741
- $groups[0] = $$ex{G0} if $$ex{G0};
3742
- $groups[1] = $$ex{G1} =~ /^\+(.*)/ ? "$groups[1]$1" : $$ex{G1} if $$ex{G1};
3743
- $groups[3] = 'Doc' . $$ex{G3} if $$ex{G3};
3744
- $groups[5] = $$ex{G5} || $groups[1] if defined $$ex{G5};
3745
- if (defined $$ex{G6}) {
3746
- $groups[5] = '' unless defined $groups[5]; # (can't leave a hole in the array)
3747
- $groups[6] = $$ex{G6};
3748
- }
3754
+ $groups[0] = $$ex{G0} if $$ex{G0};
3755
+ $groups[1] = $$ex{G1} =~ /^\+(.*)/ ? "$groups[1]$1" : $$ex{G1} if $$ex{G1};
3756
+ $groups[3] = 'Doc' . $$ex{G3} if $$ex{G3};
3757
+ $groups[5] = $$ex{G5} || $groups[1] if defined $$ex{G5};
3758
+ if (defined $$ex{G6}) {
3759
+ $groups[5] = '' unless defined $groups[5]; # (can't leave a hole in the array)
3760
+ $groups[6] = $$ex{G6};
3749
3761
  }
3750
3762
  if ($$ex{G8}) {
3751
3763
  $groups[7] = '';
@@ -3918,12 +3930,9 @@ COMPOSITE_TAG:
3918
3930
  $key = "$reqTag ($i)";
3919
3931
  }
3920
3932
  @keys = $self->GroupMatches($reqGroup, \@keys) if defined $reqGroup;
3921
- if (@keys) {
3922
- my $ex = $$self{TAG_EXTRA};
3923
- # loop through tags in reverse order of precedence so the higher
3924
- # priority tag will win in the case of duplicates within a doc
3925
- $$cacheTag[$$ex{$_} ? $$ex{$_}{G3} || 0 : 0] = $_ foreach reverse @keys;
3926
- }
3933
+ # loop through tags in reverse order of precedence so the higher
3934
+ # priority tag will win in the case of duplicates within a doc
3935
+ $$cacheTag[$$self{TAG_EXTRA}{$_}{G3} || 0] = $_ foreach reverse @keys;
3927
3936
  }
3928
3937
  # (set $reqTag to a bogus key if not found)
3929
3938
  $reqTag = $$cacheTag[$doc] || "$reqTag (0)";
@@ -4195,6 +4204,16 @@ sub CanCreate($)
4195
4204
  return 0;
4196
4205
  }
4197
4206
 
4207
+ #------------------------------------------------------------------------------
4208
+ # Return list of ordered keys if available, otherwise just sort alphabetically
4209
+ # Inputs: 0) hash ref
4210
+ # Returns: List of ordered/sorted keys
4211
+ sub OrderedKeys($)
4212
+ {
4213
+ my $hash = shift;
4214
+ return $$hash{_ordered_keys_} ? @{$$hash{_ordered_keys_}} : sort keys %$hash;
4215
+ }
4216
+
4198
4217
  #==============================================================================
4199
4218
  # Functions below this are not part of the public API
4200
4219
 
@@ -4205,9 +4224,7 @@ sub Init($)
4205
4224
  local $_;
4206
4225
  my $self = shift;
4207
4226
  # delete all DataMember variables (lower-case names)
4208
- foreach (keys %$self) {
4209
- /[a-z]/ and delete $$self{$_};
4210
- }
4227
+ delete $$self{$_} foreach grep /[a-z]/, keys %$self;
4211
4228
  undef %static_vars; # clear all static variables
4212
4229
  delete $$self{FOUND_TAGS}; # list of found tags
4213
4230
  delete $$self{EXIF_DATA}; # the EXIF data block
@@ -4222,7 +4239,6 @@ sub Init($)
4222
4239
  $$self{FILE_ORDER} = { }; # * hash of tag order in file ('*' = based on tag key)
4223
4240
  $$self{VALUE} = { }; # * hash of raw tag values
4224
4241
  $$self{BOTH} = { }; # * hash for Value/PrintConv values of Require'd tags
4225
- $$self{RATIONAL} = { }; # * hash of original rational components
4226
4242
  $$self{TAG_INFO} = { }; # * hash of tag information
4227
4243
  $$self{TAG_EXTRA} = { }; # * hash of extra tag information (dynamic group names)
4228
4244
  $$self{PRIORITY} = { }; # * priority of current tags
@@ -4455,11 +4471,7 @@ sub DoneExtract($)
4455
4471
  my $err = $$altExifTool{VALUE}{Error};
4456
4472
  $err and $self->Warn(qq{$err "$fileName"});
4457
4473
  # set family 8 group name for all tags
4458
- foreach (keys %{$$altExifTool{VALUE}}) {
4459
- my $ex = $$altExifTool{TAG_EXTRA}{$_};
4460
- $ex or $ex = $$altExifTool{TAG_EXTRA}{$_} = { };
4461
- $$ex{G8} = $g8;
4462
- }
4474
+ $$altExifTool{TAG_EXTRA}{$_}{G8} = $g8 foreach keys %{$$altExifTool{VALUE}};
4463
4475
  # prepare our sorted list of found tags
4464
4476
  $$altExifTool{FoundTags} = [ reverse sort keys %{$$altExifTool{VALUE}} ];
4465
4477
  $$altExifTool{DID_EXTRACT} = 1;
@@ -4601,36 +4613,125 @@ sub SplitFileName($)
4601
4613
 
4602
4614
  #------------------------------------------------------------------------------
4603
4615
  # Encode file name for calls to system i/o routines
4604
- # Inputs: 0) ExifTool ref, 1) file name in CharSetFileName, 2) flag to force conversion
4616
+ # Inputs: 0) ExifTool ref, 1) file name in CharsetFileName encoding, 2) flag to force conversion
4605
4617
  # Returns: true if Windows Unicode routines should be used (in which case
4606
4618
  # the file name will be encoded as a null-terminated UTF-16LE string)
4607
4619
  sub EncodeFileName($$;$)
4608
4620
  {
4609
4621
  my ($self, $file, $force) = @_;
4610
4622
  my $enc = $$self{OPTIONS}{CharsetFileName};
4611
- $force = 1 if $$self{OPTIONS}{WindowsWideFile};
4612
- if ($enc) {
4613
- if ($file =~ /[\x80-\xff]/ or $force) {
4614
- # encode for use in Windows Unicode functions if necessary
4615
- if ($^O eq 'MSWin32') {
4616
- local $SIG{'__WARN__'} = \&SetWarning;
4617
- if (eval { require Win32API::File }) {
4618
- # recode as UTF-16LE and add null terminator
4619
- $_[1] = $self->Decode($file, $enc, undef, 'UTF16', 'II') . "\0\0";
4620
- return 1;
4621
- }
4622
- $self->WarnOnce('Install Win32API::File for Windows Unicode file support');
4623
+ my $hasSpecialChars;
4624
+ if ($file =~ /[\x80-\xff]/) {
4625
+ $hasSpecialChars = 1;
4626
+ if (not $enc and $^O eq 'MSWin32') {
4627
+ if (IsUTF8(\$file) < 0) {
4628
+ $self->WarnOnce('FileName encoding must be specified') if not defined $enc;
4629
+ return 0;
4623
4630
  } else {
4624
- # recode as UTF-8 for other platforms if necessary
4625
- $_[1] = $self->Decode($file, $enc, undef, 'UTF8') unless $enc eq 'UTF8';
4631
+ $enc = 'UTF8'; # assume UTF8
4626
4632
  }
4627
4633
  }
4628
- } elsif ($^O eq 'MSWin32' and $file =~ /[\x80-\xff]/ and not defined $enc) {
4629
- $self->WarnOnce('FileName encoding not specified') if IsUTF8(\$file) < 0;
4634
+ }
4635
+ $force = 1 if $$self{OPTIONS}{WindowsWideFile} or $$self{OPTIONS}{WindowsLongPath};
4636
+ if ($hasSpecialChars or $force) {
4637
+ $enc or $enc = 'UTF8';
4638
+ if ($^O eq 'MSWin32') {
4639
+ local $SIG{'__WARN__'} = \&SetWarning;
4640
+ if (eval { require Win32API::File }) {
4641
+ $file = $self->WindowsLongPath($file) if $$self{OPTIONS}{WindowsLongPath};
4642
+ # recode as UTF-16LE and add null terminator
4643
+ $_[1] = $self->Decode($file, $enc, undef, 'UTF16', 'II') . "\0\0";
4644
+ return 1;
4645
+ }
4646
+ $self->WarnOnce('Install Win32API::File for Windows wide/long file name support');
4647
+ } elsif ($enc ne 'UTF8') {
4648
+ # recode as UTF-8 for other platforms if necessary
4649
+ $_[1] = $self->Decode($file, $enc, undef, 'UTF8');
4650
+ }
4630
4651
  }
4631
4652
  return 0;
4632
4653
  }
4633
4654
 
4655
+ #------------------------------------------------------------------------------
4656
+ # Rebuild a path as an absolute long path to be usable in Windows system calls
4657
+ # Inputs: 0) ExifTool ref, 1) path string
4658
+ # Returns: normalized long path
4659
+ # Note: this should only be called for Windows systems
4660
+ # References:
4661
+ # - https://learn.microsoft.com/en-us/dotnet/standard/io/file-path-formats
4662
+ # - https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation
4663
+ sub WindowsLongPath($$)
4664
+ {
4665
+ my ($self, $path) = @_;
4666
+ my $debug = $$self{OPTIONS}{Debug};
4667
+ my $out = $$self{OPTIONS}{TextOut};
4668
+ my @fullParts;
4669
+ my $prefixLen = 0;
4670
+
4671
+ $debug and print $out "WindowsLongPath input : $path\n";
4672
+ $path =~ tr(/)(\\); # convert slashes to backslashes
4673
+ my @pathParts = split /\\/, $path;
4674
+
4675
+ if ($path =~ /^\\\\\?\\/ or # already a device path in the format we want
4676
+ $path =~ s/^\\\\\.\\/\\\\?\\/) # convert //./ to //?/
4677
+ {
4678
+ # path is already long-path compatible
4679
+ $prefixLen = 3; # path already contains prefix of 3 parts ('', '' and '?')
4680
+ } elsif ($path =~ /[*?]/) {
4681
+ return $path; # do nothing because we don't support wildcards
4682
+ } elsif ($path =~ /^\\\\/) {
4683
+ # UNC path starts with two slashes change to "\\?\UNC\"
4684
+ splice @pathParts, 2, 0, '?', 'UNC';
4685
+ $prefixLen = (@pathParts > 6 ? 6 : @pathParts); # ('', '', '?', 'UNC', <server>, <share>)
4686
+ } elsif ($path =~ /^[a-z]:\\/i) {
4687
+ # path is already absolute but we need to add the device path prefix
4688
+ unshift @pathParts, '', '', '?';
4689
+ $prefixLen = 4;
4690
+ } elsif ({ eval { require Cwd } }) {
4691
+ my $drive;
4692
+ $drive = $1 if $pathParts[0] =~ s/^([a-z]:)//;
4693
+ my $cwd = Cwd::getdcwd($drive); # ($drive is undef for current working drive)
4694
+ $debug and print $out "WindowsLongPath getcwd: $cwd\n";
4695
+ @fullParts = split /[\\\/]/, $cwd;
4696
+ # UNC path starts with "\\", so first 2 elements are empty
4697
+ # --> shift and put UNC in first element.
4698
+ if (@fullParts > 1 and $fullParts[0] eq '' and $fullParts[1] eq '') {
4699
+ shift @fullParts;
4700
+ $fullParts[0] = 'UNC';
4701
+ unshift @fullParts, '', '', '?';
4702
+ $prefixLen = (@fullParts > 6 ? 6 : @fullParts);
4703
+ } else {
4704
+ $prefixLen = 1; # drive designator only
4705
+ }
4706
+ # if absolute path on current drive starts with "\"
4707
+ # just keep prefix and drop the rest of the cwd
4708
+ $#fullParts = $prefixLen - 1 if $pathParts[0] eq '';
4709
+ } else {
4710
+ $prefixLen = @pathParts; # (nothing more we can do)
4711
+ }
4712
+ # remove "." and ".." from path (not handled for device paths)
4713
+ my $part;
4714
+ foreach $part (@pathParts) {
4715
+ if ($part eq '.') {
4716
+ next;
4717
+ } elsif ($part eq '') {
4718
+ # only allow double slashes at start of path name (max 2)
4719
+ push @fullParts, $part if not @fullParts or (@fullParts == 1 and $fullParts[0] eq '');
4720
+ } elsif ($part eq '..') {
4721
+ # step up one directory, but not into the prefix
4722
+ pop @fullParts if @fullParts > $prefixLen;
4723
+ } else {
4724
+ push @fullParts, $part;
4725
+ }
4726
+ }
4727
+ $path = join '\\', @fullParts;
4728
+ # add device path prefix ("\\?\") if path length near the limit (the most
4729
+ # conservative limit I can find is 247, which is the limit on the directory name)
4730
+ $path = '\\\\?\\' . $path unless $prefixLen > 1 or length($path) <= 247;
4731
+ $debug and print $out "WindowsLongPath return: $path\n";
4732
+ return $path;
4733
+ }
4734
+
4634
4735
  #------------------------------------------------------------------------------
4635
4736
  # Modified perl open() routine to properly handle special characters in file names
4636
4737
  # Inputs: 0) ExifTool ref, 1) filehandle, 2) filename,
@@ -4897,11 +4998,13 @@ sub ParseArguments($;@)
4897
4998
  next if defined $$self{RAF};
4898
4999
  # convert image data from UTF-8 to character stream if necessary
4899
5000
  # (patches RHEL 3 UTF8 LANG problem)
4900
- if (ref $arg eq 'SCALAR' and $] >= 5.006 and
4901
- (eval { require Encode; Encode::is_utf8($$arg) } or $@))
5001
+ if (ref $arg eq 'SCALAR' and $] >= 5.006 and ($$self{OPTIONS}{EncodeHangs} or
5002
+ eval { require Encode; Encode::is_utf8($$arg) } or $@))
4902
5003
  {
5004
+ local $SIG{'__WARN__'} = \&SetWarning;
4903
5005
  # repack by hand if Encode isn't available
4904
- my $buff = $@ ? pack('C*',unpack($] < 5.010000 ? 'U0C*' : 'C0C*',$$arg)) : Encode::encode('utf8',$$arg);
5006
+ my $buff = ($$self{OPTIONS}{EncodeHangs} or $@) ? pack('C*',unpack($] < 5.010000 ?
5007
+ 'U0C*' : 'C0C*', $$arg)) : Encode::encode('utf8', $$arg);
4905
5008
  $arg = \$buff;
4906
5009
  }
4907
5010
  $$self{RAF} = File::RandomAccess->new($arg);
@@ -6309,10 +6412,12 @@ sub Filter($$$)
6309
6412
  # Return printable value
6310
6413
  # Inputs: 0) ExifTool object reference
6311
6414
  # 1) value to print, 2) line length limit (undef defaults to 60, 0=unlimited)
6415
+ # Returns: Printable string
6312
6416
  sub Printable($;$)
6313
6417
  {
6314
6418
  my ($self, $outStr, $maxLen) = @_;
6315
6419
  return '(undef)' unless defined $outStr;
6420
+ ref $outStr eq 'SCALAR' and return '(Binary data '.length($$outStr).' bytes)';
6316
6421
  $outStr =~ tr/\x01-\x1f\x7f-\xff/./;
6317
6422
  $outStr =~ s/\x00//g;
6318
6423
  my $verbose = $$self{OPTIONS}{Verbose};
@@ -7904,8 +8009,17 @@ sub ProcessJPEG($$;$)
7904
8009
  my $seq = Get32u($segDataPt, 4);
7905
8010
  my $len = Get32u($segDataPt, 8);
7906
8011
  my $type = substr($$segDataPt, 12, 4);
8012
+ # a Microsoft bug writes $len and $type incorrectly as little-endian
8013
+ if ($type eq 'bmuj') {
8014
+ $self->WarnOnce('Wrong byte order in C2PA APP11 JUMBF header');
8015
+ $type = 'jumb';
8016
+ $len = unpack('x8V', $$segDataPt);
8017
+ # fix the header
8018
+ substr($$segDataPt, 8, 8) = Set32u($len) . $type;
8019
+ }
7907
8020
  my $hdrLen;
7908
8021
  if ($len == 1 and length($$segDataPt) >= 24) {
8022
+ # (haven't seen this with the Microsoft bug)
7909
8023
  $len = Get64u($$segDataPt, 16);
7910
8024
  $hdrLen = 16;
7911
8025
  } else {
@@ -8902,7 +9016,7 @@ sub HandleTag($$$$;%)
8902
9016
  my $pfmt = $parms{Format};
8903
9017
  my $tagInfo = $parms{TagInfo} || $self->GetTagInfo($tagTablePtr, $tag, \$val, $pfmt, $parms{Count});
8904
9018
  my $dataPt = $parms{DataPt};
8905
- my ($subdir, $format, $noTagInfo, $rational);
9019
+ my ($subdir, $format, $noTagInfo, $rational, $binVal);
8906
9020
 
8907
9021
  if ($tagInfo) {
8908
9022
  $subdir = $$tagInfo{SubDirectory};
@@ -8926,6 +9040,7 @@ sub HandleTag($$$$;%)
8926
9040
  } else {
8927
9041
  $val = substr($$dataPt, $start, $size);
8928
9042
  }
9043
+ $binVal = substr($$dataPt, $start, $size) if $$self{OPTIONS}{SaveBin};
8929
9044
  } else {
8930
9045
  $self->Warn("Error extracting value for $$tagInfo{Name}");
8931
9046
  return undef;
@@ -9008,8 +9123,11 @@ sub HandleTag($$$$;%)
9008
9123
  return undef unless $$tagInfo{Writable};
9009
9124
  }
9010
9125
  my $key = $self->FoundTag($tagInfo, $val);
9011
- # save original components of rational numbers
9012
- $$self{RATIONAL}{$key} = $rational if defined $rational and defined $key;
9126
+ if (defined $key) {
9127
+ # save original components of rational numbers and original binary value
9128
+ $$self{TAG_EXTRA}{$key}{Rational} = $rational if defined $rational;
9129
+ $$self{TAG_EXTRA}{$key}{BinVal} = $binVal if defined $binVal;
9130
+ }
9013
9131
  return $key;
9014
9132
  }
9015
9133
  return undef;
@@ -9120,9 +9238,7 @@ sub FoundTag($$$;@)
9120
9238
  # a Warning tag because they may be added by ValueConv, which could be confusing)
9121
9239
  my $oldPriority = $$self{PRIORITY}{$tag};
9122
9240
  unless ($oldPriority) {
9123
- if ($$self{DOC_NUM} or not $$self{TAG_EXTRA}{$tag} or $tag eq 'Warning' or
9124
- not $$self{TAG_EXTRA}{$tag}{G3})
9125
- {
9241
+ if ($$self{DOC_NUM} or $tag eq 'Warning' or not $$self{TAG_EXTRA}{$tag}{G3}) {
9126
9242
  $oldPriority = 1;
9127
9243
  } else {
9128
9244
  $oldPriority = 0; # don't promote sub-document tag over main document
@@ -9140,8 +9256,7 @@ sub FoundTag($$$;@)
9140
9256
  } else {
9141
9257
  $priority = 1; # the normal default
9142
9258
  }
9143
- if ($priority >= $oldPriority and (not $$self{DOC_NUM} or
9144
- ($$self{TAG_EXTRA}{$tag} and $$self{TAG_EXTRA}{$tag}{G3} and
9259
+ if ($priority >= $oldPriority and (not $$self{DOC_NUM} or ($$self{TAG_EXTRA}{$tag}{G3} and
9145
9260
  $$self{DOC_NUM} eq $$self{TAG_EXTRA}{$tag}{G3})) and not $noListDel)
9146
9261
  {
9147
9262
  # move existing tag out of the way since this tag is higher priority
@@ -9150,12 +9265,8 @@ sub FoundTag($$$;@)
9150
9265
  $$valueHash{$nextTag} = $$valueHash{$tag};
9151
9266
  $$self{FILE_ORDER}{$nextTag} = $$self{FILE_ORDER}{$tag};
9152
9267
  my $oldInfo = $$self{TAG_INFO}{$nextTag} = $$self{TAG_INFO}{$tag};
9153
- foreach ('TAG_EXTRA','RATIONAL') {
9154
- if ($$self{$_}{$tag}) {
9155
- $$self{$_}{$nextTag} = $$self{$_}{$tag};
9156
- delete $$self{$_}{$tag};
9157
- }
9158
- }
9268
+ $$self{TAG_EXTRA}{$nextTag} = $$self{TAG_EXTRA}{$tag};
9269
+ $$self{TAG_EXTRA}{$tag} = { };
9159
9270
  delete $$self{BOTH}{$tag};
9160
9271
  # update tag key for list if necessary
9161
9272
  $$self{LIST_TAGS}{$oldInfo} = $nextTag if $$self{LIST_TAGS}{$oldInfo};
@@ -9180,6 +9291,7 @@ sub FoundTag($$$;@)
9180
9291
  $$valueHash{$tag} = $value;
9181
9292
  $$self{FILE_ORDER}{$tag} = ++$$self{NUM_FOUND};
9182
9293
  $$self{TAG_INFO}{$tag} = $tagInfo;
9294
+ $$self{TAG_EXTRA}{$tag} = { } unless $$self{TAG_EXTRA}{$tag};
9183
9295
  # set dynamic groups 0, 1 and 3 if necessary
9184
9296
  $$self{TAG_EXTRA}{$tag}{G0} = $grps[0] if $grps[0];
9185
9297
  $$self{TAG_EXTRA}{$tag}{G1} = $grps[1] if $grps[1];
@@ -9238,7 +9350,6 @@ sub DeleteTag($$)
9238
9350
  delete $$self{TAG_INFO}{$tag};
9239
9351
  delete $$self{TAG_EXTRA}{$tag};
9240
9352
  delete $$self{PRIORITY}{$tag};
9241
- delete $$self{RATIONAL}{$tag};
9242
9353
  delete $$self{BOTH}{$tag};
9243
9354
  }
9244
9355
 
@@ -9474,7 +9585,7 @@ sub ProcessBinaryData($$$)
9474
9585
  $increment = $formatSize{$defaultFormat};
9475
9586
  }
9476
9587
  # prepare list of tag numbers to extract
9477
- my (@tags, $topIndex);
9588
+ my (@tags, $topIndex, $binVal);
9478
9589
  if ($unknown > 1 and defined $$tagTablePtr{FIRST_ENTRY}) {
9479
9590
  # don't create a stupid number of tags if data is huge
9480
9591
  my $sizeLimit = $size < 65536 ? $size : 65536;
@@ -9614,6 +9725,7 @@ sub ProcessBinaryData($$$)
9614
9725
  $val = $self->Decode($val, 'UCS2') if $format eq 'ustring' or $format eq 'ustr32';
9615
9726
  $val =~ s/\0.*//s unless $format eq 'undef'; # truncate at null
9616
9727
  }
9728
+ $binVal = substr($$dataPt, $entry+$dirStart, $count) if $$self{OPTIONS}{SaveBin};
9617
9729
  $wasVar = 1;
9618
9730
  # save variable size data if required for writing
9619
9731
  if ($$dirInfo{VarFormatData}) {
@@ -9735,7 +9847,8 @@ sub ProcessBinaryData($$$)
9735
9847
  my $key = $self->FoundTag($tagInfo,$val);
9736
9848
  $$self{BASE} = $oldBase if defined $oldBase;
9737
9849
  if ($key) {
9738
- $$self{RATIONAL}{$key} = $rational if defined $rational;
9850
+ $$self{TAG_EXTRA}{$key}{Rational} = $rational if defined $rational;
9851
+ $$self{TAG_EXTRA}{$key}{BinVal} = $binVal if defined $binVal;
9739
9852
  } else {
9740
9853
  # don't increment nextIndex if we didn't extract a tag
9741
9854
  $nextIndex = $saveNextIndex if defined $saveNextIndex;