exiftool_vendored 11.99.0 → 12.11.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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/bin/Changes +201 -2
  3. data/bin/MANIFEST +8 -0
  4. data/bin/META.json +1 -1
  5. data/bin/META.yml +1 -1
  6. data/bin/README +43 -42
  7. data/bin/exiftool +172 -99
  8. data/bin/lib/Image/ExifTool.pm +170 -117
  9. data/bin/lib/Image/ExifTool.pod +132 -97
  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 +21 -10
  13. data/bin/lib/Image/ExifTool/Canon.pm +202 -13
  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 +22 -3
  17. data/bin/lib/Image/ExifTool/EXE.pm +8 -5
  18. data/bin/lib/Image/ExifTool/Exif.pm +15 -6
  19. data/bin/lib/Image/ExifTool/Font.pm +9 -2
  20. data/bin/lib/Image/ExifTool/GIF.pm +6 -1
  21. data/bin/lib/Image/ExifTool/GeoTiff.pm +2 -0
  22. data/bin/lib/Image/ExifTool/Geotag.pm +2 -2
  23. data/bin/lib/Image/ExifTool/GoPro.pm +48 -22
  24. data/bin/lib/Image/ExifTool/H264.pm +1 -1
  25. data/bin/lib/Image/ExifTool/ID3.pm +86 -12
  26. data/bin/lib/Image/ExifTool/IPTC.pm +1 -0
  27. data/bin/lib/Image/ExifTool/Import.pm +12 -9
  28. data/bin/lib/Image/ExifTool/JSON.pm +27 -4
  29. data/bin/lib/Image/ExifTool/Lang/de.pm +3 -1
  30. data/bin/lib/Image/ExifTool/Lang/es.pm +1 -1
  31. data/bin/lib/Image/ExifTool/M2TS.pm +1 -1
  32. data/bin/lib/Image/ExifTool/MPF.pm +2 -2
  33. data/bin/lib/Image/ExifTool/MacOS.pm +154 -38
  34. data/bin/lib/Image/ExifTool/Matroska.pm +3 -1
  35. data/bin/lib/Image/ExifTool/Minolta.pm +7 -2
  36. data/bin/lib/Image/ExifTool/Nikon.pm +143 -17
  37. data/bin/lib/Image/ExifTool/Olympus.pm +40 -17
  38. data/bin/lib/Image/ExifTool/PNG.pm +14 -3
  39. data/bin/lib/Image/ExifTool/PPM.pm +5 -5
  40. data/bin/lib/Image/ExifTool/Panasonic.pm +148 -14
  41. data/bin/lib/Image/ExifTool/PanasonicRaw.pm +34 -0
  42. data/bin/lib/Image/ExifTool/Parrot.pm +2 -1
  43. data/bin/lib/Image/ExifTool/Pentax.pm +11 -3
  44. data/bin/lib/Image/ExifTool/Photoshop.pm +2 -1
  45. data/bin/lib/Image/ExifTool/QuickTime.pm +240 -37
  46. data/bin/lib/Image/ExifTool/QuickTimeStream.pl +419 -60
  47. data/bin/lib/Image/ExifTool/README +25 -21
  48. data/bin/lib/Image/ExifTool/RSRC.pm +17 -11
  49. data/bin/lib/Image/ExifTool/Radiance.pm +7 -2
  50. data/bin/lib/Image/ExifTool/Ricoh.pm +19 -1
  51. data/bin/lib/Image/ExifTool/Shift.pl +1 -0
  52. data/bin/lib/Image/ExifTool/SigmaRaw.pm +40 -33
  53. data/bin/lib/Image/ExifTool/Sony.pm +423 -39
  54. data/bin/lib/Image/ExifTool/Stim.pm +2 -2
  55. data/bin/lib/Image/ExifTool/TagLookup.pm +5798 -5675
  56. data/bin/lib/Image/ExifTool/TagNames.pod +575 -100
  57. data/bin/lib/Image/ExifTool/Validate.pm +4 -4
  58. data/bin/lib/Image/ExifTool/WriteExif.pl +1 -0
  59. data/bin/lib/Image/ExifTool/WriteQuickTime.pl +30 -21
  60. data/bin/lib/Image/ExifTool/Writer.pl +49 -24
  61. data/bin/lib/Image/ExifTool/XMP.pm +99 -17
  62. data/bin/lib/Image/ExifTool/XMP2.pl +1 -0
  63. data/bin/lib/Image/ExifTool/XMPStruct.pl +3 -1
  64. data/bin/lib/Image/ExifTool/ZISRAW.pm +123 -0
  65. data/bin/perl-Image-ExifTool.spec +42 -41
  66. data/lib/exiftool_vendored/version.rb +1 -1
  67. metadata +9 -8
@@ -10,6 +10,7 @@
10
10
  # 3) https://forum.flitsservice.nl/dashcam-info/dod-ls460w-gps-data-uit-mov-bestand-lezen-t87926.html
11
11
  # 4) https://developers.google.com/streetview/publish/camm-spec
12
12
  # 5) https://sergei.nz/extracting-gps-data-from-viofo-a119-and-other-novatek-powered-cameras/
13
+ # 6) Thomas Allen https://github.com/exiftool/exiftool/pull/62
13
14
  #------------------------------------------------------------------------------
14
15
  package Image::ExifTool::QuickTime;
15
16
 
@@ -19,9 +20,11 @@ use Image::ExifTool qw(:DataAccess :Utils);
19
20
  use Image::ExifTool::QuickTime;
20
21
 
21
22
  sub Process_tx3g($$$);
23
+ sub Process_marl($$$);
22
24
  sub Process_mebx($$$);
23
25
  sub ProcessFreeGPS($$$);
24
26
  sub ProcessFreeGPS2($$$);
27
+ sub Process360Fly($$$);
25
28
 
26
29
  # QuickTime data types that have ExifTool equivalents
27
30
  # (ref https://developer.apple.com/library/content/documentation/QuickTime/QTFF/Metadata/Metadata.html#//apple_ref/doc/uid/TP40000939-CH1-SW35)
@@ -70,12 +73,14 @@ my %processByMetaFormat = (
70
73
  meta => 1, # ('CTMD' in CR3 images, 'priv' unknown in DJI video)
71
74
  data => 1, # ('RVMI')
72
75
  sbtl => 1, # (subtitle; 'tx3g' in Yuneec drone videos)
76
+ ctbx => 1, # ('marl' in GM videos)
73
77
  );
74
78
 
75
79
  # data lengths for each INSV record type
76
80
  my %insvDataLen = (
77
81
  0x300 => 56, # accelerometer
78
- 0x400 => 16, # unknown
82
+ 0x400 => 16, # exposure (ref 6)
83
+ 0x600 => 8, # timestamps (ref 6)
79
84
  0x700 => 53, # GPS
80
85
  );
81
86
 
