exiftool_vendored 12.17.1 → 12.32.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/bin/Changes +225 -1
  3. data/bin/MANIFEST +23 -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 +2 -9
  12. data/bin/exiftool +142 -87
  13. data/bin/fmt_files/gpx.fmt +2 -2
  14. data/bin/fmt_files/gpx_wpt.fmt +2 -2
  15. data/bin/fmt_files/kml.fmt +1 -1
  16. data/bin/fmt_files/kml_track.fmt +1 -1
  17. data/bin/lib/Image/ExifTool/Apple.pm +3 -2
  18. data/bin/lib/Image/ExifTool/BuildTagLookup.pm +30 -12
  19. data/bin/lib/Image/ExifTool/CBOR.pm +277 -0
  20. data/bin/lib/Image/ExifTool/Canon.pm +49 -18
  21. data/bin/lib/Image/ExifTool/DJI.pm +6 -6
  22. data/bin/lib/Image/ExifTool/DPX.pm +13 -2
  23. data/bin/lib/Image/ExifTool/DjVu.pm +6 -5
  24. data/bin/lib/Image/ExifTool/Exif.pm +28 -11
  25. data/bin/lib/Image/ExifTool/FITS.pm +13 -2
  26. data/bin/lib/Image/ExifTool/FlashPix.pm +35 -10
  27. data/bin/lib/Image/ExifTool/FujiFilm.pm +19 -8
  28. data/bin/lib/Image/ExifTool/GPS.pm +22 -11
  29. data/bin/lib/Image/ExifTool/Geotag.pm +13 -2
  30. data/bin/lib/Image/ExifTool/GoPro.pm +16 -1
  31. data/bin/lib/Image/ExifTool/ICC_Profile.pm +2 -2
  32. data/bin/lib/Image/ExifTool/ID3.pm +15 -3
  33. data/bin/lib/Image/ExifTool/JPEG.pm +74 -4
  34. data/bin/lib/Image/ExifTool/JSON.pm +27 -4
  35. data/bin/lib/Image/ExifTool/Jpeg2000.pm +393 -16
  36. data/bin/lib/Image/ExifTool/LIF.pm +153 -0
  37. data/bin/lib/Image/ExifTool/Lang/nl.pm +60 -59
  38. data/bin/lib/Image/ExifTool/M2TS.pm +137 -5
  39. data/bin/lib/Image/ExifTool/MIE.pm +4 -3
  40. data/bin/lib/Image/ExifTool/MRC.pm +341 -0
  41. data/bin/lib/Image/ExifTool/MWG.pm +3 -3
  42. data/bin/lib/Image/ExifTool/MXF.pm +1 -1
  43. data/bin/lib/Image/ExifTool/MacOS.pm +1 -1
  44. data/bin/lib/Image/ExifTool/Microsoft.pm +298 -82
  45. data/bin/lib/Image/ExifTool/Nikon.pm +19 -8
  46. data/bin/lib/Image/ExifTool/NikonSettings.pm +28 -11
  47. data/bin/lib/Image/ExifTool/Olympus.pm +6 -3
  48. data/bin/lib/Image/ExifTool/Other.pm +93 -0
  49. data/bin/lib/Image/ExifTool/PDF.pm +9 -12
  50. data/bin/lib/Image/ExifTool/PNG.pm +8 -7
  51. data/bin/lib/Image/ExifTool/Panasonic.pm +28 -3
  52. data/bin/lib/Image/ExifTool/Pentax.pm +28 -5
  53. data/bin/lib/Image/ExifTool/PhaseOne.pm +4 -3
  54. data/bin/lib/Image/ExifTool/Photoshop.pm +6 -0
  55. data/bin/lib/Image/ExifTool/QuickTime.pm +247 -88
  56. data/bin/lib/Image/ExifTool/QuickTimeStream.pl +283 -141
  57. data/bin/lib/Image/ExifTool/README +3 -0
  58. data/bin/lib/Image/ExifTool/RIFF.pm +89 -12
  59. data/bin/lib/Image/ExifTool/Samsung.pm +48 -10
  60. data/bin/lib/Image/ExifTool/Shortcuts.pm +9 -0
  61. data/bin/lib/Image/ExifTool/Sony.pm +237 -78
  62. data/bin/lib/Image/ExifTool/TagInfoXML.pm +1 -0
  63. data/bin/lib/Image/ExifTool/TagLookup.pm +4125 -4028
  64. data/bin/lib/Image/ExifTool/TagNames.pod +644 -286
  65. data/bin/lib/Image/ExifTool/Torrent.pm +18 -11
  66. data/bin/lib/Image/ExifTool/WriteExif.pl +1 -1
  67. data/bin/lib/Image/ExifTool/WriteIPTC.pl +1 -1
  68. data/bin/lib/Image/ExifTool/WritePDF.pl +1 -0
  69. data/bin/lib/Image/ExifTool/WritePNG.pl +2 -0
  70. data/bin/lib/Image/ExifTool/WritePostScript.pl +1 -0
  71. data/bin/lib/Image/ExifTool/WriteQuickTime.pl +55 -21
  72. data/bin/lib/Image/ExifTool/WriteXMP.pl +7 -3
  73. data/bin/lib/Image/ExifTool/Writer.pl +47 -10
  74. data/bin/lib/Image/ExifTool/XMP.pm +39 -14
  75. data/bin/lib/Image/ExifTool/XMP2.pl +2 -1
  76. data/bin/lib/Image/ExifTool/XMPStruct.pl +3 -1
  77. data/bin/lib/Image/ExifTool/ZISRAW.pm +121 -2
  78. data/bin/lib/Image/ExifTool.pm +223 -72
  79. data/bin/lib/Image/ExifTool.pod +114 -93
  80. data/bin/perl-Image-ExifTool.spec +43 -42
  81. data/lib/exiftool_vendored/version.rb +1 -1
  82. metadata +28 -13
