exiftool_vendored 11.94.0 → 12.06.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.

Potentially problematic release.


This version of exiftool_vendored might be problematic. Click here for more details.

Files changed (62) hide show
  1. checksums.yaml +5 -5
  2. data/bin/Changes +163 -3
  3. data/bin/MANIFEST +5 -0
  4. data/bin/META.json +1 -1
  5. data/bin/META.yml +1 -1
  6. data/bin/README +32 -32
  7. data/bin/exiftool +152 -52
  8. data/bin/lib/Image/ExifTool.pm +166 -115
  9. data/bin/lib/Image/ExifTool.pod +108 -81
  10. data/bin/lib/Image/ExifTool/AIFF.pm +2 -2
  11. data/bin/lib/Image/ExifTool/APE.pm +2 -2
  12. data/bin/lib/Image/ExifTool/BuildTagLookup.pm +13 -7
  13. data/bin/lib/Image/ExifTool/Canon.pm +6 -3
  14. data/bin/lib/Image/ExifTool/CanonCustom.pm +82 -16
  15. data/bin/lib/Image/ExifTool/DPX.pm +56 -2
  16. data/bin/lib/Image/ExifTool/DarwinCore.pm +16 -3
  17. data/bin/lib/Image/ExifTool/Exif.pm +15 -6
  18. data/bin/lib/Image/ExifTool/Font.pm +9 -2
  19. data/bin/lib/Image/ExifTool/GIF.pm +5 -0
  20. data/bin/lib/Image/ExifTool/GeoTiff.pm +2 -0
  21. data/bin/lib/Image/ExifTool/Geotag.pm +69 -21
  22. data/bin/lib/Image/ExifTool/GoPro.pm +10 -1
  23. data/bin/lib/Image/ExifTool/H264.pm +1 -1
  24. data/bin/lib/Image/ExifTool/HtmlDump.pm +2 -2
  25. data/bin/lib/Image/ExifTool/ID3.pm +91 -12
  26. data/bin/lib/Image/ExifTool/Lang/de.pm +3 -1
  27. data/bin/lib/Image/ExifTool/Lang/es.pm +1 -1
  28. data/bin/lib/Image/ExifTool/M2TS.pm +44 -24
  29. data/bin/lib/Image/ExifTool/MWG.pm +9 -1
  30. data/bin/lib/Image/ExifTool/MacOS.pm +1 -1
  31. data/bin/lib/Image/ExifTool/Minolta.pm +3 -2
  32. data/bin/lib/Image/ExifTool/MinoltaRaw.pm +11 -10
  33. data/bin/lib/Image/ExifTool/Nikon.pm +156 -18
  34. data/bin/lib/Image/ExifTool/Olympus.pm +34 -17
  35. data/bin/lib/Image/ExifTool/PNG.pm +14 -3
  36. data/bin/lib/Image/ExifTool/PPM.pm +5 -5
  37. data/bin/lib/Image/ExifTool/Panasonic.pm +147 -13
  38. data/bin/lib/Image/ExifTool/PanasonicRaw.pm +33 -0
  39. data/bin/lib/Image/ExifTool/Parrot.pm +2 -1
  40. data/bin/lib/Image/ExifTool/Pentax.pm +3 -1
  41. data/bin/lib/Image/ExifTool/Photoshop.pm +2 -1
  42. data/bin/lib/Image/ExifTool/QuickTime.pm +277 -33
  43. data/bin/lib/Image/ExifTool/QuickTimeStream.pl +460 -67
  44. data/bin/lib/Image/ExifTool/README +21 -20
  45. data/bin/lib/Image/ExifTool/RIFF.pm +123 -3
  46. data/bin/lib/Image/ExifTool/RTF.pm +12 -7
  47. data/bin/lib/Image/ExifTool/Ricoh.pm +19 -1
  48. data/bin/lib/Image/ExifTool/Shift.pl +1 -0
  49. data/bin/lib/Image/ExifTool/SigmaRaw.pm +40 -33
  50. data/bin/lib/Image/ExifTool/Sony.pm +379 -12
  51. data/bin/lib/Image/ExifTool/TagLookup.pm +1959 -1874
  52. data/bin/lib/Image/ExifTool/TagNames.pod +346 -55
  53. data/bin/lib/Image/ExifTool/Validate.pm +4 -4
  54. data/bin/lib/Image/ExifTool/WriteExif.pl +3 -2
  55. data/bin/lib/Image/ExifTool/WriteQuickTime.pl +26 -15
  56. data/bin/lib/Image/ExifTool/Writer.pl +52 -23
  57. data/bin/lib/Image/ExifTool/XMP.pm +41 -4
  58. data/bin/lib/Image/ExifTool/XMPStruct.pl +3 -1
  59. data/bin/lib/Image/ExifTool/ZISRAW.pm +123 -0
  60. data/bin/perl-Image-ExifTool.spec +31 -31
  61. data/lib/exiftool_vendored/version.rb +1 -1
  62. metadata +4 -4
@@ -19,9 +19,11 @@ use Image::ExifTool qw(:DataAccess :Utils);
19
19
  use Image::ExifTool::QuickTime;
20
20
 
21
21
  sub Process_tx3g($$$);
22
+ sub Process_marl($$$);
22
23
  sub Process_mebx($$$);
23
24
  sub ProcessFreeGPS($$$);
24
25
  sub ProcessFreeGPS2($$$);
26
+ sub Process360Fly($$$);
25
27
 
26
28
  # QuickTime data types that have ExifTool equivalents
27
29
  # (ref https://developer.apple.com/library/content/documentation/QuickTime/QTFF/Metadata/Metadata.html#//apple_ref/doc/uid/TP40000939-CH1-SW35)
@@ -70,6 +72,7 @@ my %processByMetaFormat = (
70
72
  meta => 1, # ('CTMD' in CR3 images, 'priv' unknown in DJI video)
71
73
  data => 1, # ('RVMI')
72
74
  sbtl => 1, # (subtitle; 'tx3g' in Yuneec drone videos)
75
+ ctbx => 1, # ('marl' in GM videos)
73
76
  );
74
77
 
75
78
  # data lengths for each INSV record type