@@ -91,7 +96,7 @@ my %insvLimit = (
91
96
  NOTES => q{
92
97
  Timed metadata extracted from QuickTime media data and some AVI videos when
93
98
  the ExtractEmbedded option is used. Although most of these tags are
94
- combined into the single table below, ExifTool currently reads 37 different
99
+ combined into the single table below, ExifTool currently reads 46 different
95
100
  formats of timed GPS metadata from video files.
96
101
  },
97
102
  VARS => { NO_ID => 1 },
@@ -118,6 +123,7 @@ my %insvLimit = (
118
123
  ExposureCompensation => { PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)', Groups => { 2 => 'Camera' } },
119
124
  ISO => { Groups => { 2 => 'Camera' } },
120
125
  CameraDateTime=>{ PrintConv => '$self->ConvertDateTime($val)', Groups => { 2 => 'Time' } },
126
+ VideoTimeStamp => { Groups => { 2 => 'Video' } },
121
127
  Accelerometer=> { Notes => '3-axis acceleration in units of g' },
122
128
  AccelerometerData => { },
123
129
  AngularVelocity => { },
@@ -159,10 +165,17 @@ my %insvLimit = (
159
165
  ProcessProc => \&Process_mebx,
160
166
  },
161
167
  },
162
- gpmd => {
163
- Name => 'gpmd',
168
+ gpmd => [{
169
+ Name => 'gpmd_GoPro',
170
+ Condition => '$$valPt !~ /^\0\0\xf2\xe1\xf0\xeeTT/',
164
171
  SubDirectory => { TagTable => 'Image::ExifTool::GoPro::GPMF' },
165
- },
172
+ },{
173
+ Name => 'gpmd_Rove', # Rove Stealth 4K encrypted text
174
+ SubDirectory => {
175
+ TagTable => 'Image::ExifTool::QuickTime::Stream',
176
+ ProcessProc => \&Process_text,
177
+ },
178
+ }],
166
179
  fdsc => {
167
180
  Name => 'fdsc',
168
181
  Condition => '$$valPt =~ /^GPRO/',
@@ -173,6 +186,10 @@ my %insvLimit = (
173
186
  Name => 'rtmd',
174
187
  SubDirectory => { TagTable => 'Image::ExifTool::Sony::rtmd' },
175
188
  },
189
+ marl => {
190
+ Name => 'marl',
191
+ SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::marl' },
192
+ },
176
193
  CTMD => { # (Canon Timed MetaData)
177
194
  Name => 'CTMD',
178
195
  SubDirectory => { TagTable => 'Image::ExifTool::Canon::CTMD' },
@@ -548,6 +565,137 @@ my %insvLimit = (
548
565
  },
549
566
  );
550
567
 
568
+ %Image::ExifTool::QuickTime::Tags360Fly = (
569
+ PROCESS_PROC => \&Process360Fly,
570
+ NOTES => 'Timed metadata found in MP4 videos from the 360Fly.',
571
+ 1 => {
572
+ Name => 'Accel360Fly',
573
+ SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Accel360Fly' },
574
+ },
575
+ 2 => {
576
+ Name => 'Gyro360Fly',
577
+ SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Gyro360Fly' },
578
+ },
579
+ 3 => {
580
+ Name => 'Mag360Fly',
581
+ SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Mag360Fly' },
582
+ },
583
+ 5 => {
584
+ Name => 'GPS360Fly',
585
+ SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::GPS360Fly' },
586
+ },
587
+ 6 => {
588
+ Name => 'Rot360Fly',
589
+ SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Rot360Fly' },
590
+ },
591
+ 250 => {
592
+ Name => 'Fusion360Fly',
593
+ SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Fusion360Fly' },
594
+ },
595
+ );
596
+
597
+ %Image::ExifTool::QuickTime::Accel360Fly = (
598
+ PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
599
+ GROUPS => { 2 => 'Location' },
600
+ 1 => { Name => 'AccelMode', Unknown => 1 }, # (always 2 in my sample)
601
+ 2 => {
602
+ Name => 'SampleTime',
603
+ Groups => { 2 => 'Video' },
604
+ Format => 'int64u',
605
+ ValueConv => '$val / 1e6',
606
+ PrintConv => 'ConvertDuration($val)',
607
+ },
608
+ 10 => { Name => 'AccelYPR', Format => 'float[3]' },
609
+ );
610
+
611
+ %Image::ExifTool::QuickTime::Gyro360Fly = (
612
+ PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
613
+ GROUPS => { 2 => 'Location' },
614
+ 1 => { Name => 'GyroMode', Unknown => 1 }, # (always 1 in my sample)
615
+ 2 => {
616
+ Name => 'SampleTime',
617
+ Groups => { 2 => 'Video' },
618
+ Format => 'int64u',
619
+ ValueConv => '$val / 1e6',
620
+ PrintConv => 'ConvertDuration($val)',
621
+ },
622
+ 10 => { Name => 'GyroYPR', Format => 'float[3]' },
623
+ );
624
+
625
+ %Image::ExifTool::QuickTime::Mag360Fly = (
626
+ PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
627
+ GROUPS => { 2 => 'Location' },
628
+ 1 => { Name => 'MagMode', Unknown => 1 }, # (always 1 in my sample)
629
+ 2 => {
630
+ Name => 'SampleTime',
631
+ Groups => { 2 => 'Video' },
632
+ Format => 'int64u',
633
+ ValueConv => '$val / 1e6',
634
+ PrintConv => 'ConvertDuration($val)',
635
+ },
636
+ 10 => { Name => 'MagnetometerXYZ', Format => 'float[3]' },
637
+ );
638
+
639
+ %Image::ExifTool::QuickTime::GPS360Fly = (
640
+ PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
641
+ GROUPS => { 2 => 'Location' },
642
+ 1 => { Name => 'GPSMode', Unknown => 1 }, # (always 16 in my sample)
643
+ 2 => {
644
+ Name => 'SampleTime',
645
+ Groups => { 2 => 'Video' },
646
+ Format => 'int64u',
647
+ ValueConv => '$val / 1e6',
648
+ PrintConv => 'ConvertDuration($val)',
649
+ },
650
+ 10 => { Name => 'GPSLatitude', Format => 'float', PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")' },
651
+ 14 => { Name => 'GPSLongitude', Format => 'float', PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")' },
652
+ 18 => { Name => 'GPSAltitude', Format => 'float', PrintConv => '"$val m"' }, # (questionable accuracy)
653
+ 22 => {
654
+ Name => 'GPSSpeed',
655
+ Notes => 'converted to km/hr',
656
+ Format => 'int16u',
657
+ ValueConv => '$val * 0.036',
658
+ PrintConv => 'sprintf("%.1f",$val)',
659
+ },
660
+ 24 => { Name => 'GPSTrack', Format => 'int16u', ValueConv => '$val / 100' },
661
+ 26 => { Name => 'Acceleration', Format => 'int16u', ValueConv => '$val / 1000' },
662
+ );
663
+
664
+ %Image::ExifTool::QuickTime::Rot360Fly = (
665
+ PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
666
+ GROUPS => { 2 => 'Location' },
667
+ 1 => { Name => 'RotMode', Unknown => 1 }, # (always 1 in my sample)
668
+ 2 => {
669
+ Name => 'SampleTime',
670
+ Groups => { 2 => 'Video' },
671
+ Format => 'int64u',
672
+ ValueConv => '$val / 1e6',
673
+ PrintConv => 'ConvertDuration($val)',
674
+ },
675
+ 10 => { Name => 'RotationXYZ', Format => 'float[3]' },
676
+ );
677
+
678
+ %Image::ExifTool::QuickTime::Fusion360Fly = (
679
+ PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
680
+ GROUPS => { 2 => 'Location' },
681
+ 1 => { Name => 'FusionMode', Unknown => 1 }, # (always 0 in my sample)
682
+ 2 => {
683
+ Name => 'SampleTime',
684
+ Groups => { 2 => 'Video' },
685
+ Format => 'int64u',
686
+ ValueConv => '$val / 1e6',
687
+ PrintConv => 'ConvertDuration($val)',
688
+ },
689
+ 10 => { Name => 'FusionYPR', Format => 'float[3]' },
690
+ );
691
+
692
+ # tags found in 'marl' ctbx timed metadata (ref PH)
693
+ %Image::ExifTool::QuickTime::marl = (
694
+ PROCESS_PROC => \&Process_marl,
695
+ GROUPS => { 2 => 'Other' },
696
+ NOTES => 'Tags extracted from the marl ctbx timed metadata of GM cars.',
697
+ );
698
+
551
699
  #------------------------------------------------------------------------------
552
700
  # Save information from keys in OtherSampleDesc directory for processing timed metadata
553
701
  # Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
@@ -648,7 +796,7 @@ sub FoundSomething($$;$$)
648
796
 
649
797
  #------------------------------------------------------------------------------
650
798
  # Approximate GPSDateTime value from sample time and CreateDate
651
- # Inputs: 0) ExifTool ref, 1) tag table ptr, 2) sample time (ms)
799
+ # Inputs: 0) ExifTool ref, 1) tag table ptr, 2) sample time (s)
652
800
  # 3) true if CreateDate is at end of video
653
801
  # Notes: Uses ExifTool CreateDateAtEnd as flag to subtract video duration
654
802
  sub SetGPSDateTime($$$)
@@ -696,15 +844,21 @@ sub HandleTextTags($$$)
696
844
 
697
845
  #------------------------------------------------------------------------------
698
846
  # Process subtitle 'text'
699
- # Inputs: 0) ExifTool ref, 1) tag table ref, 2) data ref, 3) optional sample time
847
+ # Inputs: 0) ExifTool ref, 1) data ref or dirInfo ref, 2) tag table ref
700
848
  sub Process_text($$$)
701
849
  {
702
- my ($et, $tagTbl, $buffPt) = @_;
850
+ my ($et, $dataPt, $tagTbl) = @_;
703
851
  my %tags;
704
852
 
705
853
  return if $$et{NoMoreTextDecoding};
706
854
 
707
- while ($$buffPt =~ /\$(\w+)([^\$]*)/g) {
855
+ if (ref $dataPt eq 'HASH') {
856
+ my $dirName = $$dataPt{DirName};
857
+ $dataPt = $$dataPt{DataPt};
858
+ $et->VerboseDir($dirName, undef, length($$dataPt));
859
+ }
860
+
861
+ while ($$dataPt =~ /\$(\w+)([^\$]*)/g) {
708
862
  my ($tag, $dat) = ($1, $2);
709
863
  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+)/) {
710
864
  my $time = "$1:$2:$3";
@@ -795,31 +949,31 @@ sub Process_text($$$)
795
949
  # 0110: 31 30 38 30 30 30 58 00 58 00 58 00 58 00 58 00 [108000X.X.X.X.X.]
796
950
  # 0120: 58 00 58 00 58 00 58 00 00 00 00 00 00 00 00 00 [X.X.X.X.........]
797
951
  # 0130: 00 00 00 00 00 00 00 [.......]
798
- if ($$buffPt =~ /^\0\0(..\xaa\xaa|\xf2\xe1\xf0\xee)/s and length $$buffPt >= 282) {
799
- my $val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$buffPt, 8, 14)));
952
+ if ($$dataPt =~ /^\0\0(..\xaa\xaa|\xf2\xe1\xf0\xee)/s and length $$dataPt >= 282) {
953
+ my $val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$dataPt, 8, 14)));
800
954
  if ($val =~ /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$/) {
801
955
  $tags{GPSDateTime} = "$1:$2:$3 $4:$5:$6";
802
- $val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$buffPt, 38, 9)));
956
+ $val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$dataPt, 38, 9)));
803
957
  if ($val =~ /^([NS])(\d{2})(\d+$)$/) {
804
958
  $tags{GPSLatitude} = ($2 + $3 / 600000) * ($1 eq 'S' ? -1 : 1);
805
959
  }