@@ -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)
@@ -65,8 +66,9 @@ my @dateMax = ( 24, 59, 59, 2200, 12, 31 );
65
66
  my $gpsBlockSize = 0x8000;
66
67
 
67
68
  # conversion factors
68
- my $knotsToKph = 1.852; # knots --> km/h
69
- my $mpsToKph = 3.6; # m/s --> km/h
69
+ my $knotsToKph = 1.852; # knots --> km/h
70
+ my $mpsToKph = 3.6; # m/s --> km/h
71
+ my $mphToKph = 1.60934; # mph --> km/h
70
72
 
71
73
  # handler types to process based on MetaFormat/OtherFormat
72
74
  my %processByMetaFormat = (
@@ -94,18 +96,18 @@ my %insvLimit = (
94
96
  %Image::ExifTool::QuickTime::Stream = (
95
97
  GROUPS => { 2 => 'Location' },
96
98
  NOTES => q{
97
- Timed metadata extracted from QuickTime media data and some AVI videos when
98
- the ExtractEmbedded option is used. Although most of these tags are
99
- combined into the single table below, ExifTool currently reads 49 different
100
- 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.
101
103
  },
102
104
  VARS => { NO_ID => 1 },
103
105
  GPSLatitude => { PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")', RawConv => '$$self{FoundGPSLatitude} = 1; $val' },
104
106
  GPSLongitude => { PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")' },
105
107
  GPSAltitude => { PrintConv => '(sprintf("%.4f", $val) + 0) . " m"' }, # round to 4 decimals
106
- 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' },
107
109
  GPSSpeedRef => { PrintConv => { K => 'km/h', M => 'mph', N => 'knots' } },
108
- GPSTrack => { PrintConv => 'sprintf("%.4f", $val) + 0' }, # round to 4 decimals
110
+ GPSTrack => { PrintConv => 'sprintf("%.4f", $val) + 0', Notes => 'true north unless GPSTrackRef says otherwise' },
109
111
  GPSTrackRef => { PrintConv => { M => 'Magnetic North', T => 'True North' } },
110
112
  GPSDateTime => {
111
113
  Groups => { 2 => 'Time' },
@@ -139,6 +141,7 @@ my %insvLimit = (
139
141
  SampleTime => { Groups => { 2 => 'Video' }, PrintConv => 'ConvertDuration($val)', Notes => 'sample decoding time' },
140
142
  SampleDuration=>{ Groups => { 2 => 'Video' }, PrintConv => 'ConvertDuration($val)' },
141
143
  UserLabel => { Groups => { 2 => 'Other' } },
144
+ KiloCalories => { Groups => { 2 => 'Other' } },
142
145
  SampleDateTime => {
143
146
  Groups => { 2 => 'Time' },
144
147
  ValueConv => q{
@@ -166,15 +169,29 @@ my %insvLimit = (
166
169
  },
167
170
  },
168
171
  gpmd => [{
169
- Name => 'gpmd_GoPro',
170
- Condition => '$$valPt !~ /^\0\0\xf2\xe1\xf0\xeeTT/',
171
- 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
+ },
172
178
  },{
173
179
  Name => 'gpmd_Rove', # Rove Stealth 4K encrypted text
180
+ Condition => '$$valPt =~ /^\0\0\xf2\xe1\xf0\xeeTT/',
174
181
  SubDirectory => {
175
182
  TagTable => 'Image::ExifTool::QuickTime::Stream',
176
183
  ProcessProc => \&Process_text,
177
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' },
178
195
  }],
179
196
  fdsc => {
180
197
  Name => 'fdsc',
@@ -406,7 +423,14 @@ my %insvLimit = (
406
423
  Groups => { 2 => 'Time' },
407
424
  Format => 'double',
408
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
409
429
  ValueConv => q{
430
+ my $offset = 315964800;
431
+ if ($$self{CreateDate} and $$self{CreateDate} - $val > 24 * 3600 * 365 * 5) {
432
+ $val += $offset;
433
+ }
410
434
  my $str = ConvertUnixTime($val);
411
435
  my $frac = $val - int($val);
412
436
  if ($frac != 0) {
@@ -885,14 +909,8 @@ sub Process_text($$$)
885
909
  $tags{GPSDateTime} = $dateTime;
886
910
  $tags{GPSLatitude} = (($4 || 0) + $5/60) * ($6 eq 'N' ? 1 : -1);
887
911
  $tags{GPSLongitude} = (($7 || 0) + $8/60) * ($9 eq 'E' ? 1 : -1);
888
- if (length $10) {
889
- $tags{GPSSpeed} = $10 * $knotsToKph;
890
- $tags{GPSSpeedRef} = 'K';
891
- }
892
- if (length $11) {
893
- $tags{GPSTrack} = $11;
894
- $tags{GPSTrackRef} = 'T';
895
- }
912
+ $tags{GPSSpeed} = $10 * $knotsToKph if length $10;
913
+ $tags{GPSTrack} = $11 if length $11;
896
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) {
897
915
  my $time = "$1:$2:$3";
898
916
  if ($$et{LastTime}) {
@@ -972,10 +990,7 @@ sub Process_text($$$)
972
990
  $val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$dataPt, 0x39, 5)));
973
991
  $tags{GPSAltitude} = $val + 0 if $val =~ /^[-+]\d+$/;
974
992
  $val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$dataPt, 0x3e, 3)));
975
- if ($val =~ /^\d+$/) {
976
- $tags{GPSSpeed} = $val + 0;
977
- $tags{GPSSpeedRef} = 'K';
978
- }
993
+ $tags{GPSSpeed} = $val + 0 if $val =~ /^\d+$/;
979
994
  if ($$dataPt =~ /^\0\0..\xaa\xaa/s) { # (BlueSkySea)
980
995
  $val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$dataPt, 0xad, 12)));
981
996
  # the first X,Y,Z accelerometer readings from the AccelerometerData
@@ -1006,10 +1021,7 @@ sub Process_text($$$)
1006
1021
  $tags{GPSLatitude} = $2;
1007
1022
  $tags{GPSLongitude} = $1;
1008
1023
  $tags{GPSAltitude} = $1 if $$dataPt =~ /,\s*H\s+([-+]?\d+\.?\d*)m/;
1009
- if ($$dataPt =~ /,\s*H.S\s+([-+]?\d+\.?\d*)/) {
1010
- $tags{GPSSpeed} = $1 * $mpsToKph;
1011
- $tags{GPSSpeedRef} = 'K';
1012
- }
1024
+ $tags{GPSSpeed} = $1 * $mpsToKph if $$dataPt =~ /,\s*H.S\s+([-+]?\d+\.?\d*)/;
1013
1025
  $tags{Distance} = $1 * $mpsToKph if $$dataPt =~ /,\s*D\s+(\d+\.?\d*)m/;
1014
1026
  $tags{VerticalSpeed} = $1 if $$dataPt =~ /,\s*V.S\s+([-+]?\d+\.?\d*)/;
1015
1027
  $tags{FNumber} = $1 if $$dataPt =~ /\bF\/(\d+\.?\d*)/;
@@ -1070,14 +1082,8 @@ sub Process_text($$$)
1070
1082
  $tags{GPSDateTime} = sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%.2dZ', $year, $14, $13, $1, $2, $3);
1071
1083
  $tags{GPSLatitude} = (($5 || 0) + $6/60) * ($7 eq 'N' ? 1 : -1);
1072
1084
  $tags{GPSLongitude} = (($8 || 0) + $9/60) * ($10 eq 'E' ? 1 : -1);
1073
- if (length $11) {
1074
- $tags{GPSSpeed} = $11 * $knotsToKph;
1075
- $tags{GPSSpeedRef} = 'K';
1076
- }
1077
- if (length $12) {
1078
- $tags{GPSTrack} = $12;
1079
- $tags{GPSTrackRef} = 'T';
1080
- }
1085
+ $tags{GPSSpeed} = $11 * $knotsToKph if length $11;
1086
+ $tags{GPSTrack} = $12 if length $12;
1081
1087
  }
1082
1088
  $tags{GSensor} = $1 if $$dataPt =~ /\bgsensori,(.*?)(;|$)/;
1083
1089
  $tags{Car} = $1 if $$dataPt =~ /\bCAR,(.*?)(;|$)/;
@@ -1214,7 +1220,10 @@ sub ProcessSamples($)
1214
1220
  $et->VPrint(1, "${hdr}, Sample ".($i+1).' of '.scalar(@$start)." ($size bytes)\n");
1215
1221
  $et->VerboseDump(\$buff, Addr => $$start[$i]);
1216
1222
  }
1217
- 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
+ {
1218
1227
 
1219
1228
  FoundSomething($et, $tagTbl, $time[$i], $dur[$i]);
1220
1229
  unless ($buff =~ /^\$BEGIN/) {
@@ -1249,8 +1258,7 @@ sub ProcessSamples($)
1249
1258
  next if length($buff) < 20 + $n;
1250
1259
  $et->HandleTag($tagTbl, GPSLatitude => Get32s(\$buff, 12+$n) * 180/0x80000000);
1251
1260
  $et->HandleTag($tagTbl, GPSLongitude => Get32s(\$buff, 16+$n) * 180/0x80000000);
1252
- $et->HandleTag($tagTbl, GPSSpeed => Get16u(\$buff, 8+$n));
1253
- $et->HandleTag($tagTbl, GPSSpeedRef => 'M');
1261
+ $et->HandleTag($tagTbl, GPSSpeed => Get16u(\$buff, 8+$n) * $mphToKph);
1254
1262
  SetGPSDateTime($et, $tagTbl, $time[$i]);
1255
1263
  next; # all done (don't store/process as text)
1256
1264
  }
@@ -1323,6 +1331,19 @@ sub ProcessSamples($)
1323
1331
  $$et{HandlerType} = $$et{HanderDesc} = '';
1324
1332
  }
1325
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
+
1326
1347
  #------------------------------------------------------------------------------
1327
1348
  # Process "freeGPS " data blocks referenced by a 'gps ' (GPSDataList) atom
1328
1349
  # Inputs: 0) ExifTool ref, 1) dirInfo ref {DataPt,SampleTime,SampleDuration}, 2) tagTable ref
@@ -1334,7 +1355,7 @@ sub ProcessFreeGPS($$$)
1334
1355
  my ($et, $dirInfo, $tagTbl) = @_;
1335
1356
  my $dataPt = $$dirInfo{DataPt};
1336
1357
  my $dirLen = length $$dataPt;
1337
- my ($yr, $mon, $day, $hr, $min, $sec, $stat, $lbl);
1358
+ my ($yr, $mon, $day, $hr, $min, $sec, $stat, $lbl, $ddd);
1338
1359
  my ($lat, $latRef, $lon, $lonRef, $spd, $trk, $alt, @acc, @xtra);
1339
1360
 
1340
1361
  return 0 if $dirLen < 92;
@@ -1424,7 +1445,7 @@ sub ProcessFreeGPS($$$)
1424
1445
  map { $_ = $_ - 4294967296 if $_ >= 0x80000000; $_ /= 256 } @acc;
1425
1446
  }
1426
1447
 
1427
- } elsif ($$dataPt =~ /^.{40}A([NS])([EW])/s) {
1448
+ } elsif ($$dataPt =~ /^.{37}\0\0\0A([NS])([EW])/s) {
1428
1449
 
1429
1450
  # decode freeGPS from ViofoA119v3 dashcam (similar to Novatek GPS format)
1430
1451
  # 0000: 00 00 40 00 66 72 65 65 47 50 53 20 f0 03 00 00 [..@.freeGPS ....]
@@ -1441,6 +1462,34 @@ sub ProcessFreeGPS($$$)
1441
1462
  $trk = GetFloat($dataPt, 0x38);
1442
1463
  SetByteOrder('MM');
1443
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
+
1444
1493
  } elsif ($$dataPt =~ /^.{60}A\0{3}.{4}([NS])\0{3}.{4}([EW])\0{3}/s) {
1445
1494
 
1446
1495
  # decode freeGPS from Akaso dashcam
@@ -1461,6 +1510,23 @@ sub ProcessFreeGPS($$$)
1461
1510
  $trk -= 360 if $trk >= 360;
1462
1511
  SetByteOrder('MM');
1463
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
+
1464
1530
  } elsif ($$dataPt =~ /^.{16}YndAkasoCar/s) {
1465
1531
 
1466
1532
  # Akaso V1 dascham
@@ -1479,13 +1545,17 @@ sub ProcessFreeGPS($$$)
1479
1545
  return 0 unless $stat eq 'A' and ($latRef eq 'N' or $latRef eq 'S') and
1480
1546
  ($lonRef eq 'E' or $lonRef eq 'W');
1481
1547
 
1482
- $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');
1483
1549
  # (see https://exiftool.org/forum/index.php?topic=11320.0)
1484
- return 1;
1485
1550
 
1486
1551
  SetByteOrder('II');
1552
+
1553
+ $spd = GetFloat($dataPt, 0x60);
1554
+ $trk = GetFloat($dataPt, 0x64) + 180; # (why is this off by 180?)
1487
1555
  $lat = GetDouble($dataPt, 0x50); # latitude is here, but encrypted somehow
1488
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
+
1489
1559
  SetByteOrder('MM');
1490
1560
  #my $serialNum = substr($$dataPt, 0x68, 20);
1491
1561
 
@@ -1549,10 +1619,7 @@ sub ProcessFreeGPS($$$)
1549
1619
  #
1550
1620
  FoundSomething($et, $tagTbl, $$dirInfo{SampleTime}, $$dirInfo{SampleDuration});
1551
1621
  # lat/long are in DDDMM.MMMM format
1552
- my $deg = int($lat / 100);
1553
- $lat = $deg + ($lat - $deg * 100) / 60;
1554
- $deg = int($lon / 100);
1555
- $lon = $deg + ($lon - $deg * 100) / 60;
1622
+ ConvertLatLon($lat, $lon) unless $ddd;
1556
1623
  $sec = '0' . $sec unless $sec =~ /^\d{2}/; # pad integer part of seconds to 2 digits
1557
1624
  if (defined $yr) {
1558
1625
  my $time = sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%sZ',$yr,$mon,$day,$hr,$min,$sec);
@@ -1564,14 +1631,8 @@ sub ProcessFreeGPS($$$)
1564
1631
  $et->HandleTag($tagTbl, GPSLatitude => $lat * ($latRef eq 'S' ? -1 : 1));
1565
1632
  $et->HandleTag($tagTbl, GPSLongitude => $lon * ($lonRef eq 'W' ? -1 : 1));
1566
1633
  $et->HandleTag($tagTbl, GPSAltitude => $alt) if defined $alt;
1567
- if (defined $spd) {
1568
- $et->HandleTag($tagTbl, GPSSpeed => $spd);
1569
- $et->HandleTag($tagTbl, GPSSpeedRef => 'K');
1570
- }
1571
- if (defined $trk) {
1572
- $et->HandleTag($tagTbl, GPSTrack => $trk);
1573
- $et->HandleTag($tagTbl, GPSTrackRef => 'T');
1574
- }
1634
+ $et->HandleTag($tagTbl, GPSSpeed => $spd) if defined $spd;
1635
+ $et->HandleTag($tagTbl, GPSTrack => $trk) if defined $trk;
1575
1636
  while (@xtra) {
1576
1637
  my $tag = shift @xtra;
1577
1638
  $et->HandleTag($tagTbl, $tag => shift @xtra);
@@ -1690,9 +1751,7 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
1690
1751
  $et->HandleTag($tagTbl, GPSLatitude => Get32s(\$b, 0x10) / 1e7);
1691
1752
  $et->HandleTag($tagTbl, GPSLongitude => Get32s(\$b, 0x18) / 1e7);
1692
1753
  $et->HandleTag($tagTbl, GPSSpeed => Get32s(\$b, 0x20) / 100 * $mpsToKph);
1693
- $et->HandleTag($tagTbl, GPSSpeedRef => 'K');
1694
1754
  $et->HandleTag($tagTbl, GPSTrack => $trk);
1695
- $et->HandleTag($tagTbl, GPSTrackRef => 'T');
1696
1755
  $et->HandleTag($tagTbl, GPSAltitude => Get32s(\$b, 0x28) / 1000);
1697
1756
  $lastRecPos = $recPos;
1698
1757
  $foundNew = 1;
@@ -1802,6 +1861,27 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
1802
1861
  $spd = GetFloat($dataPt, 0x50);
1803
1862
  $trk = GetFloat($dataPt, 0x54);
1804
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
+
1805
1885
  } else {
1806
1886
 
1807
1887
  # (look for binary GPS as stored by NextBase 512G, ref PH)
@@ -1843,9 +1923,7 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
1843
1923
  $et->HandleTag($tagTbl, GPSLatitude => $lat);
1844
1924
  $et->HandleTag($tagTbl, GPSLongitude => $lon);
1845
1925
  $et->HandleTag($tagTbl, GPSSpeed => $spd / 100 * $mpsToKph);
1846
- $et->HandleTag($tagTbl, GPSSpeedRef => 'K');
1847
1926
  $et->HandleTag($tagTbl, GPSTrack => $trk);
1848
- $et->HandleTag($tagTbl, GPSTrackRef => 'T');
1849
1927
  last if $pos += 0x20 > length($$dataPt) - 0x1e;
1850
1928
  }
1851
1929
  return $$et{DOC_NUM} ? 1 : 0; # return 0 if nothing extracted
@@ -1858,23 +1936,12 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
1858
1936
  $yr += 2000 if $yr < 2000;
1859
1937
  my $time = sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%.2dZ', $yr, $mon, $day, $hr, $min, $sec);
1860
1938
  # convert from DDMM.MMMMMM to DD.DDDDDD format if necessary
1861
- unless ($ddd) {
1862
- my $deg = int($lat / 100);
1863
- $lat = $deg + ($lat - $deg * 100) / 60;
1864
- $deg = int($lon / 100);
1865
- $lon = $deg + ($lon - $deg * 100) / 60;
1866
- }
1939
+ ConvertLatLon($lat, $lon) unless $ddd;
1867
1940
  $et->HandleTag($tagTbl, GPSDateTime => $time);
1868
1941
  $et->HandleTag($tagTbl, GPSLatitude => $lat * ($latRef eq 'S' ? -1 : 1));
1869
1942
  $et->HandleTag($tagTbl, GPSLongitude => $lon * ($lonRef eq 'W' ? -1 : 1));
1870
- if (defined $spd) {
1871
- $et->HandleTag($tagTbl, GPSSpeed => $spd); # (now in km/h)
1872
- $et->HandleTag($tagTbl, GPSSpeedRef => 'K');
1873
- }
1874
- if (defined $trk) {
1875
- $et->HandleTag($tagTbl, GPSTrack => $trk);
1876
- $et->HandleTag($tagTbl, GPSTrackRef => 'T');
1877
- }
1943
+ $et->HandleTag($tagTbl, GPSSpeed => $spd) if defined $spd; # (now in km/h)
1944
+ $et->HandleTag($tagTbl, GPSTrack => $trk) if defined $trk;
1878
1945
  if (defined $alt) {
1879
1946
  $et->HandleTag($tagTbl, GPSAltitude => $alt);
1880
1947
  }
@@ -1882,6 +1949,7 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
1882
1949
  return 1;
1883
1950
  }
1884
1951
 
1952
+
1885
1953
  #------------------------------------------------------------------------------
1886
1954
  # Extract embedded information referenced from a track
1887
1955
  # Inputs: 0) ExifTool ref, 1) tag name, 2) data ref
@@ -1960,22 +2028,18 @@ sub ParseTag($$$)
1960
2028
  while ($pos + 36 < $dataLen) {
1961
2029
  my $dat = substr($$dataPt, $pos, 36);
1962
2030
  last if $dat eq "\x0" x 36;
1963
- my @a = unpack 'VVVVCVCV', $dat;
2031
+ my @a = unpack 'VVVVaVaV', $dat;
1964
2032
  $$et{DOC_NUM} = ++$$et{DOC_COUNT};
1965
2033
  # 0=1, 1=1, 2=secs, 3=?
1966
2034
  SetGPSDateTime($et, $tagTbl, $a[2]);
1967
2035
  my $lat = $a[5] / 1e3;
1968
2036
  my $lon = $a[7] / 1e3;
1969
- my $deg = int($lat / 100);
1970
- $lat = $deg + ($lat - $deg * 100) / 60;
1971
- $deg = int($lon / 100);
1972
- $lon = $deg + ($lon - $deg * 100) / 60;
1973
- $lat = -$lat if $a[4] eq 'S';
1974
- $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';
1975
2040
  $et->HandleTag($tagTbl, GPSLatitude => $lat);
1976
2041
  $et->HandleTag($tagTbl, GPSLongitude => $lon);
1977
- $et->HandleTag($tagTbl, GPSSpeed => $a[3] / 1e3);
1978
- $et->HandleTag($tagTbl, GPSSpeedRef => 'K');
2042
+ $et->HandleTag($tagTbl, GPSSpeed => $a[3] / 1e3);
1979
2043
  $pos += 36;
1980
2044
  }
1981
2045
  SetByteOrder('MM');
@@ -2037,9 +2101,9 @@ sub Process_mebx($$$)
2037
2101
 
2038
2102
  # parse using information from 'keys' table (eg. Apple iPhone7+ hevc 'Core Media Data Handler')
2039
2103
  $et->VerboseDir('mebx', undef, length $$dataPt);
2040
- my $pos = 0;
2041
- while ($pos + 8 < length $$dataPt) {
2042
- my $len = Get32u($dataPt, $pos);
2104
+ my ($pos, $len);
2105
+ for ($pos=0; $pos+8<length($$dataPt); $pos+=$len) {
2106
+ $len = Get32u($dataPt, $pos);
2043
2107
  last if $len < 8 or $pos + $len > length $$dataPt;
2044
2108
  my $id = substr($$dataPt, $pos+4, 4);
2045
2109
  my $info = $$ee{'keys'}{$id};
@@ -2062,7 +2126,6 @@ sub Process_mebx($$$)
2062
2126
  } else {
2063
2127
  $et->WarnOnce('No key information for mebx ID ' . PrintableTagID($id,1));
2064
2128
  }
2065
- $pos += $len;
2066
2129
  }
2067
2130
  return 1;
2068
2131
  }
@@ -2118,20 +2181,14 @@ sub Process_gps0($$$)
2118
2181
  my $lat = GetDouble($dataPt, $pos);
2119
2182
  my $lon = GetDouble($dataPt, $pos+8);
2120
2183
  next if abs($lat) > 9000 or abs($lon) > 18000;
2121
- # (note: this method works fine for negative coordinates)
2122
- my $deg = int($lat / 100);
2123
- $lat = $deg + ($lat - $deg * 100) / 60;
2124
- $deg = int($lon / 100);
2125
- $lon = $deg + ($lon - $deg * 100) / 60;
2184
+ ConvertLatLon($lat, $lon);
2126
2185
  my @a = unpack('C*', substr($$dataPt, $pos+22, 6)); # unpack date/time
2127
2186
  $a[0] += 2000;
2128
2187
  $et->HandleTag($tagTbl, GPSDateTime => sprintf("%.4d:%.2d:%.2d %.2d:%.2d:%.2dZ", @a));
2129
2188
  $et->HandleTag($tagTbl, GPSLatitude => $lat);
2130
2189
  $et->HandleTag($tagTbl, GPSLongitude => $lon);
2131
2190
  $et->HandleTag($tagTbl, GPSSpeed => Get16u($dataPt, $pos+0x14));
2132
- $et->HandleTag($tagTbl, GPSSpeedRef => 'K');
2133
2191
  $et->HandleTag($tagTbl, GPSTrack => Get8u($dataPt, $pos+0x1c) * 2); # (NC)
2134
- $et->HandleTag($tagTbl, GPSTrackRef => 'T');
2135
2192
  $et->HandleTag($tagTbl, GPSAltitude => Get32s($dataPt, $pos + 0x10));
2136
2193
  # yet to be decoded:
2137
2194
  # 0x1d - int8u[3] seen: "1 1 0"
@@ -2220,10 +2277,7 @@ sub ProcessRIFFTrailer($$$)
2220
2277
  my $lat = GetDouble(\$buff, $pos+4);
2221
2278
  my $lon = GetDouble(\$buff, $pos+12);
2222
2279
  $et->Warn('Bad gps0 record') and last if abs($lat) > 9000 or abs($lon) > 18000;
2223
- my $deg = int($lat / 100);
2224
- $lat = $deg + ($lat - $deg * 100) / 60;
2225
- $deg = int($lon / 100);
2226
- $lon = $deg + ($lon - $deg * 100) / 60;
2280
+ ConvertLatLon($lat, $lon);
2227
2281
  $lat = -$lat if Get8u(\$buff, $pos+0x21) == 2; # wild guess
2228
2282
  $lon = -$lon if Get8u(\$buff, $pos+0x22) == 2; # wild guess
2229
2283
  my @a = unpack('C*', substr($buff, $pos+26, 6)); # unpack date/time
@@ -2233,9 +2287,7 @@ sub ProcessRIFFTrailer($$$)
2233
2287
  $et->HandleTag($tagTbl, GPSLatitude => $lat);
2234
2288
  $et->HandleTag($tagTbl, GPSLongitude => $lon);
2235
2289
  $et->HandleTag($tagTbl, GPSSpeed => Get16u(\$buff, $pos+0x18) * $knotsToKph);
2236
- $et->HandleTag($tagTbl, GPSSpeedRef => 'K');
2237
2290
  $et->HandleTag($tagTbl, GPSTrack => Get8u(\$buff, $pos+0x20) * 2);
2238
- $et->HandleTag($tagTbl, GPSTrackRef => 'T');
2239
2291
  }
