exiftool_vendored 11.99.0 → 12.11.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


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

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