exiftool_vendored 13.12.0 → 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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/bin/Changes +79 -22
  3. data/bin/MANIFEST +7 -0
  4. data/bin/META.json +1 -1
  5. data/bin/META.yml +1 -1
  6. data/bin/README +2 -2
  7. data/bin/arg_files/exif2xmp.args +4 -0
  8. data/bin/arg_files/xmp2exif.args +2 -1
  9. data/bin/build_geolocation +1 -1
  10. data/bin/exiftool +6 -5
  11. data/bin/lib/Image/ExifTool/AFCP.pm +5 -5
  12. data/bin/lib/Image/ExifTool/BuildTagLookup.pm +20 -15
  13. data/bin/lib/Image/ExifTool/Canon.pm +5 -3
  14. data/bin/lib/Image/ExifTool/DJI.pm +64 -11
  15. data/bin/lib/Image/ExifTool/EXE.pm +17 -3
  16. data/bin/lib/Image/ExifTool/Geolocation.pm +16 -7
  17. data/bin/lib/Image/ExifTool/ID3.pm +4 -4
  18. data/bin/lib/Image/ExifTool/JPEG.pm +5 -1
  19. data/bin/lib/Image/ExifTool/LigoGPS.pm +1 -0
  20. data/bin/lib/Image/ExifTool/MIE.pm +6 -3
  21. data/bin/lib/Image/ExifTool/Nikon.pm +328 -4
  22. data/bin/lib/Image/ExifTool/NikonCustom.pm +1 -1
  23. data/bin/lib/Image/ExifTool/Panasonic.pm +7 -1
  24. data/bin/lib/Image/ExifTool/Protobuf.pm +25 -7
  25. data/bin/lib/Image/ExifTool/QuickTime.pm +215 -59
  26. data/bin/lib/Image/ExifTool/QuickTimeStream.pl +12 -12
  27. data/bin/lib/Image/ExifTool/README +4 -1
  28. data/bin/lib/Image/ExifTool/RIFF.pm +11 -1
  29. data/bin/lib/Image/ExifTool/Samsung.pm +1 -1
  30. data/bin/lib/Image/ExifTool/TagLookup.pm +4821 -4811
  31. data/bin/lib/Image/ExifTool/TagNames.pod +231 -25
  32. data/bin/lib/Image/ExifTool/Torrent.pm +2 -2
  33. data/bin/lib/Image/ExifTool/Vivo.pm +124 -0
  34. data/bin/lib/Image/ExifTool/WriteQuickTime.pl +114 -63
  35. data/bin/lib/Image/ExifTool/WriteRIFF.pl +3 -1
  36. data/bin/lib/Image/ExifTool/Writer.pl +16 -11
  37. data/bin/lib/Image/ExifTool.pm +24 -8
  38. data/bin/lib/Image/ExifTool.pod +52 -49
  39. data/bin/perl-Image-ExifTool.spec +1 -1
  40. data/lib/exiftool_vendored/version.rb +1 -1
  41. metadata +3 -2
@@ -43,12 +43,12 @@
43
43
  package Image::ExifTool::QuickTime;
44
44
 
45
45
  use strict;
46
- use vars qw($VERSION $AUTOLOAD %stringEncoding);
46
+ use vars qw($VERSION $AUTOLOAD %stringEncoding %avType);
47
47
  use Image::ExifTool qw(:DataAccess :Utils);
48
48
  use Image::ExifTool::Exif;
49
49
  use Image::ExifTool::GPS;
50
50
 
51
- $VERSION = '3.08';
51
+ $VERSION = '3.11';
52
52
 
53
53
  sub ProcessMOV($$;$);
54
54
  sub ProcessKeys($$$);
@@ -358,6 +358,19 @@ my %vendorID = (
358
358
  5 => 'UTF16',
359
359
  );
360
360
 