@@ -91,7 +94,7 @@ my %insvLimit = (
91
94
  NOTES => q{
92
95
  Timed metadata extracted from QuickTime media data and some AVI videos when
93
96
  the ExtractEmbedded option is used. Although most of these tags are
94
- combined into the single table below, ExifTool currently reads 37 different
97
+ combined into the single table below, ExifTool currently reads 46 different
95
98
  formats of timed GPS metadata from video files.
96
99
  },
97
100
  VARS => { NO_ID => 1 },
@@ -133,6 +136,21 @@ my %insvLimit = (
133
136
  SampleTime => { Groups => { 2 => 'Video' }, PrintConv => 'ConvertDuration($val)', Notes => 'sample decoding time' },
134
137
  SampleDuration=>{ Groups => { 2 => 'Video' }, PrintConv => 'ConvertDuration($val)' },
135
138
  UserLabel => { Groups => { 2 => 'Other' } },
139
+ SampleDateTime => {
140
+ Groups => { 2 => 'Time' },
141
+ ValueConv => q{
142
+ my $str = ConvertUnixTime($val);
143
+ my $frac = $val - int($val);
144
+ if ($frac != 0) {
145
+ $frac = sprintf('%.6f', $frac);
146
+ $frac =~ s/^0//;
147
+ $frac =~ s/0+$//;
148
+ $str .= $frac;
149
+ }
150
+ return $str;
151
+ },
152
+ PrintConv => '$self->ConvertDateTime($val)',
153
+ },
136
154
  #
137
155
  # timed metadata decoded based on MetaFormat (format of 'meta' or 'data' sample description)
138
156
  # [or HandlerType, or specific 'vide' type if specified]
@@ -158,6 +176,10 @@ my %insvLimit = (
158
176
  Name => 'rtmd',
159
177
  SubDirectory => { TagTable => 'Image::ExifTool::Sony::rtmd' },
160
178
  },
179
+ marl => {
180
+ Name => 'marl',
181
+ SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::marl' },
182
+ },
161
183
  CTMD => { # (Canon Timed MetaData)
162
184
  Name => 'CTMD',
163
185
  SubDirectory => { TagTable => 'Image::ExifTool::Canon::CTMD' },
@@ -533,6 +555,137 @@ my %insvLimit = (
533
555
  },
534
556
  );
535
557
 
558
+ %Image::ExifTool::QuickTime::Tags360Fly = (
559
+ PROCESS_PROC => \&Process360Fly,
560
+ NOTES => 'Timed metadata found in MP4 videos from the 360Fly.',
561
+ 1 => {
562
+ Name => 'Accel360Fly',
563
+ SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Accel360Fly' },
564
+ },
565
+ 2 => {
566
+ Name => 'Gyro360Fly',
567
+ SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Gyro360Fly' },
568
+ },
569
+ 3 => {
570
+ Name => 'Mag360Fly',
571
+ SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Mag360Fly' },
572
+ },
573
+ 5 => {
574
+ Name => 'GPS360Fly',
575
+ SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::GPS360Fly' },
576
+ },
577
+ 6 => {
578
+ Name => 'Rot360Fly',
579
+ SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Rot360Fly' },
580
+ },
581
+ 250 => {
582
+ Name => 'Fusion360Fly',
583
+ SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Fusion360Fly' },
584
+ },
585
+ );
586
+
587
+ %Image::ExifTool::QuickTime::Accel360Fly = (
588
+ PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
589
+ GROUPS => { 2 => 'Location' },
590
+ 1 => { Name => 'AccelMode', Unknown => 1 }, # (always 2 in my sample)
591
+ 2 => {
592
+ Name => 'SampleTime',
593
+ Groups => { 2 => 'Video' },
594
+ Format => 'int64u',
595
+ ValueConv => '$val / 1e6',
596
+ PrintConv => 'ConvertDuration($val)',
597
+ },
598
+ 10 => { Name => 'AccelYPR', Format => 'float[3]' },
599
+ );
600
+
601
+ %Image::ExifTool::QuickTime::Gyro360Fly = (
602
+ PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
603
+ GROUPS => { 2 => 'Location' },
604
+ 1 => { Name => 'GyroMode', Unknown => 1 }, # (always 1 in my sample)
605
+ 2 => {
606
+ Name => 'SampleTime',
607
+ Groups => { 2 => 'Video' },
608
+ Format => 'int64u',
609
+ ValueConv => '$val / 1e6',
610
+ PrintConv => 'ConvertDuration($val)',
611
+ },
612
+ 10 => { Name => 'GyroYPR', Format => 'float[3]' },
613
+ );
614
+
615
+ %Image::ExifTool::QuickTime::Mag360Fly = (
616
+ PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
617
+ GROUPS => { 2 => 'Location' },
618
+ 1 => { Name => 'MagMode', Unknown => 1 }, # (always 1 in my sample)
619
+ 2 => {
620
+ Name => 'SampleTime',
621
+ Groups => { 2 => 'Video' },
622
+ Format => 'int64u',
623
+ ValueConv => '$val / 1e6',
624
+ PrintConv => 'ConvertDuration($val)',
625
+ },
626
+ 10 => { Name => 'MagnetometerXYZ', Format => 'float[3]' },
627
+ );
628
+
629
+ %Image::ExifTool::QuickTime::GPS360Fly = (
630
+ PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
631
+ GROUPS => { 2 => 'Location' },
632
+ 1 => { Name => 'GPSMode', Unknown => 1 }, # (always 16 in my sample)
633
+ 2 => {
634
+ Name => 'SampleTime',
635
+ Groups => { 2 => 'Video' },
636
+ Format => 'int64u',
637
+ ValueConv => '$val / 1e6',
638
+ PrintConv => 'ConvertDuration($val)',
639
+ },
640
+ 10 => { Name => 'GPSLatitude', Format => 'float', PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")' },
641
+ 14 => { Name => 'GPSLongitude', Format => 'float', PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")' },
642
+ 18 => { Name => 'GPSAltitude', Format => 'float', PrintConv => '"$val m"' }, # (questionable accuracy)
643
+ 22 => {
644
+ Name => 'GPSSpeed',
645
+ Notes => 'converted to km/hr',
646
+ Format => 'int16u',
647
+ ValueConv => '$val * 0.036',
648
+ PrintConv => 'sprintf("%.1f",$val)',
649
+ },
650
+ 24 => { Name => 'GPSTrack', Format => 'int16u', ValueConv => '$val / 100' },
651
+ 26 => { Name => 'Acceleration', Format => 'int16u', ValueConv => '$val / 1000' },
652
+ );
653
+
654
+ %Image::ExifTool::QuickTime::Rot360Fly = (
655
+ PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
656
+ GROUPS => { 2 => 'Location' },
657
+ 1 => { Name => 'RotMode', Unknown => 1 }, # (always 1 in my sample)
658
+ 2 => {
659
+ Name => 'SampleTime',
660
+ Groups => { 2 => 'Video' },
661
+ Format => 'int64u',
662
+ ValueConv => '$val / 1e6',
663
+ PrintConv => 'ConvertDuration($val)',
664
+ },
665
+ 10 => { Name => 'RotationXYZ', Format => 'float[3]' },
666
+ );
667
+
668
+ %Image::ExifTool::QuickTime::Fusion360Fly = (
669
+ PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
670
+ GROUPS => { 2 => 'Location' },
671
+ 1 => { Name => 'FusionMode', Unknown => 1 }, # (always 0 in my sample)
672
+ 2 => {
673
+ Name => 'SampleTime',
674
+ Groups => { 2 => 'Video' },
675
+ Format => 'int64u',
676
+ ValueConv => '$val / 1e6',
677
+ PrintConv => 'ConvertDuration($val)',
678
+ },
679
+ 10 => { Name => 'FusionYPR', Format => 'float[3]' },
680
+ );
681
+
682
+ # tags found in 'marl' ctbx timed metadata (ref PH)
683
+ %Image::ExifTool::QuickTime::marl = (
684
+ PROCESS_PROC => \&Process_marl,
685
+ GROUPS => { 2 => 'Other' },
686
+ NOTES => 'Tags extracted from the marl ctbx timed metadata of GM cars.',
687
+ );
688
+
536
689
  #------------------------------------------------------------------------------
537
690
  # Save information from keys in OtherSampleDesc directory for processing timed metadata
538
691
  # Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
@@ -633,7 +786,7 @@ sub FoundSomething($$;$$)
633
786
 
634
787
  #------------------------------------------------------------------------------
635
788
  # Approximate GPSDateTime value from sample time and CreateDate
636
- # Inputs: 0) ExifTool ref, 1) tag table ptr, 2) sample time (ms)
789
+ # Inputs: 0) ExifTool ref, 1) tag table ptr, 2) sample time (s)
637
790
  # 3) true if CreateDate is at end of video
638
791
  # Notes: Uses ExifTool CreateDateAtEnd as flag to subtract video duration
639
792
  sub SetGPSDateTime($$$)
@@ -663,48 +816,89 @@ sub SetGPSDateTime($$$)
663
816
  }
664
817
  }
665
818
 
819
+ #------------------------------------------------------------------------------
820
+ # Handle tags that we found in the subtitle 'text'
821
+ # Inputs: 0) ExifTool ref, 1) tag table ref, 2) hash of tag names/values
822
+ sub HandleTextTags($$$)
823
+ {
824
+ my ($et, $tagTbl, $tags) = @_;
825
+ my $tag;
826
+ delete $$tags{done};
827
+ delete $$tags{GPSTimeStamp} if $$tags{GPSDateTime};
828
+ foreach $tag (sort keys %$tags) {
829
+ $et->HandleTag($tagTbl, $tag => $$tags{$tag});
830
+ }
831
+ $$et{UnknownTextCount} = 0;
832
+ undef %$tags; # clear the hash
833
+ }
834
+
666
835
  #------------------------------------------------------------------------------
667
836
  # Process subtitle 'text'
668
- # Inputs: 0) ExifTool ref, 1) tag table ref, 2) data ref, 3) optional sample time
837
+ # Inputs: 0) ExifTool ref, 1) tag table ref, 2) data ref
669
838
  sub Process_text($$$)
670
839
  {
671
840
  my ($et, $tagTbl, $buffPt) = @_;
672
- my ($found, $val, %tags, $tag);
841
+ my %tags;
673
842
 
674
843
  return if $$et{NoMoreTextDecoding};
675
844
 
676
845
  while ($$buffPt =~ /\$(\w+)([^\$]*)/g) {
677
846
  my ($tag, $dat) = ($1, $2);
678
- if ($tag =~ /^[A-Z]{2}RMC$/ and $dat =~ /^,(\d{2})(\d{2})(\d+)(\.\d*)?,A?,(\d*?)(\d{1,2}\.\d+),([NS]),(\d*?)(\d{1,2}\.\d+),([EW]),(\d*\.?\d*),(\d*\.?\d*),(\d{2})(\d{2})(\d+)/) {
679
- my $year = $15 + ($15 >= 70 ? 1900 : 2000);
680
- my $str = sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%.2d%sZ', $year, $14, $13, $1, $2, $3, $4 || '');
681
- $et->HandleTag($tagTbl, GPSDateTime => $str);
682
- $et->HandleTag($tagTbl, GPSLatitude => (($5 || 0) + $6/60) * ($7 eq 'N' ? 1 : -1));
683
- $et->HandleTag($tagTbl, GPSLongitude => (($8 || 0) + $9/60) * ($10 eq 'E' ? 1 : -1));
847
+ if ($tag =~ /^[A-Z]{2}RMC$/ and $dat =~ /^,(\d{2})(\d{2})(\d+(?:\.\d*)),A?,(\d*?)(\d{1,2}\.\d+),([NS]),(\d*?)(\d{1,2}\.\d+),([EW]),(\d*\.?\d*),(\d*\.?\d*),(\d{2})(\d{2})(\d+)/) {
848
+ my $time = "$1:$2:$3";
849
+ if ($$et{LastTime}) {
850
+ if ($$et{LastTime} eq $time) {
851
+ $$et{DOC_NUM} = $$et{LastDoc};
852
+ } elsif (%tags) {
853
+ HandleTextTags($et, $tagTbl, \%tags);
854
+ $$et{DOC_NUM} = ++$$et{DOC_COUNT};
855
+ }
856
+ }
857
+ $$et{LastTime} = $time;
858
+ $$et{LastDoc} = $$et{DOC_NUM};
859
+ my $year = $14 + ($14 >= 70 ? 1900 : 2000);
860
+ my $dateTime = sprintf('%.4d:%.2d:%.2d %sZ', $year, $13, $12, $time);
861
+ $tags{GPSDateTime} = $dateTime;
862
+ $tags{GPSLatitude} = (($4 || 0) + $5/60) * ($6 eq 'N' ? 1 : -1);
863
+ $tags{GPSLongitude} = (($7 || 0) + $8/60) * ($9 eq 'E' ? 1 : -1);
864
+ if (length $10) {
865
+ $tags{GPSSpeed} = $10 * $knotsToKph;
866
+ $tags{GPSSpeedRef} = 'K';
867
+ }
684
868
  if (length $11) {
685
- $et->HandleTag($tagTbl, GPSSpeed => $11 * $knotsToKph);
686
- $et->HandleTag($tagTbl, GPSSpeedRef => 'K');
869
+ $tags{GPSTrack} = $11;
870
+ $tags{GPSTrackRef} = 'T';
687
871
  }
688
- if (length $12) {
689
- $et->HandleTag($tagTbl, GPSTrack => $12);
690
- $et->HandleTag($tagTbl, GPSTrackRef => 'T');
872
+ } elsif ($tag =~ /^[A-Z]{2}GGA$/ and $dat =~ /^,(\d{2})(\d{2})(\d+(?:\.\d*)?),(\d*?)(\d{1,2}\.\d+),([NS]),(\d*?)(\d{1,2}\.\d+),([EW]),[1-6]?,(\d+)?,(\.\d+|\d+\.?\d*)?,(-?\d+\.?\d*)?,M?/s) {
873
+ my $time = "$1:$2:$3";
874
+ if ($$et{LastTime}) {
875
+ if ($$et{LastTime} eq $time) {
876
+ $$et{DOC_NUM} = $$et{LastDoc};
877
+ } elsif (%tags) {
878
+ HandleTextTags($et, $tagTbl, \%tags);
879
+ $$et{DOC_NUM} = ++$$et{DOC_COUNT};
880
+ }
691
881
  }
692
- $found = 1;
882
+ $$et{LastTime} = $time;
883
+ $$et{LastDoc} = $$et{DOC_NUM};
884
+ $tags{GPSTimeStamp} = $time;
885
+ $tags{GPSLatitude} = (($4 || 0) + $5/60) * ($6 eq 'N' ? 1 : -1);
886
+ $tags{GPSLongitude} = (($7 || 0) + $8/60) * ($9 eq 'E' ? 1 : -1);
887
+ $tags{GPSSatellites} = $10 if defined $10;
888
+ $tags{GPSDOP} = $11 if defined $11;
889
+ $tags{GPSAltitude} = $12 if defined $12;
693
890
  } elsif ($tag eq 'BEGINGSENSOR' and $dat =~ /^:([-+]\d+\.\d+):([-+]\d+\.\d+):([-+]\d+\.\d+)/) {
694
- $et->HandleTag($tagTbl, Accelerometer => "$1 $2 $3");
695
- $found = 1;
891
+ $tags{Accelerometer} = "$1 $2 $3";
696
892
  } elsif ($tag eq 'TIME' and $dat =~ /^:(\d+)/) {
697
- $et->HandleTag($tagTbl, TimeCode => $1 / ($$et{MediaTS} || 1));
698
- $found = 1;
893
+ $tags{TimeCode} = $1 / ($$et{MediaTS} || 1);
699
894
  } elsif ($tag eq 'BEGIN') {
700
- $et->HandleTag($tagTbl, Text => $dat) if length $dat;
701
- $found = 1;
895
+ $tags{Text} = $dat if length $dat;
896
+ $tags{done} = 1;
702
897
  } elsif ($tag ne 'END') {
703
- $et->HandleTag($tagTbl, Text => "\$$tag$dat");
704
- $found = 1;
898
+ $tags{Text} = "\$$tag$dat";
705
899
  }
706
900
  }
707
- return if $found;
901
+ %tags and HandleTextTags($et, $tagTbl, \%tags), return;
708
902
 
709
903
  # check for enciphered binary GPS data
710
904
  # BlueSkySea:
@@ -740,7 +934,7 @@ sub Process_text($$$)
740
934
  # 0120: 58 00 58 00 58 00 58 00 00 00 00 00 00 00 00 00 [X.X.X.X.........]
741
935
  # 0130: 00 00 00 00 00 00 00 [.......]
742
936
  if ($$buffPt =~ /^\0\0(..\xaa\xaa|\xf2\xe1\xf0\xee)/s and length $$buffPt >= 282) {
743
- $val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$buffPt, 8, 14)));
937
+ my $val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$buffPt, 8, 14)));
744
938
  if ($val =~ /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$/) {
745
939
  $tags{GPSDateTime} = "$1:$2:$3 $4:$5:$6";
746
940
  $val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$buffPt, 38, 9)));
