exiftool_vendored 12.22.0 → 12.34.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/bin/Changes +201 -5
  3. data/bin/MANIFEST +22 -0
  4. data/bin/META.json +1 -1
  5. data/bin/META.yml +1 -1
  6. data/bin/README +45 -43
  7. data/bin/arg_files/xmp2exif.args +2 -1
  8. data/bin/config_files/acdsee.config +193 -6
  9. data/bin/config_files/convert_regions.config +25 -14
  10. data/bin/config_files/cuepointlist.config +70 -0
  11. data/bin/config_files/example.config +1 -1
  12. data/bin/exiftool +89 -70
  13. data/bin/fmt_files/gpx.fmt +1 -1
  14. data/bin/fmt_files/gpx_wpt.fmt +1 -1
  15. data/bin/lib/Image/ExifTool/BuildTagLookup.pm +17 -4
  16. data/bin/lib/Image/ExifTool/CBOR.pm +331 -0
  17. data/bin/lib/Image/ExifTool/Canon.pm +53 -21
  18. data/bin/lib/Image/ExifTool/Charset.pm +2 -0
  19. data/bin/lib/Image/ExifTool/DPX.pm +13 -2
  20. data/bin/lib/Image/ExifTool/DjVu.pm +6 -5
  21. data/bin/lib/Image/ExifTool/Exif.pm +120 -12
  22. data/bin/lib/Image/ExifTool/FlashPix.pm +35 -10
  23. data/bin/lib/Image/ExifTool/FujiFilm.pm +19 -8
  24. data/bin/lib/Image/ExifTool/Geotag.pm +13 -2
  25. data/bin/lib/Image/ExifTool/GoPro.pm +16 -1
  26. data/bin/lib/Image/ExifTool/ICC_Profile.pm +96 -4
  27. data/bin/lib/Image/ExifTool/ID3.pm +15 -3
  28. data/bin/lib/Image/ExifTool/JPEG.pm +74 -4
  29. data/bin/lib/Image/ExifTool/JSON.pm +30 -5
  30. data/bin/lib/Image/ExifTool/Jpeg2000.pm +395 -16
  31. data/bin/lib/Image/ExifTool/LIF.pm +153 -0
  32. data/bin/lib/Image/ExifTool/Lang/nl.pm +60 -59
  33. data/bin/lib/Image/ExifTool/M2TS.pm +103 -7
  34. data/bin/lib/Image/ExifTool/MIE.pm +2 -1
  35. data/bin/lib/Image/ExifTool/MRC.pm +341 -0
  36. data/bin/lib/Image/ExifTool/MWG.pm +3 -3
  37. data/bin/lib/Image/ExifTool/MXF.pm +1 -1
  38. data/bin/lib/Image/ExifTool/MacOS.pm +3 -3
  39. data/bin/lib/Image/ExifTool/Microsoft.pm +5 -3
  40. data/bin/lib/Image/ExifTool/Nikon.pm +17 -5
  41. data/bin/lib/Image/ExifTool/NikonSettings.pm +19 -2
  42. data/bin/lib/Image/ExifTool/Olympus.pm +9 -2
  43. data/bin/lib/Image/ExifTool/Other.pm +93 -0
  44. data/bin/lib/Image/ExifTool/PDF.pm +11 -12
  45. data/bin/lib/Image/ExifTool/PNG.pm +8 -7
  46. data/bin/lib/Image/ExifTool/Panasonic.pm +28 -3
  47. data/bin/lib/Image/ExifTool/Pentax.pm +28 -5
  48. data/bin/lib/Image/ExifTool/PhaseOne.pm +4 -3
  49. data/bin/lib/Image/ExifTool/Photoshop.pm +6 -0
  50. data/bin/lib/Image/ExifTool/QuickTime.pm +210 -65
  51. data/bin/lib/Image/ExifTool/QuickTimeStream.pl +280 -139
  52. data/bin/lib/Image/ExifTool/README +9 -2
  53. data/bin/lib/Image/ExifTool/RIFF.pm +89 -12
  54. data/bin/lib/Image/ExifTool/Samsung.pm +48 -10
  55. data/bin/lib/Image/ExifTool/Sony.pm +204 -61
  56. data/bin/lib/Image/ExifTool/TagLookup.pm +206 -19
  57. data/bin/lib/Image/ExifTool/TagNames.pod +634 -195
  58. data/bin/lib/Image/ExifTool/Torrent.pm +18 -11
  59. data/bin/lib/Image/ExifTool/WriteIPTC.pl +1 -1
  60. data/bin/lib/Image/ExifTool/WritePDF.pl +1 -0
  61. data/bin/lib/Image/ExifTool/WritePNG.pl +2 -0
  62. data/bin/lib/Image/ExifTool/WritePostScript.pl +1 -0
  63. data/bin/lib/Image/ExifTool/WriteQuickTime.pl +58 -16
  64. data/bin/lib/Image/ExifTool/WriteXMP.pl +7 -3
  65. data/bin/lib/Image/ExifTool/Writer.pl +44 -0
  66. data/bin/lib/Image/ExifTool/XMP.pm +51 -18
  67. data/bin/lib/Image/ExifTool/XMP2.pl +4 -1
  68. data/bin/lib/Image/ExifTool/XMPStruct.pl +3 -1
  69. data/bin/lib/Image/ExifTool/ZISRAW.pm +121 -2
  70. data/bin/lib/Image/ExifTool.pm +188 -61
  71. data/bin/lib/Image/ExifTool.pod +89 -68
  72. data/bin/perl-Image-ExifTool.spec +43 -42
  73. data/lib/exiftool_vendored/version.rb +1 -1
  74. metadata +10 -9
@@ -25,6 +25,7 @@ sub Process_mebx($$$);
25
25
  sub ProcessFreeGPS($$$);
26
26
  sub ProcessFreeGPS2($$$);
27
27
  sub Process360Fly($$$);
28
+ sub ProcessFMAS($$$);
28
29
 
29
30
  # QuickTime data types that have ExifTool equivalents
30
31
  # (ref https://developer.apple.com/library/content/documentation/QuickTime/QTFF/Metadata/Metadata.html#//apple_ref/doc/uid/TP40000939-CH1-SW35)
