exiftool_vendored 12.76.1 → 12.81.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|