2240
2292
  } elsif ($tag eq 'gsen') {
2241
2293
  # (similar to record decoded in Process_gsen)
@@ -2272,39 +2324,91 @@ sub ProcessNMEA($$$)
2272
2324
  {
2273
2325
  my ($et, $dirInfo, $tagTbl) = @_;
2274
2326
  my $dataPt = $$dirInfo{DataPt};
2275
- # parse only RMC sentence (with leading timestamp) for now
2276
- 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) {
2277
- my $tc = $1; # milliseconds since 1970 (local time)
2278
- my ($lat,$latRef,$lon,$lonRef) = ($6,$7,$8,$9);
2279
- my $yr = $14 + ($14 >= 70 ? 1900 : 2000);
2280
- my ($mon,$day,$hr,$min,$sec) = ($13,$12,$2,$3,$4);
2281
- my ($spd, $trk);
2282
- $spd = $10 * $knotsToKph if length $10;
2283
- $trk = $11 if length $11;
2284
- # lat/long are in DDDMM.MMMM format
2285
- my $deg = int($lat / 100);
2286
- $lat = $deg + ($lat - $deg * 100) / 60;
2287
- $deg = int($lon / 100);
2288
- $lon = $deg + ($lon - $deg * 100) / 60;
2289
- $sec = '0' . $sec unless $sec =~ /^\d{2}/; # pad integer part of seconds to 2 digits
2290
- my $time = sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%sZ',$yr,$mon,$day,$hr,$min,$sec);
2291
- my $sampleTime;
2292
- $sampleTime = ($tc - $$et{StartTime}) / 1000 if $tc and $$et{StartTime};
2293
- FoundSomething($et, $tagTbl, $sampleTime);
2294
- $et->HandleTag($tagTbl, GPSDateTime => $time);
2295
- $et->HandleTag($tagTbl, GPSLatitude => $lat * ($latRef eq 'S' ? -1 : 1));
2296
- $et->HandleTag($tagTbl, GPSLongitude => $lon * ($lonRef eq 'W' ? -1 : 1));
2297
- if (defined $spd) {
2298
- $et->HandleTag($tagTbl, GPSSpeed => $spd);
2299
- $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;
2300
2351
  }
2301
- if (defined $trk) {
2302
- $et->HandleTag($tagTbl, GPSTrack => $trk);
2303
- $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
2304
2374
  }
2305
2375
  }
2306
2376
  delete $$et{DOC_NUM};
2307
- 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;
2308
2412
  }