@@ -95,18 +96,18 @@ my %insvLimit = (
95
96
  %Image::ExifTool::QuickTime::Stream = (
96
97
  GROUPS => { 2 => 'Location' },
97
98
  NOTES => q{
98
- Timed metadata extracted from QuickTime media data and some AVI videos when
99
- the ExtractEmbedded option is used. Although most of these tags are
100
- combined into the single table below, ExifTool currently reads 51 different
101
- formats of timed GPS metadata from video files.
99
+ The tags below are extracted from timed metadata in QuickTime and other
100
+ formats of video files when the ExtractEmbedded option is used. Although
101
+ most of these tags are combined into the single table below, ExifTool
102
+ currently reads 57 different formats of timed GPS metadata from video files.
102
103
  },
103
104
  VARS => { NO_ID => 1 },
104
105
  GPSLatitude => { PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")', RawConv => '$$self{FoundGPSLatitude} = 1; $val' },
105
106
  GPSLongitude => { PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")' },
106
107
  GPSAltitude => { PrintConv => '(sprintf("%.4f", $val) + 0) . " m"' }, # round to 4 decimals
107
- GPSSpeed => { PrintConv => 'sprintf("%.4f", $val) + 0' }, # round to 4 decimals
108
+ GPSSpeed => { PrintConv => 'sprintf("%.4f", $val) + 0', Notes => 'in km/h unless GPSSpeedRef says otherwise' },
108
109
  GPSSpeedRef => { PrintConv => { K => 'km/h', M => 'mph', N => 'knots' } },
109
- GPSTrack => { PrintConv => 'sprintf("%.4f", $val) + 0' }, # round to 4 decimals
110
+ GPSTrack => { PrintConv => 'sprintf("%.4f", $val) + 0', Notes => 'relative to true north unless GPSTrackRef says otherwise' },
110
111
  GPSTrackRef => { PrintConv => { M => 'Magnetic North', T => 'True North' } },
111
112
  GPSDateTime => {
112
113
  Groups => { 2 => 'Time' },
@@ -140,6 +141,7 @@ my %insvLimit = (
140
141
  SampleTime => { Groups => { 2 => 'Video' }, PrintConv => 'ConvertDuration($val)', Notes => 'sample decoding time' },
141
142
  SampleDuration=>{ Groups => { 2 => 'Video' }, PrintConv => 'ConvertDuration($val)' },
142
143
  UserLabel => { Groups => { 2 => 'Other' } },
144
+ KiloCalories => { Groups => { 2 => 'Other' } },
143
145
  SampleDateTime => {
144
146
  Groups => { 2 => 'Time' },
145
147
  ValueConv => q{
@@ -167,15 +169,29 @@ my %insvLimit = (
167
169
  },
168
170
  },
169
171
  gpmd => [{
170
- Name => 'gpmd_GoPro',
171
- Condition => '$$valPt !~ /^\0\0\xf2\xe1\xf0\xeeTT/',
172
- SubDirectory => { TagTable => 'Image::ExifTool::GoPro::GPMF' },
172
+ Name => 'gpmd_Kingslim', # Kingslim D4 dashcam
173
+ Condition => '$$valPt =~ /^.{21}\0\0\0A[NS][EW]/s',
174
+ SubDirectory => {
175
+ TagTable => 'Image::ExifTool::QuickTime::Stream',
176
+ ProcessProc => \&ProcessFreeGPS,
177
+ },
173
178
  },{
174
179
  Name => 'gpmd_Rove', # Rove Stealth 4K encrypted text
180
+ Condition => '$$valPt =~ /^\0\0\xf2\xe1\xf0\xeeTT/',
175
181
  SubDirectory => {
176
182
  TagTable => 'Image::ExifTool::QuickTime::Stream',
177
183
  ProcessProc => \&Process_text,
178
184
  },
185
+ },{
186
+ Name => 'gpmd_FMAS', # Vantrue N2S binary format
187
+ Condition => '$$valPt =~ /^FMAS\0\0\0\0/',
188
+ SubDirectory => {
189
+ TagTable => 'Image::ExifTool::QuickTime::Stream',
190
+ ProcessProc => \&ProcessFMAS,
191
+ },
192
+ },{
193
+ Name => 'gpmd_GoPro',
194
+ SubDirectory => { TagTable => 'Image::ExifTool::GoPro::GPMF' },
179
195
  }],
180
196
  fdsc => {
181
197
  Name => 'fdsc',
@@ -407,7 +423,14 @@ my %insvLimit = (
407
423
  Groups => { 2 => 'Time' },
408
424
  Format => 'double',
409
425
  RawConv => '$$self{FoundGPSDateTime} = 1; $val',
426
+ # by the specification, this should use the GPS epoch of Jan 6, 1980,
427
+ # but I have samples which use the Unix epoch of Jan 1, 1970, so convert
428
+ # to the Unix Epoch only if it doesn't match the CreateDate within 5 years
410
429
  ValueConv => q{
430
+ my $offset = 315964800;
431
+ if ($$self{CreateDate} and $$self{CreateDate} - $val > 24 * 3600 * 365 * 5) {
432
+ $val += $offset;
433
+ }
411
434
  my $str = ConvertUnixTime($val);
412
435
  my $frac = $val - int($val);
413
436
  if ($frac != 0) {
@@ -886,14 +909,8 @@ sub Process_text($$$)
886
909
  $tags{GPSDateTime} = $dateTime;
887
910
  $tags{GPSLatitude} = (($4 || 0) + $5/60) * ($6 eq 'N' ? 1 : -1);
888
911
  $tags{GPSLongitude} = (($7 || 0) + $8/60) * ($9 eq 'E' ? 1 : -1);
889
- if (length $10) {
890
- $tags{GPSSpeed} = $10 * $knotsToKph;
891
- $tags{GPSSpeedRef} = 'K';
892
- }
893
- if (length $11) {
894
- $tags{GPSTrack} = $11;
895
- $tags{GPSTrackRef} = 'T';
896
- }
912
+ $tags{GPSSpeed} = $10 * $knotsToKph if length $10;
913
+ $tags{GPSTrack} = $11 if length $11;
897
914
  } 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) {
898
915
  my $time = "$1:$2:$3";
899
916
  if ($$et{LastTime}) {
@@ -973,10 +990,7 @@ sub Process_text($$$)
973
990
  $val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$dataPt, 0x39, 5)));
974
991
  $tags{GPSAltitude} = $val + 0 if $val =~ /^[-+]\d+$/;
975
992
  $val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$dataPt, 0x3e, 3)));
976
- if ($val =~ /^\d+$/) {
977
- $tags{GPSSpeed} = $val + 0;
978
- $tags{GPSSpeedRef} = 'K';
979
- }
993
+ $tags{GPSSpeed} = $val + 0 if $val =~ /^\d+$/;
980
994
  if ($$dataPt =~ /^\0\0..\xaa\xaa/s) { # (BlueSkySea)
981
995
  $val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$dataPt, 0xad, 12)));
982
996
  # the first X,Y,Z accelerometer readings from the AccelerometerData
@@ -1007,10 +1021,7 @@ sub Process_text($$$)
1007
1021
  $tags{GPSLatitude} = $2;
1008
1022
  $tags{GPSLongitude} = $1;
1009
1023
  $tags{GPSAltitude} = $1 if $$dataPt =~ /,\s*H\s+([-+]?\d+\.?\d*)m/;
1010
- if ($$dataPt =~ /,\s*H.S\s+([-+]?\d+\.?\d*)/) {
1011
- $tags{GPSSpeed} = $1 * $mpsToKph;
1012
- $tags{GPSSpeedRef} = 'K';
1013
- }
1024
+ $tags{GPSSpeed} = $1 * $mpsToKph if $$dataPt =~ /,\s*H.S\s+([-+]?\d+\.?\d*)/;
1014
1025
  $tags{Distance} = $1 * $mpsToKph if $$dataPt =~ /,\s*D\s+(\d+\.?\d*)m/;
1015
1026
  $tags{VerticalSpeed} = $1 if $$dataPt =~ /,\s*V.S\s+([-+]?\d+\.?\d*)/;
1016
1027
  $tags{FNumber} = $1 if $$dataPt =~ /\bF\/(\d+\.?\d*)/;
@@ -1071,14 +1082,8 @@ sub Process_text($$$)
1071
1082
  $tags{GPSDateTime} = sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%.2dZ', $year, $14, $13, $1, $2, $3);
1072
1083
  $tags{GPSLatitude} = (($5 || 0) + $6/60) * ($7 eq 'N' ? 1 : -1);
1073
1084
  $tags{GPSLongitude} = (($8 || 0) + $9/60) * ($10 eq 'E' ? 1 : -1);
1074
- if (length $11) {
1075
- $tags{GPSSpeed} = $11 * $knotsToKph;
1076
- $tags{GPSSpeedRef} = 'K';
1077
- }
1078
- if (length $12) {
1079
- $tags{GPSTrack} = $12;
1080
- $tags{GPSTrackRef} = 'T';
1081
- }
1085
+ $tags{GPSSpeed} = $11 * $knotsToKph if length $11;
1086
+ $tags{GPSTrack} = $12 if length $12;
1082
1087
  }
1083
1088
  $tags{GSensor} = $1 if $$dataPt =~ /\bgsensori,(.*?)(;|$)/;
1084
1089
  $tags{Car} = $1 if $$dataPt =~ /\bCAR,(.*?)(;|$)/;
@@ -1215,7 +1220,10 @@ sub ProcessSamples($)
1215
1220
  $et->VPrint(1, "${hdr}, Sample ".($i+1).' of '.scalar(@$start)." ($size bytes)\n");
1216
1221
  $et->VerboseDump(\$buff, Addr => $$start[$i]);
1217
1222
  }
1218
- if ($type eq 'text') {
1223
+ if ($type eq 'text' or
1224
+ # (PNDM is normally 'text', but was sbtl/tx3g in concatenated Garmin sample output_3videos.mp4)
1225
+ ($type eq 'sbtl' and $metaFormat eq 'tx3g' and $buff =~ /^..PNDM/s))
1226
+ {
1219
1227
 
1220
1228
  FoundSomething($et, $tagTbl, $time[$i], $dur[$i]);
1221
1229
  unless ($buff =~ /^\$BEGIN/) {
@@ -1250,8 +1258,7 @@ sub ProcessSamples($)
1250
1258
  next if length($buff) < 20 + $n;
1251
1259
  $et->HandleTag($tagTbl, GPSLatitude => Get32s(\$buff, 12+$n) * 180/0x80000000);
1252
1260
  $et->HandleTag($tagTbl, GPSLongitude => Get32s(\$buff, 16+$n) * 180/0x80000000);
1253
- $et->HandleTag($tagTbl, GPSSpeed => Get16u(\$buff, 8+$n) * $mphToKph);
1254
- $et->HandleTag($tagTbl, GPSSpeedRef => 'K');
1261
+ $et->HandleTag($tagTbl, GPSSpeed => Get16u(\$buff, 8+$n) * $mphToKph);
1255
1262
  SetGPSDateTime($et, $tagTbl, $time[$i]);
1256
1263
  next; # all done (don't store/process as text)
1257
1264
  }
@@ -1324,6 +1331,19 @@ sub ProcessSamples($)
1324
1331
  $$et{HandlerType} = $$et{HanderDesc} = '';
1325
1332
  }
1326
1333
 
1334
+ #------------------------------------------------------------------------------
1335
+ # Convert latitude/longitude from DDDMM.MMMM format to decimal degrees
1336
+ # Inputs: 0) latitude, 1) longitude
1337
+ # Returns: lat/lon are changed in place
1338
+ # (note: this method works fine for negative coordinates)
1339
+ sub ConvertLatLon($$)
1340
+ {
1341
+ my $deg = int($_[0] / 100); # latitude
1342
+ $_[0] = $deg + ($_[0] - $deg * 100) / 60;
1343
+ $deg = int($_[1] / 100); # longitude
1344
+ $_[1] = $deg + ($_[1] - $deg * 100) / 60;
1345
+ }
1346
+
1327
1347
  #------------------------------------------------------------------------------
1328
1348
  # Process "freeGPS " data blocks referenced by a 'gps ' (GPSDataList) atom
1329
1349
  # Inputs: 0) ExifTool ref, 1) dirInfo ref {DataPt,SampleTime,SampleDuration}, 2) tagTable ref
@@ -1335,7 +1355,7 @@ sub ProcessFreeGPS($$$)
1335
1355
  my ($et, $dirInfo, $tagTbl) = @_;
1336
1356
  my $dataPt = $$dirInfo{DataPt};
1337
1357
  my $dirLen = length $$dataPt;
1338
- my ($yr, $mon, $day, $hr, $min, $sec, $stat, $lbl);
1358
+ my ($yr, $mon, $day, $hr, $min, $sec, $stat, $lbl, $ddd);
1339
1359
  my ($lat, $latRef, $lon, $lonRef, $spd, $trk, $alt, @acc, @xtra);
1340
1360
 
1341
1361
  return 0 if $dirLen < 92;
@@ -1425,7 +1445,7 @@ sub ProcessFreeGPS($$$)
1425
1445
  map { $_ = $_ - 4294967296 if $_ >= 0x80000000; $_ /= 256 } @acc;
1426
1446
  }
1427
1447
 
1428
- } elsif ($$dataPt =~ /^.{40}A([NS])([EW])/s) {
1448
+ } elsif ($$dataPt =~ /^.{37}\0\0\0A([NS])([EW])/s) {
1429
1449
 
1430
1450
  # decode freeGPS from ViofoA119v3 dashcam (similar to Novatek GPS format)
1431
1451
  # 0000: 00 00 40 00 66 72 65 65 47 50 53 20 f0 03 00 00 [..@.freeGPS ....]
@@ -1442,6 +1462,34 @@ sub ProcessFreeGPS($$$)
1442
1462
  $trk = GetFloat($dataPt, 0x38);
1443
1463
  SetByteOrder('MM');
1444
1464
 
1465
+ } elsif ($$dataPt =~ /^.{21}\0\0\0A([NS])([EW])/s) {
1466
+
1467
+ # also decode 'gpmd' chunk from Kingslim D4 dashcam videos
1468
+ # 0000: 0a 00 00 00 0b 00 00 00 07 00 00 00 e5 07 00 00 [................]
1469
+ # 0010: 06 00 00 00 03 00 00 00 41 4e 57 31 91 52 83 45 [........ANW1.R.E]
1470
+ # 0020: 15 70 fe c5 29 5c c3 41 ae c7 af 42 00 00 d1 be [.p..)\.A...B....]
1471
+ # 0030: 00 00 80 3b 00 00 2c 3e 00 00 00 00 00 00 00 00 [...;..,>........]
1472
+ # 0040: 00 00 00 00 00 00 00 00 00 00 00 00 26 26 26 26 [............&&&&]
1473
+ # 0050: 4c 49 47 4f 47 50 53 49 4e 46 4f 00 00 00 00 05 [LIGOGPSINFO.....]
1474
+ # 0060: 01 00 00 00 23 23 23 23 75 00 00 00 c0 22 20 20 [....####u...." ]
1475
+ # 0070: 20 f0 12 10 12 21 e5 0e 10 12 2f 90 10 13 01 f2 [ ....!..../.....]
1476
+ ($latRef, $lonRef) = ($1, $2);
1477
+ ($hr,$min,$sec,$yr,$mon,$day) = unpack("V6", $$dataPt);
1478
+ SetByteOrder('II');
1479
+ # lat/lon aren't decoded properly, but spd,trk,acc are
1480
+ $lat = GetFloat($dataPt, 0x1c);
1481
+ $lon = GetFloat($dataPt, 0x20);
1482
+ $et->VPrint(0, sprintf("Raw lat/lon = %.9f %.9f\n", $lat, $lon));
1483
+ $et->WarnOnce('GPSLatitude/Longitude encryption is not yet known, so these will be wrong');
1484
+ $lat = abs $lat;
1485
+ $lon = abs $lon;
1486
+ $spd = GetFloat($dataPt, 0x24) * $knotsToKph; # (convert knots to km/h)
1487
+ $trk = GetFloat($dataPt, 0x28);
1488
+ $acc[0] = GetFloat($dataPt, 0x2c);
1489
+ $acc[1] = GetFloat($dataPt, 0x30);
1490
+ $acc[2] = GetFloat($dataPt, 0x34);
1491
+ SetByteOrder('MM');
1492
+
1445
1493
  } elsif ($$dataPt =~ /^.{60}A\0{3}.{4}([NS])\0{3}.{4}([EW])\0{3}/s) {
1446
1494
 
1447
1495
  # decode freeGPS from Akaso dashcam
@@ -1462,6 +1510,23 @@ sub ProcessFreeGPS($$$)
1462
1510
  $trk -= 360 if $trk >= 360;
1463
1511
  SetByteOrder('MM');
1464
1512
 
1513
+ } elsif ($$dataPt =~ /^.{60}4W`b]S</s and length($$dataPt) >= 140) {
1514
+
1515
+ # 0000: 00 00 40 00 66 72 65 65 47 50 53 20 f0 01 00 00 [..@.freeGPS ....]
1516
+ # 0010: 5a 58 53 42 4e 58 59 53 00 00 00 00 00 00 00 00 [ZXSBNXYS........]
1517
+ # 0020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
1518
+ # 0030: 00 00 00 00 00 00 00 00 00 00 00 00 34 57 60 62 [............4W`b]
1519
+ # 0040: 5d 53 3c 41 44 45 41 41 42 3e 40 40 3c 51 3c 45 []S<ADEAAB>@@<Q<E]
1520
+ # 0050: 41 40 43 3e 41 47 49 48 44 3c 5e 3c 40 41 46 43 [A@C>AGIHD<^<@AFC]
1521
+ # 0060: 42 3e 49 49 40 42 45 3c 55 3c 45 47 3e 45 43 41 [B>II@BE<U<EG>ECA]
1522
+ # decipher $GPRMC by subtracting 16 from each character value
1523
+ $_ = pack 'C*', map { $_>=16 and $_-=16 } unpack('x60C80', $$dataPt);
1524
+ return 0 unless /[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+)/;
1525
+ ($yr,$mon,$day,$hr,$min,$sec,$lat,$latRef,$lon,$lonRef) = ($13,$12,$11,$1,$2,$3,$5,$6,$7,$8);
1526
+ $yr += ($yr >= 70 ? 1900 : 2000);
1527
+ $spd = $9 * $knotsToKph if length $9;
1528
+ $trk = $10 if length $10;
1529
+
1465
1530
  } elsif ($$dataPt =~ /^.{16}YndAkasoCar/s) {
1466
1531
 
1467
1532
  # Akaso V1 dascham
@@ -1480,13 +1545,17 @@ sub ProcessFreeGPS($$$)
1480
1545
  return 0 unless $stat eq 'A' and ($latRef eq 'N' or $latRef eq 'S') and
1481
1546
  ($lonRef eq 'E' or $lonRef eq 'W');
1482
1547
 
1483
- $et->WarnOnce("Can't yet decrypt Akaso V1 timed GPS", 1);
1548
+ $et->WarnOnce('GPSLatitude/Longitude encryption is not yet known, so these will be wrong');
1484
1549
  # (see https://exiftool.org/forum/index.php?topic=11320.0)
1485
- return 1;
1486
1550
 
1487
1551
  SetByteOrder('II');
1552
+
1553
+ $spd = GetFloat($dataPt, 0x60);
1554
+ $trk = GetFloat($dataPt, 0x64) + 180; # (why is this off by 180?)
1488
1555
  $lat = GetDouble($dataPt, 0x50); # latitude is here, but encrypted somehow
1489
1556
  $lon = GetDouble($dataPt, 0x58); # longitude is here, but encrypted somehow
1557
+ $ddd = 1; # don't convert until we know what the format is
1558
+
1490
1559
  SetByteOrder('MM');
1491
1560
  #my $serialNum = substr($$dataPt, 0x68, 20);
1492
1561
 
@@ -1550,10 +1619,7 @@ sub ProcessFreeGPS($$$)
1550
1619
  #
1551
1620
  FoundSomething($et, $tagTbl, $$dirInfo{SampleTime}, $$dirInfo{SampleDuration});
1552
1621
  # lat/long are in DDDMM.MMMM format
1553
- my $deg = int($lat / 100);
1554
- $lat = $deg + ($lat - $deg * 100) / 60;
1555
- $deg = int($lon / 100);
1556
- $lon = $deg + ($lon - $deg * 100) / 60;
1622
+ ConvertLatLon($lat, $lon) unless $ddd;
1557
1623
  $sec = '0' . $sec unless $sec =~ /^\d{2}/; # pad integer part of seconds to 2 digits
1558
1624
  if (defined $yr) {
1559
1625
  my $time = sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%sZ',$yr,$mon,$day,$hr,$min,$sec);
@@ -1565,14 +1631,8 @@ sub ProcessFreeGPS($$$)
1565
1631
  $et->HandleTag($tagTbl, GPSLatitude => $lat * ($latRef eq 'S' ? -1 : 1));
1566
1632
  $et->HandleTag($tagTbl, GPSLongitude => $lon * ($lonRef eq 'W' ? -1 : 1));
1567
1633
  $et->HandleTag($tagTbl, GPSAltitude => $alt) if defined $alt;
1568
- if (defined $spd) {
1569
- $et->HandleTag($tagTbl, GPSSpeed => $spd);
1570
- $et->HandleTag($tagTbl, GPSSpeedRef => 'K');
1571
- }
1572
- if (defined $trk) {
1573
- $et->HandleTag($tagTbl, GPSTrack => $trk);
1574
- $et->HandleTag($tagTbl, GPSTrackRef => 'T');
1575
- }
1634
+ $et->HandleTag($tagTbl, GPSSpeed => $spd) if defined $spd;
1635
+ $et->HandleTag($tagTbl, GPSTrack => $trk) if defined $trk;
1576
1636
  while (@xtra) {
1577
1637
  my $tag = shift @xtra;
1578
1638
  $et->HandleTag($tagTbl, $tag => shift @xtra);
@@ -1691,9 +1751,7 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
1691
1751
  $et->HandleTag($tagTbl, GPSLatitude => Get32s(\$b, 0x10) / 1e7);
1692
1752
  $et->HandleTag($tagTbl, GPSLongitude => Get32s(\$b, 0x18) / 1e7);
1693
1753
  $et->HandleTag($tagTbl, GPSSpeed => Get32s(\$b, 0x20) / 100 * $mpsToKph);
1694
- $et->HandleTag($tagTbl, GPSSpeedRef => 'K');
1695
1754
  $et->HandleTag($tagTbl, GPSTrack => $trk);
1696
- $et->HandleTag($tagTbl, GPSTrackRef => 'T');
1697
1755
  $et->HandleTag($tagTbl, GPSAltitude => Get32s(\$b, 0x28) / 1000);
1698
1756
  $lastRecPos = $recPos;
1699
1757
  $foundNew = 1;
@@ -1803,6 +1861,27 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
1803
1861
  $spd = GetFloat($dataPt, 0x50);
1804
1862
  $trk = GetFloat($dataPt, 0x54);
1805
1863
 
1864
+ } elsif ($$dataPt =~ /^.{16}A([NS])([EW])\0/s) {
1865
+
1866
+ # INNOVV MP4 video (same format as INNOVV TS)
1867
+ while ($$dataPt =~ /(A[NS][EW]\0.{28})/g) {
1868
+ my $dat = $1;
1869
+ $lat = abs(GetFloat(\$dat, 4)); # (abs just to be safe)
1870
+ $lon = abs(GetFloat(\$dat, 8)); # (abs just to be safe)
1871
+ $spd = GetFloat(\$dat, 12) * $knotsToKph;
1872
+ $trk = GetFloat(\$dat, 16);
1873
+ @acc = unpack('x20V3', $dat);
1874
+ map { $_ = $_ - 4294967296 if $_ >= 0x80000000 } @acc;
1875
+ ConvertLatLon($lat, $lon);
1876
+ $$et{DOC_NUM} = ++$$et{DOC_COUNT};
1877
+ $et->HandleTag($tagTbl, GPSLatitude => $lat * (substr($dat,1,1) eq 'S' ? -1 : 1));
1878
+ $et->HandleTag($tagTbl, GPSLongitude => $lon * (substr($dat,2,1) eq 'W' ? -1 : 1));
1879
+ $et->HandleTag($tagTbl, GPSSpeed => $spd);
1880
+ $et->HandleTag($tagTbl, GPSTrack => $trk);
1881
+ $et->HandleTag($tagTbl, Accelerometer => "@acc");
1882
+ }
1883
+ return 1;
1884
+
1806
1885
  } else {
1807
1886
 
1808
1887
  # (look for binary GPS as stored by NextBase 512G, ref PH)
@@ -1844,9 +1923,7 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
1844
1923
  $et->HandleTag($tagTbl, GPSLatitude => $lat);
1845
1924
  $et->HandleTag($tagTbl, GPSLongitude => $lon);
1846
1925
  $et->HandleTag($tagTbl, GPSSpeed => $spd / 100 * $mpsToKph);
1847
- $et->HandleTag($tagTbl, GPSSpeedRef => 'K');
1848
1926
  $et->HandleTag($tagTbl, GPSTrack => $trk);
1849
- $et->HandleTag($tagTbl, GPSTrackRef => 'T');
1850
1927
  last if $pos += 0x20 > length($$dataPt) - 0x1e;
1851
1928
  }
1852
1929
  return $$et{DOC_NUM} ? 1 : 0; # return 0 if nothing extracted
@@ -1859,23 +1936,12 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
1859
1936
  $yr += 2000 if $yr < 2000;
1860
1937
  my $time = sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%.2dZ', $yr, $mon, $day, $hr, $min, $sec);
1861
1938
  # convert from DDMM.MMMMMM to DD.DDDDDD format if necessary
1862
- unless ($ddd) {
1863
- my $deg = int($lat / 100);
1864
- $lat = $deg + ($lat - $deg * 100) / 60;
1865
- $deg = int($lon / 100);
1866
- $lon = $deg + ($lon - $deg * 100) / 60;
1867
- }
1939
+ ConvertLatLon($lat, $lon) unless $ddd;
1868
1940
  $et->HandleTag($tagTbl, GPSDateTime => $time);
1869
1941
  $et->HandleTag($tagTbl, GPSLatitude => $lat * ($latRef eq 'S' ? -1 : 1));
1870
1942
  $et->HandleTag($tagTbl, GPSLongitude => $lon * ($lonRef eq 'W' ? -1 : 1));
1871
- if (defined $spd) {
1872
- $et->HandleTag($tagTbl, GPSSpeed => $spd); # (now in km/h)
1873
- $et->HandleTag($tagTbl, GPSSpeedRef => 'K');
1874
- }
1875
- if (defined $trk) {
1876
- $et->HandleTag($tagTbl, GPSTrack => $trk);
1877
- $et->HandleTag($tagTbl, GPSTrackRef => 'T');
1878
- }
1943
+ $et->HandleTag($tagTbl, GPSSpeed => $spd) if defined $spd; # (now in km/h)
1944
+ $et->HandleTag($tagTbl, GPSTrack => $trk) if defined $trk;
1879
1945
  if (defined $alt) {
1880
1946
  $et->HandleTag($tagTbl, GPSAltitude => $alt);
1881
1947
  }
@@ -1883,6 +1949,7 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
1883
1949
  return 1;
1884
1950
  }
1885
1951
 
1952
+
1886
1953
  #------------------------------------------------------------------------------
1887
1954
  # Extract embedded information referenced from a track
1888
1955
  # Inputs: 0) ExifTool ref, 1) tag name, 2) data ref
@@ -1961,22 +2028,18 @@ sub ParseTag($$$)
1961
2028
  while ($pos + 36 < $dataLen) {
1962
2029
  my $dat = substr($$dataPt, $pos, 36);
1963
2030
  last if $dat eq "\x0" x 36;
1964
- my @a = unpack 'VVVVCVCV', $dat;
2031
+ my @a = unpack 'VVVVaVaV', $dat;
1965
2032
  $$et{DOC_NUM} = ++$$et{DOC_COUNT};
1966
2033
  # 0=1, 1=1, 2=secs, 3=?
1967
2034
  SetGPSDateTime($et, $tagTbl, $a[2]);
1968
2035
  my $lat = $a[5] / 1e3;
1969
2036
  my $lon = $a[7] / 1e3;
1970
- my $deg = int($lat / 100);
1971
- $lat = $deg + ($lat - $deg * 100) / 60;
1972
- $deg = int($lon / 100);
1973
- $lon = $deg + ($lon - $deg * 100) / 60;
1974
- $lat = -$lat if $a[4] eq 'S';
1975
- $lon = -$lon if $a[6] eq 'W';
2037
+ ConvertLatLon($lat, $lon);
2038
+ $lat = -abs($lat) if $a[4] eq 'S';
2039
+ $lon = -abs($lon) if $a[6] eq 'W';
1976
2040
  $et->HandleTag($tagTbl, GPSLatitude => $lat);
1977
2041
  $et->HandleTag($tagTbl, GPSLongitude => $lon);
1978
- $et->HandleTag($tagTbl, GPSSpeed => $a[3] / 1e3);
1979
- $et->HandleTag($tagTbl, GPSSpeedRef => 'K');
2042
+ $et->HandleTag($tagTbl, GPSSpeed => $a[3] / 1e3);
1980
2043
  $pos += 36;
1981
2044
  }
1982
2045
  SetByteOrder('MM');
@@ -2038,9 +2101,9 @@ sub Process_mebx($$$)
2038
2101
 
2039
2102
  # parse using information from 'keys' table (eg. Apple iPhone7+ hevc 'Core Media Data Handler')
2040
2103
  $et->VerboseDir('mebx', undef, length $$dataPt);
2041
- my $pos = 0;
2042
- while ($pos + 8 < length $$dataPt) {
2043
- my $len = Get32u($dataPt, $pos);
2104
+ my ($pos, $len);
2105
+ for ($pos=0; $pos+8<length($$dataPt); $pos+=$len) {
2106
+ $len = Get32u($dataPt, $pos);
2044
2107
  last if $len < 8 or $pos + $len > length $$dataPt;
2045
2108
  my $id = substr($$dataPt, $pos+4, 4);
2046
2109
  my $info = $$ee{'keys'}{$id};
@@ -2063,7 +2126,6 @@ sub Process_mebx($$$)
2063
2126
  } else {
2064
2127
  $et->WarnOnce('No key information for mebx ID ' . PrintableTagID($id,1));
2065
2128
  }
2066
- $pos += $len;
2067
2129
  }
2068
2130
  return 1;
2069
2131
  }
