exiftool-vendored.exe 12.56.0 → 12.62.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 (60) hide show
  1. package/bin/exiftool_files/Changes +115 -5
  2. package/bin/exiftool_files/LICENSE +674 -0
  3. package/bin/exiftool_files/README +45 -44
  4. package/bin/exiftool_files/config_files/example.config +1 -0
  5. package/bin/exiftool_files/config_files/rotate_regions.config +1 -1
  6. package/bin/exiftool_files/exiftool.pl +262 -160
  7. package/bin/exiftool_files/lib/Image/ExifTool/AIFF.pm +2 -2
  8. package/bin/exiftool_files/lib/Image/ExifTool/APE.pm +2 -2
  9. package/bin/exiftool_files/lib/Image/ExifTool/BMP.pm +0 -1
  10. package/bin/exiftool_files/lib/Image/ExifTool/BuildTagLookup.pm +23 -19
  11. package/bin/exiftool_files/lib/Image/ExifTool/Canon.pm +26 -6
  12. package/bin/exiftool_files/lib/Image/ExifTool/CanonRaw.pm +5 -1
  13. package/bin/exiftool_files/lib/Image/ExifTool/DJI.pm +28 -2
  14. package/bin/exiftool_files/lib/Image/ExifTool/Exif.pm +77 -19
  15. package/bin/exiftool_files/lib/Image/ExifTool/FlashPix.pm +33 -10
  16. package/bin/exiftool_files/lib/Image/ExifTool/FujiFilm.pm +7 -3
  17. package/bin/exiftool_files/lib/Image/ExifTool/GPS.pm +7 -2
  18. package/bin/exiftool_files/lib/Image/ExifTool/Geotag.pm +30 -7
  19. package/bin/exiftool_files/lib/Image/ExifTool/JPEG.pm +14 -2
  20. package/bin/exiftool_files/lib/Image/ExifTool/Jpeg2000.pm +36 -11
  21. package/bin/exiftool_files/lib/Image/ExifTool/LIF.pm +10 -2
  22. package/bin/exiftool_files/lib/Image/ExifTool/LNK.pm +5 -4
  23. package/bin/exiftool_files/lib/Image/ExifTool/MIE.pm +3 -3
  24. package/bin/exiftool_files/lib/Image/ExifTool/MPEG.pm +2 -2
  25. package/bin/exiftool_files/lib/Image/ExifTool/MakerNotes.pm +3 -2
  26. package/bin/exiftool_files/lib/Image/ExifTool/Minolta.pm +6 -7
  27. package/bin/exiftool_files/lib/Image/ExifTool/MinoltaRaw.pm +2 -1
  28. package/bin/exiftool_files/lib/Image/ExifTool/Nikon.pm +1005 -909
  29. package/bin/exiftool_files/lib/Image/ExifTool/NikonCustom.pm +2 -2
  30. package/bin/exiftool_files/lib/Image/ExifTool/NikonSettings.pm +1 -1
  31. package/bin/exiftool_files/lib/Image/ExifTool/Olympus.pm +88 -6
  32. package/bin/exiftool_files/lib/Image/ExifTool/PDF.pm +17 -8
  33. package/bin/exiftool_files/lib/Image/ExifTool/PNG.pm +10 -2
  34. package/bin/exiftool_files/lib/Image/ExifTool/PanasonicRaw.pm +27 -1
  35. package/bin/exiftool_files/lib/Image/ExifTool/Pentax.pm +8 -5
  36. package/bin/exiftool_files/lib/Image/ExifTool/PhaseOne.pm +14 -1
  37. package/bin/exiftool_files/lib/Image/ExifTool/Photoshop.pm +38 -7
  38. package/bin/exiftool_files/lib/Image/ExifTool/QuickTime.pm +48 -14
  39. package/bin/exiftool_files/lib/Image/ExifTool/QuickTimeStream.pl +91 -27
  40. package/bin/exiftool_files/lib/Image/ExifTool/README +19 -2
  41. package/bin/exiftool_files/lib/Image/ExifTool/RIFF.pm +34 -13
  42. package/bin/exiftool_files/lib/Image/ExifTool/Rawzor.pm +2 -2
  43. package/bin/exiftool_files/lib/Image/ExifTool/Ricoh.pm +2 -1
  44. package/bin/exiftool_files/lib/Image/ExifTool/Sigma.pm +5 -4
  45. package/bin/exiftool_files/lib/Image/ExifTool/SigmaRaw.pm +9 -3
  46. package/bin/exiftool_files/lib/Image/ExifTool/Sony.pm +39 -10
  47. package/bin/exiftool_files/lib/Image/ExifTool/TagLookup.pm +4687 -4628
  48. package/bin/exiftool_files/lib/Image/ExifTool/TagNames.pod +338 -117
  49. package/bin/exiftool_files/lib/Image/ExifTool/Validate.pm +5 -5
  50. package/bin/exiftool_files/lib/Image/ExifTool/WPG.pm +296 -0
  51. package/bin/exiftool_files/lib/Image/ExifTool/WriteExif.pl +42 -0
  52. package/bin/exiftool_files/lib/Image/ExifTool/WritePDF.pl +7 -8
  53. package/bin/exiftool_files/lib/Image/ExifTool/WriteXMP.pl +1 -1
  54. package/bin/exiftool_files/lib/Image/ExifTool/Writer.pl +162 -40
  55. package/bin/exiftool_files/lib/Image/ExifTool/XMP.pm +35 -8
  56. package/bin/exiftool_files/lib/Image/ExifTool/XMP2.pl +2 -1
  57. package/bin/exiftool_files/lib/Image/ExifTool/ZIP.pm +159 -41
  58. package/bin/exiftool_files/lib/Image/ExifTool.pm +286 -65
  59. package/bin/exiftool_files/lib/Image/ExifTool.pod +95 -51
  60. package/package.json +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);
31
31
 
32
- $VERSION = '12.56';
32
+ $VERSION = '12.62';
33
33
  $RELEASE = '';
34
34
  @ISA = qw(Exporter);