806
- $val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$buffPt, 47, 10)));
960
+ $val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$dataPt, 47, 10)));
807
961
  if ($val =~ /^([EW])(\d{3})(\d+$)$/) {
808
962
  $tags{GPSLongitude} = ($2 + $3 / 600000) * ($1 eq 'W' ? -1 : 1);
809
963
  }
810
- $val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$buffPt, 0x39, 5)));
964
+ $val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$dataPt, 0x39, 5)));
811
965
  $tags{GPSAltitude} = $val + 0 if $val =~ /^[-+]\d+$/;
812
- $val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$buffPt, 0x3e, 3)));
966
+ $val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$dataPt, 0x3e, 3)));
813
967
  if ($val =~ /^\d+$/) {
814
968
  $tags{GPSSpeed} = $val + 0;
815
969
  $tags{GPSSpeedRef} = 'K';
816
970
  }
817
- if ($$buffPt =~ /^\0\0..\xaa\xaa/s) { # (BlueSkySea)
818
- $val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$buffPt, 0xad, 12)));
971
+ if ($$dataPt =~ /^\0\0..\xaa\xaa/s) { # (BlueSkySea)
972
+ $val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$dataPt, 0xad, 12)));
819
973
  # the first X,Y,Z accelerometer readings from the AccelerometerData
820
974
  if ($val =~ /^([-+]\d{3})([-+]\d{3})([-+]\d{3})$/) {
821
975
  $tags{Accelerometer} = "$1 $2 $3";
822
- $val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$buffPt, 0xba, 96)));
976
+ $val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$dataPt, 0xba, 96)));
823
977
  my $order = GetByteOrder();
824
978
  SetByteOrder('II');
825
979
  $val = ReadValue(\$val, 0, 'float');