@@ -2119,20 +2181,14 @@ sub Process_gps0($$$)
2119
2181
  my $lat = GetDouble($dataPt, $pos);
2120
2182
  my $lon = GetDouble($dataPt, $pos+8);
2121
2183
  next if abs($lat) > 9000 or abs($lon) > 18000;
2122
- # (note: this method works fine for negative coordinates)
2123
- my $deg = int($lat / 100);
2124
- $lat = $deg + ($lat - $deg * 100) / 60;
2125
- $deg = int($lon / 100);
2126
- $lon = $deg + ($lon - $deg * 100) / 60;
2184
+ ConvertLatLon($lat, $lon);
2127
2185
  my @a = unpack('C*', substr($$dataPt, $pos+22, 6)); # unpack date/time
2128
2186
  $a[0] += 2000;
2129
2187
  $et->HandleTag($tagTbl, GPSDateTime => sprintf("%.4d:%.2d:%.2d %.2d:%.2d:%.2dZ", @a));
2130
2188
  $et->HandleTag($tagTbl, GPSLatitude => $lat);
2131
2189
  $et->HandleTag($tagTbl, GPSLongitude => $lon);
2132
2190
  $et->HandleTag($tagTbl, GPSSpeed => Get16u($dataPt, $pos+0x14));
2133
- $et->HandleTag($tagTbl, GPSSpeedRef => 'K');
2134
2191
  $et->HandleTag($tagTbl, GPSTrack => Get8u($dataPt, $pos+0x1c) * 2); # (NC)