35
35
  %EXPORT_TAGS = (
@@ -75,6 +75,7 @@ sub GetAllGroups($;$);
75
75
  sub GetNewGroups($);
76
76
  sub GetDeleteGroups();
77
77
  sub AddUserDefinedTags($%);
78
+ sub SetAlternateFile($$$);
78
79
  # non-public routines below
79
80
  sub InsertTagValues($$$;$$$);
80
81
  sub IsWritable($);
@@ -113,6 +114,7 @@ sub WriteTIFF($$$);
113
114
  sub PackUTF8(@);
114
115
  sub UnpackUTF8($);
115
116
  sub SetPreferredByteOrder($;$);
117
+ sub ImageDataMD5($$$;$$);
116
118
  sub CopyBlock($$$);
117
119
  sub CopyFileAttrs($$$);
118
120
  sub TimeNow(;$$);
@@ -140,8 +142,8 @@ sub ReadValue($$$;$$$);
140
142
  @loadAllTables = qw(
141
143
  PhotoMechanic Exif GeoTiff CanonRaw KyoceraRaw Lytro MinoltaRaw PanasonicRaw
142
144
  SigmaRaw JPEG GIMP Jpeg2000 GIF BMP BMP::OS2 BMP::Extra BPG BPG::Extensions
143
- ICO PICT PNG MNG FLIF DjVu DPX OpenEXR ZISRAW MRC LIF MRC::FEI12 MIFF PCX
144
- PGF PSP PhotoCD Radiance Other::PFM PDF PostScript Photoshop::Header
145
+ WPG ICO PICT PNG MNG FLIF DjVu DPX OpenEXR ZISRAW MRC LIF MRC::FEI12 MIFF
146
+ PCX PGF PSP PhotoCD Radiance Other::PFM PDF PostScript Photoshop::Header
145
147
  Photoshop::Layers Photoshop::ImageData FujiFilm::RAF FujiFilm::IFD
146
148
  Samsung::Trailer Sony::SRF2 Sony::SR2SubIFD Sony::PMP ITC ID3 ID3::Lyrics3
147
149
  FLAC Ogg Vorbis APE APE::NewHeader APE::OldHeader Audible MPC MPEG::Audio
@@ -150,8 +152,9 @@ sub ReadValue($$$;$$$);
150
152
  Real::Media Real::Audio Real::Metafile Red RIFF AIFF ASF WTV DICOM FITS MIE
151
153
  JSON HTML XMP::SVG Palm Palm::MOBI Palm::EXTH Torrent EXE EXE::PEVersion
152
154
  EXE::PEString EXE::MachO EXE::PEF EXE::ELF EXE::AR EXE::CHM LNK Font VCard
153
- Text VCard::VCalendar VCard::VNote RSRC Rawzor ZIP ZIP::GZIP ZIP::RAR RTF
154
- OOXML iWork ISO FLIR::AFF FLIR::FPF MacOS MacOS::MDItem FlashPix::DocTable
155
+ Text VCard::VCalendar VCard::VNote RSRC Rawzor ZIP ZIP::GZIP ZIP::RAR
156
+ ZIP::RAR5 RTF OOXML iWork ISO FLIR::AFF FLIR::FPF MacOS MacOS::MDItem
157
+ FlashPix::DocTable
155
158
  );
156
159
 
157
160
  # alphabetical list of current Lang modules
@@ -188,12 +191,12 @@ $defaultLang = 'en'; # default language
188
191
  # 3) PLIST must be in this list for the binary PLIST format, although it may
189
192
  # cause a file to be checked twice for XML
190
193
  @fileTypes = qw(JPEG EXV CRW DR4 TIFF GIF MRW RAF X3F JP2 PNG MIE MIFF PS PDF
191
- PSD XMP BMP BPG PPM RIFF AIFF ASF MOV MPEG Real SWF PSP FLV OGG
192
- FLAC APE MPC MKV MXF DV PMP IND PGF ICC ITC FLIR FLIF FPF LFP
193
- HTML VRD RTF FITS XCF DSS QTIF FPX PICT ZIP GZIP PLIST RAR BZ2
194
- CZI TAR EXE EXR HDR CHM LNK WMF AVC DEX DPX RAW Font RSRC M2TS
195
- MacOS PHP PCX DCX DWF DWG DXF WTV Torrent VCard LRI R3D AA PDB
196
- PFM2 MRC LIF JXL MOI ISO ALIAS JSON MP3 DICOM PCD ICO TXT);
194
+ PSD XMP BMP WPG BPG PPM RIFF AIFF ASF MOV MPEG Real SWF PSP FLV
195
+ OGG FLAC APE MPC MKV MXF DV PMP IND PGF ICC ITC FLIR FLIF FPF
196
+ LFP HTML VRD RTF FITS XCF DSS QTIF FPX PICT ZIP GZIP PLIST RAR
197
+ BZ2 CZI TAR EXE EXR HDR CHM LNK WMF AVC DEX DPX RAW Font RSRC
198
+ M2TS MacOS PHP PCX DCX DWF DWG DXF WTV Torrent VCard LRI R3D AA
199
+ PDB PFM2 MRC LIF JXL MOI ISO ALIAS JSON MP3 DICOM PCD ICO TXT);
197
200
 
198
201
  # file types that we can write (edit)
199
202
  my @writeTypes = qw(JPEG TIFF GIF CRW MRW ORF RAF RAW PNG MIE PSD XMP PPM EPS
@@ -552,6 +555,7 @@ my %createTypes = map { $_ => 1 } qw(XMP ICC MIE VRD DR4 EXIF EXV);
552
555
  XMP => ['XMP', 'Extensible Metadata Platform'],
553
556
  WOFF => ['Font', 'Web Open Font Format'],
554
557
  WOFF2=> ['Font', 'Web Open Font Format2'],
558
+ WPG => ['WPG', 'WordPerfect Graphics'],
555
559
  WTV => ['WTV', 'Windows recorded TV show'],
556
560
  ZIP => ['ZIP', 'ZIP archive'],
557
561
  );
@@ -787,6 +791,7 @@ my %fileDescription = (
787
791
  WMA => 'audio/x-ms-wma',
788
792
  WMF => 'application/x-wmf',
789
793
  WMV => 'video/x-ms-wmv',
794
+ WPG => 'image/x-wpg',
790
795
  WTV => 'video/x-ms-wtv',
791
796
  X3F => 'image/x-sigma-x3f',
792
797
  XCF => 'image/x-xcf',
@@ -968,7 +973,7 @@ $testLen = 1024; # number of bytes to read when testing for magic number
968
973
  QTIF => '.{4}(idsc|idat|iicc)',
969
974
  R3D => '\0\0..RED(1|2)',
970
975
  RAF => 'FUJIFILM',
971
- RAR => 'Rar!\x1a\x07\0',
976
+ RAR => 'Rar!\x1a\x07\x01?\0',
972
977
  RAW => '(.{25}ARECOYK|II|MM)',
973
978
  Real => '(\.RMF|\.ra\xfd|pnm://|rtsp://|http://)',
974
979
  RIFF => '(RIFF|LA0[234]|OFR |LPAC|wvpk|RF64)', # RIFF plus other variants
@@ -982,6 +987,7 @@ $testLen = 1024; # number of bytes to read when testing for magic number
982
987
  VCard=> '(?i)BEGIN:(VCARD|VCALENDAR|VNOTE)\r\n',
983
988
  VRD => 'CANON OPTIONAL DATA\0',
984
989
  WMF => '(\xd7\xcd\xc6\x9a\0\0|\x01\0\x09\0\0\x03)',
990
+ WPG => '\xff\x57\x50\x43',
985
991
  WTV => '\xb7\xd8\x00\x20\x37\x49\xda\x11\xa6\x4e\x00\x07\xe9\x5e\xad\x8d',
986
992
  X3F => 'FOVb',
987
993
  XCF => 'gimp xcf ',
@@ -1821,6 +1827,17 @@ my %systemTagsNotes = (
1821
1827
  if specifically requested
1822
1828
  },
1823
1829
  },
1830
+ ImageDataMD5 => {
1831
+ Notes => q{
1832
+ MD5 of image data. Generated only if specifically requested for JPEG, TIFF,
1833
+ PNG, CRW, CR3, MRW, RAF, X3F, IIQ, JP2, JXL, HEIC and AVIF images, MOV/MP4
1834
+ videos, and some RIFF-based files such as AVI, WAV and WEBP. The MD5
1835
+ includes the main image data, plus JpgFromRaw/OtherImage for some formats,
1836
+ but does not include ThumbnailImage or PreviewImage. Includes video and
1837
+ audio data for MOV/MP4. The L<XMP-et:OriginalImageMD5 tag|XMP.html#ExifTool>
1838
+ provides a place to store these values in the file.
1839
+ },
1840
+ },
1824
1841
  );
1825
1842
 
1826
1843
  # tags defined by UserParam option (added at runtime)