@@ -828,7 +982,7 @@ sub Process_text($$$)
828
982
  }
829
983
  } else { # (Ambarella)
830
984
  my @acc;
831
- $val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$buffPt, 0x41, 195)));
985
+ $val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$dataPt, 0x41, 195)));
832
986
  push @acc, $1, $2, $3 while $val =~ /\G([-+]\d{3})([-+]\d{3})([-+]\d{3})/g;
833
987
  $tags{Accelerometer} = "@acc" if @acc;
834
988
  }
@@ -839,36 +993,36 @@ sub Process_text($$$)
839
993
  # check for DJI telemetry data, eg:
840
994
  # "F/3.5, SS 1000, ISO 100, EV 0, GPS (8.6499, 53.1665, 18), D 24.26m,
841
995
  # H 6.00m, H.S 2.10m/s, V.S 0.00m/s \n"
842
- if ($$buffPt =~ /GPS \(([-+]?\d*\.\d+),\s*([-+]?\d*\.\d+)/) {
996
+ if ($$dataPt =~ /GPS \(([-+]?\d*\.\d+),\s*([-+]?\d*\.\d+)/) {
843
997
  $$et{CreateDateAtEnd} = 1; # set flag indicating the file creation date is at the end
844
998
  $tags{GPSLatitude} = $2;
845
999
  $tags{GPSLongitude} = $1;
846
- $tags{GPSAltitude} = $1 if $$buffPt =~ /,\s*H\s+([-+]?\d+\.?\d*)m/;
847
- if ($$buffPt =~ /,\s*H.S\s+([-+]?\d+\.?\d*)/) {
1000
+ $tags{GPSAltitude} = $1 if $$dataPt =~ /,\s*H\s+([-+]?\d+\.?\d*)m/;
1001
+ if ($$dataPt =~ /,\s*H.S\s+([-+]?\d+\.?\d*)/) {
848
1002
  $tags{GPSSpeed} = $1 * $mpsToKph;
849
1003
  $tags{GPSSpeedRef} = 'K';
850
1004
  }
851
- $tags{Distance} = $1 * $mpsToKph if $$buffPt =~ /,\s*D\s+(\d+\.?\d*)m/;
852
- $tags{VerticalSpeed} = $1 if $$buffPt =~ /,\s*V.S\s+([-+]?\d+\.?\d*)/;
853
- $tags{FNumber} = $1 if $$buffPt =~ /\bF\/(\d+\.?\d*)/;
854
- $tags{ExposureTime} = 1 / $1 if $$buffPt =~ /\bSS\s+(\d+\.?\d*)/;
855
- $tags{ExposureCompensation} = ($1 / ($2 || 1)) if $$buffPt =~ /\bEV\s+([-+]?\d+\.?\d*)(\/\d+)?/;
856
- $tags{ISO} = $1 if $$buffPt =~ /\bISO\s+(\d+\.?\d*)/;
1005
+ $tags{Distance} = $1 * $mpsToKph if $$dataPt =~ /,\s*D\s+(\d+\.?\d*)m/;
1006
+ $tags{VerticalSpeed} = $1 if $$dataPt =~ /,\s*V.S\s+([-+]?\d+\.?\d*)/;
1007
+ $tags{FNumber} = $1 if $$dataPt =~ /\bF\/(\d+\.?\d*)/;
1008
+ $tags{ExposureTime} = 1 / $1 if $$dataPt =~ /\bSS\s+(\d+\.?\d*)/;
1009
+ $tags{ExposureCompensation} = ($1 / ($2 || 1)) if $$dataPt =~ /\bEV\s+([-+]?\d+\.?\d*)(\/\d+)?/;
1010
+ $tags{ISO} = $1 if $$dataPt =~ /\bISO\s+(\d+\.?\d*)/;
857
1011
  HandleTextTags($et, $tagTbl, \%tags);
858
1012
  return;
859
1013
  }
860
1014
 
861
1015
  # check for Mini 0806 dashcam GPS, eg:
862
1016
  # "A,270519,201555.000,3356.8925,N,08420.2071,W,000.0,331.0M,+01.84,-09.80,-00.61;\n"
863
- if ($$buffPt =~ /^A,(\d{2})(\d{2})(\d{2}),(\d{2})(\d{2})(\d{2}(\.\d+)?)/) {
1017
+ if ($$dataPt =~ /^A,(\d{2})(\d{2})(\d{2}),(\d{2})(\d{2})(\d{2}(\.\d+)?)/) {
864
1018
  $tags{GPSDateTime} = "20$3:$2:$1 $4:$5:$6Z";
865
- if ($$buffPt =~ /^A,.*?,.*?,(\d{2})(\d+\.\d+),([NS])/) {
1019
+ if ($$dataPt =~ /^A,.*?,.*?,(\d{2})(\d+\.\d+),([NS])/) {
866
1020
  $tags{GPSLatitude} = ($1 + $2/60) * ($3 eq 'S' ? -1 : 1);
867
1021
  }
868
- if ($$buffPt =~ /^A,.*?,.*?,.*?,.*?,(\d{3})(\d+\.\d+),([EW])/) {
1022
+ if ($$dataPt =~ /^A,.*?,.*?,.*?,.*?,(\d{3})(\d+\.\d+),([EW])/) {
869
1023
  $tags{GPSLongitude} = ($1 + $2/60) * ($3 eq 'W' ? -1 : 1);
870
1024
  }
871
- my @a = split ',', $$buffPt;
1025
+ my @a = split ',', $$dataPt;
872
1026
  $tags{GPSAltitude} = $a[8] if $a[8] and $a[8] =~ s/M$//;
873
1027
  $tags{GPSSpeed} = $a[7] if $a[7] and $a[7] =~ /^\d+\.\d+$/; # (NC)
874
1028
  $tags{Accelerometer} = "$a[9] $a[10] $a[11]" if $a[11] and $a[11] =~ s/;\s*$//;
@@ -876,10 +1030,31 @@ sub Process_text($$$)
876
1030
  return;
877
1031
  }
878
1032
 
879
- # check for Thinkware format, eg:
1033
+ # check for Roadhawk dashcam text
1034
+ # ".;;;;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~"
1035
+ # decoded:
1036
+ # "X0000.2340Y-000.0720Z0000.9900G0001.0400$GPRMC,082138,A,5330.6683,N,00641.9749,W,012.5,87.86,050213,002.1,A"
1037
+ # (note: "002.1" is magnetic variation and is not decoded; it should have ",E" or ",W" afterward for direction)
1038
+ if ($$dataPt =~ /\*[0-9A-F]{2}~$/) {
1039
+ # (ref https://reverseengineering.stackexchange.com/questions/11582/how-to-reverse-engineer-dash-cam-metadata)
1040
+ my @decode = unpack 'C*', '-I8XQWRVNZOYPUTA0B1C2SJ9K.L,M$D3E4F5G6H7';
1041
+ my @chars = unpack 'C*', substr($$dataPt, 0, -4);
1042
+ foreach (@chars) {
1043
+ my $n = $_ - 43;
1044
+ $_ = $decode[$n] if $n >= 0 and defined $decode[$n];
1045
+ }
1046
+ my $buff = pack 'C*', @chars;
1047
+ if ($buff =~ /X(.*?)Y(.*?)Z(.*?)G(.*?)\$/) {
1048
+ # yup. the decoding worked out
1049
+ $tags{Accelerometer} = "$1 $2 $3 $4";
1050
+ $$dataPt = $buff; # (process GPRMC below)
1051
+ }
1052
+ }
1053
+
1054
+ # check for Thinkware format (and other NMEA RMC), eg:
880
1055
  # "gsensori,4,512,-67,-12,100;GNRMC,161313.00,A,4529.87489,N,07337.01215,W,6.225,35.34,310819,,,A*52..;
881
1056
  # CAR,0,0,0,0.0,0,0,0,0,0,0,0,0"
882
- 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
1057
+ if ($$dataPt =~ /[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
883
1058
  # do some basic sanity checks on the date
884
1059
  $13 <= 31 and $14 <= 12 and $15 <= 99)
885
1060
  {
@@ -896,8 +1071,8 @@ sub Process_text($$$)
896
1071
  $tags{GPSTrackRef} = 'T';
897
1072
  }
898
1073
  }
899
- $tags{GSensor} = $1 if $$buffPt =~ /\bgsensori,(.*?)(;|$)/;
900
- $tags{Car} = $1 if $$buffPt =~ /\bCAR,(.*?)(;|$)/;
1074
+ $tags{GSensor} = $1 if $$dataPt =~ /\bgsensori,(.*?)(;|$)/;
1075
+ $tags{Car} = $1 if $$dataPt =~ /\bCAR,(.*?)(;|$)/;
901
1076
 
902
1077
  if (%tags) {
903
1078
  HandleTextTags($et, $tagTbl, \%tags);
@@ -953,7 +1128,7 @@ sub ProcessSamples($)
953
1128
  ($startChunk, $samplesPerChunk, $descIdx) = @{shift @$stsc};
954
1129
  $nextChunk = $$stsc[0][0] if @$stsc;
955
1130
  }
956
- @$size < @$start + $samplesPerChunk and $et->WarnOnce('Sample size error'), return;
1131
+ @$size < @$start + $samplesPerChunk and $et->WarnOnce('Sample size error'), last;
957
1132
  my $sampleStart = $chunkStart;
958
1133
  for ($i=0; ; ) {
959
1134
  push @$start, $sampleStart;
@@ -1056,11 +1231,13 @@ sub ProcessSamples($)
1056
1231
  $val =~ tr/\t/ /;
1057
1232
  $et->HandleTag($tagTbl, RawGSensor => $val) if length $val;
1058
1233
  }
1059
- } elsif ($buff =~ /^PNDM/ and length $buff >= 20) {
1234
+ } elsif ($buff =~ /^(\0.{3})?PNDM/s) {
1060
1235
  # Garmin Dashcam format (actually binary, not text)
1061
- $et->HandleTag($tagTbl, GPSLatitude => Get32s(\$buff, 12) * 180/0x80000000);
1062
- $et->HandleTag($tagTbl, GPSLongitude => Get32s(\$buff, 16) * 180/0x80000000);
1063
- $et->HandleTag($tagTbl, GPSSpeed => Get16u(\$buff, 8));
1236
+ my $n = $1 ? 4 : 0; # skip leading 4-byte size word if it exists
1237
+ next if length($buff) < 20 + $n;
1238
+ $et->HandleTag($tagTbl, GPSLatitude => Get32s(\$buff, 12+$n) * 180/0x80000000);
1239
+ $et->HandleTag($tagTbl, GPSLongitude => Get32s(\$buff, 16+$n) * 180/0x80000000);
1240
+ $et->HandleTag($tagTbl, GPSSpeed => Get16u(\$buff, 8+$n));
1064
1241
  $et->HandleTag($tagTbl, GPSSpeedRef => 'M');
1065
1242
  SetGPSDateTime($et, $tagTbl, $time[$i]);
1066
1243
  next; # all done (don't store/process as text)
@@ -1069,7 +1246,7 @@ sub ProcessSamples($)
1069
1246
  $et->HandleTag($tagTbl, Text => $buff); # just store any other text
1070
1247
  }
1071
1248
  }
1072
- Process_text($et, $tagTbl, \$buff);
1249
+ Process_text($et, \$buff, $tagTbl);
1073
1250
 
1074
1251
  } elsif ($processByMetaFormat{$type}) {
1075
1252
 
@@ -1080,8 +1257,8 @@ sub ProcessSamples($)
1080
1257
  $$et{ee} = $ee; # need ee information for 'keys'
1081
1258
  $et->HandleTag($tagTbl, $metaFormat, undef,
1082
1259
  DataPt => \$buff,
1083
- DataPos => $$start[$i],
1084
- Base => $$start[$i],
1260
+ DataPos => 0,
1261
+ Base => $$start[$i], # (Base must be set for CR3 files)
1085
1262
  TagInfo => $tagInfo,
1086
1263
  );
1087
1264
  delete $$et{ee};
@@ -1090,10 +1267,10 @@ sub ProcessSamples($)
1090
1267
  # "X0000.0000Y0000.0000Z0000.0000G0000.0000$GPRMC,000125,V,,,,,000.0,,280908,002.1,N*71~, 794021 \x0a"
1091
1268
  FoundSomething($et, $tagTbl, $time[$i], $dur[$i]);
1092
1269
  $et->HandleTag($tagTbl, Accelerometer => "$1 $2 $3 $4") if $buff =~ /X(.*?)Y(.*?)Z(.*?)G(.*?)\$/;
1093
- Process_text($et, $tagTbl, \$buff);
1270
+ Process_text($et, \$buff, $tagTbl);
1094
1271
  }
1095
1272
  } elsif ($verbose) {
1096
- $et->VPrint(0, "Unknown meta format ($metaFormat)");
1273
+ $et->VPrint(0, "Unknown $type format ($metaFormat)");
1097
1274
  }
1098
1275
 
1099
1276
  } elsif ($type eq 'gps ') { # (ie. GPSDataList tag)
@@ -1115,8 +1292,8 @@ sub ProcessSamples($)
1115
1292
  FoundSomething($et, $tagTbl, $time[$i], $dur[$i]);
1116
1293
  $et->HandleTag($tagTbl, $type, undef,
1117
1294
  DataPt => \$buff,
1118
- DataPos => $$start[$i],
1119
- Base => $$start[$i],
1295
+ DataPos => 0,
1296
+ Base => $$start[$i], # (Base must be set for CR3 files)
1120
1297
  TagInfo => $tagInfo,
1121
1298
  );
1122
1299
  }
@@ -1150,13 +1327,35 @@ sub ProcessFreeGPS($$$)
1150
1327
 
1151
1328
  return 0 if $dirLen < 92;
1152
1329
 
1153
- if (substr($$dataPt,12,1) eq "\x05") {
1330
+ if (substr($$dataPt,18,8) eq "\xaa\xaa\xf2\xe1\xf0\xee\x54\x54") {
1154
1331
 
1332
+ # (this is very similar to the encrypted text format)
1155
1333
  # decode encrypted ASCII-based GPS (DashCam Azdome GS63H, ref 5)
1156
1334
  # header looks like this in my sample:
1157
1335
  # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 05 01 00 00 [....freeGPS ....]
1158
1336
  # 0010: 01 03 aa aa f2 e1 f0 ee 54 54 98 9a 9b 92 9a 93 [........TT......]
1159
1337
  # 0020: 98 9e 98 98 9e 93 98 92 a6 9f 9f 9c 9d ed fa 8a [................]
1338
+ # decrypted (from byte 18):
1339
+ # 0000: 00 00 58 4b 5a 44 fe fe 32 30 31 38 30 39 32 34 [..XKZD..20180924]
1340
+ # 0010: 32 32 34 39 32 38 0c 35 35 36 37 47 50 20 20 20 [224928.5567GP ]
1341
+ # 0020: 00 00 00 00 00 03 4e 34 30 34 36 34 33 35 30 57 [......N40464350W]
1342
+ # 0030: 30 30 37 30 34 30 33 30 38 30 30 30 30 30 30 30 [0070403080000000]
1343
+ # 0040: 37 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [7...............]
1344
+ # [...]
1345
+ # 00a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 2b 30 39 [.............+09]
1346
+ # 00b0: 33 2d 30 30 33 2d 30 30 35 00 00 00 00 00 00 00 [3-003-005.......]
1347
+ # header looks like this for EEEkit gps:
1348
+ # 0000: 00 00 04 00 66 72 65 65 47 50 53 20 f0 03 00 00 [....freeGPS ....]
1349
+ # 0010: 01 03 aa aa f2 e1 f0 ee 54 54 98 9a 98 9a 9a 9f [........TT......]
1350
+ # 0020: 9b 93 9b 9c 98 99 99 9f a6 9a 9a 98 9a 9a 9f 9b [................]
1351
+ # 0030: 93 9b 9c 98 99 99 9c a9 e4 99 9d 9e 9f 98 9e 9b [................]
1352
+ # 0040: 9c fd 9b 98 98 98 9f 9f 9a 9a 93 81 9a 9b 9d 9f [................]
1353
+ # decrypted (from byte 18):
1354
+ # 0000: 00 00 58 4b 5a 44 fe fe 32 30 32 30 30 35 31 39 [..XKZD..20200519]
1355
+ # 0010: 31 36 32 33 33 35 0c 30 30 32 30 30 35 31 39 31 [162335.002005191]
1356
+ # 0020: 36 32 33 33 36 03 4e 33 37 34 35 32 34 31 36 57 [62336.N37452416W]
1357
+ # 0030: 31 32 32 32 35 35 30 30 39 2b 30 31 37 35 30 31 [122255009+017501]
1358
+ # 0040: 31 2b 30 31 34 2b 30 30 32 2b 30 32 36 2b 30 31 [1+014+002+026+01]
1160
1359
  my $n = $dirLen - 18;
1161
1360
  $n = 0x101 if $n > 0x101;
1162
1361
  my $buf2 = pack 'C*', map { $_ ^ 0xaa } unpack 'C*', substr($$dataPt,18,$n);
@@ -1165,13 +1364,25 @@ sub ProcessFreeGPS($$$)
1165
1364
  $et->VerboseDump(\$buf2);
1166
1365
  }
1167
1366
  # (extract longitude as 9 digits, not 8, ref PH)
1168
- 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;
1367
+ 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;
1169
1368
  ($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);
1170
- $spd += 0; # remove leading 0's
1369
+ if (defined $spd) { # (Azdome)
1370
+ $spd += 0; # remove leading 0's
1371
+ } elsif ($buf2 =~ /^.{57}([-+]\d{4})(\d{3})/s) { # (EEEkit)
1372
+ # $alt = $1 + 0; (doesn't look right for my sample, but the Ambarella A12 text has this)
1373
+ $spd = $2 + 0;
1374
+ }
1171
1375
  $lbl =~ s/\0.*//s; $lbl =~ s/\s+$//; # truncate at null and remove trailing spaces
1172
1376
  push @xtra, UserLabel => $lbl if length $lbl;
1173
1377
  # extract accelerometer data (ref PH)
1174
- @acc = ($1/100,$2/100,$3/100) if $buf2 =~ /^.{173}([-+]\d{3})([-+]\d{3})([-+]\d{3})/s;
1378
+ if ($buf2 =~ /^.{65}(([-+]\d{3})([-+]\d{3})([-+]\d{3})([-+]\d{3})*)/s) {
1379
+ $_ = $1;
1380
+ @acc = ($2/100, $3/100, $4/100);
1381
+ s/([-+])/ $1/g; s/^ //;
1382
+ push @xtra, AccelerometerData => $_;
1383
+ } elsif ($buf2 =~ /^.{173}([-+]\d{3})([-+]\d{3})([-+]\d{3})/s) { # (Azdome)
1384
+ @acc = ($1/100, $2/100, $3/100);
1385
+ }
1175
1386
 
1176
1387
  } elsif ($$dataPt =~ /^.{52}(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/s) {
1177
1388
 
@@ -1222,12 +1433,12 @@ sub ProcessFreeGPS($$$)
1222
1433
 
1223
1434
  # decode freeGPS from Akaso dashcam
1224
1435
  # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 60 00 00 00 [....freeGPS `...]
1225
- # 0000: 78 2e 78 78 00 00 00 00 00 00 00 00 00 00 00 00 [x.xx............]
1226
- # 0000: 30 30 30 30 30 00 00 00 00 00 00 00 00 00 00 00 [00000...........]
1227
- # 0000: 12 00 00 00 2f 00 00 00 19 00 00 00 41 00 00 00 [..../.......A...]
1228
- # 0000: 13 b3 ca 44 4e 00 00 00 29 92 fb 45 45 00 00 00 [...DN...)..EE...]
1229
- # 0000: d9 ee b4 41 ec d1 d3 42 e4 07 00 00 01 00 00 00 [...A...B........]
1230
- # 0000: 0c 00 00 00 01 00 00 00 05 00 00 00 00 00 00 00 [................]
1436
+ # 0010: 78 2e 78 78 00 00 00 00 00 00 00 00 00 00 00 00 [x.xx............]
1437
+ # 0020: 30 30 30 30 30 00 00 00 00 00 00 00 00 00 00 00 [00000...........]
1438
+ # 0030: 12 00 00 00 2f 00 00 00 19 00 00 00 41 00 00 00 [..../.......A...]
1439
+ # 0040: 13 b3 ca 44 4e 00 00 00 29 92 fb 45 45 00 00 00 [...DN...)..EE...]
1440
+ # 0050: d9 ee b4 41 ec d1 d3 42 e4 07 00 00 01 00 00 00 [...A...B........]
1441
+ # 0060: 0c 00 00 00 01 00 00 00 05 00 00 00 00 00 00 00 [................]
1231
1442
  ($latRef, $lonRef) = ($1, $2);
1232
1443
  ($hr, $min, $sec, $yr, $mon, $day) = unpack('x48V3x28V3', $$dataPt);
1233
1444
  SetByteOrder('II');
@@ -1238,6 +1449,56 @@ sub ProcessFreeGPS($$$)
1238
1449
  $trk -= 360 if $trk >= 360;
1239
1450
  SetByteOrder('MM');
1240
1451
 
1452
+ } elsif ($$dataPt =~ /^.{16}YndAkasoCar/s) {
1453
+
1454
+ # Akaso V1 dascham
1455
+ # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 78 00 00 00 [....freeGPS x...]
1456
+ # 0010: 59 6e 64 41 6b 61 73 6f 43 61 72 00 00 00 00 00 [YndAkasoCar.....]
1457
+ # 0020: 30 30 30 30 30 00 00 00 00 00 00 00 00 00 00 00 [00000...........]
1458
+ # 0030: 0e 00 00 00 27 00 00 00 2c 00 00 00 e3 07 00 00 [....'...,.......]
1459
+ # 0040: 05 00 00 00 1d 00 00 00 41 4e 45 00 00 00 00 00 [........ANE.....]
1460
+ # 0050: f1 4e 3e 3d 90 df ca 40 e3 50 bf 0b 0b 31 a0 40 [.N>=...@.P...1.@]
1461
+ # 0060: 4b dc c8 41 9a 79 a7 43 34 58 43 31 4f 37 31 35 [K..A.y.C4XC1O715]
1462
+ # 0070: 35 31 32 36 36 35 37 35 59 4e 44 53 0d e7 cc f9 [51266575YNDS....]
1463
+ # 0080: 00 00 00 00 05 00 00 00 00 00 00 00 00 00 00 00 [................]
1464
+ ($hr,$min,$sec,$yr,$mon,$day,$stat,$latRef,$lonRef) =
1465
+ unpack('x48V6a1a1a1x1', $$dataPt);
1466
+ # ignore invalid fixes
1467
+ return 0 unless $stat eq 'A' and ($latRef eq 'N' or $latRef eq 'S') and
1468
+ ($lonRef eq 'E' or $lonRef eq 'W');
1469
+
1470
+ $et->WarnOnce("Can't yet decrypt Akaso V1 timed GPS", 1);
1471
+ # (see https://exiftool.org/forum/index.php?topic=11320.0)
1472
+ return 1;
1473
+
1474
+ SetByteOrder('II');
1475
+ $lat = GetDouble($dataPt, 0x50); # latitude is here, but encrypted somehow
1476
+ $lon = GetDouble($dataPt, 0x58); # longitude is here, but encrypted somehow
1477
+ SetByteOrder('MM');
1478
+ #my $serialNum = substr($$dataPt, 0x68, 20);
1479
+
1480
+ } elsif ($$dataPt =~ /^.{12}\xac\0\0\0.{44}(.{72})/s) {
1481
+
1482
+ # EACHPAI dash cam
1483
+ # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 ac 00 00 00 [....freeGPS ....]
1484
+ # 0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
1485
+ # 0020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
1486
+ # 0030: 00 00 00 00 00 00 00 00 00 00 00 00 34 57 60 62 [............4W`b]
1487
+ # 0040: 5d 53 3c 41 47 45 45 42 42 3e 40 40 40 3c 51 3c []S<AGEEBB>@@@<Q<]
1488
+ # 0050: 44 42 44 40 3e 48 46 43 45 3c 5e 3c 40 48 43 41 [DBD@>HFCE<^<@HCA]
1489
+ # 0060: 42 3e 46 42 47 48 3c 67 3c 40 3e 40 42 3c 43 3e [B>FBGH<g<@>@B<C>]
1490
+ # 0070: 43 41 3c 40 42 40 46 42 40 3c 3c 3c 51 3a 47 46 [CA<@B@FB@<<<Q:GF]
1491
+ # 0080: 00 2a 36 35 00 00 00 00 00 00 00 00 00 00 00 00 [.*65............]
1492
+
1493
+ $et->WarnOnce("Can't yet decrypt EACHPAI timed GPS", 1);
1494
+ # (see https://exiftool.org/forum/index.php?topic=5095.msg61266#msg61266)
1495
+ return 1;
1496
+
1497
+ my $time = pack 'C*', map { $_ ^= 0 } unpack 'C*', $1;
1498
+ # bytes 7-12 are the timestamp in ASCII HHMMSS after xor-ing with 0x70
1499
+ substr($time,7,6) = pack 'C*', map { $_ ^= 0x70 } unpack 'C*', substr($time,7,6);
1500
+ # (other values are currently unknown)
1501
+
1241
1502
  } else {
1242
1503
 
1243
1504
  # decode binary GPS format (Viofo A119S, ref 2)
@@ -1252,6 +1513,21 @@ sub ProcessFreeGPS($$$)
1252
1513
  return 0 unless $stat eq 'A' and ($latRef eq 'N' or $latRef eq 'S') and
1253
1514
  ($lonRef eq 'E' or $lonRef eq 'W');
1254
1515
  ($lat,$lon,$spd,$trk) = unpack 'f*', pack 'L*', $lat, $lon, $spd, $trk;
1516
+ # lat/lon also stored as doubles by Transcend Driver Pro 230 (ref PH)
1517
+ SetByteOrder('II');
1518
+ my ($lat2, $lon2, $alt2) = (
1519
+ GetDouble($dataPt, 0x70),
1520
+ GetDouble($dataPt, 0x80),
1521
+ # GetDouble($dataPt, 0x98), # (don't know what this is)
1522
+ GetDouble($dataPt,0xa0),
1523
+ # GetDouble($dataPt,0xa8)) # (don't know what this is)
1524
+ );
1525
+ if (abs($lat2-$lat) < 0.001 and abs($lon2-$lon) < 0.001) {
1526
+ $lat = $lat2;
1527
+ $lon = $lon2;
1528
+ $alt = $alt2;
1529
+ }
1530
+ SetByteOrder('MM');
1255
1531
  $yr += 2000 if $yr < 2000;
1256
1532
  $spd *= $knotsToKph; # convert speed to km/h
1257
1533
  # ($trk is not confirmed; may be GPSImageDirection, ref PH)
@@ -1644,6 +1920,33 @@ sub ParseTag($$$)
1644
1920
  }
1645
1921
  $$et{HandlerType} = $tag; # fake handler type
1646
1922
  ProcessSamples($et); # we have all we need to process sample data now
1923
+ } elsif ($tag eq 'GPS ') {
1924
+ my $pos = 0;
1925
+ my $tagTbl = GetTagTable('Image::ExifTool::QuickTime::Stream');
1926
+ SetByteOrder('II');
1927
+ while ($pos + 36 < $dataLen) {
1928
+ my $dat = substr($$dataPt, $pos, 36);
1929
+ last if $dat eq "\x0" x 36;
1930
+ my @a = unpack 'VVVVCVCV', $dat;
1931
+ $$et{DOC_NUM} = ++$$et{DOC_COUNT};
1932
+ # 0=1, 1=1, 2=secs, 3=?
1933
+ SetGPSDateTime($et, $tagTbl, $a[2]);
1934
+ my $lat = $a[5] / 1e3;
1935
+ my $lon = $a[7] / 1e3;
1936
+ my $deg = int($lat / 100);
1937
+ $lat = $deg + ($lat - $deg * 100) / 60;
1938
+ $deg = int($lon / 100);
1939
+ $lon = $deg + ($lon - $deg * 100) / 60;
1940
+ $lat = -$lat if $a[4] eq 'S';
1941
+ $lon = -$lon if $a[6] eq 'W';
1942
+ $et->HandleTag($tagTbl, GPSLatitude => $lat);
1943
+ $et->HandleTag($tagTbl, GPSLongitude => $lon);
1944
+ $et->HandleTag($tagTbl, GPSSpeed => $a[3] / 1e3);
1945
+ $et->HandleTag($tagTbl, GPSSpeedRef => 'K');
1946
+ $pos += 36;
1947
+ }
1948
+ SetByteOrder('MM');
1949
+ delete $$et{DOC_NUM};
1647
1950
  }
1648
1951
  }
1649
1952
 
@@ -1661,6 +1964,26 @@ sub Process_tx3g($$$)
1661
1964
  return 1;
1662
1965
  }