2135
- $et->HandleTag($tagTbl, GPSTrackRef => 'T');
2136
2192
  $et->HandleTag($tagTbl, GPSAltitude => Get32s($dataPt, $pos + 0x10));
2137
2193
  # yet to be decoded:
2138
2194
  # 0x1d - int8u[3] seen: "1 1 0"
@@ -2221,10 +2277,7 @@ sub ProcessRIFFTrailer($$$)
2221
2277
  my $lat = GetDouble(\$buff, $pos+4);
2222
2278
  my $lon = GetDouble(\$buff, $pos+12);
2223
2279
  $et->Warn('Bad gps0 record') and last if abs($lat) > 9000 or abs($lon) > 18000;
2224
- my $deg = int($lat / 100);
2225
- $lat = $deg + ($lat - $deg * 100) / 60;
2226
- $deg = int($lon / 100);
2227
- $lon = $deg + ($lon - $deg * 100) / 60;
2280
+ ConvertLatLon($lat, $lon);
2228
2281
  $lat = -$lat if Get8u(\$buff, $pos+0x21) == 2; # wild guess
2229
2282
  $lon = -$lon if Get8u(\$buff, $pos+0x22) == 2; # wild guess
2230
2283
  my @a = unpack('C*', substr($buff, $pos+26, 6)); # unpack date/time