@@ -2043,7 +2060,9 @@ sub new
2043
2060
  $$self{DEL_GROUP} = { }; # lookup for groups to delete when writing
2044
2061
  $$self{SAVE_COUNT} = 0; # count calls to SaveNewValues()
2045
2062
  $$self{FILE_SEQUENCE} = 0; # sequence number for files when reading
2063
+ $$self{FILES_WRITTEN} = 0; # count of files successfully written
2046
2064
  $$self{INDENT2} = ''; # indentation of verbose messages from SetNewValue
2065
+ $$self{ALT_EXIFTOOL} = { }; # alternate exiftool objects
2047
2066
 
2048
2067
  # initialize our new groups for writing
2049
2068
  $self->SetNewGroups(@defaultWriteGroups);
@@ -2110,8 +2129,10 @@ sub Options($$;@)
2110
2129
 
2111
2130
  while (@_) {
2112
2131
  my $param = shift;
2132
+ my $plus;
2113
2133
  # fix parameter case if necessary
2114
2134
  unless (exists $$options{$param}) {
2135
+ $plus = $param =~ s/\+$//;
2115
2136
  my ($fixed) = grep /^$param$/i, keys %$options;
2116
2137
  if ($fixed) {
2117
2138
  $param = $fixed;
@@ -2276,6 +2297,23 @@ sub Options($$;@)
2276
2297
  $compact{$p} = $val; # preserve most recent setting
2277
2298
  }
2278
2299
  $$options{Compact} = $$options{XMPShorthand} = \%compact;
2300
+ } elsif ($param eq 'NoWarning') {
2301
+ # validate regular expression
2302
+ undef $evalWarning;
2303
+ if (defined $newVal) {
2304
+ local $SIG{'__WARN__'} = \&SetWarning;
2305
+ eval { $param =~ /$newVal/ };
2306
+ $@ and $evalWarning = $@;
2307
+ }
2308
+ if ($evalWarning) {
2309
+ warn 'NoWarning: ' . CleanWarning() . "\n";
2310
+ next;
2311
+ }
2312
+ # add to existing expression if specified
2313
+ if ($plus and defined $oldVal) {
2314
+ $newVal = defined $newVal ? "$oldVal|$newVal" : $oldVal;
2315
+ }
2316
+ $$options{$param} = $newVal;
2279
2317
  } else {
2280
2318
  if ($param eq 'Escape') {
2281
2319
  # set ESCAPE_PROC
@@ -2370,6 +2408,7 @@ sub ClearOptions($)
2370
2408
  MissingTagValue =>undef,# value for missing tags when expanded in expressions
2371
2409
  NoMultiExif => undef, # raise error when writing multi-segment EXIF
2372
2410
  NoPDFList => undef, # flag to avoid splitting PDF List-type tag values
2411
+ NoWarning => undef, # regular expression for warnings to suppress
2373
2412
  Password => undef, # password for password-protected PDF documents
2374
2413
  PrintConv => 1, # flag to enable print conversion
2375
2414
  QuickTimeHandler => 1, # flag to add mdir Handler to newly created Meta box
@@ -2478,7 +2517,15 @@ sub ExtractInfo($;@)
2478
2517
  $self->WarnOnce('Install Time::HiRes to generate ProcessingTime');
2479
2518
  }
2480
2519
  }
2481
-
2520
+
2521
+ # create MD5 object if ImageDataMD5 is requested
2522
+ if ($$req{imagedatamd5} and not $$self{ImageDataMD5}) {
2523
+ if (require Digest::MD5) {
2524
+ $$self{ImageDataMD5} = Digest::MD5->new;
2525
+ } else {
2526
+ $self->WarnOnce('Install Digest::MD5 to calculate image data MD5');
2527
+ }
2528
+ }
2482
2529
  ++$$self{FILE_SEQUENCE}; # count files read
2483
2530
  }
2484
2531
 
@@ -2621,7 +2668,7 @@ sub ExtractInfo($;@)
2621
2668
  if ($isDir or (defined $stat[2] and ($stat[2] & 0170000) == 0040000)) {
2622
2669
  $self->FoundTag('FileType', 'DIR');
2623
2670
  $self->FoundTag('FileTypeExtension', '');
2624
- $self->BuildCompositeTags() if $$options{Composite};
2671
+ $self->ExtractAltInfo();
2625
2672
  $raf->Close() if $raf;
2626
2673
  return 1;
2627
2674
  }
@@ -2639,7 +2686,7 @@ sub ExtractInfo($;@)
2639
2686
  } else {
2640
2687
  $self->Error('Unknown file type');
2641
2688
  }
2642
- $self->BuildCompositeTags() if $fast == 4 and $$options{Composite};
2689
+ $self->ExtractAltInfo();
2643
2690
  last; # don't read the file
2644
2691
  }
2645
2692
  if (@fileTypeList) {
@@ -2805,8 +2852,7 @@ sub ExtractInfo($;@)
2805
2852
  }
2806
2853
  unless ($reEntry) {
2807
2854
  $$self{PATH} = [ ]; # reset PATH
2808
- # calculate Composite tags
2809
- $self->BuildCompositeTags() if $$options{Composite};
2855
+ $self->ExtractAltInfo();
2810
2856
  # do our HTML dump if requested
2811
2857
  if ($$self{HTML_DUMP}) {
2812
2858
  $raf->Seek(0, 2); # seek to end of file
@@ -2869,6 +2915,10 @@ sub ExtractInfo($;@)
2869
2915
  # restore necessary members when exiting re-entrant code
2870
2916
  $$self{$_} = $$reEntry{$_} foreach keys %$reEntry;
2871
2917
  SetByteOrder($saveOrder);
2918
+ } elsif ($$self{ImageDataMD5}) {
2919
+ my $digest = $$self{ImageDataMD5}->hexdigest;
2920
+ # (don't store empty digest)
2921
+ $self->FoundTag(ImageDataMD5 => $digest) unless $digest eq 'd41d8cd98f00b204e9800998ecf8427e';
2872
2922
  }
2873
2923
 
2874
2924
  # ($type may be undef without an Error when processing sub-documents)
@@ -3500,6 +3550,10 @@ sub GetGroup($$;$)
3500
3550
  $groups[6] = $$ex{G6};
3501
3551
  }
3502
3552
  }
3553
+ if ($$ex{G8}) {
3554
+ $groups[7] = '';
3555
+ $groups[8] = $$ex{G8};
3556
+ }
3503
3557
  # generate tag ID group names unless obviously not needed