2309
2413
 
2310
2414
  #------------------------------------------------------------------------------
@@ -2395,9 +2499,7 @@ sub ProcessTTAD($$$)
2395
2499
  $et->HandleTag($tagTbl, GPSLongitude => GetDouble($dataPt, $pos+0x24));
2396
2500
  $et->HandleTag($tagTbl, GPSAltitude => GetDouble($dataPt, $pos+0x14));
2397
2501
  $et->HandleTag($tagTbl, GPSSpeed => GetDouble($dataPt, $pos+0x0c) * $mpsToKph);
2398
- $et->HandleTag($tagTbl, GPSSpeedRef => 'K');
2399
2502
  $et->HandleTag($tagTbl, GPSTrack => GetDouble($dataPt, $pos+0x30));
2400
- $et->HandleTag($tagTbl, GPSTrackRef => 'T');
2401
2503
  if ($unknown) {
2402
2504
  my @a = map { GetDouble($dataPt, $pos+0x38+8*$_) } 0..2;
2403
2505
  $et->HandleTag($tagTbl, Unknown03 => "@a");
@@ -2517,21 +2619,26 @@ sub ProcessInsta360($;$)
2517
2619
  my $tmp = substr($buff, $p, $dlen);
2518
2620
  my @a = unpack('VVvaa8aa8aa8a8a8', $tmp);
2519
2621
  next unless $a[3] eq 'A'; # (ignore void fixes)
2520
- last unless ($a[5] eq 'N' or $a[5] eq 'S') and # (quick validation)
2521
- ($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
+ }
2522
2631
  $$et{DOC_NUM} = ++$$et{DOC_COUNT};
2523
2632
  $a[$_] = GetDouble(\$a[$_], 0) foreach 4,6,8,9,10;
2524
2633
  $a[4] = -abs($a[4]) if $a[5] eq 'S'; # (abs just in case it was already signed)
2525
- $a[6] = -abs($a[6]) if $a[7] eq 'W';
2526
- $et->HandleTag($tagTbl, GPSDateTime => Image::ExifTool::ConvertUnixTime($a[0]) . 'Z');
2527
- $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]);
2528
2637
  $et->HandleTag($tagTbl, GPSLongitude => $a[6]);