@@ -2234,9 +2287,7 @@ sub ProcessRIFFTrailer($$$)
2234
2287
  $et->HandleTag($tagTbl, GPSLatitude => $lat);
2235
2288
  $et->HandleTag($tagTbl, GPSLongitude => $lon);
2236
2289
  $et->HandleTag($tagTbl, GPSSpeed => Get16u(\$buff, $pos+0x18) * $knotsToKph);
2237
- $et->HandleTag($tagTbl, GPSSpeedRef => 'K');
2238
2290
  $et->HandleTag($tagTbl, GPSTrack => Get8u(\$buff, $pos+0x20) * 2);
2239
- $et->HandleTag($tagTbl, GPSTrackRef => 'T');
2240
2291
  }
2241
2292
  } elsif ($tag eq 'gsen') {
2242
2293
  # (similar to record decoded in Process_gsen)
@@ -2273,39 +2324,91 @@ sub ProcessNMEA($$$)
2273
2324
  {
2274
2325
  my ($et, $dirInfo, $tagTbl) = @_;
2275
2326
  my $dataPt = $$dirInfo{DataPt};
2276
- # parse only RMC sentence (with leading timestamp) for now
2277
- while ($$dataPt =~ /(?:\[(\d+)\])?\$[A-Z]{2}RMC,(\d{2})(\d{2})(\d+(\.\d*)?),A?,(\d+\.\d+),([NS]),(\d+\.\d+),([EW]),(\d*\.?\d*),(\d*\.?\d*),(\d{2})(\d{2})(\d+)/g) {
2278
- my $tc = $1; # milliseconds since 1970 (local time)
2279
- my ($lat,$latRef,$lon,$lonRef) = ($6,$7,$8,$9);
2280
- my $yr = $14 + ($14 >= 70 ? 1900 : 2000);
2281
- my ($mon,$day,$hr,$min,$sec) = ($13,$12,$2,$3,$4);
2282
- my ($spd, $trk);
2283
- $spd = $10 * $knotsToKph if length $10;
2284
- $trk = $11 if length $11;
2285
- # lat/long are in DDDMM.MMMM format
2286
- my $deg = int($lat / 100);
2287
- $lat = $deg + ($lat - $deg * 100) / 60;
2288
- $deg = int($lon / 100);
2289
- $lon = $deg + ($lon - $deg * 100) / 60;
2290
- $sec = '0' . $sec unless $sec =~ /^\d{2}/; # pad integer part of seconds to 2 digits
2291
- my $time = sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%sZ',$yr,$mon,$day,$hr,$min,$sec);
2292
- my $sampleTime;
2293
- $sampleTime = ($tc - $$et{StartTime}) / 1000 if $tc and $$et{StartTime};
2294
- FoundSomething($et, $tagTbl, $sampleTime);
2295
- $et->HandleTag($tagTbl, GPSDateTime => $time);
2296
- $et->HandleTag($tagTbl, GPSLatitude => $lat * ($latRef eq 'S' ? -1 : 1));
2297
- $et->HandleTag($tagTbl, GPSLongitude => $lon * ($lonRef eq 'W' ? -1 : 1));
2298
- if (defined $spd) {
2299
- $et->HandleTag($tagTbl, GPSSpeed => $spd);
2300
- $et->HandleTag($tagTbl, GPSSpeedRef => 'K');
2327
+ my ($rtnVal, %fix);
2328
+ # parse only RMC and GGA sentence [with leading timecode] for now
2329
+ for (;;) {
2330
+ my ($tc, $type, $tim);
2331
+ if ($$dataPt =~ /(?:\[(\d+)\])?\$[A-Z]{2}(RMC|GGA),(\d{2}\d{2}\d+(\.\d*)?),/g) {
2332
+ ($tc, $type, $tim) = ($1, $2, $3);
2333
+ }
2334
+ # write out last fix now if complete
2335
+ # (use the GPS timestamps because they may be different for the same timecode)
2336
+ if ($fix{tim} and (not $tim or $fix{tim} != $tim)) {
2337
+ if ($fix{dat} and defined $fix{lat} and defined $fix{lon}) {
2338
+ my $sampleTime;
2339
+ $sampleTime = ($fix{tc} - $$et{StartTime}) / 1000 if $fix{tc} and $$et{StartTime};
2340
+ FoundSomething($et, $tagTbl, $sampleTime);
2341
+ $et->HandleTag($tagTbl, GPSDateTime => $fix{dat});
2342
+ $et->HandleTag($tagTbl, GPSLatitude => $fix{lat});
2343
+ $et->HandleTag($tagTbl, GPSLongitude => $fix{lon});
2344
+ $et->HandleTag($tagTbl, GPSSpeed => $fix{spd} * $knotsToKph) if defined $fix{spd};
2345
+ $et->HandleTag($tagTbl, GPSTrack => $fix{trk}) if defined $fix{trk};
2346
+ $et->HandleTag($tagTbl, GPSAltitude => $fix{alt}) if defined $fix{alt};
2347
+ $et->HandleTag($tagTbl, GPSSatellites=> $fix{nsats}+0) if defined $fix{nsats};
2348
+ $et->HandleTag($tagTbl, GPSDOP => $fix{hdop}) if defined $fix{hdop};
2349
+ }
2350
+ undef %fix;
2301
2351
  }
2302
- if (defined $trk) {
2303
- $et->HandleTag($tagTbl, GPSTrack => $trk);
2304
- $et->HandleTag($tagTbl, GPSTrackRef => 'T');
2352
+ $fix{tim} = $tim or last;
2353
+ my $pos = pos($$dataPt);
2354
+ pos($$dataPt) = $pos - length($tim) - 1; # rewind to re-parse time
2355
+ # (parsing of NMEA strings copied from Geotag.pm)
2356
+ if ($type eq 'RMC' and
2357
+ $$dataPt =~ /\G(\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+)/g)
2358
+ {
2359
+ my $year = $15 + ($15 >= 70 ? 1900 : 2000);
2360
+ $fix{tc} = $tc; # use timecode of RMC sentence
2361
+ $fix{dat} = sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%sZ',$year,$14,$13,$1,$2,$3);
2362
+ $fix{lat} = (($5 || 0) + $6/60) * ($7 eq 'N' ? 1 : -1);
2363
+ $fix{lon} = (($8 || 0) + $9/60) * ($10 eq 'E' ? 1 : -1);
2364
+ $fix{spd} = $11 if length $11;
2365
+ $fix{trk} = $12 if length $12;
2366
+ } elsif ($type eq 'GGA' and
2367
+ $$dataPt =~ /\G(\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?/g)
2368
+ {
2369
+ $fix{lat} = (($5 || 0) + $6/60) * ($7 eq 'N' ? 1 : -1);
2370
+ $fix{lon} = (($8 || 0) + $9/60) * ($10 eq 'E' ? 1 : -1);
2371
+ @fix{qw(nsats hdop alt)} = ($11,$12,$13);
2372
+ } else {
2373
+ pos($$dataPt) = $pos; # continue searching from our last match
2305
2374
  }
2306
2375
  }
2307
2376
  delete $$et{DOC_NUM};
2308
- return 1;
2377
+ return $rtnVal;
2378
+ }
2379
+
2380
+ #------------------------------------------------------------------------------
2381
+ # Process 'gps ' or 'udat' atom possibly containing NMEA (ref PH)
2382
+ # Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
2383
+ # Returns: 1 on success
2384
+ sub ProcessGPSLog($$$)
2385
+ {
2386
+ my ($et, $dirInfo, $tagTbl) = @_;
2387
+ my $dataPt = $$dirInfo{DataPt};
2388
+ my ($rtnVal, @a);
2389
+
2390
+ # try NMEA format first
2391
+ return 1 if ProcessNMEA($et,$dirInfo,$tagTbl);
2392
+
2393
+ # DENVER ACG-8050WMK2 format looks like this:
2394
+ # 210318073213[1][N][52200970][E][006362321][+00152][100][00140][C000000]+000+000+000+000+000+000+000+000+000+000+000+000+000+000+000+000+000+000
2395
+ # YYMMDDHHMMSS A? NS lat EW lon alt kph dir kCal accel
2396
+ while ($$dataPt =~ /\b(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})\[1\]\[([NS])\]\[(\d{8})\]\[([EW])\]\[(\d{9})\]\[([-+]?\d*)\]\[(\d*)\]\[(\d*)\]\[C?(\d*)\](([-+]\d{3})+)/g) {
2397
+ my $lat = substr( $8,0,2) + substr( $8,2) / 600000;
2398
+ my $lon = substr($10,0,3) + substr($10,3) / 600000;
2399
+ $$et{DOC_NUM} = ++$$et{DOC_COUNT};
2400
+ $et->HandleTag($tagTbl, GPSDateTime => "20$1:$2:$3 $4:$5:$6Z");
2401
+ $et->HandleTag($tagTbl, GPSLatitude => $lat * ($7 eq 'S' ? -1 : 1));
2402
+ $et->HandleTag($tagTbl, GPSLongitude => $lon * ($9 eq 'W' ? -1 : 1));
2403
+ $et->HandleTag($tagTbl, GPSAltitude => $11 / 10) if length $11;
2404
+ $et->HandleTag($tagTbl, GPSSpeed => $12 + 0) if length $12;
2405
+ $et->HandleTag($tagTbl, GPSTrack => $13 + 0) if length $13;
2406
+ $et->HandleTag($tagTbl, KiloCalories => $14 / 10) if length $14;
2407
+ $et->HandleTag($tagTbl, Accelerometer=> $15) if length $15;
2408
+ $rtnVal = 1;
2409
+ }
2410
+ delete $$et{DOC_NUM};
2411
+ return $rtnVal;
2309
2412
  }
2310
2413
 
2311
2414
  #------------------------------------------------------------------------------
@@ -2396,9 +2499,7 @@ sub ProcessTTAD($$$)
2396
2499
  $et->HandleTag($tagTbl, GPSLongitude => GetDouble($dataPt, $pos+0x24));
2397
2500
  $et->HandleTag($tagTbl, GPSAltitude => GetDouble($dataPt, $pos+0x14));
2398
2501
  $et->HandleTag($tagTbl, GPSSpeed => GetDouble($dataPt, $pos+0x0c) * $mpsToKph);
2399
- $et->HandleTag($tagTbl, GPSSpeedRef => 'K');
2400
2502
  $et->HandleTag($tagTbl, GPSTrack => GetDouble($dataPt, $pos+0x30));
2401
- $et->HandleTag($tagTbl, GPSTrackRef => 'T');
2402
2503
  if ($unknown) {
2403
2504
  my @a = map { GetDouble($dataPt, $pos+0x38+8*$_) } 0..2;
2404
2505
  $et->HandleTag($tagTbl, Unknown03 => "@a");
@@ -2518,21 +2619,26 @@ sub ProcessInsta360($;$)
2518
2619
  my $tmp = substr($buff, $p, $dlen);
2519
2620
  my @a = unpack('VVvaa8aa8aa8a8a8', $tmp);
2520
2621
  next unless $a[3] eq 'A'; # (ignore void fixes)
2521
- last unless ($a[5] eq 'N' or $a[5] eq 'S') and # (quick validation)
2522
- ($a[7] eq 'E' or $a[7] eq 'W');
2622
+ unless (($a[5] eq 'N' or $a[5] eq 'S') and # (quick validation)
2623
+ ($a[7] eq 'E' or $a[7] eq 'W' or
2624
+ # (odd, but I've seen "O" instead of "W". Perhaps
2625
+ # when the language is french? ie. "Ouest"?)
2626
+ $a[7] eq 'O'))
2627
+ {
2628
+ $et->Warn('Unrecognized INSV GPS format');
2629
+ last;
2630
+ }
2523
2631
  $$et{DOC_NUM} = ++$$et{DOC_COUNT};
2524
2632
  $a[$_] = GetDouble(\$a[$_], 0) foreach 4,6,8,9,10;
2525
2633
  $a[4] = -abs($a[4]) if $a[5] eq 'S'; # (abs just in case it was already signed)
2526
- $a[6] = -abs($a[6]) if $a[7] eq 'W';
2527
- $et->HandleTag($tagTbl, GPSDateTime => Image::ExifTool::ConvertUnixTime($a[0]) . 'Z');
2528
- $et->HandleTag($tagTbl, GPSLatitude => $a[4]);
2634
+ $a[6] = -abs($a[6]) if $a[7] ne 'E';
2635
+ $et->HandleTag($tagTbl, GPSDateTime => Image::ExifTool::ConvertUnixTime($a[0]) . 'Z');
2636
+ $et->HandleTag($tagTbl, GPSLatitude => $a[4]);
2529
2637
  $et->HandleTag($tagTbl, GPSLongitude => $a[6]);
2530
- $et->HandleTag($tagTbl, GPSSpeed => $a[8] * $mpsToKph);
2531
- $et->HandleTag($tagTbl, GPSSpeedRef => 'K');
2532
- $et->HandleTag($tagTbl, GPSTrack => $a[9]);
2533
- $et->HandleTag($tagTbl, GPSTrackRef => 'T');
2534
- $et->HandleTag($tagTbl, GPSAltitude => $a[10]);
2535
- $et->HandleTag($tagTbl, Unknown02 => "@a[1,2]") if $unknown; # millisecond counter (https://exiftool.org/forum/index.php?topic=9884.msg65143#msg65143)
2638
+ $et->HandleTag($tagTbl, GPSSpeed => $a[8] * $mpsToKph);
2639
+ $et->HandleTag($tagTbl, GPSTrack => $a[9]);
2640
+ $et->HandleTag($tagTbl, GPSAltitude => $a[10]);
2641
+ $et->HandleTag($tagTbl, Unknown02 => "@a[1,2]") if $unknown; # millisecond counter (https://exiftool.org/forum/index.php?topic=9884.msg65143#msg65143)
2536
2642
  }
2537
2643
  }
2538
2644
  } elsif ($id == 0x101) {
@@ -2588,6 +2694,41 @@ sub Process360Fly($$$)
2588
2694
  return 1;
2589
2695
  }
2590
2696
 
2697
+ #------------------------------------------------------------------------------
2698
+ # Process GPS from Vantrue N2S dashcam
2699
+ # Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
2700
+ # Returns: 1 on success
2701
+ sub ProcessFMAS($$$)
2702
+ {
2703
+ my ($et, $dirInfo, $tagTbl) = @_;
2704
+ my $dataPt = $$dirInfo{DataPt};
2705
+ return 0 unless $$dataPt =~ /^FMAS\0\0\0\0.{72}SAMM.{36}A/s and length($$dataPt) >= 160;
2706
+ $et->VerboseDir('FMAS', undef, length($$dataPt));
2707
+ # 0000: 46 4d 41 53 00 00 00 00 00 00 00 00 00 00 00 00 [FMAS............]
2708
+ # 0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
2709
+ # 0020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
2710
+ # 0030: 02 08 01 08 06 08 02 04 07 02 06 00 00 00 00 00 [................]
2711
+ # 0040: 00 00 00 00 00 00 00 00 4f 46 4e 49 4d 4d 41 53 [........OFNIMMAS]
2712
+ # 0050: 53 41 4d 4d 01 00 00 00 00 00 00 00 00 00 00 00 [SAMM............]
2713
+ # 0060: e5 07 09 18 08 00 22 00 02 00 00 00 a1 82 8a bf [......".........]
2714
+ # 0070: 89 23 8e bd 0b 2c 30 bc 41 57 4e 51 16 00 a1 01 [.#...,0.AWNQ....]
2715
+ # 0080: 29 26 27 0c 4b 00 49 00 00 00 00 00 00 00 00 00 [)&'.K.I.........]
2716
+ # 0090: 00 00 00 00 00 00 00 00 00 52 00 00 00 00 00 00 [.........R......]
2717
+ my @a = unpack('x96vCCCCCCx16AAACCCvCCvvv',$$dataPt);
2718
+ SetByteOrder('II');
2719
+ my $acc = ReadValue($dataPt, 0x6c, 'float', 3); # (looks like Z comes first in my sample)
2720
+ my $lon = $a[10] + ($a[11] + $a[13]/6000) / 60; # (why zero byte at $a[12]?)
2721
+ my $lat = $a[14] + ($a[15] + $a[16]/6000) / 60;
2722
+ $et->HandleTag($tagTbl, GPSDateTime => sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%.2d', @a[0..5]));
2723
+ $et->HandleTag($tagTbl, GPSLatitude => $lat * ($a[9] eq 'S' ? -1 : 1));
2724
+ $et->HandleTag($tagTbl, GPSLongitude => $lon * ($a[8] eq 'W' ? -1 : 1));
2725
+ $et->HandleTag($tagTbl, GPSSpeed => $a[17] * $mphToKph); # convert mph -> kph
2726
+ $et->HandleTag($tagTbl, GPSTrack => $a[18]);
2727
+ $et->HandleTag($tagTbl, Accelerometer=> $acc);
2728
+ SetByteOrder('MM');
2729
+ return 1;
2730
+ }
2731
+
2591
2732
  #------------------------------------------------------------------------------
2592
2733
  # Scan media data for "freeGPS" metadata if not found already (ref PH)
2593
2734
  # Inputs: 0) ExifTool ref
@@ -2661,7 +2802,7 @@ sub ScanMediaData($)
2661
2802
  $buf2 = substr($buff, $len);
2662
2803
  }
2663
2804
  if ($tagTbl) {
2664
- $$et{DOC_NUM} = 0;
2805
+ $$et{DOC_NUM} = 0; # reset DOC_NUM after extracting embedded metadata
2665
2806
  $et->VPrint(0, "--------------------------\n");
2666
2807
  SetByteOrder($oldByteOrder);
2667
2808
  $$et{INDENT} = substr $$et{INDENT}, 0, -2;