3504
3558
  unless ($noID) {
3505
3559
  my $id = $$tagInfo{KeysID} || $$tagInfo{TagID};
@@ -3600,14 +3654,15 @@ sub SetNewGroups($;@)
3600
3654
 
3601
3655
  #------------------------------------------------------------------------------
3602
3656
  # Build Composite tags from Require'd/Desire'd tags
3603
- # Inputs: 0) ExifTool object reference
3657
+ # Inputs: 0) ExifTool object reference, 1) flag to build only tags that require
3658
+ # tags from alternate files (without this, these tags are ignored)
3604
3659
  # Note: Tag values are calculated in alphabetical order unless a tag Require's
3605
3660
  # or Desire's another Composite tag, in which case the calculation is
3606
3661
  # deferred until after the other tag is calculated.
3607
3662
  sub BuildCompositeTags($)
3608
3663
  {
3609
3664
  local $_;
3610
- my $self = shift;
3665
+ my ($self, $altOnly) = @_;
3611
3666
 
3612
3667
  $$self{BuildingComposite} = 1;
3613
3668
 
@@ -3636,7 +3691,7 @@ COMPOSITE_TAG:
3636
3691
  # loop through sub-documents if necessary
3637
3692
  my $docNum = 0;
3638
3693
  for (;;) {
3639
- my (%tagKey, $found, $index);
3694
+ my (%tagKey, $found, $index, $requireAlt);
3640
3695
  # save Require'd and Desire'd tag values in list
3641
3696
  for ($index=0; ; ++$index) {
3642
3697
  my $reqTag = $$require{$index} || $$desire{$index} || $$inhibit{$index};
@@ -3685,13 +3740,23 @@ COMPOSITE_TAG:
3685
3740
  next COMPOSITE_TAG;
3686
3741
  }
3687
3742
  }
3743
+ my ($i, $key, @keys, $altFile);
3744
+ my $et = $self;
3745
+ # get tags from alternate file if a family 8 group was specified
3746
+ if ($reqTag =~ /\b(File\d+):/i and $$self{ALT_EXIFTOOL}{$1}) {
3747
+ $et = $$self{ALT_EXIFTOOL}{$1};
3748
+ $altFile = $1;
3749
+ # set flags indicating we require tags from alternate files
3750
+ $$self{DoAltComposite} = $requireAlt = 1;
3751
+ }
3688
3752
  # (CAREFUL! keys may not be sequential if one was deleted)
3689
- my ($i, $key, @keys);
3690
- for ($key=$name, $i=$$self{DUPL_TAG}{$name} || 0; ; --$i) {
3691
- push @keys, $key if defined $$rawValue{$key};
3753
+ for ($key=$name, $i=$$et{DUPL_TAG}{$name} || 0; ; --$i) {
3754
+ push @keys, $key if defined $$et{VALUE}{$key};
3692
3755
  last if $i <= 0;
3693
3756
  $key = "$name ($i)";
3694
3757
  }
3758
+ # make sure the necessary information is available from the alternate file
3759
+ $self->CopyAltInfo($altFile, \@keys) if $altFile;
3695
3760
  # find first matching tag
3696
3761
  $key = $self->GroupMatches($reqGroup, \@keys);
3697
3762
  $reqTag = $key || "$name (0)";
@@ -3714,6 +3779,8 @@ COMPOSITE_TAG:
3714
3779
  }
3715
3780
  $tagKey{$index} = $reqTag;
3716
3781
  }
3782
+ # stop now if this requires alternate tags and we aren't building them
3783
+ last if $requireAlt xor $altOnly;
3717
3784
  if ($docNum) {
3718
3785
  if ($found) {
3719
3786
  $$self{DOC_NUM} = $docNum;
@@ -4011,6 +4078,49 @@ sub CombineInfo($;@)
4011
4078
  return \%combinedInfo;
4012
4079
  }
4013
4080
 
4081
+ #------------------------------------------------------------------------------
4082
+ # Read metadata from alternate files and build composite tags
4083
+ # Inputs: 0) ExifTool ref
4084
+ # Notes: This is called after reading the main file so the tags are available
4085
+ # for being used in the file name, but before building Composite tags
4086
+ # so tags from the alternate files may be used in the Composite tags
4087
+ sub ExtractAltInfo($)
4088
+ {
4089
+ my $self = shift;
4090
+ # extract information from alternate files if necessary
4091
+ my ($g8, $altExifTool);
4092
+ my $opts = $$self{OPTIONS};
4093
+ if ($$opts{Composite} and (not $$opts{FastScan} or $$opts{FastScan} < 5)) {
4094
+ # build all composite tags except those requiring tags from alternate files
4095
+ $self->BuildCompositeTags();
4096
+ }
4097
+ foreach $g8 (sort keys %{$$self{ALT_EXIFTOOL}}) {
4098
+ $altExifTool = $$self{ALT_EXIFTOOL}{$g8};
4099
+ next if $$altExifTool{DID_EXTRACT}; # avoid extracting twice
4100
+ $$altExifTool{OPTIONS} = $$self{OPTIONS};
4101
+ $$altExifTool{GLOBAL_TIME_OFFSET} = $$self{GLOBAL_TIME_OFFSET};
4102
+ $$altExifTool{REQ_TAG_LOOKUP} = $$self{REQ_TAG_LOOKUP};
4103
+ my $fileName = $$altExifTool{ALT_FILE};
4104
+ # allow tags from the main file to be used in the alternate file names
4105
+ # (eg. -file1 '$originalfilename')
4106
+ if ($fileName =~ /\$/) {
4107
+ my @tags = reverse sort keys %{$$self{VALUE}};
4108
+ $fileName = $self->InsertTagValues(\@tags, $fileName, 'Warn');
4109
+ next unless defined $fileName;
4110
+ }
4111
+ $altExifTool->ExtractInfo($fileName);
4112
+ # set family 8 group name for all tags
4113
+ foreach (keys %{$$altExifTool{VALUE}}) {
4114
+ my $ex = $$altExifTool{TAG_EXTRA}{$_};
4115
+ $ex or $ex = $$altExifTool{TAG_EXTRA}{$_} = { };
4116
+ $$ex{G8} = $g8;
4117
+ }
4118
+ $$altExifTool{DID_EXTRACT} = 1;
4119
+ }
4120
+ # if necessary, build composite tags that rely on tags from alternate files
4121
+ $self->BuildCompositeTags(1) if $$self{DoAltComposite};
4122
+ }
4123
+
4014
4124
  #------------------------------------------------------------------------------
4015
4125
  # Get tag table name
4016
4126
  # Inputs: 0) ExifTool object reference, 1) tag key
@@ -4133,7 +4243,11 @@ sub SplitFileName($)
4133
4243
  } else {
4134
4244
  ($name = $file) =~ tr/\\/\//;
4135
4245
  # remove path
4136
- $dir = length($1) ? $1 : '/' if $name =~ s/(.*)\///;
4246
+ if ($name =~ s/(.*)\///) {
4247
+ $dir = length($1) ? $1 : '/';
4248
+ } else {
4249
+ $dir = '.';
4250
+ }
4137
4251
  }
4138
4252
  return ($dir, $name);
4139
4253
  }
@@ -4296,9 +4410,9 @@ sub GetFileTime($$)
4296
4410
  # on Windows, try to work around incorrect file times when daylight saving time is in effect
