exiftool_vendored 12.17.1 → 12.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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;