exiftool_vendored 12.76.1 → 12.80.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/bin/Changes +79 -4
  3. data/bin/MANIFEST +27 -0
  4. data/bin/META.json +1 -1
  5. data/bin/META.yml +1 -1
  6. data/bin/README +3 -3
  7. data/bin/config_files/acdsee.config +37 -57
  8. data/bin/config_files/example.config +16 -2
  9. data/bin/exiftool +84 -29
  10. data/bin/lib/Image/ExifTool/Canon.pm +12 -9
  11. data/bin/lib/Image/ExifTool/CanonVRD.pm +7 -1
  12. data/bin/lib/Image/ExifTool/Exif.pm +52 -4
  13. data/bin/lib/Image/ExifTool/FujiFilm.pm +3 -0
  14. data/bin/lib/Image/ExifTool/GPS.pm +5 -3
  15. data/bin/lib/Image/ExifTool/GeoLang/cs.pm +978 -0
  16. data/bin/lib/Image/ExifTool/GeoLang/de.pm +1975 -0
  17. data/bin/lib/Image/ExifTool/GeoLang/en_ca.pm +44 -0
  18. data/bin/lib/Image/ExifTool/GeoLang/en_gb.pm +124 -0
  19. data/bin/lib/Image/ExifTool/GeoLang/es.pm +2921 -0
  20. data/bin/lib/Image/ExifTool/GeoLang/fi.pm +1116 -0
  21. data/bin/lib/Image/ExifTool/GeoLang/fr.pm +3171 -0
  22. data/bin/lib/Image/ExifTool/GeoLang/it.pm +2750 -0
  23. data/bin/lib/Image/ExifTool/GeoLang/ja.pm +10256 -0
  24. data/bin/lib/Image/ExifTool/GeoLang/ko.pm +4499 -0
  25. data/bin/lib/Image/ExifTool/GeoLang/nl.pm +1270 -0
  26. data/bin/lib/Image/ExifTool/GeoLang/pl.pm +3019 -0
  27. data/bin/lib/Image/ExifTool/GeoLang/ru.pm +18220 -0
  28. data/bin/lib/Image/ExifTool/GeoLang/sk.pm +441 -0
  29. data/bin/lib/Image/ExifTool/GeoLang/sv.pm +714 -0
  30. data/bin/lib/Image/ExifTool/GeoLang/tr.pm +452 -0
  31. data/bin/lib/Image/ExifTool/GeoLang/zh_cn.pm +2225 -0
  32. data/bin/lib/Image/ExifTool/GeoLang/zh_tw.pm +72 -0
  33. data/bin/lib/Image/ExifTool/Geolocation.dat +0 -0
  34. data/bin/lib/Image/ExifTool/Geolocation.pm +775 -0
  35. data/bin/lib/Image/ExifTool/Geotag.pm +8 -1
  36. data/bin/lib/Image/ExifTool/HtmlDump.pm +2 -1
  37. data/bin/lib/Image/ExifTool/Import.pm +5 -2
  38. data/bin/lib/Image/ExifTool/JSON.pm +15 -10
  39. data/bin/lib/Image/ExifTool/MWG.pm +1 -0
  40. data/bin/lib/Image/ExifTool/MacOS.pm +19 -4
  41. data/bin/lib/Image/ExifTool/Nikon.pm +2 -2
  42. data/bin/lib/Image/ExifTool/Ogg.pm +3 -2
  43. data/bin/lib/Image/ExifTool/Olympus.pm +3 -1
  44. data/bin/lib/Image/ExifTool/OpenEXR.pm +17 -17
  45. data/bin/lib/Image/ExifTool/PDF.pm +5 -5
  46. data/bin/lib/Image/ExifTool/Pentax.pm +1 -1
  47. data/bin/lib/Image/ExifTool/QuickTime.pm +183 -11
  48. data/bin/lib/Image/ExifTool/QuickTimeStream.pl +253 -237
  49. data/bin/lib/Image/ExifTool/README +6 -5
  50. data/bin/lib/Image/ExifTool/TagLookup.pm +2624 -2520
  51. data/bin/lib/Image/ExifTool/TagNames.pod +184 -4
  52. data/bin/lib/Image/ExifTool/WriteQuickTime.pl +15 -1
  53. data/bin/lib/Image/ExifTool/WriteXMP.pl +1 -1
  54. data/bin/lib/Image/ExifTool/Writer.pl +59 -8
  55. data/bin/lib/Image/ExifTool/XMP.pm +17 -2
  56. data/bin/lib/Image/ExifTool/XMP2.pl +64 -0
  57. data/bin/lib/Image/ExifTool.pm +249 -47
  58. data/bin/lib/Image/ExifTool.pod +57 -25
  59. data/bin/perl-Image-ExifTool.spec +2 -2
  60. data/lib/exiftool_vendored/version.rb +1 -1
  61. metadata +22 -2
@@ -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' } },
@@ -912,9 +913,14 @@ sub Process_text($$$;$)
912
913
  $et->VerboseDir($dirName, undef, length($$dataPt));
913
914
  }
914
915
 