@@ -777,12 +971,13 @@ sub Process_text($$$)
777
971
  $tags{Accelerometer} = "@acc" if @acc;
778
972
  }
779
973
  }
974
+ %tags and HandleTextTags($et, $tagTbl, \%tags), return;
780
975
  }
781
976
 
782
977
  # check for DJI telemetry data, eg:
783
978
  # "F/3.5, SS 1000, ISO 100, EV 0, GPS (8.6499, 53.1665, 18), D 24.26m,
784
979
  # H 6.00m, H.S 2.10m/s, V.S 0.00m/s \n"
785
- if (not %tags and $$buffPt =~ /GPS \(([-+]?\d*\.\d+),\s*([-+]?\d*\.\d+)/) {
980
+ if ($$buffPt =~ /GPS \(([-+]?\d*\.\d+),\s*([-+]?\d*\.\d+)/) {
786
981
  $$et{CreateDateAtEnd} = 1; # set flag indicating the file creation date is at the end
787
982
  $tags{GPSLatitude} = $2;
788
983
  $tags{GPSLongitude} = $1;
@@ -797,11 +992,13 @@ sub Process_text($$$)
797
992
  $tags{ExposureTime} = 1 / $1 if $$buffPt =~ /\bSS\s+(\d+\.?\d*)/;
798
993
  $tags{ExposureCompensation} = ($1 / ($2 || 1)) if $$buffPt =~ /\bEV\s+([-+]?\d+\.?\d*)(\/\d+)?/;
799
994
  $tags{ISO} = $1 if $$buffPt =~ /\bISO\s+(\d+\.?\d*)/;
995
+ HandleTextTags($et, $tagTbl, \%tags);
996
+ return;
800
997
  }
801
998
 
802
999
  # check for Mini 0806 dashcam GPS, eg:
803
1000
  # "A,270519,201555.000,3356.8925,N,08420.2071,W,000.0,331.0M,+01.84,-09.80,-00.61;\n"
804
- if (not %tags and $$buffPt =~ /^A,(\d{2})(\d{2})(\d{2}),(\d{2})(\d{2})(\d{2}(\.\d+)?)/) {
1001
+ if ($$buffPt =~ /^A,(\d{2})(\d{2})(\d{2}),(\d{2})(\d{2})(\d{2}(\.\d+)?)/) {
805
1002
  $tags{GPSDateTime} = "20$3:$2:$1 $4:$5:$6Z";
806
1003
  if ($$buffPt =~ /^A,.*?,.*?,(\d{2})(\d+\.\d+),([NS])/) {
807
1004
  $tags{GPSLatitude} = ($1 + $2/60) * ($3 eq 'S' ? -1 : 1);
@@ -813,37 +1010,56 @@ sub Process_text($$$)
813
1010
  $tags{GPSAltitude} = $a[8] if $a[8] and $a[8] =~ s/M$//;
814
1011
  $tags{GPSSpeed} = $a[7] if $a[7] and $a[7] =~ /^\d+\.\d+$/; # (NC)
815
1012
  $tags{Accelerometer} = "$a[9] $a[10] $a[11]" if $a[11] and $a[11] =~ s/;\s*$//;
1013
+ HandleTextTags($et, $tagTbl, \%tags);
1014
+ return;
1015
+ }
1016
+
1017
+ # check for Roadhawk dashcam text
1018
+ # ".;;;;D?JL;6+;;;D;R?;4;;;;DBB;;O;;;=D;L;;HO71G>F;-?=J-F:FNJJ;DPP-JF3F;;PL=DBRLBF0F;=?DNF-RD-PF;N;?=JF;;?D=F:*6F~"
1019
+ # decoded:
1020
+ # "X0000.2340Y-000.0720Z0000.9900G0001.0400$GPRMC,082138,A,5330.6683,N,00641.9749,W,012.5,87.86,050213,002.1,A"
1021
+ # (note: "002.1" is magnetic variation and is not decoded; it should have ",E" or ",W" afterward for direction)
1022
+ if ($$buffPt =~ /\*[0-9A-F]{2}~$/) {
1023
+ # (ref https://reverseengineering.stackexchange.com/questions/11582/how-to-reverse-engineer-dash-cam-metadata)
1024
+ my @decode = unpack 'C*', '-I8XQWRVNZOYPUTA0B1C2SJ9K.L,M$D3E4F5G6H7';
1025
+ my @chars = unpack 'C*', substr($$buffPt, 0, -4);
1026
+ foreach (@chars) {
1027
+ my $n = $_ - 43;
1028
+ $_ = $decode[$n] if $n >= 0 and defined $decode[$n];
1029
+ }
1030
+ my $buff = pack 'C*', @chars;
1031
+ if ($buff =~ /X(.*?)Y(.*?)Z(.*?)G(.*?)\$/) {
1032
+ # yup. the decoding worked out
1033
+ $tags{Accelerometer} = "$1 $2 $3 $4";
1034
+ $$buffPt = $buff; # (process GPRMC below)
1035
+ }
816
1036
  }
817
1037
 
818
- # check for Thinkware format, eg:
1038
+ # check for Thinkware format (and other NMEA RMC), eg:
819
1039
  # "gsensori,4,512,-67,-12,100;GNRMC,161313.00,A,4529.87489,N,07337.01215,W,6.225,35.34,310819,,,A*52..;
820
1040
  # CAR,0,0,0,0.0,0,0,0,0,0,0,0,0"
821
- unless (%tags) {
822
- if ($$buffPt =~ /[A-Z]{2}RMC,(\d{2})(\d{2})(\d+(\.\d*)?),A?,(\d*?)(\d{1,2}\.\d+),([NS]),(\d*?)(\d{1,2}\.\d+),([EW]),(\d*\.?\d*),(\d*\.?\d*),(\d{2})(\d{2})(\d+)/ and
823
- # do some basic sanity checks on the date
824
- $13 <= 31 and $14 <= 12 and $15 <= 99)
825
- {
826
- my $year = $15 + ($15 >= 70 ? 1900 : 2000);
827
- $tags{GPSDateTime} = sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%.2dZ', $year, $14, $13, $1, $2, $3);
828
- $tags{GPSLatitude} = (($5 || 0) + $6/60) * ($7 eq 'N' ? 1 : -1);
829
- $tags{GPSLongitude} = (($8 || 0) + $9/60) * ($10 eq 'E' ? 1 : -1);
830
- if (length $11) {
831
- $tags{GPSSpeed} = $11 * $knotsToKph;
832
- $tags{GPSSpeedRef} = 'K';
833
- }
834
- if (length $12) {
835
- $tags{GPSTrack} = $12;
836
- $tags{GPSTrackRef} = 'T';
837
- }
1041
+ if ($$buffPt =~ /[A-Z]{2}RMC,(\d{2})(\d{2})(\d+(\.\d*)?),A?,(\d*?)(\d{1,2}\.\d+),([NS]),(\d*?)(\d{1,2}\.\d+),([EW]),(\d*\.?\d*),(\d*\.?\d*),(\d{2})(\d{2})(\d+)/ and
1042
+ # do some basic sanity checks on the date
1043
+ $13 <= 31 and $14 <= 12 and $15 <= 99)
1044
+ {
1045
+ my $year = $15 + ($15 >= 70 ? 1900 : 2000);
1046
+ $tags{GPSDateTime} = sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%.2dZ', $year, $14, $13, $1, $2, $3);
1047
+ $tags{GPSLatitude} = (($5 || 0) + $6/60) * ($7 eq 'N' ? 1 : -1);
1048
+ $tags{GPSLongitude} = (($8 || 0) + $9/60) * ($10 eq 'E' ? 1 : -1);
1049
+ if (length $11) {
1050
+ $tags{GPSSpeed} = $11 * $knotsToKph;
1051
+ $tags{GPSSpeedRef} = 'K';
1052
+ }
1053
+ if (length $12) {
1054
+ $tags{GPSTrack} = $12;
1055
+ $tags{GPSTrackRef} = 'T';
838
1056
  }
839
- $tags{GSensor} = $1 if $$buffPt =~ /\bgsensori,(.*?)(;|$)/;
840
- $tags{Car} = $1 if $$buffPt =~ /\bCAR,(.*?)(;|$)/;
841
1057
  }
1058
+ $tags{GSensor} = $1 if $$buffPt =~ /\bgsensori,(.*?)(;|$)/;
1059
+ $tags{Car} = $1 if $$buffPt =~ /\bCAR,(.*?)(;|$)/;
1060
+
842
1061
  if (%tags) {
843
- foreach $tag (sort keys %tags) {
844
- $et->HandleTag($tagTbl, $tag => $tags{$tag});
845
- }
846
- $$et{UnknownTextCount} = 0;
1062
+ HandleTextTags($et, $tagTbl, \%tags);
847
1063
  } else {
848
1064
  $$et{UnknownTextCount} = ($$et{UnknownTextCount} || 0) + 1;
849
1065
  # give up trying to decode useful information if we haven't found anything for a while
@@ -1023,8 +1239,8 @@ sub ProcessSamples($)
1023
1239
  $$et{ee} = $ee; # need ee information for 'keys'
1024
1240
  $et->HandleTag($tagTbl, $metaFormat, undef,
1025
1241
  DataPt => \$buff,
1026
- DataPos => $$start[$i],
1027
- Base => $$start[$i],
1242
+ DataPos => 0,
1243
+ Base => $$start[$i], # (Base must be set for CR3 files)
1028
1244
  TagInfo => $tagInfo,
1029
1245
  );
1030
1246
  delete $$et{ee};
@@ -1036,7 +1252,7 @@ sub ProcessSamples($)
1036
1252
  Process_text($et, $tagTbl, \$buff);
1037
1253
  }
1038
1254
  } elsif ($verbose) {
1039
- $et->VPrint(0, "Unknown meta format ($metaFormat)");
1255
+ $et->VPrint(0, "Unknown $type format ($metaFormat)");
1040
1256
  }
1041
1257
 
1042
1258
  } elsif ($type eq 'gps ') { # (ie. GPSDataList tag)
@@ -1058,8 +1274,8 @@ sub ProcessSamples($)
1058
1274
  FoundSomething($et, $tagTbl, $time[$i], $dur[$i]);
1059
1275
  $et->HandleTag($tagTbl, $type, undef,
1060
1276
  DataPt => \$buff,
1061
- DataPos => $$start[$i],
1062
- Base => $$start[$i],
1277
+ DataPos => 0,
1278
+ Base => $$start[$i], # (Base must be set for CR3 files)
1063
1279
  TagInfo => $tagInfo,
1064
1280
  );
1065
1281
  }
@@ -1093,13 +1309,35 @@ sub ProcessFreeGPS($$$)
1093
1309
 
1094
1310
  return 0 if $dirLen < 92;
1095
1311
 
1096
- if (substr($$dataPt,12,1) eq "\x05") {
1312
+ if (substr($$dataPt,18,8) eq "\xaa\xaa\xf2\xe1\xf0\xee\x54\x54") {
1097
1313
 
1314
+ # (this is very similar to the encrypted text format)
1098
1315
  # decode encrypted ASCII-based GPS (DashCam Azdome GS63H, ref 5)
1099
1316
  # header looks like this in my sample:
1100
1317
  # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 05 01 00 00 [....freeGPS ....]
1101
1318
  # 0010: 01 03 aa aa f2 e1 f0 ee 54 54 98 9a 9b 92 9a 93 [........TT......]
1102
1319
  # 0020: 98 9e 98 98 9e 93 98 92 a6 9f 9f 9c 9d ed fa 8a [................]
1320
+ # decrypted (from byte 18):
1321
+ # 0000: 00 00 58 4b 5a 44 fe fe 32 30 31 38 30 39 32 34 [..XKZD..20180924]
1322
+ # 0010: 32 32 34 39 32 38 0c 35 35 36 37 47 50 20 20 20 [224928.5567GP ]
1323
+ # 0020: 00 00 00 00 00 03 4e 34 30 34 36 34 33 35 30 57 [......N40464350W]
1324
+ # 0030: 30 30 37 30 34 30 33 30 38 30 30 30 30 30 30 30 [0070403080000000]
1325
+ # 0040: 37 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [7...............]
1326
+ # [...]
1327
+ # 00a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 2b 30 39 [.............+09]
1328
+ # 00b0: 33 2d 30 30 33 2d 30 30 35 00 00 00 00 00 00 00 [3-003-005.......]
1329
+ # header looks like this for EEEkit gps:
1330
+ # 0000: 00 00 04 00 66 72 65 65 47 50 53 20 f0 03 00 00 [....freeGPS ....]
1331
+ # 0010: 01 03 aa aa f2 e1 f0 ee 54 54 98 9a 98 9a 9a 9f [........TT......]
1332
+ # 0020: 9b 93 9b 9c 98 99 99 9f a6 9a 9a 98 9a 9a 9f 9b [................]
1333
+ # 0030: 93 9b 9c 98 99 99 9c a9 e4 99 9d 9e 9f 98 9e 9b [................]
1334
+ # 0040: 9c fd 9b 98 98 98 9f 9f 9a 9a 93 81 9a 9b 9d 9f [................]
1335
+ # decrypted (from byte 18):
1336
+ # 0000: 00 00 58 4b 5a 44 fe fe 32 30 32 30 30 35 31 39 [..XKZD..20200519]
1337
+ # 0010: 31 36 32 33 33 35 0c 30 30 32 30 30 35 31 39 31 [162335.002005191]
1338
+ # 0020: 36 32 33 33 36 03 4e 33 37 34 35 32 34 31 36 57 [62336.N37452416W]
1339
+ # 0030: 31 32 32 32 35 35 30 30 39 2b 30 31 37 35 30 31 [122255009+017501]
1340
+ # 0040: 31 2b 30 31 34 2b 30 30 32 2b 30 32 36 2b 30 31 [1+014+002+026+01]
1103
1341
  my $n = $dirLen - 18;
1104
1342
  $n = 0x101 if $n > 0x101;
1105
1343
  my $buf2 = pack 'C*', map { $_ ^ 0xaa } unpack 'C*', substr($$dataPt,18,$n);
@@ -1108,13 +1346,25 @@ sub ProcessFreeGPS($$$)
1108
1346
  $et->VerboseDump(\$buf2);
1109
1347
  }
1110
1348
  # (extract longitude as 9 digits, not 8, ref PH)
1111
- return 0 unless $buf2 =~ /^.{8}(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2}).(.{15})([NS])(\d{8})([EW])(\d{9})(\d{8})/s;
1349
+ return 0 unless $buf2 =~ /^.{8}(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2}).(.{15})([NS])(\d{8})([EW])(\d{9})(\d{8})?/s;
1112
1350
  ($yr,$mon,$day,$hr,$min,$sec,$lbl,$latRef,$lat,$lonRef,$lon,$spd) = ($1,$2,$3,$4,$5,$6,$7,$8,$9/1e4,$10,$11/1e4,$12);
1113
- $spd += 0; # remove leading 0's
1351
+ if (defined $spd) { # (Azdome)
1352
+ $spd += 0; # remove leading 0's
1353
+ } elsif ($buf2 =~ /^.{57}([-+]\d{4})(\d{3})/s) { # (EEEkit)
1354
+ # $alt = $1 + 0; (doesn't look right for my sample, but the Ambarella A12 text has this)
1355
+ $spd = $2 + 0;
1356
+ }
1114
1357
  $lbl =~ s/\0.*//s; $lbl =~ s/\s+$//; # truncate at null and remove trailing spaces
1115
1358
  push @xtra, UserLabel => $lbl if length $lbl;
1116
1359
  # extract accelerometer data (ref PH)
1117
- @acc = ($1/100,$2/100,$3/100) if $buf2 =~ /^.{173}([-+]\d{3})([-+]\d{3})([-+]\d{3})/s;
1360
+ if ($buf2 =~ /^.{65}(([-+]\d{3})([-+]\d{3})([-+]\d{3})([-+]\d{3})*)/s) {
1361
+ $_ = $1;
1362
+ @acc = ($2/100, $3/100, $4/100);
1363
+ s/([-+])/ $1/g; s/^ //;
1364
+ push @xtra, AccelerometerData => $_;
1365
+ } elsif ($buf2 =~ /^.{173}([-+]\d{3})([-+]\d{3})([-+]\d{3})/s) { # (Azdome)
1366
+ @acc = ($1/100, $2/100, $3/100);
1367
+ }
1118
1368
 
1119
1369
  } elsif ($$dataPt =~ /^.{52}(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/s) {
1120
1370
 
@@ -1165,12 +1415,12 @@ sub ProcessFreeGPS($$$)
1165
1415
 
1166
1416
  # decode freeGPS from Akaso dashcam
1167
1417
  # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 60 00 00 00 [....freeGPS `...]
1168
- # 0000: 78 2e 78 78 00 00 00 00 00 00 00 00 00 00 00 00 [x.xx............]
1169
- # 0000: 30 30 30 30 30 00 00 00 00 00 00 00 00 00 00 00 [00000...........]
1170
- # 0000: 12 00 00 00 2f 00 00 00 19 00 00 00 41 00 00 00 [..../.......A...]
1171
- # 0000: 13 b3 ca 44 4e 00 00 00 29 92 fb 45 45 00 00 00 [...DN...)..EE...]
1172
- # 0000: d9 ee b4 41 ec d1 d3 42 e4 07 00 00 01 00 00 00 [...A...B........]
1173
- # 0000: 0c 00 00 00 01 00 00 00 05 00 00 00 00 00 00 00 [................]
1418
+ # 0010: 78 2e 78 78 00 00 00 00 00 00 00 00 00 00 00 00 [x.xx............]
1419
+ # 0020: 30 30 30 30 30 00 00 00 00 00 00 00 00 00 00 00 [00000...........]
1420
+ # 0030: 12 00 00 00 2f 00 00 00 19 00 00 00 41 00 00 00 [..../.......A...]
1421
+ # 0040: 13 b3 ca 44 4e 00 00 00 29 92 fb 45 45 00 00 00 [...DN...)..EE...]
1422
+ # 0050: d9 ee b4 41 ec d1 d3 42 e4 07 00 00 01 00 00 00 [...A...B........]
1423
+ # 0060: 0c 00 00 00 01 00 00 00 05 00 00 00 00 00 00 00 [................]
1174
1424
  ($latRef, $lonRef) = ($1, $2);
1175
1425
  ($hr, $min, $sec, $yr, $mon, $day) = unpack('x48V3x28V3', $$dataPt);
1176
1426
  SetByteOrder('II');
@@ -1181,6 +1431,56 @@ sub ProcessFreeGPS($$$)
1181
1431
  $trk -= 360 if $trk >= 360;
1182
1432
  SetByteOrder('MM');
1183
1433
 
1434
+ } elsif ($$dataPt =~ /^.{16}YndAkasoCar/s) {
1435
+
1436
+ # Akaso V1 dascham
1437
+ # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 78 00 00 00 [....freeGPS x...]
1438
+ # 0010: 59 6e 64 41 6b 61 73 6f 43 61 72 00 00 00 00 00 [YndAkasoCar.....]
1439
+ # 0020: 30 30 30 30 30 00 00 00 00 00 00 00 00 00 00 00 [00000...........]
1440
+ # 0030: 0e 00 00 00 27 00 00 00 2c 00 00 00 e3 07 00 00 [....'...,.......]
1441
+ # 0040: 05 00 00 00 1d 00 00 00 41 4e 45 00 00 00 00 00 [........ANE.....]
1442
+ # 0050: f1 4e 3e 3d 90 df ca 40 e3 50 bf 0b 0b 31 a0 40 [.N>=...@.P...1.@]
1443
+ # 0060: 4b dc c8 41 9a 79 a7 43 34 58 43 31 4f 37 31 35 [K..A.y.C4XC1O715]
1444
+ # 0070: 35 31 32 36 36 35 37 35 59 4e 44 53 0d e7 cc f9 [51266575YNDS....]
1445
+ # 0080: 00 00 00 00 05 00 00 00 00 00 00 00 00 00 00 00 [................]
1446
+ ($hr,$min,$sec,$yr,$mon,$day,$stat,$latRef,$lonRef) =
1447
+ unpack('x48V6a1a1a1x1', $$dataPt);
1448
+ # ignore invalid fixes
1449
+ return 0 unless $stat eq 'A' and ($latRef eq 'N' or $latRef eq 'S') and
1450
+ ($lonRef eq 'E' or $lonRef eq 'W');
1451
+
1452
+ $et->WarnOnce("Can't yet decrypt Akaso V1 timed GPS", 1);
1453
+ # (see https://exiftool.org/forum/index.php?topic=11320.0)
1454
+ return 1;
1455
+
1456
+ SetByteOrder('II');
1457
+ $lat = GetDouble($dataPt, 0x50); # latitude is here, but encrypted somehow
1458
+ $lon = GetDouble($dataPt, 0x58); # longitude is here, but encrypted somehow
1459
+ SetByteOrder('MM');
1460
+ #my $serialNum = substr($$dataPt, 0x68, 20);
1461
+
1462
+ } elsif ($$dataPt =~ /^.{12}\xac\0\0\0.{44}(.{72})/s) {
1463
+
1464
+ # EACHPAI dash cam
1465
+ # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 ac 00 00 00 [....freeGPS ....]
1466
+ # 0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
1467
+ # 0020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
1468
+ # 0030: 00 00 00 00 00 00 00 00 00 00 00 00 34 57 60 62 [............4W`b]
1469
+ # 0040: 5d 53 3c 41 47 45 45 42 42 3e 40 40 40 3c 51 3c []S<AGEEBB>@@@<Q<]
1470
+ # 0050: 44 42 44 40 3e 48 46 43 45 3c 5e 3c 40 48 43 41 [DBD@>HFCE<^<@HCA]
1471
+ # 0060: 42 3e 46 42 47 48 3c 67 3c 40 3e 40 42 3c 43 3e [B>FBGH<g<@>@B<C>]
1472
+ # 0070: 43 41 3c 40 42 40 46 42 40 3c 3c 3c 51 3a 47 46 [CA<@B@FB@<<<Q:GF]
1473
+ # 0080: 00 2a 36 35 00 00 00 00 00 00 00 00 00 00 00 00 [.*65............]
1474
+
1475
+ $et->WarnOnce("Can't yet decrypt EACHPAI timed GPS", 1);
1476
+ # (see https://exiftool.org/forum/index.php?topic=5095.msg61266#msg61266)
1477
+ return 1;
1478
+
1479
+ my $time = pack 'C*', map { $_ ^= 0 } unpack 'C*', $1;
1480
+ # bytes 7-12 are the timestamp in ASCII HHMMSS after xor-ing with 0x70
1481
+ substr($time,7,6) = pack 'C*', map { $_ ^= 0x70 } unpack 'C*', substr($time,7,6);
1482
+ # (other values are currently unknown)
1483
+
1184
1484
  } else {
1185
1485
 
1186
1486
  # decode binary GPS format (Viofo A119S, ref 2)
@@ -1195,6 +1495,21 @@ sub ProcessFreeGPS($$$)
1195
1495
  return 0 unless $stat eq 'A' and ($latRef eq 'N' or $latRef eq 'S') and
1196
1496
  ($lonRef eq 'E' or $lonRef eq 'W');
1197
1497
  ($lat,$lon,$spd,$trk) = unpack 'f*', pack 'L*', $lat, $lon, $spd, $trk;
1498
+ # lat/lon also stored as doubles by Transcend Driver Pro 230 (ref PH)
1499
+ SetByteOrder('II');
1500
+ my ($lat2, $lon2, $alt2) = (
1501
+ GetDouble($dataPt, 0x70),
1502
+ GetDouble($dataPt, 0x80),
1503
+ # GetDouble($dataPt, 0x98), # (don't know what this is)
1504
+ GetDouble($dataPt,0xa0),
1505
+ # GetDouble($dataPt,0xa8)) # (don't know what this is)
1506
+ );
1507
+ if (abs($lat2-$lat) < 0.001 and abs($lon2-$lon) < 0.001) {
1508
+ $lat = $lat2;
1509
+ $lon = $lon2;
1510
+ $alt = $alt2;
1511
+ }
1512
+ SetByteOrder('MM');
1198
1513
  $yr += 2000 if $yr < 2000;
1199
1514
  $spd *= $knotsToKph; # convert speed to km/h
1200
1515
  # ($trk is not confirmed; may be GPSImageDirection, ref PH)
@@ -1587,6 +1902,33 @@ sub ParseTag($$$)
1587
1902
  }
1588
1903
  $$et{HandlerType} = $tag; # fake handler type
1589
1904
  ProcessSamples($et); # we have all we need to process sample data now
1905
+ } elsif ($tag eq 'GPS ') {
1906
+ my $pos = 0;
1907
+ my $tagTbl = GetTagTable('Image::ExifTool::QuickTime::Stream');
1908
+ SetByteOrder('II');
1909
+ while ($pos + 36 < $dataLen) {
1910
+ my $dat = substr($$dataPt, $pos, 36);
1911
+ last if $dat eq "\x0" x 36;
1912
+ my @a = unpack 'VVVVCVCV', $dat;
1913
+ $$et{DOC_NUM} = ++$$et{DOC_COUNT};
1914
+ # 0=1, 1=1, 2=secs, 3=?
1915
+ SetGPSDateTime($et, $tagTbl, $a[2]);
1916
+ my $lat = $a[5] / 1e3;
1917
+ my $lon = $a[7] / 1e3;
1918
+ my $deg = int($lat / 100);
1919
+ $lat = $deg + ($lat - $deg * 100) / 60;
1920
+ $deg = int($lon / 100);
1921
+ $lon = $deg + ($lon - $deg * 100) / 60;
1922
+ $lat = -$lat if $a[4] eq 'S';
1923
+ $lon = -$lon if $a[6] eq 'W';
1924
+ $et->HandleTag($tagTbl, GPSLatitude => $lat);
1925
+ $et->HandleTag($tagTbl, GPSLongitude => $lon);
1926
+ $et->HandleTag($tagTbl, GPSSpeed => $a[3] / 1e3);
1927
+ $et->HandleTag($tagTbl, GPSSpeedRef => 'K');
1928
+ $pos += 36;
1929
+ }
1930
+ SetByteOrder('MM');
1931
+ delete $$et{DOC_NUM};
1590
1932
  }
1591
1933
  }
1592
1934
 
@@ -1604,6 +1946,26 @@ sub Process_tx3g($$$)
1604
1946
  return 1;
1605
1947
  }
1606
1948
 
1949
+ #------------------------------------------------------------------------------
1950
+ # Process GM 'marl' ctbx metadata (ref PH)
1951
+ # Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
1952
+ # Returns: 1 on success
1953
+ sub Process_marl($$$)
1954
+ {
1955
+ my ($et, $dirInfo, $tagTablePtr) = @_;
1956
+ my $dataPt = $$dirInfo{DataPt};
1957
+ return 0 if length $$dataPt < 2;
1958
+
1959
+ # 8-byte records:
1960
+ # byte 0 seems to be tag ID (0=timestamp in sec * 1e7)
1961
+ # bytes 1-3 seem to be 24-bit signed integer (unknown meaning)
1962
+ # bytes 4-7 are an int32u value, usually a multiple of 10000
1963
+
1964
+ $et->WarnOnce("Can't yet decode timed GM data", 1);
1965
+ # (see https://exiftool.org/forum/index.php?topic=11335.msg61393#msg61393)
1966
+ return 1;
1967
+ }
1968
+
1607
1969
  #------------------------------------------------------------------------------
1608
1970
  # Process QuickTime 'mebx' timed metadata
1609
1971
  # Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
@@ -2132,6 +2494,37 @@ sub ProcessInsta360($;$)
2132
2494
  return 1;
2133
2495
  }