361
+ # media types for which we have separate Keys tables (AudioKeys, VideoKeys)
362
+ %avType = (
363
+ soun => 'Audio',
364
+ vide => 'Video',
365
+ );
366
+
367
+ # path to Keys/ItemList/UserData tags stored in tracks
368
+ my %trackPath = (
369
+ 'MOV-Movie-Track-Meta-ItemList' => 'Keys',
370
+ 'MOV-Movie-Track-UserData-Meta-ItemList' => 'ItemList',
371
+ 'MOV-Movie-Track-UserData' => 'UserData',
372
+ );
373
+
361
374
  my %graphicsMode = (
362
375
  # (ref http://homepage.mac.com/vanhoek/MovieGuts%20docs/64.html)
363
376
  0x00 => 'srcCopy',
@@ -594,13 +607,24 @@ my %userDefined = (
594
607
  },
595
608
  {
596
609
  Name => 'LigoGPSInfo',
597
- Condition => '$$valPt =~ /^LIGOGPSINFO\0/',
598
- SubDirectory => {
610
+ Condition => '$$valPt =~ /^LIGOGPSINFO\0/ and $$self{OPTIONS}{ExtractEmbedded}',
611
+ SubDirectory => {
599
612
  TagTable => 'Image::ExifTool::QuickTime::Stream',
600
613
  ProcessProc => 'Image::ExifTool::LigoGPS::ProcessLigoGPS',
601
614
  },
602
615
  },
603
- { Name => 'Skip', Unknown => 1, Binary => 1 },
616
+ {
617
+ Name => 'Skip',
618
+ RawConv => q{
619
+ if ($val =~ /^LIGOGPSINFO\0/) {
620
+ $self->Warn('Use the ExtractEmbedded option to decode timed GPS',3);
621
+ return undef;
622
+ }
623
+ return $val;
624
+ },
625
+ Unknown => 1,
626
+ Binary => 1,
627
+ },
604
628
  ],
605
629
  wide => { Unknown => 1, Binary => 1 },
606
630
  ftyp => { #MP4
@@ -1525,7 +1549,7 @@ my %userDefined = (
1525
1549
  # (this tag is readable/writable as a block through the Extra SphericalVideoXML tags)
1526
1550
  Condition => '$$valPt=~/^\xff\xcc\x82\x63\xf8\x55\x4a\x93\x88\x14\x58\x7a\x02\x52\x1f\xdd/',
1527
1551
  WriteGroup => 'GSpherical', # write only GSpherical XMP tags here
1528
- HandlerType => 'vide', # only write in video tracks
1552
+ MediaType => 'vide', # only write in video tracks
1529
1553
  SubDirectory => {
1530
1554
  TagTable => 'Image::ExifTool::XMP::Main',
1531
1555
  Start => 16,
@@ -2853,10 +2877,18 @@ my %userDefined = (
2853
2877
  IgnoreProp => { NonRealTimeMeta => 1 }, # ignore container for Sony 'nrtm'
2854
2878
  },
2855
2879
  },
2856
- 'keys' => {
2880
+ 'keys' => [{
2881
+ Name => 'AudioKeys',
2882
+ Condition => '$$self{MediaType} eq "soun"',
2883
+ SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::AudioKeys' },
2884
+ },{
2885
+ Name => 'VideoKeys',
2886
+ Condition => '$$self{MediaType} eq "vide"',
2887
+ SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::VideoKeys' },
2888
+ },{
2857
2889
  Name => 'Keys',
2858
2890
  SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Keys' },
2859
- },
2891
+ }],
2860
2892
  bxml => {
2861
2893
  Name => 'BinaryXML',
2862
2894
  Flags => ['Binary','Unknown'],
@@ -3490,7 +3522,11 @@ my %userDefined = (
3490
3522
  #
3491
3523
  '----' => {
3492
3524
  Name => 'iTunesInfo',
3493
- SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::iTunesInfo' },
3525
+ Deletable => 1, # (deletable via 'iTunes' group)
3526
+ SubDirectory => {
3527
+ TagTable => 'Image::ExifTool::QuickTime::iTunesInfo',
3528
+ DirName => 'iTunes', # (necessary for group 'iTunes' delete)
3529
+ },
3494
3530
  },
3495
3531
  aART => { Name => 'AlbumArtist', Groups => { 2 => 'Author' } },
3496
3532
  covr => { Name => 'CoverArt', Groups => { 2 => 'Preview' }, Binary => 1 },
@@ -3597,7 +3633,7 @@ my %userDefined = (
3597
3633
  my $id = Image::ExifTool::ID3::GetGenreID($val);
3598
3634
  return unless defined $id and $id =~ /^\d+$/;
3599
3635
  return $id + 1;
3600
- },
3636
+ },
3601
3637
  },
3602
3638
  egid => 'EpisodeGlobalUniqueID', #7
3603
3639
  geID => { #10
@@ -6599,7 +6635,7 @@ my %userDefined = (
6599
6635
  PROCESS_PROC => \&ProcessKeys,
6600
6636
  WRITE_PROC => \&WriteKeys,
6601
6637
  CHECK_PROC => \&CheckQTValue,
6602
- VARS => { LONG_TAGS => 9 },
6638
+ VARS => { LONG_TAGS => 8 },
6603
6639
  WRITABLE => 1,
6604
6640
  # (not PREFERRED when writing)
6605
6641
  GROUPS => { 1 => 'Keys' },
@@ -6645,21 +6681,11 @@ my %userDefined = (
6645
6681
  publisher => { },
6646
6682
  software => { },
6647
6683
  year => { Groups => { 2 => 'Time' } },
6648
- 'camera.identifier' => 'CameraIdentifier', # (iPhone 4)
6649
- 'camera.framereadouttimeinmicroseconds' => { # (iPhone 4)
6650
- Name => 'FrameReadoutTime',
6651
- ValueConv => '$val * 1e-6',
6652
- ValueConvInv => 'int($val * 1e6 + 0.5)',
6653
- PrintConv => '$val * 1e6 . " microseconds"',
6654
- PrintConvInv => '$val =~ s/ .*//; $val * 1e-6',
6655
- },
6656
- # 'camera.focal_length.35mm_equivalent' - not top level (written to Keys in video track)
6657
- # 'camera.lens_model' - not top level (written to Keys in video track)
6658
6684
  'location.ISO6709' => {
6659
6685
  Name => 'GPSCoordinates',
6660
6686
  Groups => { 2 => 'Location' },
6661
6687
  Notes => q{
6662
- Google Photos may ignore this if the coorinates have more than 5 digits
6688
+ Google Photos may ignore this if the coordinates have more than 5 digits
6663
6689
  after the decimal
6664
6690
  },
6665
6691
  ValueConv => \&ConvertISO6709,
@@ -6715,7 +6741,6 @@ my %userDefined = (
6715
6741
  #
6716
6742
  # the following tags aren't in the com.apple.quicktime namespace:
6717
6743
  #
6718
- 'com.apple.photos.captureMode' => 'CaptureMode',
6719
6744
  'com.android.version' => 'AndroidVersion',
6720
6745
  'com.android.capture.fps' => { Name => 'AndroidCaptureFPS', Writable => 'float' },
6721
6746
  'com.android.manufacturer' => 'AndroidMake',
@@ -6815,6 +6840,58 @@ my %userDefined = (
6815
6840
  # (mdta)com.apple.proapps.image.{TIFF}.Software (eg. "9.0")
6816
6841
  );
6817
6842
 
6843
+ # Keys tags in the audio track (ref PH)
6844
+ %Image::ExifTool::QuickTime::AudioKeys = (
6845
+ PROCESS_PROC => \&ProcessKeys,
6846
+ WRITE_PROC => \&WriteKeys,
6847
+ CHECK_PROC => \&CheckQTValue,
6848
+ WRITABLE => 1,
6849
+ GROUPS => { 1 => 'AudioKeys', 2 => 'Audio' },
6850
+ WRITE_GROUP => 'AudioKeys',
6851
+ LANG_INFO => \&GetLangInfo,
6852
+ NOTES => q{
6853
+ Keys tags written in the audio track by some Apple devices. These tags
6854
+ belong to the ExifTool AudioKeys family 1 gorup.
6855
+ },
6856
+ 'player.movie.audio.gain' => 'AudioGain',
6857
+ 'player.movie.audio.treble' => 'Treble',
6858
+ 'player.movie.audio.bass' => 'Bass',
6859
+ 'player.movie.audio.balance' => 'Balance',
6860
+ 'player.movie.audio.pitchshift' => 'PitchShift',
6861
+ 'player.movie.audio.mute' => {
6862
+ Name => 'Mute',
6863
+ Format => 'int8u',
6864
+ PrintConv => { 0 => 'Off', 1 => 'On' },
6865
+ },
6866
+ );
6867
+
6868
+ # Keys tags in the video track (ref PH)
6869
+ %Image::ExifTool::QuickTime::VideoKeys = (
6870
+ PROCESS_PROC => \&ProcessKeys,
6871
+ WRITE_PROC => \&WriteKeys,
6872
+ CHECK_PROC => \&CheckQTValue,
6873
+ VARS => { LONG_TAGS => 2 },
6874
+ WRITABLE => 1,
6875
+ GROUPS => { 1 => 'VideoKeys', 2 => 'Camera' },
6876
+ WRITE_GROUP => 'VideoKeys',
6877
+ LANG_INFO => \&GetLangInfo,
6878
+ NOTES => q{
6879
+ Keys tags written in the video track. These tags belong to the ExifTool
6880
+ VideoKeys family 1 gorup.
6881
+ },
6882
+ 'camera.identifier' => 'CameraIdentifier',
6883
+ 'camera.lens_model' => 'LensModel',
6884
+ 'camera.focal_length.35mm_equivalent' => 'FocalLengthIn35mmFormat',
6885
+ 'camera.framereadouttimeinmicroseconds' => {
6886
+ Name => 'FrameReadoutTime',
6887
+ ValueConv => '$val * 1e-6',
6888
+ ValueConvInv => 'int($val * 1e6 + 0.5)',
6889
+ PrintConv => '$val * 1e6 . " microseconds"',
6890
+ PrintConvInv => '$val =~ s/ .*//; $val * 1e-6',
6891
+ },
6892
+ 'com.apple.photos.captureMode' => 'CaptureMode',
6893
+ );
6894
+
6818
6895
  # iTunes info ('----') atoms
6819
6896
  %Image::ExifTool::QuickTime::iTunesInfo = (
6820
6897
  PROCESS_PROC => \&ProcessMOV,
@@ -7280,7 +7357,7 @@ my %userDefined = (
7280
7357
  {
7281
7358
  Name => 'VideoFrameRate',
7282
7359
  Notes => 'average rate calculated from time-to-sample table for video media',
7283
- Condition => '$$self{HandlerType} and $$self{HandlerType} eq "vide"',
7360
+ Condition => '$$self{MediaType} eq "vide"',
7284
7361
  Format => 'undef', # (necessary to prevent decoding as string!)
7285
7362
  # (must be RawConv so appropriate MediaTS is used in calculation)
7286
7363
  RawConv => 'Image::ExifTool::QuickTime::CalcSampleRate($self, \$val)',
@@ -7871,7 +7948,6 @@ my %userDefined = (
7871
7948
  mode => 'ModeFlags', #PH (?) 0x04 is HD flag (https://compilr.com/heksesang/requiem-mac/UnDrm.java)
7872
7949
  # sing - seen 4 zeros
7873
7950
  # hi32 - seen "00 00 00 04"
7874
-
7875
7951
  );
7876
7952
 
7877
7953
  # MP4 hint sample description box (ref 5)
@@ -8119,6 +8195,7 @@ my %userDefined = (
8119
8195
  Format => 'undef[4]',
8120
8196
  RawConv => q{
8121
8197
  $$self{HandlerType} = $val unless $val eq 'alis' or $val eq 'url ';
8198
+ $$self{MediaType} = $val if @{$$self{PATH}} > 1 and $$self{PATH}[-2] eq 'Media';
8122
8199
  $$self{HasHandler}{$val} = 1; # remember all our handlers
8123
8200
  return $val;
8124
8201
  },
@@ -8703,24 +8780,28 @@ sub UnpackLang($;$)
8703
8780
  # Get language code string given QuickTime language and country codes
8704
8781
  # Inputs: 0) numerical language code, 1) numerical country code, 2) no defaults
8705
8782
  # Returns: language code string (ie. "fra-FR") or undef for default language
8783
+ # ex) 0x15c7 0x0000 is 'eng' with no country (ie. returns 'und' unless $noDef)
8784
+ # 0x15c7 0x5553 is 'eng-US'
8785
+ # 0x1a41 0x4652 is 'fra-FR'
8786
+ # 0x55c4 is 'und'
8706
8787
  sub GetLangCode($;$$)
8707
8788
  {
8708
8789
  my ($lang, $ctry, $noDef) = @_;
8709
8790
  # ignore country ('ctry') and language lists ('lang') for now
8710
8791
  undef $ctry if $ctry and $ctry <= 255;
8711
8792
  undef $lang if $lang and $lang <= 255;
8712
- $lang = UnpackLang($lang, $noDef);
8793
+ my $langCode = UnpackLang($lang, $noDef);
8713
8794
  # add country code if specified
8714
8795
  if ($ctry) {
8715
8796
  $ctry = unpack('a2',pack('n',$ctry)); # unpack as ISO 3166-1
8716
8797
  # treat 'ZZ' like a default country (see ref 12)
8717
8798
  undef $ctry if $ctry eq 'ZZ';
8718
8799
  if ($ctry and $ctry =~ /^[A-Z]{2}$/) {
8719
- $lang or $lang = 'und';
8720
- $lang .= "-$ctry";
8800
+ $langCode or $langCode = UnpackLang($lang,1) || 'und';
8801
+ $langCode .= "-$ctry";
8721
8802
  }
8722
8803
  }
8723
- return $lang;
8804
+ return $langCode;
8724
8805
  }
8725
8806
 
8726
8807
  #------------------------------------------------------------------------------
@@ -9538,6 +9619,8 @@ sub ProcessKeys($$$)
9538
9619
  my $groups = $$tagInfo{Groups};
9539
9620
  $$newInfo{Groups} = $groups ? { %$groups } : { };
9540
9621
  $$newInfo{Groups}{$_} or $$newInfo{Groups}{$_} = $$tagTablePtr{GROUPS}{$_} foreach 0..2;
9622
+ # set Keys group. This is necessary for logic when reading the associated ItemList entry,
9623
+ # but note that the group name will be overridden by TAG_EXTRA G1 for tags in a track
9541
9624
  $$newInfo{Groups}{1} = 'Keys';
9542
9625
  } elsif ($tag =~ /^[-\w. ]+$/ or $tag =~ /\w{4}/) {
9543
9626
  # create info for tags with reasonable id's
@@ -9584,6 +9667,43 @@ sub ProcessMetaKeys($$$)
9584
9667
  return 1;
9585
9668
  }
9586
9669
 
9670
+ #------------------------------------------------------------------------------
9671
+ # Identify trailers at specified offset from end of file
9672
+ # Inputs: 0) RAF reference, 1) Offset from end of file
9673
+ # Returns: Array ref to first trailer in linked list: 0) name of trailer,
9674
+ # 1) absolute offset to start of this trailer, 2) trailer length,
9675
+ # 3) ref to next trailer. Or undef if no trailer found, or error string on error
9676
+ # - file position is returned to its original location
9677
+ sub IdentifyTrailers($)
9678
+ {
9679
+ my $raf = shift;
9680
+ my ($trailer, $nextTrail, $buff, $type, $len);
9681
+ my $pos = $raf->Tell();
9682
+ my $offset = 0; # positive offset back from end of file
9683
+ while ($raf->Seek(-40-$offset, 2) and $raf->Read($buff, 40) == 40) {
9684
+ if (substr($buff, 8) eq '8db42d694ccc418790edff439fe026bf') {
9685
+ ($type, $len) = ('Insta360', unpack('V',$buff));
9686
+ } elsif ($buff =~ /\&\&\&\&(.{4})$/) {
9687
+ ($type, $len) = ('LigoGPS', Get32u(\$buff, 36));
9688
+ } elsif ($buff =~ /~\0\x04\0zmie~\0\0\x06.{4}([\x10\x18])(\x04)$/s or
9689
+ $buff =~ /~\0\x04\0zmie~\0\0\x0a.{8}([\x10\x18])(\x08)$/s)
9690
+ {
9691
+ my $oldOrder = GetByteOrder();
9692
+ SetByteOrder($1 eq "\x10" ? 'MM' : 'II');
9693
+ $type = 'MIE';
9694
+ $len = ($2 eq "\x04") ? Get32u(\$buff, 34) : Get64u(\$buff, 30);
9695
+ SetByteOrder($oldOrder);
9696
+ } else {
9697
+ last;
9698
+ }
9699
+ $trailer = [ $type , $raf->Tell() - $len, $len, $nextTrail ];
9700
+ $nextTrail = $trailer;
9701
+ $offset += $len;
9702
+ }
9703
+ $raf->Seek($pos,0) or return 'Seek error';
9704
+ return $trailer;
9705
+ }
9706
+
9587
9707
  #------------------------------------------------------------------------------
9588
9708
  # Process a QuickTime atom
9589
9709
  # Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) optional tag table ref
@@ -9605,7 +9725,7 @@ sub ProcessMOV($$;$)
9605
9725
 
9606
9726
  my $topLevel = not $$et{InQuickTime};
9607
9727
  $$et{InQuickTime} = 1;
9608
- $$et{HandlerType} = $$et{MetaFormat} = '' unless defined $$et{HandlerType};
9728
+ $$et{HandlerType} = $$et{MetaFormat} = $$et{MediaType} = '' if $topLevel;
9609
9729
 
9610
9730
  unless (defined $$et{KeysCount}) {
9611
9731
  $$et{KeysCount} = 0; # initialize ItemList key directory count
@@ -9631,15 +9751,10 @@ sub ProcessMOV($$;$)
9631
9751
  }
9632
9752
  ($size, $tag) = unpack('Na4', $buff);
9633
9753
  my $fast = $$et{OPTIONS}{FastScan} || 0;
9634
- # check for Insta360 trailer
9754
+ # check for Insta360, LIGOGPSINFO or MIE trailer
9635
9755
  if ($topLevel and not $fast) {
9636
- my $pos = $raf->Tell();
9637
- if ($raf->Seek(-40, 2) and $raf->Read($buff, 40) == 40 and
9638
- substr($buff, 8) eq '8db42d694ccc418790edff439fe026bf')
9639
- {
9640
- $trailer = [ 'Insta360', $raf->Tell() - unpack('V',$buff) ];
9641
- }
9642
- $raf->Seek($pos,0) or return 0;
9756
+ $trailer = IdentifyTrailers($raf);
9757
+ $trailer and not ref $trailer and $et->Warn($trailer), return 0;
9643
9758
  }
9644
9759
  if ($dataPt) {
9645
9760
  $verbose and $et->VerboseDir($$dirInfo{DirName});
@@ -9754,16 +9869,18 @@ sub ProcessMOV($$;$)
9754
9869
  }
9755
9870
  if ($isUserData and $$et{SET_GROUP1}) {
9756
9871
  my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag);
9757
- # add track name to UserData tags inside tracks
9758
- $tag = $$et{SET_GROUP1} . $tag;
9759
- if (not $$tagTablePtr{$tag} and $tagInfo) {
9760
- my %newInfo = %$tagInfo;
9761
- foreach ('Name', 'Description') {
9762
- next unless $$tagInfo{$_};
9763
- $newInfo{$_} = $$et{SET_GROUP1} . $$tagInfo{$_};
9764
- $newInfo{$_} =~ s/^(Track\d+)Track/$1/; # remove duplicate "Track" in name
9872
+ unless ($$tagInfo{SubDirectory}) {
9873
+ # add track name to UserData tags inside tracks
9874
+ $tag = $$et{SET_GROUP1} . $tag;
9875
+ if (not $$tagTablePtr{$tag} and $tagInfo) {
9876
+ my %newInfo = %$tagInfo;
9877
+ foreach ('Name', 'Description') {
9878
+ next unless $$tagInfo{$_};
9879
+ $newInfo{$_} = $$et{SET_GROUP1} . $$tagInfo{$_};
9880
+ $newInfo{$_} =~ s/^(Track\d+)Track/$1/; # remove duplicate "Track" in name
9881
+ }
9882
+ AddTagToTable($tagTablePtr, $tag, \%newInfo);
9765
9883
  }
9766
- AddTagToTable($tagTablePtr, $tag, \%newInfo);
9767
9884
  }
9768
9885
  }
9769
9886
  # set flag to store additional information for ExtractEmbedded option
@@ -9830,7 +9947,7 @@ sub ProcessMOV($$;$)
9830
9947
  # check for RIFF trailer (written by Auto-Vox dashcam)
9831
9948
  if ($buff =~ /^(gpsa|gps0|gsen|gsea)...\0/s) { # (yet seen only gpsa as first record)
9832
9949
  $et->VPrint(0, sprintf("Found RIFF trailer at offset 0x%x",$lastPos));
9833
- if ($et->Options('ExtractEmbedded')) {
9950
+ if ($ee) {
9834
9951
  $raf->Seek(-8, 1) or last; # seek back to start of trailer
9835
9952
  my $tbl = GetTagTable('Image::ExifTool::QuickTime::Stream');
9836
9953
  ProcessRIFFTrailer($et, { RAF => $raf }, $tbl);
@@ -9980,6 +10097,7 @@ ItemID: foreach $id (reverse sort { $a <=> $b } keys %$items) {
9980
10097
  }
9981
10098
  }
9982
10099
  if ($tagInfo) {
10100
+ my @found;
9983
10101
  my $subdir = $$tagInfo{SubDirectory};
9984
10102
  if ($subdir) {
9985
10103
  my $start = $$subdir{Start} || 0;
@@ -10098,21 +10216,23 @@ ItemID: foreach $id (reverse sort { $a <=> $b } keys %$items) {
10098
10216
  Index => $index,
10099
10217
  Extra => sprintf(", Type='${type}', Flags=0x%x%s, Lang=0x%.4x",$flags,$str,$lang),
10100
10218
  ) if $verbose;
10101
- # use "Keys" in path instead of ItemList if this was defined by a Keys tag
10102
- my $isKey = $$tagInfo{Groups} && $$tagInfo{Groups}{1} && $$tagInfo{Groups}{1} eq 'Keys';
10103
- if ($isKey) {
10104
- $oldDir = $$et{PATH}[-1];
10105
- $$et{PATH}[-1] = 'Keys';
10219
+ if (defined $value) {
10220
+ # use "Keys" in path instead of ItemList if this was defined by a Keys tag
10221
+ # (the only reason for this is to have "Keys" in the family 5 group name)
10222
+ # Note that the Keys group is specifically set by the ProcessKeys routine,
10223
+ # even though this tag would be in the ItemList table
10224
+ my $isKeys = $$tagInfo{Groups} && $$tagInfo{Groups}{1} && $$tagInfo{Groups}{1} eq 'Keys';
10225
+ $isKeys and $oldDir = $$et{PATH}[-1], $$et{PATH}[-1] = 'Keys';
10226
+ push @found, $et->FoundTag($langInfo, $value);
10227
+ $$et{PATH}[-1] = $oldDir if $isKeys;
10106
10228
  }
10107
- $et->FoundTag($langInfo, $value) if defined $value;
10108
- $$et{PATH}[-1] = $oldDir if $isKey;
10109
10229
  $pos += $len;
10110
10230
  }
10111
10231
  } elsif ($tag =~ /^\xa9/ or $$tagInfo{IText}) {
10112
10232
  # parse international text to extract all languages
10113
10233
  my $pos = 0;
10114
10234
  if ($$tagInfo{Format}) {
10115
- $et->FoundTag($tagInfo, ReadValue(\$val, 0, $$tagInfo{Format}, undef, length($val)));
10235
+ push @found, $et->FoundTag($tagInfo, ReadValue(\$val, 0, $$tagInfo{Format}, undef, length($val)));
10116
10236
  $pos = $size;
10117
10237
  }
10118
10238
  for (;;) {
@@ -10177,7 +10297,7 @@ ItemID: foreach $id (reverse sort { $a <=> $b } keys %$items) {
10177
10297
  $str = substr($val, $pos-$n-2, $n) . $str;
10178
10298
  }
10179
10299
  $langInfo = GetLangInfoQT($et, $tagInfo, $lang) if $lang;
10180
- $et->FoundTag($langInfo || $tagInfo, $str);
10300
+ push @found, $et->FoundTag($langInfo || $tagInfo, $str);
10181
10301
  $pos += $len;
10182
10302
  }
10183
10303
  } else {
@@ -10191,6 +10311,7 @@ ItemID: foreach $id (reverse sort { $a <=> $b } keys %$items) {
10191
10311
  $$et{BASE} = $dataPos;
10192
10312
  }
10193
10313
  my $key = $et->FoundTag($tagInfo, $val);
10314
+ push @found, $key;
10194
10315
  $$et{BASE} = $oldBase if defined $oldBase;
10195
10316
  # decode if necessary (NOTE: must be done after RawConv)
10196
10317
  if (defined $key and (not $format or $format =~ /^string/) and
@@ -10206,6 +10327,14 @@ ItemID: foreach $id (reverse sort { $a <=> $b } keys %$items) {
10206
10327
  }
10207
10328
  }
10208
10329
  }
10330
+ # tweak family 1 group names for Keys/ItemList/UserData tags in a track
10331
+ if ($$et{SET_GROUP1} and ($dirID eq 'ilst' or $dirID eq 'udta') and @found) {
10332
+ my $type = $trackPath{join '-', @{$$et{PATH}}};
10333
+ if ($type) {
10334
+ my $grp = ($avType{$$et{MediaType}} || $$et{SET_GROUP1}) . $type;
10335
+ defined and $et->SetGroup($_, $grp) foreach @found;
10336
+ }
10337
+ }
10209
10338
  }
10210
10339
  } else {
10211
10340
  $et->VerboseInfo($tag, $tagInfo,
@@ -10218,11 +10347,12 @@ ItemID: foreach $id (reverse sort { $a <=> $b } keys %$items) {
10218
10347
  last;
10219
10348
  }
10220
10349
  }
10350
+ $$et{MediaType} = '' if $tag eq 'trak'; # reset track type at end of track
10221
10351
  $dataPos += $size + 8; # point to start of next atom data
10222
10352
  last if $dirEnd and $dataPos >= $dirEnd; # (note: ignores last value if 0 bytes)
10223
10353
  $lastPos = $raf->Tell() + $dirBase;
10224
10354
  if ($trailer and $lastPos >= $$trailer[1]) {
10225
- $et->Warn(sprintf('%s trailer at offset 0x%x', @$trailer), 1);
10355
+ $et->Warn(sprintf('%s trailer at offset 0x%x (%d bytes)', @$trailer[0..2]), 1);
10226
10356
  last;
10227
10357
  }
10228
10358
  $raf->Read($buff, 8) == 8 or last;
@@ -10273,7 +10403,33 @@ QTLang: foreach $tag (@{$$et{QTLang}}) {
10273
10403
  # process item information now that we are done processing its 'meta' container
10274
10404
  HandleItemInfo($et) if $topLevel or $dirID eq 'meta';
10275
10405
 
10276
- ScanMediaData($et) if $ee and $topLevel; # brute force scan for metadata embedded in media data
10406
+ # process linked list of trailers
10407
+ for (; $trailer; $trailer=$$trailer[3]) {
10408
+ next if $lastPos > $$trailer[1]; # skip if we have already processed this as an atom
10409
+ last unless $raf->Seek($$trailer[1], 0);
10410
+ if ($$trailer[0] eq 'LigoGPS' and $raf->Read($buff, 8) == 8 and $buff =~ /skip$/) {
10411
+ $ee or $et->Warn('Use the ExtractEmbedded option to decode timed GPS',3), next;
10412
+ my $len = Get32u(\$buff, 0) - 16;
10413
+ if ($len > 0 and $raf->Read($buff, $len) == $len and $buff =~ /^LIGOGPSINFO\0/) {
10414
+ my $tbl = GetTagTable('Image::ExifTool::QuickTime::Stream');
10415
+ my %dirInfo = ( DataPt => \$buff, DataPos => $$trailer[1] + 8, DirName => 'LigoGPSTrailer' );
10416
+ Image::ExifTool::LigoGPS::ProcessLigoGPS($et, \%dirInfo, $tbl);
10417
+ } else {
10418
+ $et->Warn('Unrecognized data in LigoGPS trailer');
10419
+ }
10420
+ } elsif ($$trailer[0] eq 'Insta360' and $ee) {
10421
+ # process Insta360 trailer if it exists
10422
+ $raf->Seek(0, 2) or $et->Warn('Seek error'), last;
10423
+ my $offset = $raf->Tell() - $$trailer[1] - $$trailer[2];
10424
+ ProcessInsta360($et, { RAF => $raf, DirName => $$trailer[0], Offset => $offset });
10425
+ } elsif ($$trailer[0] eq 'MIE') {
10426
+ require Image::ExifTool::MIE;
10427
+ Image::ExifTool::MIE::ProcessMIE($et, { RAF => $raf, DirName => 'MIE', Trailer => 1 });
10428
+ }
10429
+ }
10430
+ # brute force scan for metadata embedded in media data
10431
+ # (and process Insta360 trailer if it exists)
10432
+ ScanMediaData($et) if $ee and $topLevel;
10277
10433
 
10278
10434
  # restore any changed options
10279
10435
  $et->Options($_ => $saveOptions{$_}) foreach keys %saveOptions;
@@ -111,7 +111,7 @@ my %insvLimit = (
111
111
  The tags below are extracted from timed metadata in QuickTime and other
112
112
  formats of video files when the ExtractEmbedded option is used. Although
113
113
  most of these tags are combined into the single table below, ExifTool
114
- currently reads 98 different types of timed GPS metadata from video files.
114
+ currently reads 100 different types of timed GPS metadata from video files.
115
115
  },
116
116
  VARS => { NO_ID => 1 },
117
117
  GPSLatitude => { PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")', RawConv => '$$self{FoundGPSLatitude} = 1; $val' },
@@ -1512,7 +1512,7 @@ Sample: for ($i=0; ; ) {
1512
1512
  # clean up
1513
1513
  $raf->Seek($tell, 0); # restore original file position
1514
1514
  delete $$et{DOC_NUM};
1515
- $$et{HandlerType} = $$et{HanderDesc} = '';
1515
+ $$et{HandlerType} = '';
1516
1516
  }
1517
1517
 
1518
1518
  #------------------------------------------------------------------------------
@@ -3154,16 +3154,18 @@ sub ProcessInsta360($;$)
3154
3154
  my $verbose = $et->Options('Verbose');
3155
3155
  my $tagTbl = GetTagTable('Image::ExifTool::QuickTime::Stream');
3156
3156
  my $fileEnd = $raf->Tell();
3157
+ my $trailEnd = $fileEnd - $offset;
3157
3158
  my $trailerLen = unpack('x38V', $buff);
3158
- $trailerLen > $fileEnd and $et->Warn('Bad Insta360 trailer size'), return 0;
3159
+ $trailerLen > $trailEnd and $et->Warn('Bad Insta360 trailer size'), return 0;
3159
3160
  if ($dirInfo) {
3160
3161
  $$dirInfo{DirLen} = $trailerLen;
3161
- $$dirInfo{DataPos} = $fileEnd - $trailerLen;
3162
+ $$dirInfo{DataPos} = $trailEnd - $trailerLen;
3162
3163
  if ($$dirInfo{OutFile}) {
3163
3164
  if ($$et{DEL_GROUP}{Insta360}) {
3164
3165
  ++$$et{CHANGED};
3166
+ return 1;
3165
3167
  # just copy the trailer when writing
3166
- } elsif ($trailerLen > $fileEnd or not $raf->Seek($$dirInfo{DataPos}, 0) or
3168
+ } elsif ($trailerLen > $trailEnd or not $raf->Seek($$dirInfo{DataPos}, 0) or
3167
3169
  $raf->Read(${$$dirInfo{OutFile}}, $trailerLen) != $trailerLen)
3168
3170
  {
3169
3171
  return 0;
@@ -3181,7 +3183,7 @@ sub ProcessInsta360($;$)
3181
3183
 
3182
3184
  my $unknown = $et->Options('Unknown');
3183
3185
  # position relative to end of trailer (avoids using large offsets for files > 2 GB)
3184
- my $epos = -78-$offset;
3186
+ my $epos = -78;
3185
3187
  my ($i, $p);
3186
3188
  $$et{SET_GROUP0} = 'Trailer';
3187
3189
  $$et{SET_GROUP1} = 'Insta360';
@@ -3190,7 +3192,7 @@ sub ProcessInsta360($;$)
3190
3192
  for (;;) {
3191
3193
  my ($id, $len) = unpack('vV', $buff);
3192
3194
  ($epos -= $len) + $trailerLen < 0 and last;
3193
- $raf->Seek($epos, 2) or last;
3195
+ $raf->Seek($epos-$offset, 2) or last;
3194
3196
  if ($verbose) {
3195
3197
  $et->VPrint(0, sprintf("Insta360 Record 0x%x (offset 0x%x, %d bytes):\n", $id, $fileEnd + $epos, $len));
3196
3198
  }
@@ -3218,7 +3220,7 @@ sub ProcessInsta360($;$)
3218
3220
  $dlen = 20;
3219
3221
  }
3220
3222
  }
3221
- $raf->Seek($epos, 2) or last;
3223
+ $raf->Seek($epos-$offset, 2) or last;
3222
3224
  }
3223
3225
  } elsif ($id == 0x200) {
3224
3226
  $dlen = $len;
@@ -3347,8 +3349,8 @@ sub ProcessInsta360($;$)
3347
3349
  } else {
3348
3350
  ($epos -= 6) + $trailerLen < 0 and last; # step back to previous record
3349
3351
  }
3350
- $raf->Seek($epos, 2) or last; # seek to start of next footer
3351
- $raf->Read($buff, 6) == 6 or last; # read footer
3352
+ $raf->Seek($epos-$offset, 2) or last; # seek to start of next footer
3353
+ $raf->Read($buff, 6) == 6 or last; # read footer
3352
3354
  }
3353
3355
  delete $$et{DOC_NUM};
3354
3356
  SetByteOrder('MM');
@@ -3607,8 +3609,6 @@ sub ScanMediaData($)
3607
3609
  $et->VPrint(0, "--------------------------\n");
3608
3610
  $$et{INDENT} = substr $$et{INDENT}, 0, -2;
3609
3611
  }
3610
- # process Insta360 trailer if it exists
3611
- ProcessInsta360($et);
3612
3612
  }
3613
3613
 
3614
3614
  1; # end
@@ -905,7 +905,10 @@ numerical, and generated automatically otherwise.
905
905
  Deletable : [Writable SubDirectory's only] Overrides internal test for
906
906
  metadata types with permanent directories (currently QuickTime
907
907
  and Jpeg2000), allowing the tag containing these directories
908
- to be deleted
908
+ to be deleted. Note that either the tag's SubDirectory
909
+ DirName (which defaults to the TagName if not specified) or
910
+ the family 0 group name of the SubDirectory TagTable must
911
+ match a deletable group name.
909
912
 
910
913
  OffsetPair : Used in EXIF table to specify the tagID for the corresponding
911
914
  offset or length tag.
@@ -30,7 +30,7 @@ use strict;
30
30
  use vars qw($VERSION $AUTOLOAD);
31
31
  use Image::ExifTool qw(:DataAccess :Utils);
32
32
 
33
- $VERSION = '1.69';
33
+ $VERSION = '1.70';
34
34
 
35
35
  sub ConvertTimecode($);
36
36
  sub ProcessSGLT($$$);
@@ -1319,6 +1319,11 @@ my %code2charset = (
1319
1319
  Name => 'ImageWidth',
1320
1320
  Format => 'int16u',
1321
1321
  Priority => 0,
1322
+ # add " (lossless)" to FileType since image has a VP8L (lossless) chunk
1323
+ RawConv => q{
1324
+ $self->OverrideFileType($$self{VALUE}{FileType} . ' (lossless)', undef, 'webp');
1325
+ return $val;
1326
+ },
1322
1327
  ValueConv => '($val & 0x3fff) + 1',
1323
1328
  },
1324
1329
  2 => {
@@ -1327,6 +1332,11 @@ my %code2charset = (
1327
1332
  Priority => 0,
1328
1333
  ValueConv => '(($val >> 6) & 0x3fff) + 1',
1329
1334
  },
1335
+ 4 => {
1336
+ Name => 'AlphaIsUsed',
1337
+ Mask => 0x10,
1338
+ PrintConv => { 0 => 'No', 1 => 'Yes' },
1339
+ },
1330
1340
  );
1331
1341
 
1332
1342
  # WebP extended info (ref 14)
@@ -1718,7 +1718,7 @@ SamBlock:
1718
1718
  $fixup->AddFixup(length($buff) - $offsetPos);
1719
1719
  $fixup->AddFixup(length($buff) - $offsetPos + 4);
1720
1720
  }
1721
- $et->VPrint(0, "Writing Samsung trailer ($dirLen bytes)\n") if $verbose;
1721
+ $et->VPrint(0, " Writing Samsung trailer ($dirLen bytes)\n") if $verbose;
1722
1722
  Write($$dirInfo{OutFile}, $buff) or return -1;
1723
1723
  return 1;
1724
1724
  }