exiftool-vendored.pl 12.73.0 → 12.78.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 (70) hide show
  1. package/bin/Changes +94 -6
  2. package/bin/MANIFEST +4 -0
  3. package/bin/META.json +1 -1
  4. package/bin/META.yml +1 -1
  5. package/bin/README +46 -45
  6. package/bin/config_files/example.config +10 -2
  7. package/bin/exiftool +143 -94
  8. package/bin/lib/File/RandomAccess.pm +31 -5
  9. package/bin/lib/File/RandomAccess.pod +4 -4
  10. package/bin/lib/Image/ExifTool/7Z.pm +3 -3
  11. package/bin/lib/Image/ExifTool/AFCP.pm +2 -2
  12. package/bin/lib/Image/ExifTool/BZZ.pm +2 -2
  13. package/bin/lib/Image/ExifTool/BuildTagLookup.pm +7 -7
  14. package/bin/lib/Image/ExifTool/Canon.pm +17 -13
  15. package/bin/lib/Image/ExifTool/CanonVRD.pm +8 -2
  16. package/bin/lib/Image/ExifTool/DICOM.pm +2 -2
  17. package/bin/lib/Image/ExifTool/DNG.pm +4 -4
  18. package/bin/lib/Image/ExifTool/Exif.pm +54 -5
  19. package/bin/lib/Image/ExifTool/FLIR.pm +2 -2
  20. package/bin/lib/Image/ExifTool/Fixup.pm +3 -3
  21. package/bin/lib/Image/ExifTool/FlashPix.pm +3 -3
  22. package/bin/lib/Image/ExifTool/FujiFilm.pm +8 -3
  23. package/bin/lib/Image/ExifTool/GPS.pm +5 -3
  24. package/bin/lib/Image/ExifTool/Geolocation.dat +0 -0
  25. package/bin/lib/Image/ExifTool/Geolocation.pm +237 -0
  26. package/bin/lib/Image/ExifTool/Geotag.pm +4 -4
  27. package/bin/lib/Image/ExifTool/HtmlDump.pm +7 -4
  28. package/bin/lib/Image/ExifTool/ID3.pm +2 -2
  29. package/bin/lib/Image/ExifTool/Import.pm +9 -6
  30. package/bin/lib/Image/ExifTool/JSON.pm +11 -11
  31. package/bin/lib/Image/ExifTool/Jpeg2000.pm +51 -12
  32. package/bin/lib/Image/ExifTool/MIE.pm +3 -3
  33. package/bin/lib/Image/ExifTool/MWG.pm +1 -0
  34. package/bin/lib/Image/ExifTool/MacOS.pm +19 -4
  35. package/bin/lib/Image/ExifTool/MinoltaRaw.pm +2 -2
  36. package/bin/lib/Image/ExifTool/Nikon.pm +5 -3
  37. package/bin/lib/Image/ExifTool/NikonCustom.pm +3 -3
  38. package/bin/lib/Image/ExifTool/Ogg.pm +4 -3
  39. package/bin/lib/Image/ExifTool/Olympus.pm +3 -1
  40. package/bin/lib/Image/ExifTool/PDF.pm +59 -9
  41. package/bin/lib/Image/ExifTool/PLIST.pm +3 -3
  42. package/bin/lib/Image/ExifTool/PanasonicRaw.pm +3 -3
  43. package/bin/lib/Image/ExifTool/Pentax.pm +1 -1
  44. package/bin/lib/Image/ExifTool/PhaseOne.pm +2 -2
  45. package/bin/lib/Image/ExifTool/Photoshop.pm +3 -3
  46. package/bin/lib/Image/ExifTool/PostScript.pm +2 -2
  47. package/bin/lib/Image/ExifTool/QuickTime.pm +223 -117
  48. package/bin/lib/Image/ExifTool/QuickTimeStream.pl +260 -242
  49. package/bin/lib/Image/ExifTool/RSRC.pm +2 -2
  50. package/bin/lib/Image/ExifTool/Samsung.pm +4 -4
  51. package/bin/lib/Image/ExifTool/Shift.pl +1 -2
  52. package/bin/lib/Image/ExifTool/SigmaRaw.pm +3 -3
  53. package/bin/lib/Image/ExifTool/Sony.pm +3 -3
  54. package/bin/lib/Image/ExifTool/TagInfoXML.pm +2 -2
  55. package/bin/lib/Image/ExifTool/TagLookup.pm +85 -8
  56. package/bin/lib/Image/ExifTool/TagNames.pod +148 -8
  57. package/bin/lib/Image/ExifTool/WriteCanonRaw.pl +1 -1
  58. package/bin/lib/Image/ExifTool/WriteExif.pl +26 -23
  59. package/bin/lib/Image/ExifTool/WritePDF.pl +1 -1
  60. package/bin/lib/Image/ExifTool/WriteQuickTime.pl +15 -2
  61. package/bin/lib/Image/ExifTool/WriteXMP.pl +4 -2
  62. package/bin/lib/Image/ExifTool/Writer.pl +77 -52
  63. package/bin/lib/Image/ExifTool/XMP.pm +2 -1
  64. package/bin/lib/Image/ExifTool/XMP2.pl +9 -0
  65. package/bin/lib/Image/ExifTool/ZIP.pm +6 -6
  66. package/bin/lib/Image/ExifTool.pm +204 -63
  67. package/bin/lib/Image/ExifTool.pod +118 -94
  68. package/bin/perl-Image-ExifTool.spec +45 -44
  69. package/bin/pp_build_exe.args +4 -230
  70. package/package.json +4 -4
@@ -5,6 +5,8 @@
5
5
  #
6
6
  # Revisions: 2018-01-03 - P. Harvey Created
7
7
  #
8
+ # Notes: Set API "Debug" option to generate GPSType tag
9
+ #
8
10
  # References: 1) https://developer.apple.com/library/content/documentation/QuickTime/QTFF/QTFFChap3/qtff3.html#//apple_ref/doc/uid/TP40000939-CH205-SW130
9
11
  # 2) http://sergei.nz/files/nvtk_mp42gpx.py
10
12
  # 3) https://forum.flitsservice.nl/dashcam-info/dod-ls460w-gps-data-uit-mov-bestand-lezen-t87926.html
@@ -22,14 +24,12 @@ use Image::ExifTool::QuickTime;
22
24
  sub Process_tx3g($$$);
23
25
  sub Process_marl($$$);
24
26
  sub Process_mebx($$$);
27
+ sub Process_text($$$;$);
25
28
  sub ProcessFreeGPS($$$);
26
- sub ProcessFreeGPS2($$$);
27
29
  sub Process360Fly($$$);
28
30
  sub ProcessFMAS($$$);
29
31
  sub ProcessCAMM($$$);
30
32
 
31
- my $debug; # set to 'tEST' (all caps) for extra debugging messages
32
-
33
33
  # QuickTime data types that have ExifTool equivalents
34
34
  # (ref https://developer.apple.com/library/content/documentation/QuickTime/QTFF/Metadata/Metadata.html#//apple_ref/doc/uid/TP40000939-CH1-SW35)
