exiftool_vendored 13.12.0 → 13.14.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -71,7 +71,7 @@ package Image::ExifTool::Geolocation;
71
71
  use strict;
72
72
  use vars qw($VERSION $geoDir $altDir $dbInfo);
73
73
 
74
- $VERSION = '1.08'; # (this is the module version number, not the database version)
74
+ $VERSION = '1.09'; # (this is the module version number, not the database version)
75
75
 
76
76
  my $debug; # set to output processing time for testing
77
77
 
@@ -463,7 +463,7 @@ sub GetAltNames($;$)
463
463
  sub Geolocate($;$)
464
464
  {
465
465
  my ($arg, $opts) = @_;
466
- my ($city, @exact, %regex, @multiCity, $other, $idx, @cargs, $useLastFound);
466
+ my ($city, @exact, %regex, @multiCity, $other, $idx, @cargs);
467
467
  my ($minPop, $minDistU, $minDistC, @matchParms, @coords, %fcOK, $both);
468
468
  my ($pop, $maxDist, $multi, $fcodes, $altNames, @startTime);
469
469
 
@@ -594,8 +594,16 @@ Entry: for (; $i<@cityList; ++$i) {
594
594
  }
595
595
  @startTime and printf("= Processing time: %.3f sec\n", Time::HiRes::tv_interval(\@startTime));
596
596
  if (%lastFound) {
597
- @coords == 2 and $useLastFound = 1, last; # continue to use coords with last city matches
597
+ last if @coords == 2; # continue to use coords with last city matches
598
598
  scalar(keys %lastFound) > 200 and warn("Too many matching cities\n"), return();
599
+ # return nearby cities if "num=" is used and only one match found
600
+ if ($num > 1 and scalar(keys %lastFound) == 1) {
601
+ my ($i) = keys %lastFound;
602
+ my @entry = GetEntry($i);
603
+ @coords = @entry[8,9];
604
+ SortDatabase('Latitude'); # (make sure we are sorted by latitude)
605
+ last;
606
+ }
599
607
  unless (@lastByPop) {
600
608
  @lastByPop = sort { $lastFound{$b} cmp $lastFound{$a} or $cityList[$a] cmp $cityList[$b] } keys %lastFound;
601
609
  }
@@ -777,7 +785,7 @@ on the first call.
777
785
 
778
786
  Sort database in specified order.
779
787
 
780
- Image::ExifTool::Geolocation::ReadDatabase('City');
788
+ Image::ExifTool::Geolocation::SortDatabase('City');
781
789
 
782
790
  =over 4
783
791
 
@@ -924,9 +932,10 @@ to the argument list:
924
932
  both to determine the closest city matching the specified
925
933
  name(s) instead of using GPS only.
926
934
 
927
- 'num=##' - When the search includes GPS coordinates, return the nearest
928
- ## cities instead of just the closest one. Returned cities
929
- are in the order from nearest to farthest.
935
+ 'num=##' - When the search includes GPS coordinates, or when a single
936
+ city is matched by name, return the nearest ## cities instead
937
+ of just the closest or named one. Returned cities are in the
938
+ order from nearest to farthest.
930
939
 
931
940
  See L<https://exiftool.org/geolocation.html#Read> for more details.
932
941
 
@@ -11,7 +11,7 @@ use strict;
11
11
  use vars qw($VERSION);
12
12
  use Image::ExifTool qw(:DataAccess :Utils);
13
13
 
14
- $VERSION = '1.38';
14
+ $VERSION = '1.39';
15
15
 
16
16
  sub ProcessOcad($$$);
17
17
  sub ProcessJPEG_HDR($$$);
@@ -347,6 +347,10 @@ sub ProcessJPEG_HDR($$$);
347
347
  Name => 'Samsung',
348
348
  Condition => '$$valPt =~ /QDIOBS$/',
349
349
  SubDirectory => { TagTable => 'Image::ExifTool::Samsung::Trailer' },
350
+ }, {
351
+ Name => 'Vivo',
352
+ Condition => '$$valPt =~ /^(streamdata|vivo\{")/',
353
+ SubDirectory => { TagTable => 'Image::ExifTool::Vivo::Main' },
350
354
  }, {
351
355
  Name => 'EmbeddedVideo',
352
356
  Notes => 'extracted only when ExtractEmbedded option is used',
@@ -269,6 +269,7 @@ sub ParseLigoGPS($$$;$)
269
269
  # Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
270
270
  # 3) 1=LIGOGPS lat/lon/spd weren't fuzzed
271
271
  # Returns: 1 on success
272
+ # Notes: The directory data should start with the string "LIGOGPSINFO\0"
272
273
  sub ProcessLigoGPS($$$;$)
273
274
  {
274
275
  my ($et, $dirInfo, $tagTbl, $noFuzz) = @_;
@@ -3118,6 +3118,7 @@ my %base64coord = (
3118
3118
  Condition => '$$valPt =~ /^040[012]/',
3119
3119
  SubDirectory => { TagTable => 'Image::ExifTool::Nikon::AFInfo2V0400' },
3120
3120
  },{ #JD
3121
+ # (expeed 5 processor cameras are version 0101, expeed 6 are version 03xx)
3121
3122
  Name => 'AFInfo2',
3122
3123
  # (this structure may be byte swapped when rewritten by CaptureNX)
3123
3124
  SubDirectory => { TagTable => 'Image::ExifTool::Nikon::AFInfo2' },
@@ -4160,6 +4161,7 @@ my %base64coord = (
4160
4161
  DATAMEMBER => [ 0, 4, 6 ],
4161
4162
  NOTES => "These tags are written by Nikon DSLR's which have the live view feature.",
4162
4163
  0 => {
4164
+ # (expeed 5 processor cameras are version 0101, expeed 6 are version 03xx)
4163
4165
  Name => 'AFInfo2Version',
4164
4166
  Format => 'undef[4]',
4165
4167
  Writable => 0,
@@ -5,8 +5,9 @@
5
5
  #
6
6
  # Revisions: 2024-12-04 - P. Harvey Created
7
7
  #
8
- # Notes: Tag definitions for Protobuf tags support additional 'signed'
9
- # and 'unsigned' formats for varInt (type 0) values
8
+ # Notes: Tag definitions for Protobuf tags support additional 'signed',
9
+ # 'unsigned' and 'int64s' formats for varInt (type 0) values,
10
+ # and 'rational' for byte (type 2) values
10
11
  #
11
12
  # References: 1) https://protobuf.dev/programming-guides/encoding/
12
13
  #------------------------------------------------------------------------------
@@ -17,13 +18,13 @@ use strict;
17
18
  use vars qw($VERSION);
18
19
  use Image::ExifTool qw(:DataAccess :Utils);
19
20
 
20
- $VERSION = '1.01';
21
+ $VERSION = '1.02';
21
22
 
22
23
  sub ProcessProtobuf($$$;$);
23
24
 
24
25
  #------------------------------------------------------------------------------
25
26
  # Read bytes from dirInfo object
26
- # Inputs: 0) dirInfo ref, 1) number of bytes
27
+ # Inputs: 0) dirInfo ref (with DataPt and Pos set), 1) number of bytes
27
28
  # Returns: binary data or undef on error
28
29
  sub GetBytes($$)
29
30
  {
@@ -162,14 +163,31 @@ sub ProcessProtobuf($$$;$)
162
163
  if ($$tagInfo{Format}) {
163
164
  if ($type == 0) {
164
165
  $val = $buff;
165
- $val = ($val & 1) ? -($val >> 1)-1 : ($val >> 1) if $$tagInfo{Format} eq 'signed';
166
+ if ($$tagInfo{Format} eq 'signed') {
167
+ $val = ($val & 1) ? -($val >> 1)-1 : ($val >> 1);
168
+ } elsif ($$tagInfo{Format} eq 'int64s' and $val > 0xffffffff) {
169
+ # hack for DJI drones which store 64-bit signed integers improperly
170
+ # (just toss upper 32 bits which should be all 1's anyway)
171
+ $val = ($val & 0xffffffff) - 4294967296;
172
+ }
173
+ } elsif ($type == 2 and $$tagInfo{Format} eq 'rational') {
174
+ my $dir = { DataPt => \$buff, Pos => 0 };
175
+ my $num = VarInt($dir);
176
+ my $den = VarInt($dir);
177
+ $val = (defined $num and $den) ? "$num/$den" : 'err';
166
178
  } else {
167
179
  $val = ReadValue(\$buff, 0, $$tagInfo{Format}, undef, length($buff));
168
180
  }
169
181
  } elsif ($type == 0) {
170
182
  $val = $buff;
171
- my $signed = ($val & 1) ? -($val >> 1)-1 : ($val >> 1);
172
- $val .= sprintf(" (0x%x, signed $signed)", $val);
183
+ my $hex = sprintf('%x', $val);
184
+ if (length($hex) == 16 and $hex =~ /^ffffffff/) {
185
+ my $s64 = hex(substr($hex, 8)) - 4294967296;
186
+ $val .= " (0x$hex, int64s $s64)";
187
+ } else {
188
+ my $signed = ($val & 1) ? -($val >> 1)-1 : ($val >> 1);
189
+ $val .= " (0x$hex, signed $signed)";
190
+ }
173
191
  } elsif ($type == 1) {
174
192
  $val = '0x' . unpack('H*', $buff) . ' (double ' . GetDouble(\$buff,0) . ')';
175
193
  } elsif ($type == 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.09';
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'],
@@ -3597,7 +3629,7 @@ my %userDefined = (
3597
3629
  my $id = Image::ExifTool::ID3::GetGenreID($val);
3598
3630
  return unless defined $id and $id =~ /^\d+$/;
3599
3631
  return $id + 1;
3600
- },
3632
+ },
3601
3633
  },
3602
3634
  egid => 'EpisodeGlobalUniqueID', #7
3603
3635
  geID => { #10
@@ -6599,7 +6631,7 @@ my %userDefined = (
6599
6631
  PROCESS_PROC => \&ProcessKeys,
6600
6632
  WRITE_PROC => \&WriteKeys,
6601
6633
  CHECK_PROC => \&CheckQTValue,
6602
- VARS => { LONG_TAGS => 9 },
6634
+ VARS => { LONG_TAGS => 8 },
6603
6635
  WRITABLE => 1,
6604
6636
  # (not PREFERRED when writing)
6605
6637
  GROUPS => { 1 => 'Keys' },
@@ -6645,21 +6677,11 @@ my %userDefined = (
6645
6677
  publisher => { },
6646
6678
  software => { },
6647
6679
  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
6680
  'location.ISO6709' => {
6659
6681
  Name => 'GPSCoordinates',
6660
6682
  Groups => { 2 => 'Location' },
6661
6683
  Notes => q{
6662
- Google Photos may ignore this if the coorinates have more than 5 digits
6684
+ Google Photos may ignore this if the coordinates have more than 5 digits
6663
6685
  after the decimal
6664
6686
  },
6665
6687
  ValueConv => \&ConvertISO6709,
@@ -6715,7 +6737,6 @@ my %userDefined = (
6715
6737
  #
6716
6738
  # the following tags aren't in the com.apple.quicktime namespace:
6717
6739
  #
6718
- 'com.apple.photos.captureMode' => 'CaptureMode',
6719
6740
  'com.android.version' => 'AndroidVersion',
6720
6741
  'com.android.capture.fps' => { Name => 'AndroidCaptureFPS', Writable => 'float' },
6721
6742
  'com.android.manufacturer' => 'AndroidMake',
@@ -6815,6 +6836,58 @@ my %userDefined = (
6815
6836
  # (mdta)com.apple.proapps.image.{TIFF}.Software (eg. "9.0")
6816
6837
  );
6817
6838
 
6839
+ # Keys tags in the audio track (ref PH)
6840
+ %Image::ExifTool::QuickTime::AudioKeys = (
6841
+ PROCESS_PROC => \&ProcessKeys,
6842
+ WRITE_PROC => \&WriteKeys,
6843
+ CHECK_PROC => \&CheckQTValue,
6844
+ WRITABLE => 1,
6845
+ GROUPS => { 1 => 'AudioKeys', 2 => 'Audio' },
6846
+ WRITE_GROUP => 'AudioKeys',
6847
+ LANG_INFO => \&GetLangInfo,
6848
+ NOTES => q{
6849
+ Keys tags written in the audio track by some Apple devices. These tags
6850
+ belong to the ExifTool AudioKeys family 1 gorup.
6851
+ },
6852
+ 'player.movie.audio.gain' => 'AudioGain',
6853
+ 'player.movie.audio.treble' => 'Treble',
6854
+ 'player.movie.audio.bass' => 'Bass',
6855
+ 'player.movie.audio.balance' => 'Balance',
6856
+ 'player.movie.audio.pitchshift' => 'PitchShift',
6857
+ 'player.movie.audio.mute' => {
6858
+ Name => 'Mute',
6859
+ Format => 'int8u',
6860
+ PrintConv => { 0 => 'Off', 1 => 'On' },
6861
+ },
6862
+ );
6863
+
6864
+ # Keys tags in the video track (ref PH)
6865
+ %Image::ExifTool::QuickTime::VideoKeys = (
6866
+ PROCESS_PROC => \&ProcessKeys,
6867
+ WRITE_PROC => \&WriteKeys,
6868
+ CHECK_PROC => \&CheckQTValue,
6869
+ VARS => { LONG_TAGS => 2 },
6870
+ WRITABLE => 1,
6871
+ GROUPS => { 1 => 'VideoKeys', 2 => 'Camera' },
6872
+ WRITE_GROUP => 'VideoKeys',
6873
+ LANG_INFO => \&GetLangInfo,
6874
+ NOTES => q{
6875
+ Keys tags written in the video track. These tags belong to the ExifTool
6876
+ VideoKeys family 1 gorup.
6877
+ },
6878
+ 'camera.identifier' => 'CameraIdentifier',
6879
+ 'camera.lens_model' => 'LensModel',
6880
+ 'camera.focal_length.35mm_equivalent' => 'FocalLengthIn35mmFormat',
6881
+ 'camera.framereadouttimeinmicroseconds' => {
6882
+ Name => 'FrameReadoutTime',
6883
+ ValueConv => '$val * 1e-6',
6884
+ ValueConvInv => 'int($val * 1e6 + 0.5)',
6885
+ PrintConv => '$val * 1e6 . " microseconds"',
6886
+ PrintConvInv => '$val =~ s/ .*//; $val * 1e-6',
6887
+ },
6888
+ 'com.apple.photos.captureMode' => 'CaptureMode',
6889
+ );
6890
+
6818
6891
  # iTunes info ('----') atoms
6819
6892
  %Image::ExifTool::QuickTime::iTunesInfo = (
6820
6893
  PROCESS_PROC => \&ProcessMOV,
@@ -7280,7 +7353,7 @@ my %userDefined = (
7280
7353
  {
7281
7354
  Name => 'VideoFrameRate',
7282
7355
  Notes => 'average rate calculated from time-to-sample table for video media',
7283
- Condition => '$$self{HandlerType} and $$self{HandlerType} eq "vide"',
7356
+ Condition => '$$self{MediaType} eq "vide"',
7284
7357
  Format => 'undef', # (necessary to prevent decoding as string!)
7285
7358
  # (must be RawConv so appropriate MediaTS is used in calculation)
7286
7359
  RawConv => 'Image::ExifTool::QuickTime::CalcSampleRate($self, \$val)',
@@ -7871,7 +7944,6 @@ my %userDefined = (
7871
7944
  mode => 'ModeFlags', #PH (?) 0x04 is HD flag (https://compilr.com/heksesang/requiem-mac/UnDrm.java)
7872
7945
  # sing - seen 4 zeros
7873
7946
  # hi32 - seen "00 00 00 04"
7874
-
7875
7947
  );
7876
7948
 
7877
7949
  # MP4 hint sample description box (ref 5)
@@ -8119,6 +8191,7 @@ my %userDefined = (
8119
8191
  Format => 'undef[4]',
8120
8192
  RawConv => q{
8121
8193
  $$self{HandlerType} = $val unless $val eq 'alis' or $val eq 'url ';
8194
+ $$self{MediaType} = $val if @{$$self{PATH}} > 1 and $$self{PATH}[-2] eq 'Media';
8122
8195
  $$self{HasHandler}{$val} = 1; # remember all our handlers
8123
8196
  return $val;
8124
8197
  },
@@ -9538,6 +9611,8 @@ sub ProcessKeys($$$)
9538
9611
  my $groups = $$tagInfo{Groups};
9539
9612
  $$newInfo{Groups} = $groups ? { %$groups } : { };
9540
9613
  $$newInfo{Groups}{$_} or $$newInfo{Groups}{$_} = $$tagTablePtr{GROUPS}{$_} foreach 0..2;
9614
+ # set Keys group. This is necessary for logic when reading the associated ItemList entry,
9615
+ # but note that the group name will be overridden by TAG_EXTRA G1 for tags in a track
9541
9616
  $$newInfo{Groups}{1} = 'Keys';
9542
9617
  } elsif ($tag =~ /^[-\w. ]+$/ or $tag =~ /\w{4}/) {
9543
9618
  # create info for tags with reasonable id's
@@ -9605,7 +9680,7 @@ sub ProcessMOV($$;$)
9605
9680
 
9606
9681
  my $topLevel = not $$et{InQuickTime};
9607
9682
  $$et{InQuickTime} = 1;
9608
- $$et{HandlerType} = $$et{MetaFormat} = '' unless defined $$et{HandlerType};
9683
+ $$et{HandlerType} = $$et{MetaFormat} = $$et{MediaType} = '' if $topLevel;
9609
9684
 
9610
9685
  unless (defined $$et{KeysCount}) {
9611
9686
  $$et{KeysCount} = 0; # initialize ItemList key directory count
@@ -9631,13 +9706,15 @@ sub ProcessMOV($$;$)
9631
9706
  }
9632
9707
  ($size, $tag) = unpack('Na4', $buff);
9633
9708
  my $fast = $$et{OPTIONS}{FastScan} || 0;
9634
- # check for Insta360 trailer
9709
+ # check for Insta360 or LIGOGPSINFO trailer
9635
9710
  if ($topLevel and not $fast) {
9636
9711
  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) ];
9712
+ if ($raf->Seek(-40, 2) and $raf->Read($buff, 40) == 40) {
9713
+ if (substr($buff, 8) eq '8db42d694ccc418790edff439fe026bf') {
9714
+ $trailer = [ 'Insta360', $raf->Tell() - unpack('V',$buff) ];
9715
+ } elsif ($buff =~ /\&\&\&\&(.{4})$/) {
9716
+ $trailer = [ 'LigoGPS', $raf->Tell() - Get32u(\$buff, 36) ];
9717
+ }
9641
9718
  }
9642
9719
  $raf->Seek($pos,0) or return 0;
9643
9720
  }
@@ -9754,16 +9831,18 @@ sub ProcessMOV($$;$)
9754
9831
  }
9755
9832
  if ($isUserData and $$et{SET_GROUP1}) {
9756
9833
  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
9834
+ unless ($$tagInfo{SubDirectory}) {
9835
+ # add track name to UserData tags inside tracks
9836
+ $tag = $$et{SET_GROUP1} . $tag;
9837
+ if (not $$tagTablePtr{$tag} and $tagInfo) {
9838
+ my %newInfo = %$tagInfo;
9839
+ foreach ('Name', 'Description') {
9840
+ next unless $$tagInfo{$_};
9841
+ $newInfo{$_} = $$et{SET_GROUP1} . $$tagInfo{$_};
9842
+ $newInfo{$_} =~ s/^(Track\d+)Track/$1/; # remove duplicate "Track" in name
9843
+ }
9844
+ AddTagToTable($tagTablePtr, $tag, \%newInfo);
9765
9845
  }
9766
- AddTagToTable($tagTablePtr, $tag, \%newInfo);
9767
9846
  }
9768
9847
  }
9769
9848
  # set flag to store additional information for ExtractEmbedded option
@@ -9830,7 +9909,7 @@ sub ProcessMOV($$;$)
9830
9909
  # check for RIFF trailer (written by Auto-Vox dashcam)
9831
9910
  if ($buff =~ /^(gpsa|gps0|gsen|gsea)...\0/s) { # (yet seen only gpsa as first record)
9832
9911
  $et->VPrint(0, sprintf("Found RIFF trailer at offset 0x%x",$lastPos));
9833
- if ($et->Options('ExtractEmbedded')) {
9912
+ if ($ee) {
9834
9913
  $raf->Seek(-8, 1) or last; # seek back to start of trailer
9835
9914
  my $tbl = GetTagTable('Image::ExifTool::QuickTime::Stream');
9836
9915
  ProcessRIFFTrailer($et, { RAF => $raf }, $tbl);
@@ -9980,6 +10059,7 @@ ItemID: foreach $id (reverse sort { $a <=> $b } keys %$items) {
9980
10059
  }
9981
10060
  }
9982
10061
  if ($tagInfo) {
10062
+ my @found;
9983
10063
  my $subdir = $$tagInfo{SubDirectory};
9984
10064
  if ($subdir) {
9985
10065
  my $start = $$subdir{Start} || 0;
@@ -10098,21 +10178,23 @@ ItemID: foreach $id (reverse sort { $a <=> $b } keys %$items) {
10098
10178
  Index => $index,
10099
10179
  Extra => sprintf(", Type='${type}', Flags=0x%x%s, Lang=0x%.4x",$flags,$str,$lang),
10100
10180
  ) 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';
10181
+ if (defined $value) {
10182
+ # use "Keys" in path instead of ItemList if this was defined by a Keys tag
10183
+ # (the only reason for this is to have "Keys" in the family 5 group name)
10184
+ # Note that the Keys group is specifically set by the ProcessKeys routine,
10185
+ # even though this tag would be in the ItemList table
10186
+ my $isKeys = $$tagInfo{Groups} && $$tagInfo{Groups}{1} && $$tagInfo{Groups}{1} eq 'Keys';
10187
+ $isKeys and $oldDir = $$et{PATH}[-1], $$et{PATH}[-1] = 'Keys';
10188
+ push @found, $et->FoundTag($langInfo, $value);
10189
+ $$et{PATH}[-1] = $oldDir if $isKeys;
10106
10190
  }
10107
- $et->FoundTag($langInfo, $value) if defined $value;
10108
- $$et{PATH}[-1] = $oldDir if $isKey;
10109
10191
  $pos += $len;
10110
10192
  }
10111
10193
  } elsif ($tag =~ /^\xa9/ or $$tagInfo{IText}) {
10112
10194
  # parse international text to extract all languages
10113
10195
  my $pos = 0;
10114
10196
  if ($$tagInfo{Format}) {
10115
- $et->FoundTag($tagInfo, ReadValue(\$val, 0, $$tagInfo{Format}, undef, length($val)));
10197
+ push @found, $et->FoundTag($tagInfo, ReadValue(\$val, 0, $$tagInfo{Format}, undef, length($val)));
10116
10198
  $pos = $size;
10117
10199
  }
10118
10200
  for (;;) {
@@ -10177,7 +10259,7 @@ ItemID: foreach $id (reverse sort { $a <=> $b } keys %$items) {
10177
10259
  $str = substr($val, $pos-$n-2, $n) . $str;
10178
10260
  }
10179
10261
  $langInfo = GetLangInfoQT($et, $tagInfo, $lang) if $lang;
10180
- $et->FoundTag($langInfo || $tagInfo, $str);
10262
+ push @found, $et->FoundTag($langInfo || $tagInfo, $str);
10181
10263
  $pos += $len;
10182
10264
  }
10183
10265
  } else {
@@ -10191,6 +10273,7 @@ ItemID: foreach $id (reverse sort { $a <=> $b } keys %$items) {
10191
10273
  $$et{BASE} = $dataPos;
10192
10274
  }
10193
10275
  my $key = $et->FoundTag($tagInfo, $val);
10276
+ push @found, $key;
10194
10277
  $$et{BASE} = $oldBase if defined $oldBase;
10195
10278
  # decode if necessary (NOTE: must be done after RawConv)
10196
10279
  if (defined $key and (not $format or $format =~ /^string/) and
@@ -10206,6 +10289,14 @@ ItemID: foreach $id (reverse sort { $a <=> $b } keys %$items) {
10206
10289
  }
10207
10290
  }
10208
10291
  }
10292
+ # tweak family 1 group names for Keys/ItemList/UserData tags in a track
10293
+ if ($$et{SET_GROUP1} and ($dirID eq 'ilst' or $dirID eq 'udta') and @found) {
10294
+ my $type = $trackPath{join '-', @{$$et{PATH}}};
10295
+ if ($type) {
10296
+ my $grp = ($avType{$$et{MediaType}} || $$et{SET_GROUP1}) . $type;
10297
+ defined and $et->SetGroup($_, $grp) foreach @found;
10298
+ }
10299
+ }
10209
10300
  }
10210
10301
  } else {
10211
10302
  $et->VerboseInfo($tag, $tagInfo,
@@ -10218,6 +10309,7 @@ ItemID: foreach $id (reverse sort { $a <=> $b } keys %$items) {
10218
10309
  last;
10219
10310
  }
10220
10311
  }
10312
+ $$et{MediaType} = '' if $tag eq 'trak'; # reset track type at end of track
10221
10313
  $dataPos += $size + 8; # point to start of next atom data
10222
10314
  last if $dirEnd and $dataPos >= $dirEnd; # (note: ignores last value if 0 bytes)
10223
10315
  $lastPos = $raf->Tell() + $dirBase;
@@ -10273,7 +10365,26 @@ QTLang: foreach $tag (@{$$et{QTLang}}) {
10273
10365
  # process item information now that we are done processing its 'meta' container
10274
10366
  HandleItemInfo($et) if $topLevel or $dirID eq 'meta';
10275
10367
 
10276
- ScanMediaData($et) if $ee and $topLevel; # brute force scan for metadata embedded in media data
10368
+ # process LigoGPS trailer now if it exists and we haven't already processed it
10369
+ if ($trailer and $$trailer[0] eq 'LigoGPS' and $lastPos <= $$trailer[1] and
10370
+ $raf->Seek($$trailer[1]) and $raf->Read($buff, 8) == 8 and $buff =~ /skip$/)
10371
+ {
10372
+ if ($ee) {
10373
+ my $len = Get32u(\$buff, 0) - 16;
10374
+ if ($len > 0 and $raf->Read($buff, $len) == $len and $buff =~ /^LIGOGPSINFO\0/) {
10375
+ my $tbl = GetTagTable('Image::ExifTool::QuickTime::Stream');
10376
+ my %dirInfo = ( DataPt => \$buff, DataPos => $$trailer[1] + 8, DirName => 'LigoGPSTrailer' );
10377
+ Image::ExifTool::LigoGPS::ProcessLigoGPS($et, \%dirInfo, $tbl);
10378
+ } else {
10379
+ $et->Warn('Unrecognized data in LigoGPS trailer');
10380
+ }
10381
+ } else {
10382
+ $et->Warn('Use the ExtractEmbedded option to decode timed GPS',3);
10383
+ }
10384
+ }
10385
+ # brute force scan for metadata embedded in media data
10386
+ # (and process Insta360 trailer if it exists)
10387
+ ScanMediaData($et) if $ee and $topLevel;
10277
10388
 
10278
10389
  # restore any changed options
10279
10390
  $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
  #------------------------------------------------------------------------------
@@ -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
  }