2529
- $et->HandleTag($tagTbl, GPSSpeed => $a[8] * $mpsToKph);
2530
- $et->HandleTag($tagTbl, GPSSpeedRef => 'K');
2531
- $et->HandleTag($tagTbl, GPSTrack => $a[9]);
2532
- $et->HandleTag($tagTbl, GPSTrackRef => 'T');
2533
- $et->HandleTag($tagTbl, GPSAltitude => $a[10]);
2534
- $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)
2535
2642
  }
2536
2643
  }
2537
2644
  } elsif ($id == 0x101) {
@@ -2587,6 +2694,41 @@ sub Process360Fly($$$)
2587
2694
  return 1;
2588
2695
  }
2589
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
+
2590
2732
  #------------------------------------------------------------------------------
2591
2733
  # Scan media data for "freeGPS" metadata if not found already (ref PH)
2592
2734
  # Inputs: 0) ExifTool ref
@@ -2660,7 +2802,7 @@ sub ScanMediaData($)
2660
2802
  $buf2 = substr($buff, $len);
2661
2803
  }
2662
2804
  if ($tagTbl) {
2663
- $$et{DOC_NUM} = 0;
2805
+ $$et{DOC_NUM} = 0; # reset DOC_NUM after extracting embedded metadata
2664
2806
  $et->VPrint(0, "--------------------------\n");
2665
2807
  SetByteOrder($oldByteOrder);
2666
2808
  $$et{INDENT} = substr $$et{INDENT}, 0, -2;