exiftool_vendored 12.76.1 → 12.81.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.
- checksums.yaml +4 -4
- data/bin/Changes +105 -4
- data/bin/MANIFEST +29 -0
- data/bin/META.json +1 -1
- data/bin/META.yml +1 -1
- data/bin/README +4 -3
- data/bin/config_files/acdsee.config +37 -57
- data/bin/config_files/example.config +16 -2
- data/bin/exiftool +102 -31
- data/bin/lib/Image/ExifTool/Canon.pm +12 -9
- data/bin/lib/Image/ExifTool/CanonVRD.pm +8 -2
- data/bin/lib/Image/ExifTool/Exif.pm +52 -4
- data/bin/lib/Image/ExifTool/FujiFilm.pm +14 -5
- data/bin/lib/Image/ExifTool/GPS.pm +5 -3
- data/bin/lib/Image/ExifTool/GeoLang/cs.pm +978 -0
- data/bin/lib/Image/ExifTool/GeoLang/de.pm +1975 -0
- data/bin/lib/Image/ExifTool/GeoLang/en_ca.pm +44 -0
- data/bin/lib/Image/ExifTool/GeoLang/en_gb.pm +124 -0
- data/bin/lib/Image/ExifTool/GeoLang/es.pm +2921 -0
- data/bin/lib/Image/ExifTool/GeoLang/fi.pm +1116 -0
- data/bin/lib/Image/ExifTool/GeoLang/fr.pm +3171 -0
- data/bin/lib/Image/ExifTool/GeoLang/it.pm +2750 -0
- data/bin/lib/Image/ExifTool/GeoLang/ja.pm +10256 -0
- data/bin/lib/Image/ExifTool/GeoLang/ko.pm +4499 -0
- data/bin/lib/Image/ExifTool/GeoLang/nl.pm +1270 -0
- data/bin/lib/Image/ExifTool/GeoLang/pl.pm +3019 -0
- data/bin/lib/Image/ExifTool/GeoLang/ru.pm +18220 -0
- data/bin/lib/Image/ExifTool/GeoLang/sk.pm +441 -0
- data/bin/lib/Image/ExifTool/GeoLang/sv.pm +714 -0
- data/bin/lib/Image/ExifTool/GeoLang/tr.pm +452 -0
- data/bin/lib/Image/ExifTool/GeoLang/zh_cn.pm +2225 -0
- data/bin/lib/Image/ExifTool/GeoLang/zh_tw.pm +72 -0
- data/bin/lib/Image/ExifTool/Geolocation.dat +0 -0
- data/bin/lib/Image/ExifTool/Geolocation.pm +935 -0
- data/bin/lib/Image/ExifTool/Geotag.pm +14 -2
- data/bin/lib/Image/ExifTool/HtmlDump.pm +2 -1
- data/bin/lib/Image/ExifTool/Import.pm +5 -2
- data/bin/lib/Image/ExifTool/JSON.pm +15 -10
- data/bin/lib/Image/ExifTool/M2TS.pm +32 -4
- data/bin/lib/Image/ExifTool/MWG.pm +1 -0
- data/bin/lib/Image/ExifTool/MacOS.pm +19 -4
- data/bin/lib/Image/ExifTool/MakerNotes.pm +2 -2
- data/bin/lib/Image/ExifTool/Microsoft.pm +1 -1
- data/bin/lib/Image/ExifTool/Nikon.pm +331 -23
- data/bin/lib/Image/ExifTool/NikonCustom.pm +55 -1
- data/bin/lib/Image/ExifTool/Ogg.pm +3 -2
- data/bin/lib/Image/ExifTool/Olympus.pm +4 -1
- data/bin/lib/Image/ExifTool/OpenEXR.pm +37 -19
- data/bin/lib/Image/ExifTool/PDF.pm +5 -5
- data/bin/lib/Image/ExifTool/PNG.pm +3 -3
- data/bin/lib/Image/ExifTool/Pentax.pm +1 -1
- data/bin/lib/Image/ExifTool/QuickTime.pm +195 -12
- data/bin/lib/Image/ExifTool/QuickTimeStream.pl +253 -237
- data/bin/lib/Image/ExifTool/README +6 -5
- data/bin/lib/Image/ExifTool/Sony.pm +1 -1
- data/bin/lib/Image/ExifTool/TagLookup.pm +4871 -4752
- data/bin/lib/Image/ExifTool/TagNames.pod +722 -383
- data/bin/lib/Image/ExifTool/WriteQuickTime.pl +43 -9
- data/bin/lib/Image/ExifTool/WriteXMP.pl +1 -1
- data/bin/lib/Image/ExifTool/Writer.pl +65 -8
- data/bin/lib/Image/ExifTool/XMP.pm +18 -2
- data/bin/lib/Image/ExifTool/XMP2.pl +64 -0
- data/bin/lib/Image/ExifTool.pm +265 -49
- data/bin/lib/Image/ExifTool.pod +63 -25
- data/bin/perl-Image-ExifTool.spec +2 -2
- data/lib/exiftool_vendored/version.rb +1 -1
- 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
|
112
|
+
currently reads 72 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+)([
|
916
|
+
while ($$dataPt =~ /\$(\w+)([^\$\0]*)/g) {
|
916
917
|
my ($tag, $dat) = ($1, $2);
|
917
|
-
if ($tag =~ /^[A-Z]{2}RMC$/
|
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"
|
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, $
|
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
|
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
|
-
|
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
|
-
#
|
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}
|
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
|
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 <
|
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 =>
|
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 =>
|
1521
|
-
# decode NMEA-format GPS data (
|
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 =>
|
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
|
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 =>
|
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 =>
|
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('
|
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)
|
1629
|
-
|
1630
|
-
|
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 =>
|
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
|
-
|
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 =>
|
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 =>
|
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 =>
|
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
|
-
|
1732
|
-
|
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 =>
|
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 =>
|
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 =>
|
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 =>
|
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
|
-
}
|
1958
|
+
} elsif ($$dataPt =~ /^.{72}A[NS][EW]\0/s) {
|
2083
1959
|
|
2084
|
-
|
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
|
-
#
|
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,$
|
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
|
-
|
2133
|
-
|
2134
|
-
$
|
2135
|
-
|
2136
|
-
|
2137
|
-
|
2138
|
-
|
2139
|
-
|
2140
|
-
|
2141
|
-
|
2142
|
-
|
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(
|
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}
|
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, $
|
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
|
-
|
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}
|
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
|