915
- while ($$dataPt =~ /\$(\w+)([^\$]*)/g) {
916
+ while ($$dataPt =~ /\$(\w+)([^\$\0]*)/g) {
916
917
  my ($tag, $dat) = ($1, $2);
917
- 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
+ }
918
924
  my $time = "$1:$2:$3";
919
925
  if ($$et{LastTime}) {
920
926
  if ($$et{LastTime} eq $time) {
@@ -974,8 +980,8 @@ sub Process_text($$$;$)
974
980
  } elsif ($tag eq 'BEGIN') {
975
981
  $tags{Text} = $dat if length $dat;
976
982
  $tags{done} = 1;
977
- } elsif ($tag ne 'END') {
978
- $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";
979
985
  }
980
986
  }
981
987
  %tags and HandleTextTags($et, $tagTbl, \%tags), return;
@@ -1146,13 +1152,13 @@ sub ProcessSamples($)
1146
1152
  {
1147
1153
  my $et = shift;
1148
1154
  my ($raf, $ee) = @$et{qw(RAF ee)};
1149
- my ($i, $buff, $pos, $hdrLen, $hdrFmt, @time, @dur, $oldIndent, $hash);
1155
+ my ($i, $pos, $hdrLen, $hdrFmt, @time, @dur, $oldIndent, $hash);
1150
1156
  my ($mdatOffset, $mdatSize); # (for range-checking samples when hash is done)
1151
1157
 
1152
1158
  return unless $ee;
1153
1159
  delete $$et{ee}; # use only once
1154
1160
 
1155
- my $eeOpt = $et->Options('ExtractEmbedded');
1161
+ my $eeOpt = $et->Options('ExtractEmbedded') || 0;
1156
1162
  my $type = $$et{HandlerType} || '';
1157
1163
  if ($type eq 'vide') {
1158
1164
  # only process specific types of video streams
@@ -1254,6 +1260,7 @@ Sample: for ($i=0; ; ) {
1254
1260
  $hdrFmt = ($hdrLen == 4 ? 'N' : $hdrLen == 2 ? 'n' : 'C');
1255
1261
  require Image::ExifTool::H264;
1256
1262
  }
1263
+
1257
1264
  # loop through all samples
1258
1265
  for ($i=0; $i<@$start and $i<@$size; ++$i) {
1259
1266
 
@@ -1274,6 +1281,7 @@ Sample: for ($i=0; ; ) {
1274
1281
  }
1275
1282
  # read the sample data
1276
1283
  $raf->Seek($$start[$i], 0) or $et->WarnOnce("Seek error in $type data"), next;
1284
+ my $buff;
1277
1285
  my $n = $raf->Read($buff, $size);
1278
1286
  unless ($n == $size) {
1279
1287
  $et->WarnOnce("Error reading $type data");
@@ -1295,10 +1303,7 @@ Sample: for ($i=0; ; ) {
1295
1303
  $pos += $hdrLen + $len;
1296
1304
  last if $pos + $hdrLen >= length($buff);
1297
1305
  }
1298
- if ($$et{GotNAL06}) {
1299
- my $eeOpt = $et->Options('ExtractEmbedded');
1300
- last unless $eeOpt and $eeOpt > 2;
1301
- }
1306
+ last if $$et{GotNAL06} and $eeOpt < 3;
1302
1307
  next;
1303
1308
  }
1304
1309
  if ($verbose > 1) {
@@ -1349,8 +1354,9 @@ Sample: for ($i=0; ; ) {
1349
1354
  SetGPSDateTime($et, $tagTbl, $time[$i], 1);
1350
1355
  next; # all done (don't store/process as text)
1351
1356
  }
1352
- unless (defined $val) {
1353
- $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);
1354
1360
  $handled = 1;
1355
1361
  }
1356
1362
  }
@@ -1384,7 +1390,10 @@ Sample: for ($i=0; ; ) {
1384
1390
  } elsif ($type eq 'gps ') { # (ie. GPSDataList tag)
1385
1391
 
1386
1392
  if ($buff =~ /^....freeGPS /s) {
1387
- # 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)
1388
1397
  ProcessFreeGPS($et, {
1389
1398
  DataPt => \$buff,
1390
1399
  DataPos => $$start[$i],
@@ -1417,7 +1426,7 @@ Sample: for ($i=0; ; ) {
1417
1426
  }
1418
1427
  # clean up
1419
1428
  $raf->Seek($tell, 0); # restore original file position
1420
- $$et{DOC_NUM} = 0;
1429
+ delete $$et{DOC_NUM};
1421
1430
  $$et{HandlerType} = $$et{HanderDesc} = '';
1422
1431
  }
1423
1432
 
@@ -1435,11 +1444,10 @@ sub ConvertLatLon($$)
1435
1444
  }
1436
1445
 
1437
1446
  #------------------------------------------------------------------------------
1438
- # Process "freeGPS " data blocks referenced by a 'gps ' (GPSDataList) atom
1447
+ # Process "freeGPS " data blocks
1439
1448
  # Inputs: 0) ExifTool ref, 1) dirInfo ref {DataPt,SampleTime,SampleDuration}, 2) tagTable ref
1440
1449
  # Returns: 1 on success (or 0 on unrecognized or "measurement-void" GPS data)
1441
1450
  # Notes:
1442
- # - also see ProcessFreeGPS2() below for processing of other types of freeGPS blocks
1443
1451
  sub ProcessFreeGPS($$$)
1444
1452
  {
1445
1453
  my ($et, $dirInfo, $tagTbl) = @_;
@@ -1448,11 +1456,15 @@ sub ProcessFreeGPS($$$)
1448
1456
  my ($yr, $mon, $day, $hr, $min, $sec, $stat, $lbl, $ddd);
1449
1457
  my ($lat, $latRef, $lon, $lonRef, $spd, $trk, $alt, @acc, @xtra);
1450
1458
 
1451
- 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');
1452
1464
 
1453
1465
  if (substr($$dataPt,18,8) eq "\xaa\xaa\xf2\xe1\xf0\xee\x54\x54") {
1454
1466
 
1455
- $debug and $et->FoundTag(GPSType => '1A');
1467
+ $debug and $et->FoundTag(GPSType => 1);
1456
1468
  # (this is very similar to the encrypted text format)
1457
1469
  # decode encrypted ASCII-based GPS (DashCam Azdome GS63H, ref 5)
1458
1470
  # header looks like this in my sample:
@@ -1517,12 +1529,19 @@ sub ProcessFreeGPS($$$)
1517
1529
 
1518
1530
  } elsif ($$dataPt =~ /^.{52}(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/s) {
1519
1531
 
1520
- $debug and $et->FoundTag(GPSType => '1B');
1521
- # 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)
1522
1534
  # header looks like this in my sample:
1523
1535
  # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 40 01 00 00 [....freeGPS @...]
1524
1536
  # 0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
1525
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........]
1526
1545
  push @xtra, CameraDateTime => "$1:$2:$3 $4:$5:$6";
1527
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) {
1528
1547
  ($lat,$latRef,$lon,$lonRef) = ($5,$6,$7,$8);
@@ -1546,7 +1565,7 @@ sub ProcessFreeGPS($$$)
1546
1565
 
1547
1566
  } elsif ($$dataPt =~ /^.{37}\0\0\0A([NS])([EW])/s) {
1548
1567
 
1549
- $debug and $et->FoundTag(GPSType => '1C');
1568
+ $debug and $et->FoundTag(GPSType => 3);
1550
1569
  # decode freeGPS from ViofoA119v3 dashcam (similar to Novatek GPS format)
1551
1570
  # 0000: 00 00 40 00 66 72 65 65 47 50 53 20 f0 03 00 00 [..@.freeGPS ....]
1552
1571
  # 0010: 05 00 00 00 2f 00 00 00 03 00 00 00 13 00 00 00 [..../...........]
@@ -1554,9 +1573,7 @@ sub ProcessFreeGPS($$$)
1554
1573
  # 0030: f1 47 40 46 66 66 d2 41 85 eb 83 41 00 00 00 00 [.G@Fff.A...A....]
1555
1574
  ($latRef, $lonRef) = ($1, $2);
1556
1575
  ($hr,$min,$sec,$yr,$mon,$day) = unpack('x16V6', $$dataPt);
1557
- if ($yr < 2000) {
1558
- $yr += 2000;
1559
- } else {
1576
+ if ($yr >= 2000) {
1560
1577
  # Kenwood dashcam sometimes stores absolute year and local time
1561
1578
  # (but sometimes year since 2000 and UTC time in same video!)
1562
1579
  require Time::Local;
@@ -1566,7 +1583,6 @@ sub ProcessFreeGPS($$$)
1566
1583
  ++$mon;
1567
1584
  $et->WarnOnce('Converting GPSDateTime to UTC based on local time zone',1);
1568
1585
  }
1569
- SetByteOrder('II');
1570
1586
  $lat = GetFloat($dataPt, 0x2c);
1571
1587
  $lon = GetFloat($dataPt, 0x30);
1572
1588
  $spd = GetFloat($dataPt, 0x34) * $knotsToKph; # (convert knots to km/h)
@@ -1577,11 +1593,10 @@ sub ProcessFreeGPS($$$)
1577
1593
  @acc = unpack('V3', $tmp);
1578
1594
  map { $_ = $_ - 4294967296 if $_ >= 0x80000000; $_ /= 256 } @acc;
1579
1595
  }
1580
- SetByteOrder('MM');
1581
1596
 
1582
1597
  } elsif ($$dataPt =~ /^.{21}\0\0\0A([NS])([EW])/s) {
1583
1598
 
1584
- $debug and $et->FoundTag(GPSType => '1D');
1599
+ $debug and $et->FoundTag(GPSType => 4);
1585
1600
  # also decode 'gpmd' chunk from Kingslim D4 dashcam videos
1586
1601
  # 0000: 0a 00 00 00 0b 00 00 00 07 00 00 00 e5 07 00 00 [................]
1587
1602
  # 0010: 06 00 00 00 03 00 00 00 41 4e 57 31 91 52 83 45 [........ANW1.R.E]
@@ -1593,7 +1608,6 @@ sub ProcessFreeGPS($$$)
1593
1608
  # 0070: 20 f0 12 10 12 21 e5 0e 10 12 2f 90 10 13 01 f2 [ ....!..../.....]
1594
1609
  ($latRef, $lonRef) = ($1, $2);
1595
1610
  ($hr,$min,$sec,$yr,$mon,$day) = unpack("V6", $$dataPt);
1596
- SetByteOrder('II');
1597
1611
  # lat/lon aren't decoded properly, but spd,trk,acc are
1598
1612
  $lat = GetFloat($dataPt, 0x1c);
1599
1613
  $lon = GetFloat($dataPt, 0x20);
@@ -1606,11 +1620,10 @@ sub ProcessFreeGPS($$$)
1606
1620
  $acc[0] = GetFloat($dataPt, 0x2c);
1607
1621
  $acc[1] = GetFloat($dataPt, 0x30);
1608
1622
  $acc[2] = GetFloat($dataPt, 0x34);
1609
- SetByteOrder('MM');
1610
1623
 
1611
1624
  } elsif ($$dataPt =~ /^.{60}A\0{3}.{4}([NS])\0{3}.{4}([EW])\0{3}/s) {
1612
1625
 
1613
- $debug and $et->FoundTag(GPSType => '1E');
1626
+ $debug and $et->FoundTag(GPSType => 5);
1614
1627
  # decode freeGPS from Akaso dashcam
1615
1628
  # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 60 00 00 00 [....freeGPS `...]
1616
1629
  # 0010: 78 2e 78 78 00 00 00 00 00 00 00 00 00 00 00 00 [x.xx............]
@@ -1619,19 +1632,32 @@ sub ProcessFreeGPS($$$)
1619
1632
  # 0040: 13 b3 ca 44 4e 00 00 00 29 92 fb 45 45 00 00 00 [...DN...)..EE...]
1620
1633
  # 0050: d9 ee b4 41 ec d1 d3 42 e4 07 00 00 01 00 00 00 [...A...B........]
1621
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 [................]
1622
1644
  ($latRef, $lonRef) = ($1, $2);
1623
- ($hr, $min, $sec, $yr, $mon, $day) = unpack('x48V3x28V3', $$dataPt);
1624
- SetByteOrder('II');
1645
+ ($hr, $min, $sec, $yr, $mon, $day, @acc) = unpack('x48V3x28V6', $$dataPt);
1625
1646
  $lat = GetFloat($dataPt, 0x40);
1626
1647
  $lon = GetFloat($dataPt, 0x48);
1627
1648
  $spd = GetFloat($dataPt, 0x50);
1628
- $trk = GetFloat($dataPt, 0x54) + 180; # (why is this off by 180?)
1629
- $trk -= 360 if $trk >= 360;
1630
- 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
+ }
1631
1657
 
1632
1658
  } elsif ($$dataPt =~ /^.{60}4W`b]S</s and length($$dataPt) >= 140) {
1633
1659
 
1634
- $debug and $et->FoundTag(GPSType => '1F');
1660
+ $debug and $et->FoundTag(GPSType => 6);
1635
1661
  # 0000: 00 00 40 00 66 72 65 65 47 50 53 20 f0 01 00 00 [..@.freeGPS ....]
1636
1662
  # 0010: 5a 58 53 42 4e 58 59 53 00 00 00 00 00 00 00 00 [ZXSBNXYS........]
1637
1663
  # 0020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
@@ -1641,7 +1667,10 @@ sub ProcessFreeGPS($$$)
1641
1667
  # 0060: 42 3e 49 49 40 42 45 3c 55 3c 45 47 3e 45 43 41 [B>II@BE<U<EG>ECA]
1642
1668
  # decipher $GPRMC by subtracting 16 from each character value
1643
1669
  $_ = pack 'C*', map { $_>=16 and $_-=16 } unpack('x60C80', $$dataPt);
1644
- 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
+ }
1645
1674
  ($yr,$mon,$day,$hr,$min,$sec,$lat,$latRef,$lon,$lonRef) = ($13,$12,$11,$1,$2,$3,$5,$6,$7,$8);
1646
1675
  $yr += ($yr >= 70 ? 1900 : 2000);
1647
1676
  $spd = $9 * $knotsToKph if length $9;
@@ -1649,7 +1678,7 @@ sub ProcessFreeGPS($$$)
1649
1678
 
1650
1679
  } elsif ($$dataPt =~ /^.{64}[\x01-\x0c]\0{3}[\x01-\x1f]\0{3}A[NS][EW]\0{5}/s) {
1651
1680
 
1652
- $debug and $et->FoundTag(GPSType => '1G');
1681
+ $debug and $et->FoundTag(GPSType => 7);
1653
1682
  # Akaso V1 dascham
1654
1683
  # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 78 00 00 00 [....freeGPS x...]
1655
1684
  # 0010: 59 6e 64 41 6b 61 73 6f 43 61 72 00 00 00 00 00 [YndAkasoCar.....]
@@ -1673,27 +1702,20 @@ sub ProcessFreeGPS($$$)
1673
1702
  # 0090: 0a 00 00 00 e5 07 00 00 0c 00 00 00 1c 00 00 00 [................]
1674
1703
  ($hr,$min,$sec,$yr,$mon,$day,$stat,$latRef,$lonRef) =
1675
1704
  unpack('x48V6a1a1a1x1', $$dataPt);
1676
- # ignore invalid fixes
1677
- return 0 unless $stat eq 'A' and ($latRef eq 'N' or $latRef eq 'S') and
1678
- ($lonRef eq 'E' or $lonRef eq 'W');
1679
1705
 
1680
1706
  $et->WarnOnce('GPSLatitude/Longitude encryption is not yet known, so these will be wrong');
1681
1707
  # (see https://exiftool.org/forum/index.php?topic=11320.0)
1682
1708
 
1683
- SetByteOrder('II');
1684
-
1685
1709
  $spd = GetFloat($dataPt, 0x60);
1686
1710
  $trk = GetFloat($dataPt, 0x64) + 180; # (why is this off by 180?)
1687
1711
  $lat = GetDouble($dataPt, 0x50); # latitude is here, but encrypted somehow
1688
1712
  $lon = GetDouble($dataPt, 0x58); # longitude is here, but encrypted somehow
1689
1713
  $ddd = 1; # don't convert until we know what the format is
1690
-
1691
- SetByteOrder('MM');
1692
1714
  #my $serialNum = substr($$dataPt, 0x68, 20);
1693
1715
 
1694
1716
  } elsif ($$dataPt =~ /^.{12}\xac\0\0\0.{44}(.{72})/s) {
1695
1717
 
1696
- $debug and $et->FoundTag(GPSType => '1H');
1718
+ $debug and $et->FoundTag(GPSType => 8);
1697
1719
  # EACHPAI dash cam
1698
1720
  # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 ac 00 00 00 [....freeGPS ....]
1699
1721
  # 0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
@@ -1707,6 +1729,7 @@ sub ProcessFreeGPS($$$)
1707
1729
 
1708
1730
  $et->WarnOnce("Can't yet decrypt EACHPAI timed GPS", 1);
1709
1731
  # (see https://exiftool.org/forum/index.php?topic=5095.msg61266#msg61266)
1732
+ SetByteOrder($oldOrder);
1710
1733
  return 1;
1711
1734
 
1712
1735
  my $time = pack 'C*', map { $_ ^= 0 } unpack 'C*', $1;
@@ -1716,7 +1739,7 @@ sub ProcessFreeGPS($$$)
1716
1739
 
1717
1740
  } elsif ($$dataPt =~ /^.{64}A([NS])([EW])\0/s) {
1718
1741
 
1719
- $debug and $et->FoundTag(GPSType => '1I');
1742
+ $debug and $et->FoundTag(GPSType => 9);
1720
1743
  # Vantrue S1 dashcam
1721
1744
  # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 78 00 00 00 [....freeGPS x...]
1722
1745
  # 0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
@@ -1728,103 +1751,21 @@ sub ProcessFreeGPS($$$)
1728
1751
  # 0070: 05 00 00 00 7f 00 00 00 07 01 00 00 00 00 00 00 [................]
1729
1752
  ($latRef, $lonRef) = ($1, $2);
1730
1753
  ($yr,$mon,$day,$hr,$min,$sec,@acc) = unpack('x68V6x20V3', $$dataPt);
1731
- return 0 unless $mon>=1 and $mon<=12 and $day>=1 and $day<=31;
1732
- $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
+ }
1733
1758
  # (not sure about acc scaling)
1734
1759
  map { $_ = $_ - 4294967296 if $_ >= 0x80000000; $_ /= 1000 } @acc;
1735
- SetByteOrder('II');
1736
1760
  $lon = GetFloat($dataPt, 0x5c);
1737
1761
  $lat = GetFloat($dataPt, 0x60);
1738
1762
  $spd = GetFloat($dataPt, 0x64) * $knotsToKph;
1739
1763
  $trk = GetFloat($dataPt, 0x68);
1740
1764
  $alt = GetFloat($dataPt, 0x6c);
1741
- SetByteOrder('MM');
1742
-
1743
- } else {
1744
-
1745
- $debug and $et->FoundTag(GPSType => '1J');
1746
- # decode binary GPS format (Viofo A119S, ref 2)
1747
- # header looks like this in my sample:
1748
- # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 4c 00 00 00 [....freeGPS L...]
1749
- # 0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
1750
- # 0020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
1751
- # 0030: 10 00 00 00 2d 00 00 00 14 00 00 00 11 00 00 00 [....-...........]
1752
- # 0040: 0c 00 00 00 1f 00 00 00 41 4e 45 00 5d 9a a9 45 [........ANE.]..E]
1753
- # 0050: ab 1e e5 44 ec 51 f0 40 b8 5e a5 43 00 00 00 00 [...D.Q.@.^.C....]
1754
- # (records are same structure as Type 3 Novatek GPS in ProcessFreeGPS2() below)
1755
- ($hr,$min,$sec,$yr,$mon,$day,$stat,$latRef,$lonRef,$lat,$lon,$spd,$trk) =
1756
- unpack('x48V6a1a1a1x1V4', $$dataPt);
1757
- # ignore invalid fixes
1758
- return 0 unless $stat eq 'A' and ($latRef eq 'N' or $latRef eq 'S') and
1759
- ($lonRef eq 'E' or $lonRef eq 'W');
1760
- ($lat,$lon,$spd,$trk) = unpack 'f*', pack 'L*', $lat, $lon, $spd, $trk;
1761
- # lat/lon also stored as doubles by Transcend Driver Pro 230 (ref PH)
1762
- SetByteOrder('II');
1763
- my ($lat2, $lon2, $alt2) = (
1764
- GetDouble($dataPt, 0x70),
1765
- GetDouble($dataPt, 0x80),
1766
- # GetDouble($dataPt, 0x98), # (don't know what this is)
1767
- GetDouble($dataPt,0xa0),
1768
- # GetDouble($dataPt,0xa8)) # (don't know what this is)
1769
- );
1770
- if (abs($lat2-$lat) < 0.001 and abs($lon2-$lon) < 0.001) {
1771
- $lat = $lat2;
1772
- $lon = $lon2;
1773
- $alt = $alt2;
1774
- }
1775
- SetByteOrder('MM');
1776
- $yr += 2000 if $yr < 2000;
1777
- $spd *= $knotsToKph; # convert speed to km/h
1778
- # ($trk is not confirmed; may be GPSImageDirection, ref PH)
1779
- }
1780
- #
1781
- # save tag values extracted by above code
1782
- #
1783
- FoundSomething($et, $tagTbl, $$dirInfo{SampleTime}, $$dirInfo{SampleDuration});
1784
- $sec = '0' . $sec unless $sec =~ /^\d{2}/; # pad integer part of seconds to 2 digits
1785
- if (defined $yr) {
1786
- my $time = sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%sZ',$yr,$mon,$day,$hr,$min,$sec);
1787
- $et->HandleTag($tagTbl, GPSDateTime => $time);
1788
- } elsif (defined $hr) {
1789
- my $time = sprintf('%.2d:%.2d:%sZ',$hr,$min,$sec);
1790
- $et->HandleTag($tagTbl, GPSTimeStamp => $time);
1791
- }
1792
- if (defined $lat) {
1793
- # lat/long are in DDDMM.MMMM format unless $ddd is set
1794
- ConvertLatLon($lat, $lon) unless $ddd;
1795
- $et->HandleTag($tagTbl, GPSLatitude => $lat * ($latRef eq 'S' ? -1 : 1));
1796
- $et->HandleTag($tagTbl, GPSLongitude => $lon * ($lonRef eq 'W' ? -1 : 1));
1797
- }
1798
- $et->HandleTag($tagTbl, GPSAltitude => $alt) if defined $alt;
1799
- $et->HandleTag($tagTbl, GPSSpeed => $spd) if defined $spd;
1800
- $et->HandleTag($tagTbl, GPSTrack => $trk) if defined $trk;
1801
- while (@xtra) {
1802
- my $tag = shift @xtra;
1803
- $et->HandleTag($tagTbl, $tag => shift @xtra);
1804
- }
1805
- $et->HandleTag($tagTbl, Accelerometer => \@acc) if @acc;
1806
- return 1;
1807
- }
1808
1765
 
1809
- #------------------------------------------------------------------------------
1810
- # Process "freeGPS " data blocks _not_ referenced by a 'gps ' atom
1811
- # Inputs: 0) ExifTool ref, 1) dirInfo ref {DataPt,DataPos,DirLen}, 2) tagTable ref
1812
- # Returns: 1 on success
1813
- # Notes:
1814
- # - also see ProcessFreeGPS() above
1815
- sub ProcessFreeGPS2($$$)
1816
- {
1817
- my ($et, $dirInfo, $tagTbl) = @_;
1818
- my $dataPt = $$dirInfo{DataPt};
1819
- my $dirLen = $$dirInfo{DirLen};
1820
- my ($yr, $mon, $day, $hr, $min, $sec, $pos, @acc);
1821
- my ($lat, $latRef, $lon, $lonRef, $spd, $trk, $alt, $ddd, $unk);
1822
-
1823
- return 0 if $dirLen < 82; # minimum size of block with a single GPS record
1824
-
1825
- if (substr($$dataPt,0x45,3) eq 'ATC') {
1766
+ } elsif (substr($$dataPt,0x45,3) eq 'ATC') {
1826
1767
 
1827
- $debug and $et->FoundTag(GPSType => '2A');
1768
+ $debug and $et->FoundTag(GPSType => 10);
1828
1769
  # header looks like this: (sample 1)
1829
1770
  # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 38 06 00 00 [....freeGPS 8...]
1830
1771
  # 0010: 49 51 53 32 30 31 33 30 33 30 36 42 00 00 00 00 [IQS20130306B....]
@@ -1932,11 +1873,12 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
1932
1873
  }
1933
1874
  # save position of most recent record (needed when parsing the next freeGPS block)
1934
1875
  $$et{FreeGPS2}{RecentRecPos} = $lastRecPos;
1876
+ SetByteOrder($oldOrder);
1935
1877
  return 1;
1936
1878
 
1937
- } 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) {
1938
1880
 
1939
- $debug and $et->FoundTag(GPSType => '2B');
1881
+ $debug and $et->FoundTag(GPSType => 11);
1940
1882
  # header looks like this in my sample:
1941
1883
  # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 08 01 00 00 [....freeGPS ....]
1942
1884
  # 0010: 32 30 31 33 30 38 31 35 2e 30 31 00 00 00 00 00 [20130815.01.....]
@@ -1965,76 +1907,9 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
1965
1907
  $spd = GetDouble($dataPt, 0x60) * $knotsToKph;
1966
1908
  $trk = GetDouble($dataPt, 0x68);
1967
1909
 
1968
- } elsif ($$dataPt =~ /^.{72}A([NS])([EW])/s) {
1969
-
1970
- # Type 3 (Novatek GPS, ref 2): (in case it wasn't decoded via 'gps ' atom)
1971
- # 0x30 - int32u hour
1972
- # 0x34 - int32u minute
1973
- # 0x38 - int32u second
1974
- # 0x3c - int32u year - 2000
1975
- # 0x40 - int32u month
1976
- # 0x44 - int32u day
1977
- # 0x48 - int8u GPS status ('A' or 'V')
1978
- # 0x49 - int8u latitude ref ('N' or 'S')
1979
- # 0x4a - int8u longitude ref ('E' or 'W')
1980
- # 0x4b - 0
1981
- # 0x4c - float latitude (DDMM.MMMMMM)
1982
- # 0x50 - float longitude (DDMM.MMMMMM)
1983
- # 0x54 - float speed (knots)
1984
- # 0x58 - float heading (deg)
1985
- # Type 3b, same as above for 0x30-0x4a (ref PH)
1986
- # 0x4c - int32s latitude (decimal degrees * 1e7)
1987
- # 0x50 - int32s longitude (decimal degrees * 1e7)
1988
- # 0x54 - int32s speed (m/s * 100)
1989
- # 0x58 - float altitude (m * 1000, NC)
1990
- ($latRef, $lonRef) = ($1, $2);
1991
- ($hr,$min,$sec,$yr,$mon,$day) = unpack('x48V6', $$dataPt);
1992
- if (substr($$dataPt, 16, 3) eq 'IQS') {
1993
- $debug and $et->FoundTag(GPSType => '2C');
1994
- # Type 3b (ref PH)
1995
- # header looks like this in my sample:
1996
- # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 4c 00 00 00 [....freeGPS L...]
1997
- # 0010: 49 51 53 5f 41 37 5f 32 30 31 35 30 34 31 37 00 [IQS_A7_20150417.]
1998
- # 0020: 4d 61 72 20 32 39 20 32 30 31 37 2c 20 31 36 3a [Mar 29 2017, 16:]
1999
- $ddd = 1;
2000
- $lat = abs Get32s($dataPt, 0x4c) / 1e7;
2001
- $lon = abs Get32s($dataPt, 0x50) / 1e7;
2002
- $spd = Get32s($dataPt, 0x54) / 100 * $mpsToKph;
2003
- $alt = GetFloat($dataPt, 0x58) / 1000; # (NC)
2004
-
2005
- } else {
2006
- $debug and $et->FoundTag(GPSType => '2D');
2007
- # Type 3 (ref 2)
2008
- # (no sample with this format)
2009
- $lat = GetFloat($dataPt, 0x4c);
2010
- $lon = GetFloat($dataPt, 0x50);
2011
- $spd = GetFloat($dataPt, 0x54) * $knotsToKph;
2012
- $trk = GetFloat($dataPt, 0x58);
2013
- }
2014
-
2015
- } elsif ($$dataPt =~ /^.{60}A\0.{6}([NS])\0.{6}([EW])\0/s and $dirLen >= 112) {
2016
-
2017
- $debug and $et->FoundTag(GPSType => '2E');
2018
- # header looks like this in my sample (unknown dashcam, "Anticlock 2 2020_1125_1455_007.MOV"):
2019
- # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 68 00 00 00 [....freeGPS h...]
2020
- # 0010: 32 30 31 33 30 33 32 35 41 00 00 00 00 00 00 00 [20130325A.......]
2021
- # 0020: 41 70 72 20 20 36 20 32 30 31 36 2c 20 31 36 3a [Apr 6 2016, 16:]
2022
- # 0030: 0e 00 00 00 38 00 00 00 22 00 00 00 41 00 00 00 [....8..."...A...]
2023
- # 0040: 8a 63 24 45 53 00 00 00 9f e6 42 45 45 00 00 00 [.c$ES.....BEE...]
2024
- # 0050: 59 c0 04 3f 52 b8 42 41 14 00 00 00 0b 00 00 00 [Y..?R.BA........]
2025
- # 0060: 19 00 00 00 06 00 00 00 05 00 00 00 f6 ff ff ff [................]
2026
- # 0070: 03 00 00 00 04 00 00 00 00 00 00 00 00 00 00 00 [................]
2027
- ($latRef, $lonRef) = ($1, $2);
2028
- ($hr,$min,$sec,$yr,$mon,$day,@acc) = unpack('x48V3x28V6',$$dataPt);
2029
- map { $_ = $_ - 4294967296 if $_ >= 0x80000000; $_ /= 1000 } @acc; # (NC)
2030
- $lat = GetFloat($dataPt, 0x40);
2031
- $lon = GetFloat($dataPt, 0x48);
2032
- $spd = GetFloat($dataPt, 0x50);
2033
- $trk = GetFloat($dataPt, 0x54);
2034
-
2035
1910
  } elsif ($$dataPt =~ /^.{16}A([NS])([EW])\0/s) {
2036
1911
 
2037
- $debug and $et->FoundTag(GPSType => '2F');
1912
+ $debug and $et->FoundTag(GPSType => 12);
2038
1913
  # INNOVV MP4 video (same format as INNOVV TS)
2039
1914
  while ($$dataPt =~ /(A[NS][EW]\0.{28})/g) {
2040
1915
  my $dat = $1;
@@ -2052,11 +1927,12 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
2052
1927
  $et->HandleTag($tagTbl, GPSTrack => $trk);
2053
1928
  $et->HandleTag($tagTbl, Accelerometer => "@acc");
2054
1929
  }
1930
+ SetByteOrder($oldOrder);
2055
1931
  return 1;
2056
1932
 
2057
1933
  } elsif ($$dataPt =~ /^.{28}A.{11}([NS]).{15}([EW])/s) {
2058
1934
 
2059
- $debug and $et->FoundTag(GPSType => '2G');
1935
+ $debug and $et->FoundTag(GPSType => 13);
2060
1936
  # Vantrue N4 dashcam
2061
1937
  # 0000: 00 00 40 00 66 72 65 65 47 50 53 20 f0 03 00 00 [..@.freeGPS ....]
2062
1938
  # 0010: 0d 00 00 00 16 00 00 00 1e 00 00 00 41 00 00 00 [............A...]
@@ -2079,16 +1955,82 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
2079
1955
  map { $_ = $_ - 4294967296 if $_ >= 0x80000000; $_ /= 1000 } @acc; # (NC)
2080
1956
  # (not necessary to read RMC sentence because we already have it all)
2081
1957
 
2082
- } else {
1958
+ } elsif ($$dataPt =~ /^.{72}A[NS][EW]\0/s) {
2083
1959
 
2084
- $debug and $et->FoundTag(GPSType => '2H');
2085
- # (look for binary GPS as stored by NextBase 512G, ref PH)
1960
+ # decode binary GPS format (Viofo A119S, ref 2)
2086
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)
2087
2026
  # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 78 01 00 00 [....freeGPS x...]
2088
2027
  # 0010: 78 2e 78 78 00 00 00 00 00 00 00 00 00 00 00 00 [x.xx............]
2089
2028
  # 0020: 30 30 30 30 30 00 00 00 00 00 00 00 00 00 00 00 [00000...........]
2090
-
2091
- # 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!):
2092
2034
  # 0x30 - int16u unknown (seen: 0x24 0x53 = "$S")
2093
2035
  # 0x32 - int16u speed (m/s * 100)
2094
2036
  # 0x34 - int16s heading (deg * 100) (or GPSImgDirection?)
@@ -2103,8 +2045,9 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
2103
2045
  # 0x43 - int32s longitude (decimal degrees * 1e7)
2104
2046
  # 0x47 - int8u unknown (seen: 16)
2105
2047
  # 0x48-0x4f - all zero
2048
+ my $pos;
2106
2049
  for ($pos=0x32; ; ) {
2107
- ($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;
2108
2051
  # validate record using date/time
2109
2052
  last if $yr < 2000 or $yr > 2200 or
2110
2053
  $mon < 1 or $mon > 12 or
@@ -2124,28 +2067,41 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
2124
2067
  $et->HandleTag($tagTbl, GPSTrack => $trk);
2125
2068
  last if $pos += 0x20 > length($$dataPt) - 0x1e;
2126
2069
  }
2070
+ SetByteOrder($oldOrder);
2127
2071
  return $$et{DOC_NUM} ? 1 : 0; # return 0 if nothing extracted
2128
2072
  }
2129
2073
  #
2130
2074
  # save tag values extracted by above code
2131
2075
  #
2132
- return 0 if $mon < 1 or $mon > 12; # quick sanity check
2133
- $$et{DOC_NUM} = ++$$et{DOC_COUNT};
2134
- $yr += 2000 if $yr < 2000;
2135
- my $time = sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%.2dZ', $yr, $mon, $day, $hr, $min, $sec);
2136
- # convert from DDMM.MMMMMM to DD.DDDDDD format if necessary
2137
- ConvertLatLon($lat, $lon) unless $ddd;
2138
- $et->HandleTag($tagTbl, GPSDateTime => $time);
2139
- $et->HandleTag($tagTbl, GPSLatitude => $lat * ($latRef eq 'S' ? -1 : 1));
2140
- $et->HandleTag($tagTbl, GPSLongitude => $lon * ($lonRef eq 'W' ? -1 : 1));
2141
- $et->HandleTag($tagTbl, GPSSpeed => $spd) if defined $spd; # (now in km/h)
2142
- $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
+ }
2143
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
+ }
2144
2101
  $et->HandleTag($tagTbl, Accelerometer => "@acc") if @acc;
2145
2102
  return 1;
2146
2103
  }
2147
2104
 
2148
-
2149
2105
  #------------------------------------------------------------------------------
2150
2106
  # Extract embedded information referenced from a track
2151
2107
  # Inputs: 0) ExifTool ref, 1) tag name, 2) data ref
@@ -2435,6 +2391,68 @@ sub Process_gsen($$$)
2435
2391
  return 1;
2436
2392
  }
2437
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
+
2438
2456
  #------------------------------------------------------------------------------
2439
2457
  # Process LIGOGPS JSON-format GPS from Yada RoadCam Pro 4K BT58189
2440
2458
  # Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
@@ -2456,9 +2474,8 @@ sub ProcessLIGO_JSON($$$)
2456
2474
  $et->VerboseDir('LIGO_JSON', undef, length($$dataPt));
2457
2475
  while ($$dataPt =~ /LIGOGPSINFO (\{.*?\})/g) {
2458
2476
  my $json = $1;
2459
- my $raf = File::RandomAccess->new(\$json);
2460
2477
  my %dbase;
2461
- Image::ExifTool::Import::ReadJSON($raf, \%dbase);
2478
+ Image::ExifTool::Import::ReadJSON(\$json, \%dbase);
2462
2479
  my $info = $dbase{'*'} or next;
2463
2480
  # my sample contains the following JSON fields (in this order):
2464
2481
  # Hour Minute Second Year Month Day (GPS UTC time)
@@ -2865,6 +2882,8 @@ sub ProcessTTAD($$$)
2865
2882
  # Extract information from Insta360 trailer (INSV and INSP files) (ref PH)
2866
2883
  # Inputs: 0) ExifTool ref, 1) Optional dirInfo ref for returning trailer info
2867
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
2868
2887
  sub ProcessInsta360($;$)
2869
2888
  {
2870
2889
  local $_;
@@ -3075,7 +3094,7 @@ sub ProcessInsta360($;$)
3075
3094
  $raf->Seek($epos, 2) or last; # seek to start of next footer
3076
3095
  $raf->Read($buff, 6) == 6 or last; # read footer
3077
3096
  }
3078
- $$et{DOC_NUM} = 0;
3097
+ delete $$et{DOC_NUM};
3079
3098
  SetByteOrder('MM');
3080
3099
  delete $$et{SET_GROUP0};
3081
3100
  delete $$et{SET_GROUP1};
@@ -3217,7 +3236,7 @@ sub ScanMediaData($)
3217
3236
  {
3218
3237
  my $et = shift;
3219
3238
  my $raf = $$et{RAF} or return;
3220
- my ($tagTbl, $oldByteOrder, $verbose, $buff, $dataLen);
3239
+ my ($tagTbl, $verbose, $buff, $dataLen);
3221
3240
  my ($pos, $buf2) = (0, '');
3222
3241
 
3223
3242
  # don't rescan for freeGPS if we already found embedded metadata
@@ -3253,8 +3272,6 @@ sub ScanMediaData($)
3253
3272
  # initialize variables for extracting metadata from this block
3254
3273
  $tagTbl = GetTagTable('Image::ExifTool::QuickTime::Stream');
3255
3274
  $verbose = $$et{OPTIONS}{Verbose};
3256
- $oldByteOrder = GetByteOrder();
3257
- SetByteOrder('II');
3258
3275
  $et->VPrint(0, "---- Extract Embedded ----\n");
3259
3276
  $$et{INDENT} .= '| ';
3260
3277
  }
@@ -3277,15 +3294,14 @@ sub ScanMediaData($)
3277
3294
  $et->VerboseDump(\$buff, DataPos => $pos + $dataPos);
3278
3295
  }
3279
3296
  my $dirInfo = { DataPt => \$buff, DataPos => $pos + $dataPos, DirLen => $len };
3280
- ProcessFreeGPS2($et, $dirInfo, $tagTbl);
3297
+ ProcessFreeGPS($et, $dirInfo, $tagTbl);
3281
3298
  }
3282
3299
  $pos += $len;
3283
3300
  $buf2 = substr($buff, $len);
3284
3301
  }
3285
3302
  if ($tagTbl) {
3286
- $$et{DOC_NUM} = 0; # reset DOC_NUM after extracting embedded metadata
3303
+ delete $$et{DOC_NUM}; # reset DOC_NUM after extracting embedded metadata
3287
3304
  $et->VPrint(0, "--------------------------\n");
3288
- SetByteOrder($oldByteOrder);
3289
3305
  $$et{INDENT} = substr $$et{INDENT}, 0, -2;
3290
3306
  }
3291
3307
  # process Insta360 trailer if it exists