2134
2496
 
2497
+ #------------------------------------------------------------------------------
2498
+ # Process 360Fly 'uuid' atom containing sensor data
2499
+ # (ref https://github.com/JamesHeinrich/getID3/blob/master/getid3/module.audio-video.quicktime.php)
2500
+ # Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
2501
+ # Returns: 1 on success
2502
+ sub Process360Fly($$$)
2503
+ {
2504
+ my ($et, $dirInfo, $tagTbl) = @_;
2505
+ my $dataPt = $$dirInfo{DataPt};
2506
+ my $dataLen = length $$dataPt;
2507
+ my $pos = 16;
2508
+ my $lastTime = -1;
2509
+ my $streamTbl = GetTagTable('Image::ExifTool::QuickTime::Stream');
2510
+ while ($pos + 32 <= $dataLen) {
2511
+ my $type = ord substr $$dataPt, $pos, 1;
2512
+ my $time = Get64u($dataPt, $pos + 2); # (only valid for some types)
2513
+ if ($$tagTbl{$type}) {
2514
+ if ($time != $lastTime) {
2515
+ $$et{DOC_NUM} = ++$$et{DOC_COUNT};
2516
+ $lastTime = $time;
2517
+ }
2518
+ }
2519
+ $et->HandleTag($tagTbl, $type, undef, DataPt => $dataPt, Start => $pos, Size => 32);
2520
+ # synthesize GPSDateTime from the timestamp for GPS records
2521
+ SetGPSDateTime($et, $streamTbl, $time / 1e6) if $type == 5;
2522
+ $pos += 32;
2523
+ }
2524
+ delete $$et{DOC_NUM};
2525
+ return 1;
2526
+ }
2527
+
2135
2528
  #------------------------------------------------------------------------------
2136
2529
  # Scan media data for "freeGPS" metadata if not found already (ref PH)
2137
2530
  # Inputs: 0) ExifTool ref