35
35
  my %qtFmt = (
@@ -109,7 +109,7 @@ my %insvLimit = (
109
109
  The tags below are extracted from timed metadata in QuickTime and other
110
110
  formats of video files when the ExtractEmbedded option is used. Although
111
111
  most of these tags are combined into the single table below, ExifTool
112
- currently reads 68 different formats of timed GPS metadata from video files.
112
+ currently reads 71 different formats of timed GPS metadata from video files.
113
113
  },
114
114
  VARS => { NO_ID => 1 },
115
115
  GPSLatitude => { PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")', RawConv => '$$self{FoundGPSLatitude} = 1; $val' },
@@ -137,6 +137,7 @@ my %insvLimit = (
137
137
  GPSDOP => { Description => 'GPS Dilution Of Precision' },
138
138
  Distance => { PrintConv => '"$val m"' },
139
139
  VerticalSpeed=> { PrintConv => '"$val m/s"' },
140
+ CameraModel => { Groups => { 2 => 'Camera' } },
140
141
  FNumber => { PrintConv => 'Image::ExifTool::Exif::PrintFNumber($val)', Groups => { 2 => 'Camera' } },
141
142
  ExposureTime => { PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', Groups => { 2 => 'Camera' } },
142
143
  ExposureCompensation => { PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)', Groups => { 2 => 'Camera' } },
@@ -848,11 +849,11 @@ sub FoundSomething($$;$$)
848
849
  #------------------------------------------------------------------------------
849
850
  # Approximate GPSDateTime value from sample time and CreateDate
850
851
  # Inputs: 0) ExifTool ref, 1) tag table ptr, 2) sample time (s)
851
- # 3) true if CreateDate is at end of video
852
+ # 3) true if CreateDate is at end of video, 4) flag if CreateDate is UTC
852
853
  # Notes: Uses ExifTool CreateDateAtEnd as flag to subtract video duration
853
- sub SetGPSDateTime($$$)
854
+ sub SetGPSDateTime($$$;$)
854
855
  {
855
- my ($et, $tagTbl, $sampleTime) = @_;
856
+ my ($et, $tagTbl, $sampleTime, $isUTC) = @_;
856
857
  my $value = $$et{VALUE};
857
858
  if (defined $sampleTime and $$value{CreateDate}) {
858
859
  $sampleTime += $$value{CreateDate}; # adjust sample time to seconds since the epoch
@@ -863,7 +864,9 @@ sub SetGPSDateTime($$$)
863
864
  } else {
864
865
  $et->WarnOnce('Approximating GPSDateTime as CreateDate + SampleTime', 1);
865
866
  }
866
- unless ($et->Options('QuickTimeUTC')) {
867
+ my $utc = $et->Options('QuickTimeUTC');
868
+ $utc = $isUTC unless defined $utc; # (allow QuickTimeUTC=0 to override $isUTC default)
869
+ unless ($utc) {
867
870
  my $tzOff = $$et{tzOff}; # use previously calculated offset
868
871
  unless (defined $tzOff) {
869
872
  # adjust to UTC, assuming time is local
@@ -910,9 +913,14 @@ sub Process_text($$$;$)
910
913
  $et->VerboseDir($dirName, undef, length($$dataPt));
911
914
  }
912
915
 
913
- while ($$dataPt =~ /\$(\w+)([^\$]*)/g) {
916
+ while ($$dataPt =~ /\$(\w+)([^\$\0]*)/g) {
914
917
  my ($tag, $dat) = ($1, $2);
915
- if ($tag =~ /^[A-Z]{2}RMC$/ and $dat =~ /^,(\d{2})(\d{2})(\d+(?:\.\d*)),A?,(\d*?)(\d{1,2}\.\d+),([NS]),(\d*?)(\d{1,2}\.\d+),([EW]),(\d*\.?\d*),(\d*\.?\d*),(\d{2})(\d{2})(\d+)/) {
918
+ if ($tag =~ /^[A-Z]{2}RMC$/) {
919
+ unless ($dat =~ /^,(\d{2})(\d{2})(\d+(?:\.\d*)),A?,(\d*?)(\d{1,2}\.\d+),([NS]),(\d*?)(\d{1,2}\.\d+),([EW]),(\d*\.?\d*),(\d*\.?\d*),(\d{2})(\d{2})(\d+)/) {
920
+ $tags{Text} = defined $tags{Text} ? $tags{Text} . "\$$tag$dat" : "\$$tag$dat" unless $handled;
921
+ $dat =~ /^,\d+\.?\d*,V,/ and $$et{UnknownTextCount} = 0; # (allow any number of void fixes)
922
+ next;
923
+ }
916
924
  my $time = "$1:$2:$3";
917
925
  if ($$et{LastTime}) {
918
926
  if ($$et{LastTime} eq $time) {
@@ -972,8 +980,8 @@ sub Process_text($$$;$)
972
980
  } elsif ($tag eq 'BEGIN') {
973
981
  $tags{Text} = $dat if length $dat;
974
982
  $tags{done} = 1;
975
- } elsif ($tag ne 'END') {
976
- $tags{Text} = "\$$tag$dat" unless $handled;
983
+ } elsif ($tag ne 'END' and not $handled) {
984
+ $tags{Text} = defined $tags{Text} ? $tags{Text} . "\$$tag$dat" : "\$$tag$dat";
977
985
  }
978
986
  }
979
987
  %tags and HandleTextTags($et, $tagTbl, \%tags), return;
@@ -1144,13 +1152,13 @@ sub ProcessSamples($)
1144
1152
  {
1145
1153
  my $et = shift;
1146
1154
  my ($raf, $ee) = @$et{qw(RAF ee)};
1147
- my ($i, $buff, $pos, $hdrLen, $hdrFmt, @time, @dur, $oldIndent, $hash);
1155
+ my ($i, $pos, $hdrLen, $hdrFmt, @time, @dur, $oldIndent, $hash);
1148
1156
  my ($mdatOffset, $mdatSize); # (for range-checking samples when hash is done)
1149
1157
 
1150
1158
  return unless $ee;
1151
1159
  delete $$et{ee}; # use only once
1152
1160
 
1153
- my $eeOpt = $et->Options('ExtractEmbedded');
1161
+ my $eeOpt = $et->Options('ExtractEmbedded') || 0;
1154
1162
  my $type = $$et{HandlerType} || '';
1155
1163
  if ($type eq 'vide') {
1156
1164
  # only process specific types of video streams
@@ -1252,6 +1260,7 @@ Sample: for ($i=0; ; ) {
1252
1260
  $hdrFmt = ($hdrLen == 4 ? 'N' : $hdrLen == 2 ? 'n' : 'C');
1253
1261
  require Image::ExifTool::H264;
1254
1262
  }
1263
+
1255
1264
  # loop through all samples
1256
1265
  for ($i=0; $i<@$start and $i<@$size; ++$i) {
1257
1266
 
@@ -1272,6 +1281,7 @@ Sample: for ($i=0; ; ) {
1272
1281
  }
1273
1282
  # read the sample data
1274
1283
  $raf->Seek($$start[$i], 0) or $et->WarnOnce("Seek error in $type data"), next;
1284
+ my $buff;
1275
1285
  my $n = $raf->Read($buff, $size);
1276
1286
  unless ($n == $size) {
1277
1287
  $et->WarnOnce("Error reading $type data");
@@ -1293,10 +1303,7 @@ Sample: for ($i=0; ; ) {
1293
1303
  $pos += $hdrLen + $len;
1294
1304
  last if $pos + $hdrLen >= length($buff);
1295
1305
  }
1296
- if ($$et{GotNAL06}) {
1297
- my $eeOpt = $et->Options('ExtractEmbedded');
1298
- last unless $eeOpt and $eeOpt > 2;
1299
- }
1306
+ last if $$et{GotNAL06} and $eeOpt < 3;
1300
1307
  next;
1301
1308
  }
1302
1309
  if ($verbose > 1) {
@@ -1344,11 +1351,12 @@ Sample: for ($i=0; ; ) {
1344
1351
  $et->HandleTag($tagTbl, GPSLatitude => Get32s(\$buff, 12+$n) * 180/0x80000000);
1345
1352
  $et->HandleTag($tagTbl, GPSLongitude => Get32s(\$buff, 16+$n) * 180/0x80000000);
1346
1353
  $et->HandleTag($tagTbl, GPSSpeed => Get16u(\$buff, 8+$n) * $mphToKph);
1347
- SetGPSDateTime($et, $tagTbl, $time[$i]);
1354
+ SetGPSDateTime($et, $tagTbl, $time[$i], 1);
1348
1355
  next; # all done (don't store/process as text)
1349
1356
  }
1350
- unless (defined $val) {
1351
- $et->HandleTag($tagTbl, Text => $buff); # just store any other text
1357
+ unless (defined $val or $buff =~ /\0[^\0]/) {
1358
+ # just store any other plain text
1359
+ $et->HandleTag($tagTbl, Text => $buff);
1352
1360
  $handled = 1;
1353
1361
  }
1354
1362
  }
@@ -1382,7 +1390,10 @@ Sample: for ($i=0; ; ) {
1382
1390
  } elsif ($type eq 'gps ') { # (ie. GPSDataList tag)
1383
1391
 
1384
1392
  if ($buff =~ /^....freeGPS /s) {
1385
- # decode "freeGPS " data (Novatek)
1393
+ # process by brute scan instead if ExtractEmbedded >= 3
1394
+ # (some videos don't reference all freeGPS info from 'gps ' table, eg. INNOV)
1395
+ last if $eeOpt >= 3;
1396
+ # decode "freeGPS " data (Novatek and others)
1386
1397
  ProcessFreeGPS($et, {
1387
1398
  DataPt => \$buff,
1388
1399
  DataPos => $$start[$i],
@@ -1415,7 +1426,7 @@ Sample: for ($i=0; ; ) {
1415
1426
  }
1416
1427
  # clean up
1417
1428
  $raf->Seek($tell, 0); # restore original file position
1418
- $$et{DOC_NUM} = 0;
1429
+ delete $$et{DOC_NUM};
1419
1430
  $$et{HandlerType} = $$et{HanderDesc} = '';
1420
1431
  }
1421
1432
 
@@ -1433,11 +1444,10 @@ sub ConvertLatLon($$)
1433
1444
  }
1434
1445
 
1435
1446
  #------------------------------------------------------------------------------
1436
- # Process "freeGPS " data blocks referenced by a 'gps ' (GPSDataList) atom
1447
+ # Process "freeGPS " data blocks
1437
1448
  # Inputs: 0) ExifTool ref, 1) dirInfo ref {DataPt,SampleTime,SampleDuration}, 2) tagTable ref
1438
1449
  # Returns: 1 on success (or 0 on unrecognized or "measurement-void" GPS data)
1439
1450
  # Notes:
1440
- # - also see ProcessFreeGPS2() below for processing of other types of freeGPS blocks
1441
1451
  sub ProcessFreeGPS($$$)
1442
1452
  {
1443
1453
  my ($et, $dirInfo, $tagTbl) = @_;
@@ -1446,11 +1456,15 @@ sub ProcessFreeGPS($$$)
1446
1456
  my ($yr, $mon, $day, $hr, $min, $sec, $stat, $lbl, $ddd);
1447
1457
  my ($lat, $latRef, $lon, $lonRef, $spd, $trk, $alt, @acc, @xtra);
1448
1458
 
1449
- return 0 if $dirLen < 92;
1459
+ return 0 if $dirLen < 82;
1460
+
1461
+ my $debug = $et->Options('Debug');
1462
+ my $oldOrder = GetByteOrder();
1463
+ SetByteOrder('II');
1450
1464
 
1451
1465
  if (substr($$dataPt,18,8) eq "\xaa\xaa\xf2\xe1\xf0\xee\x54\x54") {
1452
1466
 
1453
- $debug and $et->FoundTag(GPSType => '1A');
1467
+ $debug and $et->FoundTag(GPSType => 1);
1454
1468
  # (this is very similar to the encrypted text format)
1455
1469
  # decode encrypted ASCII-based GPS (DashCam Azdome GS63H, ref 5)
1456
1470
  # header looks like this in my sample:
@@ -1515,12 +1529,19 @@ sub ProcessFreeGPS($$$)
1515
1529
 
1516
1530
  } elsif ($$dataPt =~ /^.{52}(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/s) {
1517
1531
 
1518
- $debug and $et->FoundTag(GPSType => '1B');
1519
- # decode NMEA-format GPS data (NextBase 512GW dashcam, ref PH)
1532
+ $debug and $et->FoundTag(GPSType => 2);
1533
+ # decode NMEA-format GPS data (Nextbase 512GW dashcam, ref PH)
1520
1534
  # header looks like this in my sample:
1521
1535
  # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 40 01 00 00 [....freeGPS @...]
1522
1536
  # 0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
1523
1537
  # 0020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
1538
+ # 0030: 00 00 00 00 32 30 31 38 30 39 31 39 31 30 30 39 [....201809191009]
1539
+ # 0040: 35 39 00 00 1c 01 00 00 06 00 00 00 ef ff ff ff [59..............]
1540
+ # 0050: 20 24 47 50 52 4d 43 2c 30 38 30 39 35 31 2e 30 [ $GPRMC,080951.0]
1541
+ # 0060: 30 30 2c 41 2c 35 32 30 37 2e 39 30 39 37 2c 4e [00,A,5207.9097,N]
1542
+ # 0070: 2c 30 30 35 30 35 2e 35 31 37 35 2c 45 2c 35 35 [,00505.5175,E,55]
1543
+ # 0080: 2e 31 31 2c 31 32 35 2e 35 38 2c 31 39 30 39 31 [.11,125.58,19091]
1544
+ # 0090: 38 2c 2c 2c 41 2a 35 39 0d 0a 00 00 00 00 00 00 [8,,,A*59........]
1524
1545
  push @xtra, CameraDateTime => "$1:$2:$3 $4:$5:$6";
1525
1546
  if ($$dataPt =~ /\$[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+)/s) {
1526
1547
  ($lat,$latRef,$lon,$lonRef) = ($5,$6,$7,$8);
@@ -1544,7 +1565,7 @@ sub ProcessFreeGPS($$$)
1544
1565
 
1545
1566
  } elsif ($$dataPt =~ /^.{37}\0\0\0A([NS])([EW])/s) {
1546
1567
 
1547
- $debug and $et->FoundTag(GPSType => '1C');
1568
+ $debug and $et->FoundTag(GPSType => 3);
1548
1569
  # decode freeGPS from ViofoA119v3 dashcam (similar to Novatek GPS format)
1549
1570
  # 0000: 00 00 40 00 66 72 65 65 47 50 53 20 f0 03 00 00 [..@.freeGPS ....]
1550
1571
  # 0010: 05 00 00 00 2f 00 00 00 03 00 00 00 13 00 00 00 [..../...........]
@@ -1552,9 +1573,7 @@ sub ProcessFreeGPS($$$)
1552
1573
  # 0030: f1 47 40 46 66 66 d2 41 85 eb 83 41 00 00 00 00 [.G@Fff.A...A....]
1553
1574
  ($latRef, $lonRef) = ($1, $2);
1554
1575
  ($hr,$min,$sec,$yr,$mon,$day) = unpack('x16V6', $$dataPt);
1555
- if ($yr < 2000) {
1556
- $yr += 2000;
1557
- } else {
1576
+ if ($yr >= 2000) {
1558
1577
  # Kenwood dashcam sometimes stores absolute year and local time
1559
1578
  # (but sometimes year since 2000 and UTC time in same video!)
1560
1579
  require Time::Local;
@@ -1564,7 +1583,6 @@ sub ProcessFreeGPS($$$)
1564
1583
  ++$mon;
1565
1584
  $et->WarnOnce('Converting GPSDateTime to UTC based on local time zone',1);
1566
1585
  }
1567
- SetByteOrder('II');
1568
1586
  $lat = GetFloat($dataPt, 0x2c);
1569
1587
  $lon = GetFloat($dataPt, 0x30);
1570
1588
  $spd = GetFloat($dataPt, 0x34) * $knotsToKph; # (convert knots to km/h)
@@ -1575,11 +1593,10 @@ sub ProcessFreeGPS($$$)
1575
1593
  @acc = unpack('V3', $tmp);
1576
1594
  map { $_ = $_ - 4294967296 if $_ >= 0x80000000; $_ /= 256 } @acc;
1577
1595
  }
1578
- SetByteOrder('MM');
1579
1596
 
1580
1597
  } elsif ($$dataPt =~ /^.{21}\0\0\0A([NS])([EW])/s) {
1581
1598
 
1582
- $debug and $et->FoundTag(GPSType => '1D');
1599
+ $debug and $et->FoundTag(GPSType => 4);
1583
1600
  # also decode 'gpmd' chunk from Kingslim D4 dashcam videos
1584
1601
  # 0000: 0a 00 00 00 0b 00 00 00 07 00 00 00 e5 07 00 00 [................]
1585
1602
  # 0010: 06 00 00 00 03 00 00 00 41 4e 57 31 91 52 83 45 [........ANW1.R.E]
@@ -1591,7 +1608,6 @@ sub ProcessFreeGPS($$$)
1591
1608
  # 0070: 20 f0 12 10 12 21 e5 0e 10 12 2f 90 10 13 01 f2 [ ....!..../.....]
1592
1609
  ($latRef, $lonRef) = ($1, $2);
1593
1610
  ($hr,$min,$sec,$yr,$mon,$day) = unpack("V6", $$dataPt);
1594
- SetByteOrder('II');
1595
1611
  # lat/lon aren't decoded properly, but spd,trk,acc are
1596
1612
  $lat = GetFloat($dataPt, 0x1c);
1597
1613
  $lon = GetFloat($dataPt, 0x20);
@@ -1604,11 +1620,10 @@ sub ProcessFreeGPS($$$)
1604
1620
  $acc[0] = GetFloat($dataPt, 0x2c);
1605
1621
  $acc[1] = GetFloat($dataPt, 0x30);
1606
1622
  $acc[2] = GetFloat($dataPt, 0x34);
1607
- SetByteOrder('MM');
1608
1623
 
1609
1624
  } elsif ($$dataPt =~ /^.{60}A\0{3}.{4}([NS])\0{3}.{4}([EW])\0{3}/s) {
1610
1625
 
1611
- $debug and $et->FoundTag(GPSType => '1E');
1626
+ $debug and $et->FoundTag(GPSType => 5);
1612
1627
  # decode freeGPS from Akaso dashcam
1613
1628
  # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 60 00 00 00 [....freeGPS `...]
1614
1629
  # 0010: 78 2e 78 78 00 00 00 00 00 00 00 00 00 00 00 00 [x.xx............]
@@ -1617,19 +1632,32 @@ sub ProcessFreeGPS($$$)
1617
1632
  # 0040: 13 b3 ca 44 4e 00 00 00 29 92 fb 45 45 00 00 00 [...DN...)..EE...]
1618
1633
  # 0050: d9 ee b4 41 ec d1 d3 42 e4 07 00 00 01 00 00 00 [...A...B........]
1619
1634
  # 0060: 0c 00 00 00 01 00 00 00 05 00 00 00 00 00 00 00 [................]
1635
+ # (unknown dashcam, "Anticlock 2 2020_1125_1455_007.MOV"):
1636
+ # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 68 00 00 00 [....freeGPS h...]
1637
+ # 0010: 32 30 31 33 30 33 32 35 41 00 00 00 00 00 00 00 [20130325A.......]
1638
+ # 0020: 41 70 72 20 20 36 20 32 30 31 36 2c 20 31 36 3a [Apr 6 2016, 16:]
1639
+ # 0030: 0e 00 00 00 38 00 00 00 22 00 00 00 41 00 00 00 [....8..."...A...]
1640
+ # 0040: 8a 63 24 45 53 00 00 00 9f e6 42 45 45 00 00 00 [.c$ES.....BEE...]
1641
+ # 0050: 59 c0 04 3f 52 b8 42 41 14 00 00 00 0b 00 00 00 [Y..?R.BA........]
1642
+ # 0060: 19 00 00 00 06 00 00 00 05 00 00 00 f6 ff ff ff [................]
1643
+ # 0070: 03 00 00 00 04 00 00 00 00 00 00 00 00 00 00 00 [................]
1620
1644
  ($latRef, $lonRef) = ($1, $2);
1621
- ($hr, $min, $sec, $yr, $mon, $day) = unpack('x48V3x28V3', $$dataPt);
1622
- SetByteOrder('II');
1645
+ ($hr, $min, $sec, $yr, $mon, $day, @acc) = unpack('x48V3x28V6', $$dataPt);
1623
1646
  $lat = GetFloat($dataPt, 0x40);
1624
1647
  $lon = GetFloat($dataPt, 0x48);
1625
1648
  $spd = GetFloat($dataPt, 0x50);
1626
- $trk = GetFloat($dataPt, 0x54) + 180; # (why is this off by 180?)
1627
- $trk -= 360 if $trk >= 360;
1628
- SetByteOrder('MM');
1649
+ $trk = GetFloat($dataPt, 0x54);
1650
+ if (substr($$dataPt, 16, 4) eq 'x.xx') {
1651
+ $trk += 180; # (why is this off by 180?)
1652
+ $trk -= 360 if $trk >= 360;
1653
+ undef @acc;
1654
+ } else {
1655
+ map { $_ = $_ - 4294967296 if $_ >= 0x80000000; $_ /= 1000 } @acc; # (NC)
1656
+ }
1629
1657
 
1630
1658
  } elsif ($$dataPt =~ /^.{60}4W`b]S</s and length($$dataPt) >= 140) {
1631
1659
 
1632
- $debug and $et->FoundTag(GPSType => '1F');
1660
+ $debug and $et->FoundTag(GPSType => 6);
1633
1661
  # 0000: 00 00 40 00 66 72 65 65 47 50 53 20 f0 01 00 00 [..@.freeGPS ....]
1634
1662
  # 0010: 5a 58 53 42 4e 58 59 53 00 00 00 00 00 00 00 00 [ZXSBNXYS........]
1635
1663
  # 0020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
@@ -1639,7 +1667,10 @@ sub ProcessFreeGPS($$$)
1639
1667
  # 0060: 42 3e 49 49 40 42 45 3c 55 3c 45 47 3e 45 43 41 [B>II@BE<U<EG>ECA]
1640
1668
  # decipher $GPRMC by subtracting 16 from each character value
1641
1669
  $_ = pack 'C*', map { $_>=16 and $_-=16 } unpack('x60C80', $$dataPt);
1642
- 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+)/;
1670
+ 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+)/) {
1671
+ SetByteOrder($oldOrder);
1672
+ return 0;
1673
+ }
1643
1674
  ($yr,$mon,$day,$hr,$min,$sec,$lat,$latRef,$lon,$lonRef) = ($13,$12,$11,$1,$2,$3,$5,$6,$7,$8);
1644
1675
  $yr += ($yr >= 70 ? 1900 : 2000);
1645
1676
  $spd = $9 * $knotsToKph if length $9;
@@ -1647,7 +1678,7 @@ sub ProcessFreeGPS($$$)
1647
1678
 
1648
1679
  } elsif ($$dataPt =~ /^.{64}[\x01-\x0c]\0{3}[\x01-\x1f]\0{3}A[NS][EW]\0{5}/s) {
1649
1680
 
1650
- $debug and $et->FoundTag(GPSType => '1G');
1681
+ $debug and $et->FoundTag(GPSType => 7);
1651
1682
  # Akaso V1 dascham
1652
1683
  # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 78 00 00 00 [....freeGPS x...]
1653
1684
  # 0010: 59 6e 64 41 6b 61 73 6f 43 61 72 00 00 00 00 00 [YndAkasoCar.....]
@@ -1671,27 +1702,20 @@ sub ProcessFreeGPS($$$)
1671
1702
  # 0090: 0a 00 00 00 e5 07 00 00 0c 00 00 00 1c 00 00 00 [................]
1672
1703
  ($hr,$min,$sec,$yr,$mon,$day,$stat,$latRef,$lonRef) =
1673
1704
  unpack('x48V6a1a1a1x1', $$dataPt);
1674
- # ignore invalid fixes
1675
- return 0 unless $stat eq 'A' and ($latRef eq 'N' or $latRef eq 'S') and
1676
- ($lonRef eq 'E' or $lonRef eq 'W');
1677
1705
 
1678
1706
  $et->WarnOnce('GPSLatitude/Longitude encryption is not yet known, so these will be wrong');
1679
1707
  # (see https://exiftool.org/forum/index.php?topic=11320.0)
1680
1708
 
1681
- SetByteOrder('II');
1682
-
1683
1709
  $spd = GetFloat($dataPt, 0x60);
1684
1710
  $trk = GetFloat($dataPt, 0x64) + 180; # (why is this off by 180?)
1685
1711
  $lat = GetDouble($dataPt, 0x50); # latitude is here, but encrypted somehow
1686
1712
  $lon = GetDouble($dataPt, 0x58); # longitude is here, but encrypted somehow
1687
1713
  $ddd = 1; # don't convert until we know what the format is
1688
-
1689
- SetByteOrder('MM');
1690
1714
  #my $serialNum = substr($$dataPt, 0x68, 20);
1691
1715
 
1692
1716
  } elsif ($$dataPt =~ /^.{12}\xac\0\0\0.{44}(.{72})/s) {
1693
1717
 
1694
- $debug and $et->FoundTag(GPSType => '1H');
1718
+ $debug and $et->FoundTag(GPSType => 8);
1695
1719
  # EACHPAI dash cam
1696
1720
  # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 ac 00 00 00 [....freeGPS ....]
1697
1721
  # 0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
@@ -1705,6 +1729,7 @@ sub ProcessFreeGPS($$$)
1705
1729
 
1706
1730
  $et->WarnOnce("Can't yet decrypt EACHPAI timed GPS", 1);
1707
1731
  # (see https://exiftool.org/forum/index.php?topic=5095.msg61266#msg61266)
1732
+ SetByteOrder($oldOrder);
1708
1733
  return 1;
1709
1734
 
1710
1735
  my $time = pack 'C*', map { $_ ^= 0 } unpack 'C*', $1;
@@ -1714,7 +1739,7 @@ sub ProcessFreeGPS($$$)
1714
1739
 
1715
1740
  } elsif ($$dataPt =~ /^.{64}A([NS])([EW])\0/s) {
1716
1741
 
1717
- $debug and $et->FoundTag(GPSType => '1I');
1742
+ $debug and $et->FoundTag(GPSType => 9);
1718
1743
  # Vantrue S1 dashcam
1719
1744
  # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 78 00 00 00 [....freeGPS x...]
1720
1745
  # 0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
@@ -1726,103 +1751,21 @@ sub ProcessFreeGPS($$$)
1726
1751
  # 0070: 05 00 00 00 7f 00 00 00 07 01 00 00 00 00 00 00 [................]
1727
1752
  ($latRef, $lonRef) = ($1, $2);
1728
1753
  ($yr,$mon,$day,$hr,$min,$sec,@acc) = unpack('x68V6x20V3', $$dataPt);
1729
- return 0 unless $mon>=1 and $mon<=12 and $day>=1 and $day<=31;
1730
- $yr += 2000 if $yr < 2000;
1754
+ unless ($mon>=1 and $mon<=12 and $day>=1 and $day<=31) {
1755
+ SetByteOrder($oldOrder);
1756
+ return 0;
1757
+ }
1731
1758
  # (not sure about acc scaling)
1732
1759
  map { $_ = $_ - 4294967296 if $_ >= 0x80000000; $_ /= 1000 } @acc;
1733
- SetByteOrder('II');
1734
1760
  $lon = GetFloat($dataPt, 0x5c);
1735
1761
  $lat = GetFloat($dataPt, 0x60);
1736
1762
  $spd = GetFloat($dataPt, 0x64) * $knotsToKph;
1737
1763
  $trk = GetFloat($dataPt, 0x68);
1738
1764
  $alt = GetFloat($dataPt, 0x6c);
1739
- SetByteOrder('MM');
1740
-
1741
- } else {
1742
-
1743
- $debug and $et->FoundTag(GPSType => '1J');
1744
- # decode binary GPS format (Viofo A119S, ref 2)
1745
- # header looks like this in my sample:
1746
- # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 4c 00 00 00 [....freeGPS L...]
1747
- # 0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
1748
- # 0020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
1749
- # 0030: 10 00 00 00 2d 00 00 00 14 00 00 00 11 00 00 00 [....-...........]
1750
- # 0040: 0c 00 00 00 1f 00 00 00 41 4e 45 00 5d 9a a9 45 [........ANE.]..E]
1751
- # 0050: ab 1e e5 44 ec 51 f0 40 b8 5e a5 43 00 00 00 00 [...D.Q.@.^.C....]
1752
- # (records are same structure as Type 3 Novatek GPS in ProcessFreeGPS2() below)
1753
- ($hr,$min,$sec,$yr,$mon,$day,$stat,$latRef,$lonRef,$lat,$lon,$spd,$trk) =
1754
- unpack('x48V6a1a1a1x1V4', $$dataPt);
1755
- # ignore invalid fixes
1756
- return 0 unless $stat eq 'A' and ($latRef eq 'N' or $latRef eq 'S') and
1757
- ($lonRef eq 'E' or $lonRef eq 'W');
1758
- ($lat,$lon,$spd,$trk) = unpack 'f*', pack 'L*', $lat, $lon, $spd, $trk;
1759
- # lat/lon also stored as doubles by Transcend Driver Pro 230 (ref PH)
1760
- SetByteOrder('II');
1761
- my ($lat2, $lon2, $alt2) = (
1762
- GetDouble($dataPt, 0x70),
1763
- GetDouble($dataPt, 0x80),
1764
- # GetDouble($dataPt, 0x98), # (don't know what this is)
1765
- GetDouble($dataPt,0xa0),
1766
- # GetDouble($dataPt,0xa8)) # (don't know what this is)
1767
- );
1768
- if (abs($lat2-$lat) < 0.001 and abs($lon2-$lon) < 0.001) {
1769
- $lat = $lat2;
1770
- $lon = $lon2;
1771
- $alt = $alt2;
1772
- }
1773
- SetByteOrder('MM');
1774
- $yr += 2000 if $yr < 2000;
1775
- $spd *= $knotsToKph; # convert speed to km/h
1776
- # ($trk is not confirmed; may be GPSImageDirection, ref PH)
1777
- }
1778
- #
1779
- # save tag values extracted by above code
1780
- #
1781
- FoundSomething($et, $tagTbl, $$dirInfo{SampleTime}, $$dirInfo{SampleDuration});
1782
- $sec = '0' . $sec unless $sec =~ /^\d{2}/; # pad integer part of seconds to 2 digits
1783
- if (defined $yr) {
1784
- my $time = sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%sZ',$yr,$mon,$day,$hr,$min,$sec);
1785
- $et->HandleTag($tagTbl, GPSDateTime => $time);
1786
- } elsif (defined $hr) {
1787
- my $time = sprintf('%.2d:%.2d:%sZ',$hr,$min,$sec);
1788
- $et->HandleTag($tagTbl, GPSTimeStamp => $time);
1789
- }
1790
- if (defined $lat) {
1791
- # lat/long are in DDDMM.MMMM format unless $ddd is set
1792
- ConvertLatLon($lat, $lon) unless $ddd;
1793
- $et->HandleTag($tagTbl, GPSLatitude => $lat * ($latRef eq 'S' ? -1 : 1));
1794
- $et->HandleTag($tagTbl, GPSLongitude => $lon * ($lonRef eq 'W' ? -1 : 1));
1795
- }
1796
- $et->HandleTag($tagTbl, GPSAltitude => $alt) if defined $alt;
1797
- $et->HandleTag($tagTbl, GPSSpeed => $spd) if defined $spd;
1798
- $et->HandleTag($tagTbl, GPSTrack => $trk) if defined $trk;
1799
- while (@xtra) {
1800
- my $tag = shift @xtra;
1801
- $et->HandleTag($tagTbl, $tag => shift @xtra);
1802
- }
1803
- $et->HandleTag($tagTbl, Accelerometer => \@acc) if @acc;
1804
- return 1;
1805
- }
1806
1765
 
1807
- #------------------------------------------------------------------------------
1808
- # Process "freeGPS " data blocks _not_ referenced by a 'gps ' atom
1809
- # Inputs: 0) ExifTool ref, 1) dirInfo ref {DataPt,DataPos,DirLen}, 2) tagTable ref
1810
- # Returns: 1 on success
1811
- # Notes:
1812
- # - also see ProcessFreeGPS() above
1813
- sub ProcessFreeGPS2($$$)
1814
- {
1815
- my ($et, $dirInfo, $tagTbl) = @_;
1816
- my $dataPt = $$dirInfo{DataPt};
1817
- my $dirLen = $$dirInfo{DirLen};
1818
- my ($yr, $mon, $day, $hr, $min, $sec, $pos, @acc);
1819
- my ($lat, $latRef, $lon, $lonRef, $spd, $trk, $alt, $ddd, $unk);
1820
-
1821
- return 0 if $dirLen < 82; # minimum size of block with a single GPS record
1822
-
1823
- if (substr($$dataPt,0x45,3) eq 'ATC') {
1766
+ } elsif (substr($$dataPt,0x45,3) eq 'ATC') {
1824
1767
 
1825
- $debug and $et->FoundTag(GPSType => '2A');
1768
+ $debug and $et->FoundTag(GPSType => 10);
1826
1769
  # header looks like this: (sample 1)
1827
1770
  # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 38 06 00 00 [....freeGPS 8...]
1828
1771
  # 0010: 49 51 53 32 30 31 33 30 33 30 36 42 00 00 00 00 [IQS20130306B....]
@@ -1930,11 +1873,12 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
1930
1873
  }
1931
1874
  # save position of most recent record (needed when parsing the next freeGPS block)
1932
1875
  $$et{FreeGPS2}{RecentRecPos} = $lastRecPos;
1876
+ SetByteOrder($oldOrder);
1933
1877
  return 1;
1934
1878
 
1935
- } elsif ($$dataPt =~ /^.{60}A\0.{10}([NS])\0.{14}([EW])\0/s) {
1879
+ } elsif ($$dataPt =~ /^.{60}A\0.{10}([NS])\0.{14}([EW])\0/s and $dirLen >= 0x88) {
1936
1880
 
1937
- $debug and $et->FoundTag(GPSType => '2B');
1881
+ $debug and $et->FoundTag(GPSType => 11);
1938
1882
  # header looks like this in my sample:
1939
1883
  # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 08 01 00 00 [....freeGPS ....]
1940
1884
  # 0010: 32 30 31 33 30 38 31 35 2e 30 31 00 00 00 00 00 [20130815.01.....]
@@ -1963,76 +1907,9 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
1963
1907
  $spd = GetDouble($dataPt, 0x60) * $knotsToKph;
1964
1908
  $trk = GetDouble($dataPt, 0x68);
1965
1909
 
1966
- } elsif ($$dataPt =~ /^.{72}A([NS])([EW])/s) {
1967
-
1968
- # Type 3 (Novatek GPS, ref 2): (in case it wasn't decoded via 'gps ' atom)
1969
- # 0x30 - int32u hour
1970
- # 0x34 - int32u minute
1971
- # 0x38 - int32u second
1972
- # 0x3c - int32u year - 2000
1973
- # 0x40 - int32u month
1974
- # 0x44 - int32u day
1975
- # 0x48 - int8u GPS status ('A' or 'V')
1976
- # 0x49 - int8u latitude ref ('N' or 'S')
1977
- # 0x4a - int8u longitude ref ('E' or 'W')
1978
- # 0x4b - 0
1979
- # 0x4c - float latitude (DDMM.MMMMMM)
1980
- # 0x50 - float longitude (DDMM.MMMMMM)
1981
- # 0x54 - float speed (knots)
1982
- # 0x58 - float heading (deg)
1983
- # Type 3b, same as above for 0x30-0x4a (ref PH)
1984
- # 0x4c - int32s latitude (decimal degrees * 1e7)
1985
- # 0x50 - int32s longitude (decimal degrees * 1e7)
1986
- # 0x54 - int32s speed (m/s * 100)
1987
- # 0x58 - float altitude (m * 1000, NC)
1988
- ($latRef, $lonRef) = ($1, $2);
1989
- ($hr,$min,$sec,$yr,$mon,$day) = unpack('x48V6', $$dataPt);
1990
- if (substr($$dataPt, 16, 3) eq 'IQS') {
1991
- $debug and $et->FoundTag(GPSType => '2C');
1992
- # Type 3b (ref PH)
1993
- # header looks like this in my sample:
1994
- # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 4c 00 00 00 [....freeGPS L...]
1995
- # 0010: 49 51 53 5f 41 37 5f 32 30 31 35 30 34 31 37 00 [IQS_A7_20150417.]
1996
- # 0020: 4d 61 72 20 32 39 20 32 30 31 37 2c 20 31 36 3a [Mar 29 2017, 16:]
1997
- $ddd = 1;
1998
- $lat = abs Get32s($dataPt, 0x4c) / 1e7;
1999
- $lon = abs Get32s($dataPt, 0x50) / 1e7;
2000
- $spd = Get32s($dataPt, 0x54) / 100 * $mpsToKph;
2001
- $alt = GetFloat($dataPt, 0x58) / 1000; # (NC)
2002
-
2003
- } else {
2004
- $debug and $et->FoundTag(GPSType => '2D');
2005
- # Type 3 (ref 2)
2006
- # (no sample with this format)
2007
- $lat = GetFloat($dataPt, 0x4c);
2008
- $lon = GetFloat($dataPt, 0x50);
2009
- $spd = GetFloat($dataPt, 0x54) * $knotsToKph;
2010
- $trk = GetFloat($dataPt, 0x58);
2011
- }
2012
-
2013
- } elsif ($$dataPt =~ /^.{60}A\0.{6}([NS])\0.{6}([EW])\0/s and $dirLen >= 112) {
2014
-
2015
- $debug and $et->FoundTag(GPSType => '2E');
2016
- # header looks like this in my sample (unknown dashcam, "Anticlock 2 2020_1125_1455_007.MOV"):
2017
- # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 68 00 00 00 [....freeGPS h...]
2018
- # 0010: 32 30 31 33 30 33 32 35 41 00 00 00 00 00 00 00 [20130325A.......]
2019
- # 0020: 41 70 72 20 20 36 20 32 30 31 36 2c 20 31 36 3a [Apr 6 2016, 16:]
2020
- # 0030: 0e 00 00 00 38 00 00 00 22 00 00 00 41 00 00 00 [....8..."...A...]
2021
- # 0040: 8a 63 24 45 53 00 00 00 9f e6 42 45 45 00 00 00 [.c$ES.....BEE...]
2022
- # 0050: 59 c0 04 3f 52 b8 42 41 14 00 00 00 0b 00 00 00 [Y..?R.BA........]
2023
- # 0060: 19 00 00 00 06 00 00 00 05 00 00 00 f6 ff ff ff [................]
2024
- # 0070: 03 00 00 00 04 00 00 00 00 00 00 00 00 00 00 00 [................]
2025
- ($latRef, $lonRef) = ($1, $2);
2026
- ($hr,$min,$sec,$yr,$mon,$day,@acc) = unpack('x48V3x28V6',$$dataPt);
2027
- map { $_ = $_ - 4294967296 if $_ >= 0x80000000; $_ /= 1000 } @acc; # (NC)
2028
- $lat = GetFloat($dataPt, 0x40);
2029
- $lon = GetFloat($dataPt, 0x48);
2030
- $spd = GetFloat($dataPt, 0x50);
2031
- $trk = GetFloat($dataPt, 0x54);
2032
-
2033
1910
  } elsif ($$dataPt =~ /^.{16}A([NS])([EW])\0/s) {
2034
1911
 
2035
- $debug and $et->FoundTag(GPSType => '2F');
1912
+ $debug and $et->FoundTag(GPSType => 12);
2036
1913
  # INNOVV MP4 video (same format as INNOVV TS)
2037
1914
  while ($$dataPt =~ /(A[NS][EW]\0.{28})/g) {
2038
1915
  my $dat = $1;
@@ -2050,11 +1927,12 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
2050
1927
  $et->HandleTag($tagTbl, GPSTrack => $trk);
2051
1928
  $et->HandleTag($tagTbl, Accelerometer => "@acc");
2052
1929
  }
1930
+ SetByteOrder($oldOrder);
2053
1931
  return 1;
2054
1932
 
2055
1933
  } elsif ($$dataPt =~ /^.{28}A.{11}([NS]).{15}([EW])/s) {
2056
1934
 
2057
- $debug and $et->FoundTag(GPSType => '2G');
1935
+ $debug and $et->FoundTag(GPSType => 13);
2058
1936
  # Vantrue N4 dashcam
2059
1937
  # 0000: 00 00 40 00 66 72 65 65 47 50 53 20 f0 03 00 00 [..@.freeGPS ....]
2060
1938
  # 0010: 0d 00 00 00 16 00 00 00 1e 00 00 00 41 00 00 00 [............A...]
@@ -2077,16 +1955,82 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
2077
1955
  map { $_ = $_ - 4294967296 if $_ >= 0x80000000; $_ /= 1000 } @acc; # (NC)
2078
1956
  # (not necessary to read RMC sentence because we already have it all)
2079
1957
 
2080
- } else {
1958
+ } elsif ($$dataPt =~ /^.{72}A[NS][EW]\0/s) {
2081
1959
 
2082
- $debug and $et->FoundTag(GPSType => '2H');
2083
- # (look for binary GPS as stored by NextBase 512G, ref PH)
1960
+ # decode binary GPS format (Viofo A119S, ref 2)
2084
1961
  # header looks like this in my sample:
1962
+ # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 4c 00 00 00 [....freeGPS L...]
1963
+ # 0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
1964
+ # 0020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
1965
+ # 0030: 10 00 00 00 2d 00 00 00 14 00 00 00 11 00 00 00 [....-...........]
1966
+ # 0040: 0c 00 00 00 1f 00 00 00 41 4e 45 00 5d 9a a9 45 [........ANE.]..E]
1967
+ # 0050: ab 1e e5 44 ec 51 f0 40 b8 5e a5 43 00 00 00 00 [...D.Q.@.^.C....]
1968
+ # (records are same structure as Type 3 Novatek GPS:)
1969
+ # Type 3 (Novatek GPS, ref 2):
1970
+ # 0x30 - int32u hour
1971
+ # 0x34 - int32u minute
1972
+ # 0x38 - int32u second
1973
+ # 0x3c - int32u year - 2000
1974
+ # 0x40 - int32u month
1975
+ # 0x44 - int32u day
1976
+ # 0x48 - int8u GPS status ('A' or 'V')
1977
+ # 0x49 - int8u latitude ref ('N' or 'S')
1978
+ # 0x4a - int8u longitude ref ('E' or 'W')
1979
+ # 0x4b - 0
1980
+ # 0x4c - float latitude (DDMM.MMMMMM)
1981
+ # 0x50 - float longitude (DDMM.MMMMMM)
1982
+ # 0x54 - float speed (knots)
1983
+ # 0x58 - float heading (deg)
1984
+ # Type 3b, same as above for 0x30-0x4a (ref PH)
1985
+ # 0x4c - int32s latitude (decimal degrees * 1e7)
1986
+ # 0x50 - int32s longitude (decimal degrees * 1e7)
1987
+ # 0x54 - int32s speed (m/s * 100)
1988
+ # 0x58 - float altitude (m * 1000, NC)
1989
+ ($hr,$min,$sec,$yr,$mon,$day,$stat,$latRef,$lonRef) =
1990
+ unpack('x48V6a1a1a1x1V4', $$dataPt);
1991
+ if (substr($$dataPt, 16, 3) eq 'IQS') {
1992
+ $debug and $et->FoundTag(GPSType => 14);
1993
+ # Type 3b (ref PH)
1994
+ # header looks like this in my sample:
1995
+ # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 4c 00 00 00 [....freeGPS L...]
1996
+ # 0010: 49 51 53 5f 41 37 5f 32 30 31 35 30 34 31 37 00 [IQS_A7_20150417.]
1997
+ # 0020: 4d 61 72 20 32 39 20 32 30 31 37 2c 20 31 36 3a [Mar 29 2017, 16:]
1998
+ $ddd = 1;
1999
+ $lat = abs Get32s($dataPt, 0x4c) / 1e7;
2000
+ $lon = abs Get32s($dataPt, 0x50) / 1e7;
2001
+ $spd = Get32s($dataPt, 0x54) / 100 * $mpsToKph;
2002
+ $alt = GetFloat($dataPt, 0x58) / 1000; # (NC)
2003
+ } else {
2004
+ $debug and $et->FoundTag(GPSType => 15);
2005
+ $lat = GetFloat($dataPt, 0x4c);
2006
+ $lon = GetFloat($dataPt, 0x50);
2007
+ $spd = GetFloat($dataPt, 0x54) * $knotsToKph;
2008
+ $trk = GetFloat($dataPt, 0x58);
2009
+ # ($trk is not confirmed; may be GPSImageDirection, ref PH)
2010
+ }
2011
+ if ($dirLen >= 0xb0) {
2012
+ # lat/lon also stored as doubles by Transcend Driver Pro 230 (ref PH)
2013
+ my ($lat2, $lon2) = ( GetDouble($dataPt, 0x70), GetDouble($dataPt, 0x80) );
2014
+ # (0xa0 is altitude, don't know what 0x98 and 0xa8 are)
2015
+ if (abs($lat2-$lat) < 0.001 and abs($lon2-$lon) < 0.001) {
2016
+ $lat = $lat2;
2017
+ $lon = $lon2;
2018
+ $alt = GetDouble($dataPt, 0xa0);
2019
+ }
2020
+ }
2021
+
2022
+ } else {
2023
+
2024
+ $debug and $et->FoundTag(GPSType => 16);
2025
+ # (look for binary GPS as stored by Nextbase 512G, ref PH)
2085
2026
  # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 78 01 00 00 [....freeGPS x...]
2086
2027
  # 0010: 78 2e 78 78 00 00 00 00 00 00 00 00 00 00 00 00 [x.xx............]
2087
2028
  # 0020: 30 30 30 30 30 00 00 00 00 00 00 00 00 00 00 00 [00000...........]
2088
-
2089
- # followed by a number of 32-byte records in this format (big endian!):
2029
+ # 0030: 24 53 02 79 d4 85 07 e2 0a 08 06 2a 01 d1 02 20 [$S.y.......*... ]
2030
+ # 0040: 14 98 ff ff 21 67 97 10 00 00 00 00 00 00 00 00 [....!g..........]
2031
+ # 0050: 24 53 02 a2 d4 42 07 e2 0a 08 06 2a 01 d2 02 20 [$S...B.....*... ]
2032
+ # 0060: 14 98 e3 ff 21 67 3b 10 00 00 00 00 00 00 00 00 [....!g;.........]
2033
+ # 32-byte record structure (big endian!):
2090
2034
  # 0x30 - int16u unknown (seen: 0x24 0x53 = "$S")
2091
2035
  # 0x32 - int16u speed (m/s * 100)
2092
2036
  # 0x34 - int16s heading (deg * 100) (or GPSImgDirection?)
@@ -2101,8 +2045,9 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
2101
2045
  # 0x43 - int32s longitude (decimal degrees * 1e7)
2102
2046
  # 0x47 - int8u unknown (seen: 16)
2103
2047
  # 0x48-0x4f - all zero
2048
+ my $pos;
2104
2049
  for ($pos=0x32; ; ) {
2105
- ($spd,$trk,$yr,$mon,$day,$hr,$min,$sec,$unk,$lat,$lon) = unpack "x${pos}nnnCCCCnCNN", $$dataPt;
2050
+ ($spd,$trk,$yr,$mon,$day,$hr,$min,$sec,$lat,$lon) = unpack "x${pos}nnnCCCCnx1NN", $$dataPt;
2106
2051
  # validate record using date/time
2107
2052
  last if $yr < 2000 or $yr > 2200 or
2108
2053
  $mon < 1 or $mon > 12 or
@@ -2122,28 +2067,41 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
2122
2067
  $et->HandleTag($tagTbl, GPSTrack => $trk);
2123
2068
  last if $pos += 0x20 > length($$dataPt) - 0x1e;
2124
2069
  }
2070
+ SetByteOrder($oldOrder);
2125
2071
  return $$et{DOC_NUM} ? 1 : 0; # return 0 if nothing extracted
2126
2072
  }
2127
2073
  #
2128
2074
  # save tag values extracted by above code
2129
2075
  #
2130
- return 0 if $mon < 1 or $mon > 12; # quick sanity check
2131
- $$et{DOC_NUM} = ++$$et{DOC_COUNT};
2132
- $yr += 2000 if $yr < 2000;
2133
- my $time = sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%.2dZ', $yr, $mon, $day, $hr, $min, $sec);
2134
- # convert from DDMM.MMMMMM to DD.DDDDDD format if necessary
2135
- ConvertLatLon($lat, $lon) unless $ddd;
2136
- $et->HandleTag($tagTbl, GPSDateTime => $time);
2137
- $et->HandleTag($tagTbl, GPSLatitude => $lat * ($latRef eq 'S' ? -1 : 1));
2138
- $et->HandleTag($tagTbl, GPSLongitude => $lon * ($lonRef eq 'W' ? -1 : 1));
2139
- $et->HandleTag($tagTbl, GPSSpeed => $spd) if defined $spd; # (now in km/h)
2140
- $et->HandleTag($tagTbl, GPSTrack => $trk) if defined $trk;
2076
+ SetByteOrder($oldOrder);
2077
+ return 0 if defined $yr and $mon < 1 or $mon > 12; # quick sanity check
2078
+ FoundSomething($et, $tagTbl, $$dirInfo{SampleTime}, $$dirInfo{SampleDuration});
2079
+ $sec = '0' . $sec unless $sec =~ /^\d{2}/; # pad integer part of seconds to 2 digits
2080
+ if (defined $yr) {
2081
+ $yr += 2000 if $yr < 2000;
2082
+ my $time = sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%sZ',$yr,$mon,$day,$hr,$min,$sec);
2083
+ $et->HandleTag($tagTbl, GPSDateTime => $time);
2084
+ } elsif (defined $hr) {
2085
+ my $time = sprintf('%.2d:%.2d:%sZ',$hr,$min,$sec);
2086
+ $et->HandleTag($tagTbl, GPSTimeStamp => $time);
2087
+ }
2088
+ if (defined $lat) {
2089
+ # lat/long are in DDDMM.MMMM format unless $ddd is set
2090
+ ConvertLatLon($lat, $lon) unless $ddd;
2091
+ $et->HandleTag($tagTbl, GPSLatitude => $lat * ($latRef eq 'S' ? -1 : 1));
2092
+ $et->HandleTag($tagTbl, GPSLongitude => $lon * ($lonRef eq 'W' ? -1 : 1));
2093
+ }
2141
2094
  $et->HandleTag($tagTbl, GPSAltitude => $alt) if defined $alt;
2095
+ $et->HandleTag($tagTbl, GPSSpeed => $spd) if defined $spd;
2096
+ $et->HandleTag($tagTbl, GPSTrack => $trk) if defined $trk;
2097
+ while (@xtra) {
2098
+ my $tag = shift @xtra;
2099
+ $et->HandleTag($tagTbl, $tag => shift @xtra);
2100
+ }
2142
2101
  $et->HandleTag($tagTbl, Accelerometer => "@acc") if @acc;
2143
2102
  return 1;
2144
2103
  }
2145
2104
 
2146
-
2147
2105
  #------------------------------------------------------------------------------
2148
2106
  # Extract embedded information referenced from a track
2149
2107
  # Inputs: 0) ExifTool ref, 1) tag name, 2) data ref
@@ -2433,6 +2391,68 @@ sub Process_gsen($$$)
2433
2391
  return 1;
2434
2392
  }
2435
2393
 
2394
+ #------------------------------------------------------------------------------
2395
+ # Process 'gdat' atom Base64-encoded JSON-format timed GPS used by Nextbase software (ref PH)
2396
+ # Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
2397
+ # Returns: 1 on success
2398
+ sub Process_gdat($$$)
2399
+ {
2400
+ my ($et, $dirInfo, $tagTbl) = @_;
2401
+ unless ($$et{OPTIONS}{ExtractEmbedded}) {
2402
+ $et->WarnOnce('Use the ExtractEmbedded option to extract timed GPSData',3);
2403
+ return 0;
2404
+ }
2405
+ my $dataPt = $$dirInfo{DataPt};
2406
+ require Image::ExifTool::XMP;
2407
+ $dataPt = Image::ExifTool::XMP::DecodeBase64($$dataPt);
2408
+ my (%dbase, $fix);
2409
+ require Image::ExifTool::Import;
2410
+ Image::ExifTool::Import::ReadJSON($dataPt, \%dbase);
2411
+ my $info = $dbase{'*'} or return 0;
2412
+ $et->HandleTag($tagTbl, CameraModel => $$info{cameraModel}) if $$info{cameraModel};
2413
+ my $gps = $$info{gpsData} or return 0;
2414
+ return 0 unless ref $gps eq 'ARRAY';
2415
+ foreach $fix (@$gps) {
2416
+ next unless ref $fix eq 'HASH' and $$fix{gpsStatus} and $$fix{gpsStatus} eq 'A';
2417
+ $$et{DOC_NUM} = ++$$et{DOC_COUNT};
2418
+ if ($$fix{datetime}) {
2419
+ $$fix{datetime} =~ tr/-T/: /;
2420
+ $et->HandleTag($tagTbl, GPSDateTime => $$fix{datetime});
2421
+ }
2422
+ $et->HandleTag($tagTbl, GPSLatitude => $$fix{lat}) if defined $$fix{lat};
2423
+ $et->HandleTag($tagTbl, GPSLongitude => $$fix{lon}) if defined $$fix{lon};
2424
+ $et->HandleTag($tagTbl, GPSSpeed => $$fix{speed} * $mphToKph) if defined $$fix{speed};
2425
+ $et->HandleTag($tagTbl, GPSTrack => $$fix{bearing}) if defined $$fix{bearing};
2426
+ if (defined $$fix{xAcc} and defined $$fix{yAcc} and defined $$fix{zAcc}) {
2427
+ $et->HandleTag($tagTbl, Accelerometer => "$$fix{xAcc} $$fix{yAcc} $$fix{zAcc}");
2428
+ }
2429
+ }
2430
+ delete $$et{DOC_NUM};
2431
+ return 1;
2432
+ }
2433
+
2434
+ #------------------------------------------------------------------------------
2435
+ # Extract GPS from Nextbase 'nbmt' atom
2436
+ # Inputs: 0) ExifTool ref, 1) data ref or dirInfo ref, 2) tag table ref
2437
+ # Returns: 1 on success
2438
+ sub Process_nbmt($$$)
2439
+ {
2440
+ my ($et, $dataPt, $tagTbl) = @_;
2441
+
2442
+ if ($$et{OPTIONS}{ExtractEmbedded}) {
2443
+ $$et{DOC_NUM} = $$et{DOC_COUNT} + 1;
2444
+ delete $$et{UnknownTextCount};
2445
+ delete $$et{NoMoreTextDecoding};
2446
+ Process_text($et, $dataPt, $tagTbl, 1);
2447
+ delete $$et{UnknownTextCount};
2448
+ delete $$et{NoMoreTextDecoding};
2449
+ delete $$et{DOC_NUM};
2450
+ } else {
2451
+ $et->WarnOnce('Use the ExtractEmbedded option to extract timed GPSData',3);
2452
+ }
2453
+ return 1;
2454
+ }
2455
+
2436
2456
  #------------------------------------------------------------------------------
2437
2457
  # Process LIGOGPS JSON-format GPS from Yada RoadCam Pro 4K BT58189
2438
2458
  # Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
@@ -2454,9 +2474,8 @@ sub ProcessLIGO_JSON($$$)
2454
2474
  $et->VerboseDir('LIGO_JSON', undef, length($$dataPt));
2455
2475
  while ($$dataPt =~ /LIGOGPSINFO (\{.*?\})/g) {
2456
2476
  my $json = $1;
2457
- my $raf = new File::RandomAccess(\$json);
2458
2477
  my %dbase;
2459
- Image::ExifTool::Import::ReadJSON($raf, \%dbase);
2478
+ Image::ExifTool::Import::ReadJSON(\$json, \%dbase);
2460
2479
  my $info = $dbase{'*'} or next;
2461
2480
  # my sample contains the following JSON fields (in this order):
2462
2481
  # Hour Minute Second Year Month Day (GPS UTC time)
@@ -2863,6 +2882,8 @@ sub ProcessTTAD($$$)
2863
2882
  # Extract information from Insta360 trailer (INSV and INSP files) (ref PH)
2864
2883
  # Inputs: 0) ExifTool ref, 1) Optional dirInfo ref for returning trailer info
2865
2884
  # Returns: true on success
2885
+ # Notes: There looks to be some useful information by telemetry-parser, but
2886
+ # the code is cryptic: https://github.com/AdrianEddy/telemetry-parser
2866
2887
  sub ProcessInsta360($;$)
2867
2888
  {
2868
2889
  local $_;
@@ -3073,7 +3094,7 @@ sub ProcessInsta360($;$)
3073
3094
  $raf->Seek($epos, 2) or last; # seek to start of next footer
3074
3095
  $raf->Read($buff, 6) == 6 or last; # read footer
3075
3096
  }
3076
- $$et{DOC_NUM} = 0;
3097
+ delete $$et{DOC_NUM};
3077
3098
  SetByteOrder('MM');
3078
3099
  delete $$et{SET_GROUP0};
3079
3100
  delete $$et{SET_GROUP1};
@@ -3215,7 +3236,7 @@ sub ScanMediaData($)
3215
3236
  {
3216
3237
  my $et = shift;
3217
3238
  my $raf = $$et{RAF} or return;
3218
- my ($tagTbl, $oldByteOrder, $verbose, $buff, $dataLen);
3239
+ my ($tagTbl, $verbose, $buff, $dataLen);
3219
3240
  my ($pos, $buf2) = (0, '');
3220
3241
 
3221
3242
  # don't rescan for freeGPS if we already found embedded metadata
@@ -3251,8 +3272,6 @@ sub ScanMediaData($)
3251
3272
  # initialize variables for extracting metadata from this block
3252
3273
  $tagTbl = GetTagTable('Image::ExifTool::QuickTime::Stream');
3253
3274
  $verbose = $$et{OPTIONS}{Verbose};
3254
- $oldByteOrder = GetByteOrder();
3255
- SetByteOrder('II');
3256
3275
  $et->VPrint(0, "---- Extract Embedded ----\n");
3257
3276
  $$et{INDENT} .= '| ';
3258
3277
  }
@@ -3275,15 +3294,14 @@ sub ScanMediaData($)
3275
3294
  $et->VerboseDump(\$buff, DataPos => $pos + $dataPos);
3276
3295
  }
3277
3296
  my $dirInfo = { DataPt => \$buff, DataPos => $pos + $dataPos, DirLen => $len };
3278
- ProcessFreeGPS2($et, $dirInfo, $tagTbl);
3297
+ ProcessFreeGPS($et, $dirInfo, $tagTbl);
3279
3298
  }
3280
3299
  $pos += $len;
3281
3300
  $buf2 = substr($buff, $len);
3282
3301
  }
3283
3302
  if ($tagTbl) {
3284
- $$et{DOC_NUM} = 0; # reset DOC_NUM after extracting embedded metadata
3303
+ delete $$et{DOC_NUM}; # reset DOC_NUM after extracting embedded metadata
3285
3304
  $et->VPrint(0, "--------------------------\n");
3286
- SetByteOrder($oldByteOrder);
3287
3305
  $$et{INDENT} = substr $$et{INDENT}, 0, -2;
3288
3306
  }
3289
3307
  # process Insta360 trailer if it exists