1663
1966
 
1967
+ #------------------------------------------------------------------------------
1968
+ # Process GM 'marl' ctbx metadata (ref PH)
1969
+ # Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
1970
+ # Returns: 1 on success
1971
+ sub Process_marl($$$)
1972
+ {
1973
+ my ($et, $dirInfo, $tagTablePtr) = @_;
1974
+ my $dataPt = $$dirInfo{DataPt};
1975
+ return 0 if length $$dataPt < 2;
1976
+
1977
+ # 8-byte records:
1978
+ # byte 0 seems to be tag ID (0=timestamp in sec * 1e7)
1979
+ # bytes 1-3 seem to be 24-bit signed integer (unknown meaning)
1980
+ # bytes 4-7 are an int32u value, usually a multiple of 10000
1981
+
1982
+ $et->WarnOnce("Can't yet decode timed GM data", 1);
1983
+ # (see https://exiftool.org/forum/index.php?topic=11335.msg61393#msg61393)
1984
+ return 1;
1985
+ }
1986
+
1664
1987
  #------------------------------------------------------------------------------
1665
1988
  # Process QuickTime 'mebx' timed metadata
1666
1989
  # Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
@@ -2139,11 +2462,16 @@ sub ProcessInsta360($;$)
2139
2462
  $et->HandleTag($tagTbl, Accelerometer => "@a[0..2]"); # (NC)