4297
4411
  if ($^O eq 'MSWin32') {
4298
4412
  if (not eval { require Win32::API }) {
4299
- $self->WarnOnce('Install Win32::API for proper handling of Windows file times');
4413
+ $self->WarnOnce('Install Win32::API for proper handling of Windows file times', 1);
4300
4414
  } elsif (not eval { require Win32API::File }) {
4301
- $self->WarnOnce('Install Win32API::File for proper handling of Windows file times');
4415
+ $self->WarnOnce('Install Win32API::File for proper handling of Windows file times', 1);
4302
4416
  } else {
4303
4417
  # get Win32 handle, needed for GetFileTime
4304
4418
  my $win32Handle = eval { Win32API::File::GetOsFHandle($file) };
@@ -4548,6 +4662,29 @@ sub RemoveTagsFromList($$$$;$)
4548
4662
  $_[0] = \@filteredTags; # update tag list
4549
4663
  }
4550
4664
 
4665
+ #------------------------------------------------------------------------------
4666
+ # Copy tags from alternate input file
4667
+ # Inputs: 0) ExifTool ref, 1) family 8 group, 2) list ref for tag keys to copy
4668
+ # - updates tag key list to match keys newly added to $self
4669
+ sub CopyAltInfo($$$)
4670
+ {
4671
+ my ($self, $g8, $tags) = @_;
4672
+ my ($tag, $vtag);
4673
+ return unless $g8 =~ /(\d+)/;
4674
+ my $et = $$self{ALT_EXIFTOOL}{$g8} or return;
4675
+ my $altOrder = ($1 + 1) * 100000; # increment file order
4676
+ foreach $tag (@$tags) {
4677
+ ($vtag = $tag) =~ s/( |$)/ #[$g8]/;
4678
+ unless (defined $$self{VALUE}{$vtag}) {
4679
+ $$self{VALUE}{$vtag} = $$et{VALUE}{$tag};
4680
+ $$self{TAG_INFO}{$vtag} = $$et{TAG_INFO}{$tag};
4681
+ $$self{TAG_EXTRA}{$vtag} = $$et{TAG_EXTRA}{$tag} || { };
4682
+ $$self{FILE_ORDER}{$vtag} = ($$et{FILE_ORDER}{$tag} || 0) + $altOrder;
4683
+ }
4684
+ $tag = $vtag;
4685
+ }
4686
+ }
4687
+
4551
4688
  #------------------------------------------------------------------------------
4552
4689
  # Set list of found tags from previously requested tags
4553
4690
  # Inputs: 0) ExifTool object reference
@@ -4574,11 +4711,17 @@ sub SetFoundTags($)
4574
4711
  my $tagHash = $$self{VALUE};
4575
4712
  my $reqTag;
4576
4713
  foreach $reqTag (@$reqTags) {
4577
- my (@matches, $group, $allGrp, $allTag, $byValue);
4714
+ my (@matches, $group, $allGrp, $allTag, $byValue, $g8);
4715
+ my $et = $self;
4578
4716
  if ($reqTag =~ /^(.*):(.+)/) {
4579
4717
  ($group, $tag) = ($1, $2);
4580
4718
  if ($group =~ /^(\*|all)$/i) {
4581
4719
  $allGrp = 1;
4720
+ } elsif ($reqTag =~ /\bfile(\d+):/i) {
4721
+ $g8 = "File$1";
4722
+ $et = $$self{ALT_EXIFTOOL}{$g8} || $self;
4723
+ $fileOrder = $$et{FILE_ORDER};
4724
+ $tagHash = $$et{VALUE};
4582
4725
  } elsif ($group !~ /^[-\w:]*$/) {
4583
4726
  $self->Warn("Invalid group name '${group}'");
4584
4727
  $group = 'invalid';
@@ -4620,7 +4763,7 @@ sub SetFoundTags($)
4620
4763
  }
4621
4764
  if (defined $group and not $allGrp) {
4622
4765
  # keep only specified group
4623
- @matches = $self->GroupMatches($group, \@matches);
4766
+ @matches = $et->GroupMatches($group, \@matches);
4624
4767
  next unless @matches or not $allTag;
4625
4768
  }
4626
4769
  if (@matches > 1) {
@@ -4629,9 +4772,9 @@ sub SetFoundTags($)
4629
4772
  # return only the highest priority tag unless duplicates wanted
4630
4773
  unless ($doDups or $allTag or $allGrp) {
4631
4774
  $tag = shift @matches;
4632
- my $oldPriority = $$self{PRIORITY}{$tag} || 1;
4775
+ my $oldPriority = $$et{PRIORITY}{$tag} || 1;
4633
4776
  foreach (@matches) {
4634
- my $priority = $$self{PRIORITY}{$_};
4777
+ my $priority = $$et{PRIORITY}{$_};
4635
4778
  $priority = 1 unless defined $priority;
4636
4779
  next unless $priority >= $oldPriority;
4637
4780
  $tag = $_;
@@ -4645,6 +4788,13 @@ sub SetFoundTags($)
4645
4788
  # bogus file order entry to avoid warning if sorting in file order
4646
4789
  $$self{FILE_ORDER}{$matches[0]} = 9999;
4647
4790
  }
4791
+ # copy over necessary information for tags from alternate files
4792
+ if ($g8) {
4793
+ $self->CopyAltInfo($g8, \@matches);
4794
+ # restore variables to original values for main file
4795
+ $fileOrder = $$self{FILE_ORDER};
4796
+ $tagHash = $$self{VALUE};
4797
+ }
4648
4798
  # save indices of tags extracted by value
4649
4799
  push @byValue, scalar(@$rtnTags) .. (scalar(@$rtnTags)+scalar(@matches)-1) if $byValue;
4650
4800
  # save indices of wildcard tags
@@ -4821,12 +4971,14 @@ sub AUTOLOAD
4821
4971
  sub Warn($$;$)
4822
4972
  {
4823
4973
  my ($self, $str, $ignorable) = @_;
4974
+ my $noWarn = $self->Options('NoWarning');
4824
4975
  if ($ignorable) {
4825
4976
  return 0 if $$self{OPTIONS}{IgnoreMinorErrors};
4826
4977
  return 0 if $ignorable eq '3' and $$self{OPTIONS}{Validate};
4978
+ return 1 if defined $noWarn and eval { $str =~ /$noWarn/ };
4827
4979
  $str = $ignorable eq '2' ? "[Minor] $str" : "[minor] $str";
4828
4980
  }
4829
- $self->FoundTag('Warning', $str);
4981
+ $self->FoundTag('Warning', $str) unless defined $noWarn and eval { $str =~ /$noWarn/ };
4830
4982
  return 1;
4831
4983
  }
4832
4984
 
@@ -5867,7 +6019,8 @@ sub ConvertTimeSpan($;$)
5867
6019
  #------------------------------------------------------------------------------
5868
6020
  # Patched timelocal() that fixes ActivePerl timezone bug
5869
6021
  # Inputs/Returns: same as timelocal()
5870
- # Notes: must 'require Time::Local' before calling this routine
6022
+ # Notes: must 'require Time::Local' before calling this routine.
6023
+ # Also note that year should be full year, and not relative to 1900 as with localtime
5871
6024
  sub TimeLocal(@)
5872
6025
  {
5873
6026
  my $tm = Time::Local::timelocal(@_);
@@ -6326,7 +6479,6 @@ sub ProcessJPEG($$)
6326
6479
  {
6327
6480
  local $_;
6328
6481
  my ($self, $dirInfo) = @_;
6329
- my ($ch, $s, $length);
6330
6482
  my $options = $$self{OPTIONS};
6331
6483
  my $verbose = $$options{Verbose};
6332
6484
  my $out = $$options{TextOut};
@@ -6335,10 +6487,17 @@ sub ProcessJPEG($$)
6335
6487
  my $req = $$self{REQ_TAG_LOOKUP};
6336
6488
  my $htmlDump = $$self{HTML_DUMP};
6337
6489
  my %dumpParms = ( Out => $out );
6338
- my ($success, $wantTrailer, $trailInfo, $foundSOS, %jumbfChunk);
6490
+ my ($ch, $s, $length, $md5, $md5size);
6491
+ my ($success, $wantTrailer, $trailInfo, $foundSOS, $gotSize, %jumbfChunk);
6339
6492
  my (@iccChunk, $iccChunkCount, $iccChunksTotal, @flirChunk, $flirCount, $flirTotal);
6340
6493
  my ($preview, $scalado, @dqt, $subSampling, $dumpEnd, %extendedXMP);
6341
6494
 
6495
+ # get pointer to MD5 object if it exists and we are the top-level JPEG or JP2
6496
+ if ($$self{FILE_TYPE} =~ /^(JPEG|JP2)$/ and not $$self{DOC_NUM}) {
6497
+ $md5 = $$self{ImageDataMD5};
6498
+ $md5size = 0;
6499
+ }
6500
+
6342
6501
  # check to be sure this is a valid JPG (or J2C, or EXV) file
6343
6502
  return 0 unless $raf->Read($s, 2) == 2 and $s =~ /^\xff[\xd8\x4f\x01]/;
6344
6503
  if ($s eq "\xff\x01") {
@@ -6385,7 +6544,9 @@ sub ProcessJPEG($$)
6385
6544
  #
6386
6545
  # read ahead to the next segment unless we have reached EOI, SOS or SOD
6387
6546
  #
6388
- unless ($marker and ($marker==0xd9 or ($marker==0xda and not $wantTrailer) or $marker==0x93)) {
6547
+ unless ($marker and ($marker==0xd9 or ($marker==0xda and not $wantTrailer and not $md5) or
6548
+ $marker==0x93))
6549
+ {
6389
6550
  # read up to next marker (JPEG markers begin with 0xff)
6390
6551
  my $buff;
6391
6552
  $raf->ReadLine($buff) or last;
@@ -6415,6 +6576,19 @@ sub ProcessJPEG($$)
6415
6576
  $nextSegPos = $raf->Tell();
6416
6577
  $len -= 4; # subtract size of length word
6417
6578
  last unless $raf->Seek($len, 1);
6579
+ } elsif ($md5 and defined $marker and ($marker == 0x00 or $marker == 0xda or
6580
+ ($marker >= 0xd0 and $marker <= 0xd7)))
6581
+ {
6582
+ # calculate MD5 for image data (includes leading ff d9 but not trailing ff da)
6583
+ $md5->add("\xff" . chr($marker));
6584
+ my $n = $skipped - (length($buff) - 1); # number of extra 0xff's
6585
+ if (not $n) {
6586
+ $buff = substr($buff, 0, -1); # remove trailing 0xff
6587
+ } elsif ($n > 1) {
6588
+ $buff .= "\xff" x ($n - 1); # add back extra 0xff's
6589
+ }
6590
+ $md5->add($buff);
6591
+ $md5size += $skipped + 2;
6418
6592
  }
6419
6593
  # read second segment too if this was the first
6420
6594
  next unless defined $marker;
@@ -6443,7 +6617,8 @@ sub ProcessJPEG($$)
6443
6617
  $self->HDump($segPos-4, $length+4, "[JPEG $markerName]", undef, 0x08);
6444
6618
  $dumpEnd = $segPos + $length;
6445
6619
  }
6446
- next unless $length >= 6;
6620
+ next if $length < 6 or $gotSize;
6621
+ $gotSize = 1; # (ignore subsequent SOF segments in probably corrupted JPEG)
6447
6622
  # extract some useful information
6448
6623
  my ($p, $h, $w, $n) = unpack('Cn2C', $$segDataPt);
6449
6624
  my $sof = GetTagTable('Image::ExifTool::JPEG::SOF');
@@ -6625,7 +6800,7 @@ sub ProcessJPEG($$)
6625
6800
  next if $trailInfo or $wantTrailer or $verbose > 2 or $htmlDump;
6626
6801
  }
6627
6802
  # must scan to EOI if Validate or JpegCompressionFactor used
6628
- next if $$options{Validate} or $calcImageLen or $$req{trailer};
6803
+ next if $$options{Validate} or $calcImageLen or $$req{trailer} or $md5;
6629
6804
  # nothing interesting to parse after start of scan (SOS)
6630
6805
  $success = 1;
6631
6806
  last; # all done parsing file
@@ -6633,6 +6808,11 @@ sub ProcessJPEG($$)
6633
6808
  pop @$path;
6634
6809
  $verbose and print $out "JPEG SOD\n";
6635
6810
  $success = 1;
6811
+ if ($md5 and $$self{FILE_TYPE} eq 'JP2') {
6812
+ my $pos = $raf->Tell();
6813
+ $self->ImageDataMD5($raf, undef, 'SOD');
6814
+ $raf->Seek($pos, 0);
6815
+ }
6636
6816
  next if $verbose > 2 or $htmlDump;
6637
6817
  last; # all done parsing file
6638
6818
  } elsif (defined $markerLenBytes{$marker}) {
@@ -6707,7 +6887,7 @@ sub ProcessJPEG($$)
6707
6887
  } elsif ($marker == 0xe1) { # APP1 (EXIF, XMP, QVCI, PARROT)
6708
6888
  # (some Kodak cameras don't put a second "\0", and I have seen an
6709
6889
  # example where there was a second 4-byte APP1 segment header)
6710
- if ($$segDataPt =~ /^(.{0,4})Exif\0/is) {
6890
+ if ($$segDataPt =~ /^(.{0,4})Exif\0./is) {
6711
6891
  undef $dumpType; # (will be dumped here)
6712
6892
  # this is EXIF data --
6713
6893
  # get the data block (into a common variable)
@@ -7008,7 +7188,7 @@ sub ProcessJPEG($$)
7008
7188
  $self->FoundTag('PreviewImage', $preview);
7009
7189
  undef $preview;
7010
7190
  }
7011
- } elsif ($marker == 0xe4) { # APP4 (InfiRay, "SCALADO", FPXR, PreviewImage)
7191
+ } elsif ($marker == 0xe4) { # APP4 (InfiRay, "SCALADO", FPXR, DJI, PreviewImage)
7012
7192
  if ($$segDataPt =~ /^SCALADO\0/ and $length >= 16) {
7013
7193
  $dumpType = 'SCALADO';
7014
7194
  my ($num, $idx, $len) = unpack('x8n2N', $$segDataPt);
@@ -7039,6 +7219,16 @@ sub ProcessJPEG($$)
7039
7219
  DirStart(\%dirInfo, 0, 0);
7040
7220
  my $tagTablePtr = GetTagTable('Image::ExifTool::DJI::ThermalParams');
7041
7221
  $self->ProcessDirectory(\%dirInfo, $tagTablePtr);
7222
+ } elsif ($$self{Make} eq 'DJI' and $$segDataPt =~ /^(.{32})?.{32}\x2c\x01\x20\0/s) {
7223
+ $dumpType = 'DJI ThermalParams2';
7224
+ DirStart(\%dirInfo, $1 ? 32 : 0, 0);
7225
+ my $tagTablePtr = GetTagTable('Image::ExifTool::DJI::ThermalParams2');
7226
+ $self->ProcessDirectory(\%dirInfo, $tagTablePtr);
7227
+ } elsif ($$self{Make} eq 'DJI' and $$segDataPt =~ /^.{32}\xaa\x55\x38\0/s) {
7228
+ $dumpType = 'DJI ThermalParams3';
7229
+ DirStart(\%dirInfo, 32, 0);
7230
+ my $tagTablePtr = GetTagTable('Image::ExifTool::DJI::ThermalParams3');
7231
+ $self->ProcessDirectory(\%dirInfo, $tagTablePtr);
7042
7232
  } elsif ($$self{HasIJPEG} and $length >= 120) {
7043
7233
  $dumpType = 'InfiRay Factory';
7044
7234
  SetByteOrder('II');
@@ -7152,6 +7342,13 @@ sub ProcessJPEG($$)
7152
7342
  $self->ProcessDirectory(\%dirInfo, $tagTablePtr);
7153
7343
  delete $$self{SET_GROUP0};
7154
7344
  delete $$self{SET_GROUP1};
7345
+ } elsif ($$segDataPt =~ /^DJI-DBG\0/) {
7346
+ $dumpType = 'DJI Info';
7347
+ my $tagTablePtr = GetTagTable('Image::ExifTool::DJI::Info');
7348
+ DirStart(\%dirInfo, 8, 0);
7349
+ $$self{SET_GROUP0} = 'APP7';
7350
+ $self->ProcessDirectory(\%dirInfo, $tagTablePtr);
7351
+ delete $$self{SET_GROUP0};
7155
7352
  } elsif ($$segDataPt =~ /^\x1aQualcomm Camera Attributes/) {
7156
7353
  # found in HP iPAQ_VoiceMessenger
7157
7354
  $dumpType = 'Qualcomm';
@@ -7348,8 +7545,11 @@ sub ProcessJPEG($$)
7348
7545
  }
7349
7546
  } elsif ($marker == 0x51) { # SIZ (J2C)
7350
7547
  my ($w, $h) = unpack('x2N2', $$segDataPt);
7351
- $self->FoundTag('ImageWidth', $w);
7352
- $self->FoundTag('ImageHeight', $h);
7548
+ unless ($gotSize) {
7549
+ $gotSize = 1;
7550
+ $self->FoundTag('ImageWidth', $w);
7551
+ $self->FoundTag('ImageHeight', $h);
7552
+ }
7353
7553
  } elsif (($marker & 0xf0) != 0xe0) {
7354
7554
  $dumpType = "$markerName segment";
7355
7555
  $desc = "[JPEG $markerName]"; # (other known JPEG segments)
@@ -7412,6 +7612,8 @@ sub ProcessJPEG($$)
7412
7612
  delete $extendedXMP{$guid};
7413
7613
  }
7414
7614
  }
7615
+ # print verbose MD5 message if necessary
7616
+ print $out "$$self{INDENT}(ImageDataMD5: $md5size bytes of JPEG image data)\n" if $md5size and $verbose;
7415
7617
  # calculate JPEGDigest if requested
7416
7618
  if (@dqt) {
7417
7619
  require Image::ExifTool::JPEGDigest;
@@ -7680,13 +7882,16 @@ sub DoProcessTIFF($$;$)
7680
7882
  }
7681
7883
  }
7682
7884
  # update FileType if necessary now that we know more about the file
7683
- if ($$self{DNGVersion} and $$self{VALUE}{FileType} !~ /^(DNG|GPR)$/) {
7885
+ if ($$self{DNGVersion} and $$self{FileType} !~ /^(DNG|GPR)$/) {
7684
7886
  # override whatever FileType we set since we now know it is DNG
7685
7887
  $self->OverrideFileType($$self{TIFF_TYPE} = 'DNG');
7686
7888
  }
7687
7889
  if ($$self{TIFF_TYPE} eq 'TIFF') {
7688
7890
  $self->FoundTag(PageCount => $$self{PageCount}) if $$self{MultiPage};
7689
7891
  }
7892
+ if ($$self{ImageDataMD5} and $$self{A100DataOffset} and $raf->Seek($$self{A100DataOffset},0)) {
7893
+ $self->ImageDataMD5($raf, undef, 'A100');
7894
+ }
7690
7895
  return 1;
7691
7896
  }
7692
7897
  #
@@ -7988,7 +8193,7 @@ sub ProcessDirectory($$$;$)
7988
8193
  # patch for bug in Windows phone 7.5 O/S that writes incorrect InteropIFD pointer
7989
8194
  return 0 unless $dirName eq 'GPS' and $$self{PROCESSED}{$addr} eq 'InteropIFD';
7990
8195
  }
7991
- $$self{PROCESSED}{$addr} = $dirName;
8196
+ $$self{PROCESSED}{$addr} = $dirName unless $$tagTablePtr{VARS} and $$tagTablePtr{VARS}{ALLOW_REPROCESS};
7992
8197
  }
7993
8198
  my $oldOrder = GetByteOrder();
7994
8199
  my @save = @$self{'INDENT','DIR_NAME','Compression','SubfileType'};
@@ -8566,7 +8771,7 @@ sub DoEscape($$)
8566
8771
  sub SetFileType($;$$$)
8567
8772
  {
8568
8773
  my ($self, $fileType, $mimeType, $normExt) = @_;
8569
- unless ($$self{VALUE}{FileType} and not $$self{DOC_NUM}) {
8774
+ unless ($$self{FileType} and not $$self{DOC_NUM}) {
8570
8775
  my $baseType = $$self{FILE_TYPE};
8571
8776
  my $ext = $$self{FILE_EXT};
8572
8777
  $fileType or $fileType = $baseType;
@@ -8585,7 +8790,8 @@ sub SetFileType($;$$$)
8585
8790
  $normExt = $fileTypeExt{$fileType};
8586
8791
  $normExt = $fileType unless defined $normExt;
8587
8792
  }
8588
- $$self{FileType} = $fileType;
8793
+ # ($$self{FileType} is the file type of the main document)
8794
+ $$self{FileType} = $fileType unless $$self{DOC_NUM};
8589
8795
  $self->FoundTag('FileType', $fileType);
8590
8796
  $self->FoundTag('FileTypeExtension', uc $normExt);
8591
8797
  $self->FoundTag('MIMEType', $mimeType || 'application/unknown');
@@ -8749,13 +8955,16 @@ sub ProcessBinaryData($$$)
8749
8955
  {
8750
8956
  my ($self, $dirInfo, $tagTablePtr) = @_;
8751
8957
  my $dataPt = $$dirInfo{DataPt};
8752
- my $offset = $$dirInfo{DirStart} || 0;
8753
- my $size = $$dirInfo{DirLen} || (length($$dataPt) - $offset);
8958
+ my $dataLen = length $$dataPt;
8959
+ my $dirStart = $$dirInfo{DirStart} || 0;
8960
+ my $maxLen = $dataLen - $dirStart;
8961
+ my $size = $$dirInfo{DirLen};
8754
8962
  my $base = $$dirInfo{Base} || 0;
8755
8963
  my $verbose = $$self{OPTIONS}{Verbose};
8756
8964
  my $unknown = $$self{OPTIONS}{Unknown};
8757
8965
  my $dataPos = $$dirInfo{DataPos} || 0;
8758
8966
 
8967
+ $size = $maxLen if not defined $size or $size > $maxLen;
8759
8968
  # get default format ('int8u' unless specified)
8760
8969
  my $defaultFormat = $$tagTablePtr{FORMAT} || 'int8u';
8761
8970
  my $increment = $formatSize{$defaultFormat};
@@ -8797,6 +9006,7 @@ sub ProcessBinaryData($$$)
8797
9006
  $tagInfo = $self->GetTagInfo($tagTablePtr, $index);
8798
9007
  unless ($tagInfo) {
8799
9008
  next unless defined $tagInfo;
9009
+ # $entry = offset of value relative to directory start (or end if negative)
8800
9010
  my $entry = int($index) * $increment + $varSize;
8801
9011
  if ($entry < 0) {
8802
9012
  $entry += $size;
@@ -8805,7 +9015,7 @@ sub ProcessBinaryData($$$)
8805
9015
  next if $entry >= $size;
8806
9016
  my $more = $size - $entry;
8807
9017
  $more = 128 if $more > 128;
8808
- my $v = substr($$dataPt, $entry+$offset, $more);
9018
+ my $v = substr($$dataPt, $entry+$dirStart, $more);
8809
9019
  $tagInfo = $self->GetTagInfo($tagTablePtr, $index, \$v);
8810
9020
  next unless $tagInfo;
8811
9021
  }
@@ -8838,7 +9048,7 @@ sub ProcessBinaryData($$$)
8838
9048
  $count = $more;
8839
9049
  } elsif ($format eq 'pstring') {
8840
9050
  $format = 'string';
8841
- $count = Get8u($dataPt, ($entry++)+$offset);
9051
+ $count = Get8u($dataPt, ($entry++)+$dirStart);
8842
9052
  --$more;
8843
9053
  } elsif (not $formatSize{$format}) {
8844
9054
  if ($format =~ /(.*)\[(.*)\]/) {
@@ -8867,17 +9077,17 @@ sub ProcessBinaryData($$$)
8867
9077
  } elsif ($format =~ /^var_/) {
8868
9078
  # handle variable-length string formats
8869
9079
  $format = substr($format, 4);
8870
- pos($$dataPt) = $entry + $offset;
9080
+ pos($$dataPt) = $entry + $dirStart;
8871
9081
  undef $count;
8872
9082
  if ($format eq 'ustring') {
8873
- $count = pos($$dataPt) - ($entry+$offset) if $$dataPt =~ /\G(..)*?\0\0/sg;
9083
+ $count = pos($$dataPt) - ($entry+$dirStart) if $$dataPt =~ /\G(..)*?\0\0/sg;
8874
9084
  $varSize -= 2; # ($count includes base size of 2 bytes)
8875
9085
  } elsif ($format eq 'pstring') {
8876
- $count = Get8u($dataPt, ($entry++)+$offset);
9086
+ $count = Get8u($dataPt, ($entry++)+$dirStart);
8877
9087
  --$more;
8878
9088
  } elsif ($format eq 'pstr32' or $format eq 'ustr32') {
8879
9089
  last if $more < 4;
8880
- $count = Get32u($dataPt, $entry + $offset);
9090
+ $count = Get32u($dataPt, $entry + $dirStart);
8881
9091
  $count *= 2 if $format eq 'ustr32';
8882
9092
  $entry += 4;
8883
9093
  $more -= 4;
@@ -8885,22 +9095,22 @@ sub ProcessBinaryData($$$)
8885
9095
  } elsif ($format eq 'int16u') {
8886
9096
  # int16u size of binary data to follow
8887
9097
  last if $more < 2;
8888
- $count = Get16u($dataPt, $entry + $offset) + 2;
9098
+ $count = Get16u($dataPt, $entry + $dirStart) + 2;
8889
9099
  $varSize -= 2; # ($count includes size word)
8890
9100
  $format = 'undef';
8891
9101
  } elsif ($format eq 'ue7') {
8892
9102
  require Image::ExifTool::BPG;
8893
- ($val, $count) = Image::ExifTool::BPG::Get_ue7($dataPt, $entry + $offset);
9103
+ ($val, $count) = Image::ExifTool::BPG::Get_ue7($dataPt, $entry + $dirStart);
8894
9104
  last unless defined $val;
8895
9105
  --$varSize; # ($count includes base size of 1 byte)
8896
9106
  } elsif ($$dataPt =~ /\0/g) {
8897
- $count = pos($$dataPt) - ($entry+$offset);
9107
+ $count = pos($$dataPt) - ($entry+$dirStart);
8898
9108
  --$varSize; # ($count includes base size of 1 byte)
8899
9109
  }
8900
9110
  $count = $more if not defined $count or $count > $more;
8901
9111
  $varSize += $count; # shift subsequent indices
8902
9112
  unless (defined $val) {
8903
- $val = substr($$dataPt, $entry+$offset, $count);
9113
+ $val = substr($$dataPt, $entry+$dirStart, $count);
8904
9114
  $val = $self->Decode($val, 'UCS2') if $format eq 'ustring' or $format eq 'ustr32';
8905
9115
  $val =~ s/\0.*//s unless $format eq 'undef'; # truncate at null
8906
9116
  }
@@ -8914,7 +9124,7 @@ sub ProcessBinaryData($$$)
8914
9124
  # hook to allow format, etc to be set dynamically
8915
9125
  if (defined $$tagInfo{Hook}) {
8916
9126
  my $oldVarSize = $varSize;
8917
- my $pos = $entry + $offset;
9127
+ my $pos = $entry + $dirStart;
8918
9128
  #### eval Hook ($format, $varSize, $size, $dataPt, $pos)
8919
9129
  eval $$tagInfo{Hook};
8920
9130
  # save variable size data if required for writing (in case changed by Hook)
@@ -8939,7 +9149,7 @@ sub ProcessBinaryData($$$)
8939
9149
  next if $$tagInfo{LargeTag} and $$self{EXCL_TAG_LOOKUP}{lc $$tagInfo{Name}};
8940
9150
  # read value now if necessary
8941
9151
  unless (defined $val and not $$tagInfo{SubDirectory}) {
8942
- $val = ReadValue($dataPt, $entry+$offset, $format, $count, $more, \$rational);
9152
+ $val = ReadValue($dataPt, $entry+$dirStart, $format, $count, $more, \$rational);
8943
9153
  next unless defined $val;
8944
9154
  $mask = $$tagInfo{Mask};
8945
9155
  $val = ($val & $mask) >> $$tagInfo{BitShift} if $mask;
@@ -8956,8 +9166,8 @@ sub ProcessBinaryData($$$)
8956
9166
  Value => $val,
8957
9167
  DataPt => $dataPt,
8958
9168
  Size => $len,
8959
- Start => $entry+$offset,
8960
- Addr => $entry+$offset+$base+$dataPos,
9169
+ Start => $entry+$dirStart,
9170
+ Addr => $entry+$dirStart+$base+$dataPos,
8961
9171
  Format => $format,
8962
9172
  Count => $count,
8963
9173
  Extra => $mask ? sprintf(', mask 0x%.2x',$mask) : undef,
@@ -8983,16 +9193,27 @@ sub ProcessBinaryData($$$)
8983
9193
  my $subdirBase = $base;
8984
9194
  if (defined $$subdir{Base}) {
8985
9195
  #### eval Base ($start,$base)
8986
- my $start = $entry + $offset + $dataPos;
9196
+ my $start = $entry + $dirStart + $dataPos;
8987
9197
  $subdirBase = eval($$subdir{Base}) + $base;
8988
9198
  }
8989
9199
  my $start = $$subdir{Start} || 0;
9200
+ if ($start =~ /\$/) {
9201
+ # ignore directories with a zero offset (ie. missing Nikon ShotInfo entries)
9202
+ next unless $val;
9203
+ #### eval Start ($val, $dirStart)
9204
+ $start = eval($start);
9205
+ next if $start < $dirStart or $start > $dataLen;
9206
+ $len = $$subdir{DirLen};
9207
+ $len = $dataLen - $start unless $len and $len <= $dataLen - $start;
9208
+ } else {
9209
+ $start += $dirStart + $entry;
9210
+ }
8990
9211
  my %subdirInfo = (
8991
9212
  DataPt => $dataPt,
8992
9213
  DataPos => $dataPos,
8993
- DataLen => length $$dataPt,
8994
- DirStart => $entry + $offset + $start,
8995
- DirLen => $len - $start,
9214
+ DataLen => $dataLen,
9215
+ DirStart => $start,
9216
+ DirLen => $len,
8996
9217
  Base => $subdirBase,
8997
9218
  );
8998
9219
  delete $$self{NO_UNKNOWN};