2140
2463
  $et->HandleTag($tagTbl, AngularVelocity => "@a[3..5]"); # (NC)
2141
2464
  }
2142
- } elsif ($id == 0x400 and $unknown) {
2465
+ } elsif ($id == 0x400) {
2143
2466
  for ($p=0; $p<$len; $p+=$dlen) {
2144
2467
  $$et{DOC_NUM} = ++$$et{DOC_COUNT};
2145
2468
  $et->HandleTag($tagTbl, TimeCode => sprintf('%.3f', Get64u(\$buff, $p) / 1000));
2146
- $et->HandleTag($tagTbl, Unknown01 => GetDouble(\$buff, $p + 8));
2469
+ $et->HandleTag($tagTbl, ExposureTime => GetDouble(\$buff, $p + 8)); #6
2470
+ }
2471
+ } elsif ($id == 0x600) { #6
2472
+ for ($p=0; $p<$len; $p+=$dlen) {
2473
+ $$et{DOC_NUM} = ++$$et{DOC_COUNT};
2474
+ $et->HandleTag($tagTbl, VideoTimeStamp => sprintf('%.3f', Get64u(\$buff, $p) / 1000));
2147
2475
  }
2148
2476
  } elsif ($id == 0x700) {
2149
2477
  for ($p=0; $p<$len; $p+=$dlen) {
@@ -2189,6 +2517,37 @@ sub ProcessInsta360($;$)
2189
2517
  return 1;
2190
2518
  }
2191
2519
 
2520
+ #------------------------------------------------------------------------------
2521
+ # Process 360Fly 'uuid' atom containing sensor data
2522
+ # (ref https://github.com/JamesHeinrich/getID3/blob/master/getid3/module.audio-video.quicktime.php)
2523
+ # Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
2524
+ # Returns: 1 on success
2525
+ sub Process360Fly($$$)
2526
+ {
2527
+ my ($et, $dirInfo, $tagTbl) = @_;
2528
+ my $dataPt = $$dirInfo{DataPt};
2529
+ my $dataLen = length $$dataPt;
2530
+ my $pos = 16;
2531
+ my $lastTime = -1;
2532
+ my $streamTbl = GetTagTable('Image::ExifTool::QuickTime::Stream');
2533
+ while ($pos + 32 <= $dataLen) {
2534
+ my $type = ord substr $$dataPt, $pos, 1;
2535
+ my $time = Get64u($dataPt, $pos + 2); # (only valid for some types)
2536
+ if ($$tagTbl{$type}) {
2537
+ if ($time != $lastTime) {
2538
+ $$et{DOC_NUM} = ++$$et{DOC_COUNT};
2539
+ $lastTime = $time;
2540
+ }
2541
+ }
2542
+ $et->HandleTag($tagTbl, $type, undef, DataPt => $dataPt, Start => $pos, Size => 32);
2543
+ # synthesize GPSDateTime from the timestamp for GPS records
2544
+ SetGPSDateTime($et, $streamTbl, $time / 1e6) if $type == 5;
2545
+ $pos += 32;
2546
+ }
2547
+ delete $$et{DOC_NUM};
2548
+ return 1;
2549
+ }
2550
+
2192
2551
  #------------------------------------------------------------------------------
2193
2552
  # Scan media data for "freeGPS" metadata if not found already (ref PH)
2194
2553
  # Inputs: 0) ExifTool ref