exiftool_vendored 13.04.0 → 13.08.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 +48 -0
- data/bin/MANIFEST +1 -0
- data/bin/META.json +1 -1
- data/bin/META.yml +1 -1
- data/bin/README +2 -2
- data/bin/exiftool +30 -23
- data/bin/lib/Image/ExifTool/AIFF.pm +1 -1
- data/bin/lib/Image/ExifTool/APE.pm +1 -1
- data/bin/lib/Image/ExifTool/ASF.pm +1 -1
- data/bin/lib/Image/ExifTool/BuildTagLookup.pm +4 -3
- data/bin/lib/Image/ExifTool/Canon.pm +19 -1
- data/bin/lib/Image/ExifTool/DJI.pm +91 -29
- data/bin/lib/Image/ExifTool/Exif.pm +2 -2
- data/bin/lib/Image/ExifTool/FITS.pm +2 -2
- data/bin/lib/Image/ExifTool/FLIF.pm +2 -2
- data/bin/lib/Image/ExifTool/FlashPix.pm +11 -11
- data/bin/lib/Image/ExifTool/Font.pm +1 -1
- data/bin/lib/Image/ExifTool/Geolocation.pm +2 -1
- data/bin/lib/Image/ExifTool/GoPro.pm +3 -3
- data/bin/lib/Image/ExifTool/HP.pm +1 -1
- data/bin/lib/Image/ExifTool/ID3.pm +3 -3
- data/bin/lib/Image/ExifTool/IPTC.pm +2 -2
- data/bin/lib/Image/ExifTool/InDesign.pm +1 -1
- data/bin/lib/Image/ExifTool/JPEG.pm +19 -4
- data/bin/lib/Image/ExifTool/Jpeg2000.pm +6 -6
- data/bin/lib/Image/ExifTool/M2TS.pm +39 -9
- data/bin/lib/Image/ExifTool/MXF.pm +2 -2
- data/bin/lib/Image/ExifTool/Matroska.pm +1 -1
- data/bin/lib/Image/ExifTool/Microsoft.pm +1 -1
- data/bin/lib/Image/ExifTool/PDF.pm +15 -15
- data/bin/lib/Image/ExifTool/PLIST.pm +1 -1
- data/bin/lib/Image/ExifTool/PNG.pm +4 -4
- data/bin/lib/Image/ExifTool/Panasonic.pm +1 -1
- data/bin/lib/Image/ExifTool/PhaseOne.pm +3 -3
- data/bin/lib/Image/ExifTool/Photoshop.pm +63 -3
- data/bin/lib/Image/ExifTool/Protobuf.pm +242 -0
- data/bin/lib/Image/ExifTool/QuickTime.pm +23 -14
- data/bin/lib/Image/ExifTool/QuickTimeStream.pl +395 -109
- data/bin/lib/Image/ExifTool/README +4 -1
- data/bin/lib/Image/ExifTool/RIFF.pm +3 -3
- data/bin/lib/Image/ExifTool/RTF.pm +1 -1
- data/bin/lib/Image/ExifTool/Ricoh.pm +3 -3
- data/bin/lib/Image/ExifTool/Sony.pm +4 -3
- data/bin/lib/Image/ExifTool/TagInfoXML.pm +4 -3
- data/bin/lib/Image/ExifTool/TagLookup.pm +6988 -6967
- data/bin/lib/Image/ExifTool/TagNames.pod +85 -5
- data/bin/lib/Image/ExifTool/VCard.pm +2 -2
- data/bin/lib/Image/ExifTool/Validate.pm +3 -3
- data/bin/lib/Image/ExifTool/WriteExif.pl +2 -2
- data/bin/lib/Image/ExifTool/WriteQuickTime.pl +4 -4
- data/bin/lib/Image/ExifTool/WriteXMP.pl +2 -2
- data/bin/lib/Image/ExifTool/Writer.pl +17 -17
- data/bin/lib/Image/ExifTool/XMP.pm +20 -10
- data/bin/lib/Image/ExifTool/XMP2.pl +38 -0
- data/bin/lib/Image/ExifTool/ZIP.pm +1 -1
- data/bin/lib/Image/ExifTool.pm +109 -82
- data/bin/lib/Image/ExifTool.pod +8 -7
- data/bin/perl-Image-ExifTool.spec +1 -1
- data/lib/exiftool_vendored/version.rb +1 -1
- metadata +3 -2
@@ -94,6 +94,7 @@ my %insvDataLen = (
|
|
94
94
|
# 0xb00 => 10, # ? (Insta360 X3)
|
95
95
|
# 0xd00 => 10, # ? (Insta360 Ace Pro)
|
96
96
|
# 0x1200 ? # ? (Insta360 Ace Pro)
|
97
|
+
# 0x1600 ? # ? (?)
|
97
98
|
);
|
98
99
|
|
99
100
|
# limit the default amount of data we read for some record types
|
@@ -109,7 +110,7 @@ my %insvLimit = (
|
|
109
110
|
The tags below are extracted from timed metadata in QuickTime and other
|
110
111
|
formats of video files when the ExtractEmbedded option is used. Although
|
111
112
|
most of these tags are combined into the single table below, ExifTool
|
112
|
-
currently reads
|
113
|
+
currently reads 96 different types of timed GPS metadata from video files.
|
113
114
|
},
|
114
115
|
VARS => { NO_ID => 1 },
|
115
116
|
GPSLatitude => { PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")', RawConv => '$$self{FoundGPSLatitude} = 1; $val' },
|
@@ -339,14 +340,22 @@ my %insvLimit = (
|
|
339
340
|
Groups => { 2 => 'Preview' },
|
340
341
|
RawConv => '$self->ValidateImage(\$val,$tag)',
|
341
342
|
},
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
343
|
+
djmd => { # (DJI AC003 Osmo Action 4 cam)
|
344
|
+
Name => 'DJIMetadata',
|
345
|
+
SubDirectory => { TagTable => 'Image::ExifTool::DJI::Protobuf' },
|
346
|
+
},
|
347
|
+
dbgi => { # (DJI AC003 Osmo Action 4 cam)
|
348
|
+
Name => 'DJIDebug',
|
349
|
+
Unknown => 2,
|
350
|
+
Notes => 'extracted only if Unknown option is 2 or greater',
|
351
|
+
SubDirectory => { TagTable => 'Image::ExifTool::DJI::Protobuf' },
|
352
|
+
},
|
346
353
|
Unknown00 => { Unknown => 1 },
|
347
354
|
Unknown01 => { Unknown => 1 },
|
348
355
|
Unknown02 => { Unknown => 1 },
|
349
356
|
Unknown03 => { Unknown => 1 },
|
357
|
+
M => { Name => 'Unknown_M', Unknown => 1 }, # (from LIGOGPSINFO)
|
358
|
+
H => { Name => 'Unknown_H', Unknown => 1 }, # (from LIGOGPSINFO)
|
350
359
|
);
|
351
360
|
|
352
361
|
# tags found in 'camm' type 0 timed metadata (ref 4)
|
@@ -894,7 +903,7 @@ sub FoundSomething($$;$$)
|
|
894
903
|
#------------------------------------------------------------------------------
|
895
904
|
# Approximate GPSDateTime value from sample time and CreateDate
|
896
905
|
# Inputs: 0) ExifTool ref, 1) tag table ptr, 2) sample time (s)
|
897
|
-
# 3) true if CreateDate is
|
906
|
+
# 3) true if CreateDate is UTC
|
898
907
|
# Notes: Uses ExifTool CreateDateAtEnd as flag to subtract video duration
|
899
908
|
sub SetGPSDateTime($$$;$)
|
900
909
|
{
|
@@ -905,9 +914,9 @@ sub SetGPSDateTime($$$;$)
|
|
905
914
|
if ($$et{CreateDateAtEnd}) { # adjust if CreateDate is at end of video
|
906
915
|
return unless $$value{TimeScale} and $$value{Duration};
|
907
916
|
$sampleTime -= $$value{Duration} / $$value{TimeScale};
|
908
|
-
$et->
|
917
|
+
$et->Warn('Approximating GPSDateTime as CreateDate - Duration + SampleTime', 1);
|
909
918
|
} else {
|
910
|
-
$et->
|
919
|
+
$et->Warn('Approximating GPSDateTime as CreateDate + SampleTime', 1);
|
911
920
|
}
|
912
921
|
my $utc = $et->Options('QuickTimeUTC');
|
913
922
|
$utc = $isUTC unless defined $utc; # (allow QuickTimeUTC=0 to override $isUTC default)
|
@@ -1268,7 +1277,7 @@ sub ProcessSamples($)
|
|
1268
1277
|
($startChunk, $samplesPerChunk, $descIdx) = @{shift @$stsc};
|
1269
1278
|
$nextChunk = $$stsc[0][0] if @$stsc;
|
1270
1279
|
}
|
1271
|
-
@$size < @$start + $samplesPerChunk and $et->
|
1280
|
+
@$size < @$start + $samplesPerChunk and $et->Warn('Sample size error'), last;
|
1272
1281
|
last unless defined $chunkStart and length $chunkStart;
|
1273
1282
|
my $sampleStart = $chunkStart;
|
1274
1283
|
my $chunkSize = 0;
|
@@ -1296,7 +1305,7 @@ Sample: for ($i=0; ; ) {
|
|
1296
1305
|
push @chunkSize, $chunkSize;
|
1297
1306
|
++$iChunk;
|
1298
1307
|
}
|
1299
|
-
@$start == @$size or $et->
|
1308
|
+
@$start == @$size or $et->Warn('Incorrect sample start/size count'), return;
|
1300
1309
|
# process as chunks if we are only interested in calculating hash
|
1301
1310
|
if ($type eq 'soun' or $type eq 'vide') {
|
1302
1311
|
$start = $stco;
|
@@ -1326,7 +1335,7 @@ Sample: for ($i=0; ; ) {
|
|
1326
1335
|
$hdrFmt = ($hdrLen == 4 ? 'N' : $hdrLen == 2 ? 'n' : 'C');
|
1327
1336
|
require Image::ExifTool::H264;
|
1328
1337
|
}
|
1329
|
-
|
1338
|
+
|
1330
1339
|
# loop through all samples
|
1331
1340
|
for ($i=0; $i<@$start and $i<@$size; ++$i) {
|
1332
1341
|
|
@@ -1346,11 +1355,11 @@ Sample: for ($i=0; ; ) {
|
|
1346
1355
|
}
|
1347
1356
|
}
|
1348
1357
|
# read the sample data
|
1349
|
-
$raf->Seek($$start[$i], 0) or $et->
|
1358
|
+
$raf->Seek($$start[$i], 0) or $et->Warn("Seek error in $type data"), next;
|
1350
1359
|
my $buff;
|
1351
1360
|
my $n = $raf->Read($buff, $size);
|
1352
1361
|
unless ($n == $size) {
|
1353
|
-
$et->
|
1362
|
+
$et->Warn("Error reading $type data");
|
1354
1363
|
next unless $n;
|
1355
1364
|
$size = $n;
|
1356
1365
|
}
|
@@ -1432,7 +1441,7 @@ Sample: for ($i=0; ; ) {
|
|
1432
1441
|
|
1433
1442
|
if ($$tagTbl{$metaFormat}) {
|
1434
1443
|
my $tagInfo = $et->GetTagInfo($tagTbl, $metaFormat, \$buff);
|
1435
|
-
if ($tagInfo) {
|
1444
|
+
if ($tagInfo and (not $$tagInfo{Unknown} or $$et{OPTIONS}{Unknown} >= $$tagInfo{Unknown})) {
|
1436
1445
|
FoundSomething($et, $tagTbl, $time[$i], $dur[$i]);
|
1437
1446
|
$$et{ee} = $ee; # need ee information for 'keys'
|
1438
1447
|
$et->HandleTag($tagTbl, $metaFormat, undef,
|
@@ -1442,6 +1451,15 @@ Sample: for ($i=0; ; ) {
|
|
1442
1451
|
TagInfo => $tagInfo,
|
1443
1452
|
);
|
1444
1453
|
delete $$et{ee};
|
1454
|
+
# synthesize GPSDateTime if necessary for djmd metadata
|
1455
|
+
if ($metaFormat eq 'djmd') {
|
1456
|
+
if (defined $$et{GPSLatitude} and defined $$et{GPSLongitude} and not $$et{GPSDateTime}) {
|
1457
|
+
SetGPSDateTime($et, $tagTbl, $time[$i], 1); # (NC)
|
1458
|
+
}
|
1459
|
+
delete $$et{GPSLatitude};
|
1460
|
+
delete $$et{GPSLongitude};
|
1461
|
+
delete $$et{GPSDateTime};
|
1462
|
+
}
|
1445
1463
|
} elsif ($metaFormat eq 'camm' and $buff =~ /^X/) {
|
1446
1464
|
# seen 'camm' metadata in this format (X/Y/Z acceleration and G force? + GPRMC + ?)
|
1447
1465
|
# "X0000.0000Y0000.0000Z0000.0000G0000.0000$GPRMC,000125,V,,,,,000.0,,280908,002.1,N*71~, 794021 \x0a"
|
@@ -1545,7 +1563,7 @@ sub ProcessFreeGPS($$$)
|
|
1545
1563
|
my ($et, $dirInfo, $tagTbl) = @_;
|
1546
1564
|
my $dataPt = $$dirInfo{DataPt};
|
1547
1565
|
my $dirLen = length $$dataPt;
|
1548
|
-
my ($yr, $mon, $day, $hr, $min, $sec, $stat, $lbl, $ddd);
|
1566
|
+
my ($yr, $mon, $day, $hr, $min, $sec, $ss, $stat, $lbl, $ddd, $done);
|
1549
1567
|
my ($lat, $latRef, $lon, $lonRef, $spd, $trk, $alt, @acc, @xtra);
|
1550
1568
|
|
1551
1569
|
return 0 if $dirLen < 82;
|
@@ -1670,7 +1688,7 @@ sub ProcessFreeGPS($$$)
|
|
1670
1688
|
}
|
1671
1689
|
if ($notEnc and $notStr) {
|
1672
1690
|
|
1673
|
-
$debug and $et->FoundTag(GPSType =>
|
1691
|
+
$debug and $et->FoundTag(GPSType => 3);
|
1674
1692
|
# decode freeGPS from ViofoA119v3 dashcam (similar to Novatek GPS format)
|
1675
1693
|
# 0000: 00 00 40 00 66 72 65 65 47 50 53 20 f0 03 00 00 [..@.freeGPS ....]
|
1676
1694
|
# 0010: 05 00 00 00 2f 00 00 00 03 00 00 00 13 00 00 00 [..../...........]
|
@@ -1684,7 +1702,7 @@ sub ProcessFreeGPS($$$)
|
|
1684
1702
|
($sec,$min,$hr,$day,$mon,$yr) = gmtime($time);
|
1685
1703
|
$yr += 1900;
|
1686
1704
|
++$mon;
|
1687
|
-
$et->
|
1705
|
+
$et->Warn('Converting GPSDateTime to UTC based on local time zone',1);
|
1688
1706
|
}
|
1689
1707
|
$lat = GetFloat($dataPt, 0x2c);
|
1690
1708
|
$lon = GetFloat($dataPt, 0x30);
|
@@ -1698,7 +1716,7 @@ sub ProcessFreeGPS($$$)
|
|
1698
1716
|
|
1699
1717
|
} else {
|
1700
1718
|
|
1701
|
-
$debug and $et->FoundTag(GPSType =>
|
1719
|
+
$debug and $et->FoundTag(GPSType => 4);
|
1702
1720
|
# decode freeGPS from E-ACE B44 dashcam
|
1703
1721
|
# 0000: 00 00 40 00 66 72 65 65 47 50 53 20 f0 03 00 00 [..@.freeGPS ....]
|
1704
1722
|
# 0010: 08 00 00 00 22 00 00 00 01 00 00 00 18 00 00 00 [...."...........]
|
@@ -1729,40 +1747,73 @@ sub ProcessFreeGPS($$$)
|
|
1729
1747
|
($lon = DecryptLucky($ln, $key)) =~ /^\d{1,5}\.\d+$/ or undef($lon), next;
|
1730
1748
|
last;
|
1731
1749
|
}
|
1732
|
-
$lon or $et->
|
1750
|
+
$lon or $et->Warn('Unknown encryption for latitude/longitude');
|
1733
1751
|
}
|
1734
1752
|
}
|
1735
1753
|
|
1736
|
-
} elsif ($$dataPt =~
|
1754
|
+
} elsif ($$dataPt =~ /^(.{16}|.{48}|.{80})LIGOGPSINFO\0/s and length($$dataPt) >= length($1) + 0x84) {
|
1737
1755
|
|
1738
|
-
$debug and $et->FoundTag(GPSType =>
|
1739
|
-
|
1740
|
-
#
|
1741
|
-
#
|
1742
|
-
#
|
1743
|
-
#
|
1744
|
-
#
|
1745
|
-
#
|
1746
|
-
#
|
1747
|
-
#
|
1748
|
-
|
1749
|
-
|
1750
|
-
#
|
1751
|
-
|
1752
|
-
|
1753
|
-
|
1754
|
-
|
1755
|
-
|
1756
|
-
|
1757
|
-
|
1758
|
-
|
1759
|
-
|
1760
|
-
|
1761
|
-
|
1756
|
+
$debug and $et->FoundTag(GPSType => 5);
|
1757
|
+
my $pos = length $1;
|
1758
|
+
# iiway s1 dual dash cam
|
1759
|
+
# 0000: 00 00 40 00 66 72 65 65 47 50 53 20 f0 03 00 00 [..@.freeGPS ....]
|
1760
|
+
# 0010: 4c 49 47 4f 47 50 53 49 4e 46 4f 00 00 00 00 05 [LIGOGPSINFO.....]
|
1761
|
+
# 0020: 0a 00 00 00 23 23 23 23 6a 00 00 00 c0 20 20 20 [....####j.... ]
|
1762
|
+
# 0030: 20 f0 12 10 12 22 e1 0e 10 12 2f 90 10 13 02 f2 [ ...."..../.....]
|
1763
|
+
# ABASK A8 4K Dashcam (different scaling factor)
|
1764
|
+
# 0000: 00 00 40 00 66 72 65 65 47 50 53 20 f0 03 00 00 [..@.freeGPS ....]
|
1765
|
+
# 0010: 4c 49 47 4f 47 50 53 49 4e 46 4f 00 00 00 00 05 [LIGOGPSINFO.....]
|
1766
|
+
# 0020: 00 00 00 00 23 23 23 23 69 00 00 00 c0 20 20 20 [....####i.... ]
|
1767
|
+
# 0030: 20 f0 12 10 12 23 e5 0e 10 12 2f 99 10 11 02 f2 [ ....#..../.....]
|
1768
|
+
# XGODY 12" 4K Dashcam
|
1769
|
+
# 0000: 00 00 00 a8 66 72 65 65 47 50 53 20 98 00 00 00 [....freeGPS ....]
|
1770
|
+
# 0010: 4c 49 47 4f 47 50 53 49 4e 46 4f 00 00 00 00 05 [LIGOGPSINFO.....]
|
1771
|
+
# 0020: cd 61 00 00 23 23 23 23 6d 00 00 00 c1 ec 41 20 [.a..####m.....A ]
|
1772
|
+
# 0030: 20 f0 12 10 12 24 e5 0e 10 11 2f 92 10 12 00 f6 [ ....$..../.....]
|
1773
|
+
# Rexing dashcam V1GW-4K
|
1774
|
+
# 0000: 00 00 40 00 66 72 65 65 47 50 53 20 f0 03 00 00 [..@.freeGPS ....]
|
1775
|
+
# 0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
|
1776
|
+
# 0020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
|
1777
|
+
# 0030: 4c 49 47 4f 47 50 53 49 4e 46 4f 00 00 00 00 05 [LIGOGPSINFO.....]
|
1778
|
+
# 0040: 01 00 00 00 23 23 23 23 73 00 00 00 c0 20 20 20 [....####s.... ]
|
1779
|
+
# 0050: 20 f0 12 10 12 23 e5 0e 10 12 2f 95 10 12 01 f3 [ ....#..../.....]
|
1780
|
+
# 0060: 16 18 10 26 b4 1a 10 04 f2 6e 18 12 20 f0 0e 11 [...&.....n.. ...]
|
1781
|
+
# 0070: 13 22 b3 16 10 01 fb 76 18 10 24 fa 0e 11 10 22 [.".....v..$...."]
|
1782
|
+
# Kingslim D4 dashcam
|
1783
|
+
# 0000: 0a 00 00 00 0b 00 00 00 07 00 00 00 e5 07 00 00 [................]
|
1784
|
+
# 0010: 06 00 00 00 03 00 00 00 41 4e 57 31 91 52 83 45 [........ANW1.R.E]
|
1785
|
+
# 0020: 15 70 fe c5 29 5c c3 41 ae c7 af 42 00 00 d1 be [.p..)\.A...B....]
|
1786
|
+
# 0030: 00 00 80 3b 00 00 2c 3e 00 00 00 00 00 00 00 00 [...;..,>........]
|
1787
|
+
# 0040: 00 00 00 00 00 00 00 00 00 00 00 00 26 26 26 26 [............&&&&]
|
1788
|
+
# 0050: 4c 49 47 4f 47 50 53 49 4e 46 4f 00 00 00 00 05 [LIGOGPSINFO.....]
|
1789
|
+
# 0060: 01 00 00 00 23 23 23 23 75 00 00 00 c0 22 20 20 [....####u...." ]
|
1790
|
+
# 0070: 20 f0 12 10 12 21 e5 0e 10 12 2f 90 10 13 01 f2 [ ....!..../.....]
|
1791
|
+
my %dirInfo = ( DataPt => $dataPt, DirStart => $pos, DirName => "LigoGPS_$pos" );
|
1792
|
+
# (this is weak, but the only difference I could find between these 2 headers)
|
1793
|
+
# (NOTE: ../testpics/gps_video/forum16229.mp4 uses this word for a counter!)
|
1794
|
+
$$et{LigoGPSScale} = 3 if $pos == 16 and $$dataPt =~ /^.{12}\xf0\x03\0\0.{16}\0{4}/s;
|
1795
|
+
ProcessLigoGPS($et, \%dirInfo, $tagTbl);
|
1796
|
+
$done = 1;
|
1797
|
+
|
1798
|
+
# also... when offset is 0x50 (Kingslim), the GPS also exists in this format:
|
1799
|
+
# ($latRef, $lonRef) = ($1, $2);
|
1800
|
+
# ($hr,$min,$sec,$yr,$mon,$day) = unpack("V6", $$dataPt);
|
1801
|
+
# # lat/lon aren't decoded properly, but spd,trk,acc are
|
1802
|
+
# $lat = GetFloat($dataPt, 0x1c);
|
1803
|
+
# $lon = GetFloat($dataPt, 0x20);
|
1804
|
+
# $et->VPrint(0, sprintf("Raw lat/lon = %.9f %.9f\n", $lat, $lon));
|
1805
|
+
# $et->Warn('GPSLatitude/Longitude encryption is not yet known, so these will be wrong');
|
1806
|
+
# $lat = abs $lat;
|
1807
|
+
# $lon = abs $lon;
|
1808
|
+
# $spd = GetFloat($dataPt, 0x24) * $knotsToKph; # (convert knots to km/h)
|
1809
|
+
# $trk = GetFloat($dataPt, 0x28);
|
1810
|
+
# $acc[0] = GetFloat($dataPt, 0x2c);
|
1811
|
+
# $acc[1] = GetFloat($dataPt, 0x30);
|
1812
|
+
# $acc[2] = GetFloat($dataPt, 0x34);
|
1762
1813
|
|
1763
1814
|
} elsif ($$dataPt =~ /^.{60}A\0{3}.{4}([NS])\0{3}.{4}([EW])\0{3}/s) {
|
1764
1815
|
|
1765
|
-
$debug and $et->FoundTag(GPSType =>
|
1816
|
+
$debug and $et->FoundTag(GPSType => 6);
|
1766
1817
|
# decode freeGPS from Akaso dashcam
|
1767
1818
|
# 0000: 00 00 80 00 66 72 65 65 47 50 53 20 60 00 00 00 [....freeGPS `...]
|
1768
1819
|
# 0010: 78 2e 78 78 00 00 00 00 00 00 00 00 00 00 00 00 [x.xx............]
|
@@ -1796,7 +1847,7 @@ sub ProcessFreeGPS($$$)
|
|
1796
1847
|
|
1797
1848
|
} elsif ($$dataPt =~ /^.{60}4W`b]S</s and length($$dataPt) >= 140) {
|
1798
1849
|
|
1799
|
-
$debug and $et->FoundTag(GPSType =>
|
1850
|
+
$debug and $et->FoundTag(GPSType => 7);
|
1800
1851
|
# 0000: 00 00 40 00 66 72 65 65 47 50 53 20 f0 01 00 00 [..@.freeGPS ....]
|
1801
1852
|
# 0010: 5a 58 53 42 4e 58 59 53 00 00 00 00 00 00 00 00 [ZXSBNXYS........]
|
1802
1853
|
# 0020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
|
@@ -1806,18 +1857,18 @@ sub ProcessFreeGPS($$$)
|
|
1806
1857
|
# 0060: 42 3e 49 49 40 42 45 3c 55 3c 45 47 3e 45 43 41 [B>II@BE<U<EG>ECA]
|
1807
1858
|
# decipher $GPRMC by subtracting 16 from each character value
|
1808
1859
|
$_ = pack 'C*', map { $_>=16 and $_-=16 } unpack('x60C80', $$dataPt);
|
1809
|
-
|
1810
|
-
|
1811
|
-
|
1860
|
+
if (/[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+)/) {
|
1861
|
+
($yr,$mon,$day,$hr,$min,$sec,$lat,$latRef,$lon,$lonRef) = ($13,$12,$11,$1,$2,$3,$5,$6,$7,$8);
|
1862
|
+
$yr += ($yr >= 70 ? 1900 : 2000);
|
1863
|
+
$spd = $9 * $knotsToKph if length $9;
|
1864
|
+
$trk = $10 if length $10;
|
1865
|
+
} else {
|
1866
|
+
$done = 1;
|
1812
1867
|
}
|
1813
|
-
($yr,$mon,$day,$hr,$min,$sec,$lat,$latRef,$lon,$lonRef) = ($13,$12,$11,$1,$2,$3,$5,$6,$7,$8);
|
1814
|
-
$yr += ($yr >= 70 ? 1900 : 2000);
|
1815
|
-
$spd = $9 * $knotsToKph if length $9;
|
1816
|
-
$trk = $10 if length $10;
|
1817
1868
|
|
1818
1869
|
} elsif ($$dataPt =~ /^.{64}[\x01-\x0c]\0{3}[\x01-\x1f]\0{3}A[NS][EW]\0{5}/s) {
|
1819
1870
|
|
1820
|
-
$debug and $et->FoundTag(GPSType =>
|
1871
|
+
$debug and $et->FoundTag(GPSType => 8);
|
1821
1872
|
# Akaso V1 dascham
|
1822
1873
|
# 0000: 00 00 80 00 66 72 65 65 47 50 53 20 78 00 00 00 [....freeGPS x...]
|
1823
1874
|
# 0010: 59 6e 64 41 6b 61 73 6f 43 61 72 00 00 00 00 00 [YndAkasoCar.....]
|
@@ -1842,7 +1893,7 @@ sub ProcessFreeGPS($$$)
|
|
1842
1893
|
($hr,$min,$sec,$yr,$mon,$day,$stat,$latRef,$lonRef) =
|
1843
1894
|
unpack('x48V6a1a1a1x1', $$dataPt);
|
1844
1895
|
|
1845
|
-
$et->
|
1896
|
+
$et->Warn('GPSLatitude/Longitude encryption is not yet known, so these will be wrong');
|
1846
1897
|
# (see https://exiftool.org/forum/index.php?topic=11320.0)
|
1847
1898
|
|
1848
1899
|
$spd = GetFloat($dataPt, 0x60);
|
@@ -1850,11 +1901,11 @@ sub ProcessFreeGPS($$$)
|
|
1850
1901
|
$lat = GetDouble($dataPt, 0x50); # latitude is here, but encrypted somehow
|
1851
1902
|
$lon = GetDouble($dataPt, 0x58); # longitude is here, but encrypted somehow
|
1852
1903
|
$ddd = 1; # don't convert until we know what the format is
|
1853
|
-
#my $serialNum = substr($$dataPt, 0x68, 20);
|
1904
|
+
#my $serialNum = substr($$dataPt, 0x68, 20); # (confirmed)
|
1854
1905
|
|
1855
1906
|
} elsif ($$dataPt =~ /^.{12}\xac\0\0\0.{44}(.{72})/s) {
|
1856
1907
|
|
1857
|
-
$debug and $et->FoundTag(GPSType =>
|
1908
|
+
$debug and $et->FoundTag(GPSType => 9);
|
1858
1909
|
# EACHPAI dash cam
|
1859
1910
|
# 0000: 00 00 80 00 66 72 65 65 47 50 53 20 ac 00 00 00 [....freeGPS ....]
|
1860
1911
|
# 0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
|
@@ -1866,19 +1917,18 @@ sub ProcessFreeGPS($$$)
|
|
1866
1917
|
# 0070: 43 41 3c 40 42 40 46 42 40 3c 3c 3c 51 3a 47 46 [CA<@B@FB@<<<Q:GF]
|
1867
1918
|
# 0080: 00 2a 36 35 00 00 00 00 00 00 00 00 00 00 00 00 [.*65............]
|
1868
1919
|
|
1869
|
-
$et->
|
1920
|
+
$et->Warn("Can't yet decrypt EACHPAI timed GPS", 1);
|
1870
1921
|
# (see https://exiftool.org/forum/index.php?topic=5095.msg61266#msg61266)
|
1871
|
-
|
1872
|
-
return 1;
|
1922
|
+
$done = 1;
|
1873
1923
|
|
1874
|
-
|
1875
|
-
|
1876
|
-
|
1877
|
-
|
1924
|
+
# my $time = pack 'C*', map { $_ ^= 0 } unpack 'C*', $1;
|
1925
|
+
# # bytes 7-12 are the timestamp in ASCII HHMMSS after xor-ing with 0x70
|
1926
|
+
# substr($time,7,6) = pack 'C*', map { $_ ^= 0x70 } unpack 'C*', substr($time,7,6);
|
1927
|
+
# # (other values are currently unknown)
|
1878
1928
|
|
1879
1929
|
} elsif ($$dataPt =~ /^.{64}A([NS])([EW])\0/s) {
|
1880
1930
|
|
1881
|
-
$debug and $et->FoundTag(GPSType =>
|
1931
|
+
$debug and $et->FoundTag(GPSType => 10);
|
1882
1932
|
# Vantrue S1 dashcam
|
1883
1933
|
# 0000: 00 00 80 00 66 72 65 65 47 50 53 20 78 00 00 00 [....freeGPS x...]
|
1884
1934
|
# 0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
|
@@ -1890,21 +1940,21 @@ sub ProcessFreeGPS($$$)
|
|
1890
1940
|
# 0070: 05 00 00 00 7f 00 00 00 07 01 00 00 00 00 00 00 [................]
|
1891
1941
|
($latRef, $lonRef) = ($1, $2);
|
1892
1942
|
($yr,$mon,$day,$hr,$min,$sec,@acc) = unpack('x68V6x20V3', $$dataPt);
|
1893
|
-
|
1894
|
-
|
1895
|
-
|
1943
|
+
if ($mon>=1 and $mon<=12 and $day>=1 and $day<=31) {
|
1944
|
+
# (not sure about acc scaling)
|
1945
|
+
@acc = map { SignedInt32 / 1000 } @acc;
|
1946
|
+
$lon = GetFloat($dataPt, 0x5c);
|
1947
|
+
$lat = GetFloat($dataPt, 0x60);
|
1948
|
+
$spd = GetFloat($dataPt, 0x64) * $knotsToKph;
|
1949
|
+
$trk = GetFloat($dataPt, 0x68);
|
1950
|
+
$alt = GetFloat($dataPt, 0x6c);
|
1951
|
+
} else {
|
1952
|
+
$done = 1;
|
1896
1953
|
}
|
1897
|
-
# (not sure about acc scaling)
|
1898
|
-
@acc = map { SignedInt32 / 1000 } @acc;
|
1899
|
-
$lon = GetFloat($dataPt, 0x5c);
|
1900
|
-
$lat = GetFloat($dataPt, 0x60);
|
1901
|
-
$spd = GetFloat($dataPt, 0x64) * $knotsToKph;
|
1902
|
-
$trk = GetFloat($dataPt, 0x68);
|
1903
|
-
$alt = GetFloat($dataPt, 0x6c);
|
1904
1954
|
|
1905
1955
|
} elsif (substr($$dataPt,0x45,3) eq 'ATC') {
|
1906
1956
|
|
1907
|
-
$debug and $et->FoundTag(GPSType =>
|
1957
|
+
$debug and $et->FoundTag(GPSType => 11);
|
1908
1958
|
# header looks like this: (sample 1)
|
1909
1959
|
# 0000: 00 00 80 00 66 72 65 65 47 50 53 20 38 06 00 00 [....freeGPS 8...]
|
1910
1960
|
# 0010: 49 51 53 32 30 31 33 30 33 30 36 42 00 00 00 00 [IQS20130306B....]
|
@@ -1945,7 +1995,7 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
|
|
1945
1995
|
my $i;
|
1946
1996
|
for ($i=0; $i<@dateMax; ++$i) {
|
1947
1997
|
next if $now[$i] <= $dateMax[$i];
|
1948
|
-
$et->
|
1998
|
+
$et->Warn('Invalid GPS date/time');
|
1949
1999
|
next ATCRec; # ignore this record
|
1950
2000
|
}
|
1951
2001
|
# look for next ATC record in temporal sequence
|
@@ -2012,12 +2062,11 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
|
|
2012
2062
|
}
|
2013
2063
|
# save position of most recent record (needed when parsing the next freeGPS block)
|
2014
2064
|
$$et{FreeGPS2}{RecentRecPos} = $lastRecPos;
|
2015
|
-
|
2016
|
-
return 1;
|
2065
|
+
$done = 1;
|
2017
2066
|
|
2018
2067
|
} elsif ($$dataPt =~ /^.{60}A\0.{10}([NS])\0.{14}([EW])\0/s and $dirLen >= 0x88) {
|
2019
2068
|
|
2020
|
-
$debug and $et->FoundTag(GPSType =>
|
2069
|
+
$debug and $et->FoundTag(GPSType => 12);
|
2021
2070
|
# header looks like this in my sample:
|
2022
2071
|
# 0000: 00 00 80 00 66 72 65 65 47 50 53 20 08 01 00 00 [....freeGPS ....]
|
2023
2072
|
# 0010: 32 30 31 33 30 38 31 35 2e 30 31 00 00 00 00 00 [20130815.01.....]
|
@@ -2048,9 +2097,14 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
|
|
2048
2097
|
|
2049
2098
|
} elsif ($$dataPt =~ /^.{16}A([NS])([EW])\0/s) {
|
2050
2099
|
|
2051
|
-
$debug and $et->FoundTag(GPSType =>
|
2100
|
+
$debug and $et->FoundTag(GPSType => 13);
|
2052
2101
|
# INNOVV MP4 video (same format as INNOVV TS)
|
2053
|
-
|
2102
|
+
# 0000: 00 00 40 00 66 72 65 65 47 50 53 20 f0 03 00 00 [..@.freeGPS ....]
|
2103
|
+
# 0010: 41 4e 45 00 e4 56 96 45 86 b1 ca 44 5c 8f e2 40 [ANE..V.E...D\..@]
|
2104
|
+
# 0020: 33 33 58 43 c3 00 00 00 30 00 00 00 a0 fe ff ff [33XC....0.......]
|
2105
|
+
# 0030: 41 4e 45 00 e3 56 96 45 82 b1 ca 44 5c 8f fa 40 [ANE..V.E...D\..@]
|
2106
|
+
# 0040: c3 75 56 43 8c ff ff ff 8c 00 00 00 c3 fd ff ff [.uVC............]
|
2107
|
+
while ($$dataPt =~ /(A[NS][EW]\0.{28})/sg) {
|
2054
2108
|
my $dat = $1;
|
2055
2109
|
$lat = abs(GetFloat(\$dat, 4)); # (abs just to be safe)
|
2056
2110
|
$lon = abs(GetFloat(\$dat, 8)); # (abs just to be safe)
|
@@ -2065,12 +2119,35 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
|
|
2065
2119
|
$et->HandleTag($tagTbl, GPSTrack => $trk);
|
2066
2120
|
$et->HandleTag($tagTbl, Accelerometer => "@acc");
|
2067
2121
|
}
|
2068
|
-
|
2069
|
-
|
2122
|
+
$done = 1;
|
2123
|
+
|
2124
|
+
} elsif ($$dataPt =~ /^.{20}[\0-\x18][\0-\x3b]{2}[\0-\x09]A([NS])([EW])/s) {
|
2125
|
+
|
2126
|
+
$debug and $et->FoundTag(GPSType => 14);
|
2127
|
+
# XBHT motorcycle dashcam Model XB702
|
2128
|
+
# 0000: 00 00 40 00 66 72 65 65 47 50 53 20 f0 03 00 00 [..@.freeGPS ....]
|
2129
|
+
# 0010: 00 17 05 11 0d 25 18 00 41 4e 45 64 83 3f 00 00 [.....%..ANEd.?..]
|
2130
|
+
# 0020: 44 3d c5 02 48 6d ff 07 df 03 00 00 6b 00 00 00 [D=..Hm......k...]
|
2131
|
+
# 0030: 00 00 00 00 00 17 05 11 0d 25 18 01 41 4e 45 64 [.........%..ANEd]
|
2132
|
+
# 0040: 8b 3f 00 00 30 3d c5 02 50 6d ff 07 df 03 00 00 [.?..0=..Pm......]
|
2133
|
+
while ($$dataPt =~ /(.{7}[\0-\x09]A[NS][EW].{25})/sg) {
|
2134
|
+
my $dat = $1;
|
2135
|
+
($yr,$mon,$day,$hr,$min,$sec,$ss,$latRef,$lonRef,$lat,$lon,$spd) =
|
2136
|
+
unpack('xC7xCCx5VVx4v', $dat);
|
2137
|
+
$yr += 2000; $lat /= 1e4; $lon /= 1e4;
|
2138
|
+
ConvertLatLon($lat, $lon);
|
2139
|
+
$$et{DOC_NUM} = ++$$et{DOC_COUNT};
|
2140
|
+
my $time = sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%.2d.%d',$yr,$mon,$day,$hr,$min,$sec,$ss);
|
2141
|
+
$et->HandleTag($tagTbl, GPSDateTime => $time);
|
2142
|
+
$et->HandleTag($tagTbl, GPSLatitude => $lat * ($latRef eq 'S' ? -1 : 1));
|
2143
|
+
$et->HandleTag($tagTbl, GPSLongitude => $lon * ($lonRef eq 'W' ? -1 : 1));
|
2144
|
+
$et->HandleTag($tagTbl, GPSSpeed => $spd);
|
2145
|
+
}
|
2146
|
+
$done = 1;
|
2070
2147
|
|
2071
2148
|
} elsif ($$dataPt =~ /^.{28}A.{11}([NS]).{15}([EW])/s) {
|
2072
2149
|
|
2073
|
-
$debug and $et->FoundTag(GPSType =>
|
2150
|
+
$debug and $et->FoundTag(GPSType => 15);
|
2074
2151
|
# Vantrue N4 dashcam
|
2075
2152
|
# 0000: 00 00 40 00 66 72 65 65 47 50 53 20 f0 03 00 00 [..@.freeGPS ....]
|
2076
2153
|
# 0010: 0d 00 00 00 16 00 00 00 1e 00 00 00 41 00 00 00 [............A...]
|
@@ -2127,7 +2204,7 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
|
|
2127
2204
|
($hr,$min,$sec,$yr,$mon,$day,$stat,$latRef,$lonRef) =
|
2128
2205
|
unpack('x48V6a1a1a1x1V4', $$dataPt);
|
2129
2206
|
if (substr($$dataPt, 16, 3) eq 'IQS') {
|
2130
|
-
$debug and $et->FoundTag(GPSType =>
|
2207
|
+
$debug and $et->FoundTag(GPSType => 16);
|
2131
2208
|
# Type 3b (ref PH)
|
2132
2209
|
# header looks like this in my sample:
|
2133
2210
|
# 0000: 00 00 80 00 66 72 65 65 47 50 53 20 4c 00 00 00 [....freeGPS L...]
|
@@ -2139,7 +2216,7 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
|
|
2139
2216
|
$spd = Get32s($dataPt, 0x54) / 100 * $mpsToKph;
|
2140
2217
|
$alt = GetFloat($dataPt, 0x58) / 1000; # (NC)
|
2141
2218
|
} else {
|
2142
|
-
$debug and $et->FoundTag(GPSType =>
|
2219
|
+
$debug and $et->FoundTag(GPSType => 17);
|
2143
2220
|
$lat = GetFloat($dataPt, 0x4c);
|
2144
2221
|
$lon = GetFloat($dataPt, 0x50);
|
2145
2222
|
$spd = GetFloat($dataPt, 0x54) * $knotsToKph;
|
@@ -2159,7 +2236,7 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
|
|
2159
2236
|
|
2160
2237
|
} elsif ($$dataPt =~ m<^.{23}(\d{4})/(\d{2})/(\d{2}) (\d{2}):(\d{2}):(\d{2}) [N|S]>s) {
|
2161
2238
|
|
2162
|
-
$debug and $et->FoundTag(GPSType =>
|
2239
|
+
$debug and $et->FoundTag(GPSType => 18);
|
2163
2240
|
# XGODY 12" 4K Dashcam
|
2164
2241
|
# 0000: 00 00 00 a8 66 72 65 65 47 50 53 20 98 00 00 00 [....freeGPS ....]
|
2165
2242
|
# 0010: 6e 6f 72 6d 61 6c 3a 32 30 32 34 2f 30 35 2f 32 [normal:2024/05/2]
|
@@ -2190,8 +2267,8 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
|
|
2190
2267
|
}
|
2191
2268
|
|
2192
2269
|
} elsif ($$dataPt =~ m/^.{30}A.{20}VV/) {
|
2193
|
-
|
2194
|
-
$debug and $et->FoundTag(GPSType =>
|
2270
|
+
|
2271
|
+
$debug and $et->FoundTag(GPSType => 19);
|
2195
2272
|
# 70mai A810 dashcam (note: no timestamps in the samples I have)
|
2196
2273
|
# 0000: 00 00 40 00 66 72 65 65 47 50 53 20 ed 01 00 00 [..@.freeGPS ....]
|
2197
2274
|
# 0010: 03 00 ed 01 00 00 00 0f 00 00 70 08 00 00 41 66 [..........p...Af]
|
@@ -2208,7 +2285,7 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
|
|
2208
2285
|
|
2209
2286
|
} else {
|
2210
2287
|
|
2211
|
-
$debug and $et->FoundTag(GPSType =>
|
2288
|
+
$debug and $et->FoundTag(GPSType => 20);
|
2212
2289
|
# (look for binary GPS as stored by Nextbase 512G, ref PH)
|
2213
2290
|
# 0000: 00 00 80 00 66 72 65 65 47 50 53 20 78 01 00 00 [....freeGPS x...]
|
2214
2291
|
# 0010: 78 2e 78 78 00 00 00 00 00 00 00 00 00 00 00 00 [x.xx............]
|
@@ -2254,14 +2331,14 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
|
|
2254
2331
|
$et->HandleTag($tagTbl, GPSTrack => $trk);
|
2255
2332
|
last if $pos += 0x20 > length($$dataPt) - 0x1e;
|
2256
2333
|
}
|
2257
|
-
|
2258
|
-
return $$et{DOC_NUM} ? 1 : 0; # return 0 if nothing extracted
|
2334
|
+
$done = 1;
|
2259
2335
|
}
|
2336
|
+
SetByteOrder($oldOrder);
|
2337
|
+
return $$et{DOC_NUM} ? 1 : 0 if $done;
|
2338
|
+
return 0 if defined $yr and $mon < 1 or $mon > 12; # quick sanity check
|
2260
2339
|
#
|
2261
2340
|
# save tag values extracted by above code
|
2262
2341
|
#
|
2263
|
-
SetByteOrder($oldOrder);
|
2264
|
-
return 0 if defined $yr and $mon < 1 or $mon > 12; # quick sanity check
|
2265
2342
|
FoundSomething($et, $tagTbl, $$dirInfo{SampleTime}, $$dirInfo{SampleDuration});
|
2266
2343
|
$sec = '0' . $sec unless $sec =~ /^\d{2}/; # pad integer part of seconds to 2 digits
|
2267
2344
|
if (defined $yr) {
|
@@ -2401,8 +2478,8 @@ sub Process_tx3g($$$)
|
|
2401
2478
|
if ($text =~ /^HOME\(/) {
|
2402
2479
|
# --- sample text from Autel Evo II drone ---
|
2403
2480
|
# HOME(W: 109.318642, N: 40.769371) 2023-09-12 10:28:07
|
2404
|
-
# GPS(W: 109.339287, N: 40.768574, 2371.76m)
|
2405
|
-
# HDR ISO:100 SHUTTER:1000 EV:-0.7 F-NUM:1.8
|
2481
|
+
# GPS(W: 109.339287, N: 40.768574, 2371.76m)
|
2482
|
+
# HDR ISO:100 SHUTTER:1000 EV:-0.7 F-NUM:1.8
|
2406
2483
|
# F.PRY (1.0\xc2\xb0, -3.7\xc2\xb0, -59.0\xc2\xb0), G.PRY (-51.1\xc2\xb0, 0.0\xc2\xb0, -58.9\xc2\xb0)
|
2407
2484
|
my $line;
|
2408
2485
|
foreach $line (split /\x0a/, $text) {
|
@@ -2479,7 +2556,7 @@ sub Process_mebx($$$)
|
|
2479
2556
|
Size => $len - 8,
|
2480
2557
|
);
|
2481
2558
|
} else {
|
2482
|
-
$et->
|
2559
|
+
$et->Warn('No key information for mebx ID ' . PrintableTagID($id,1));
|
2483
2560
|
}
|
2484
2561
|
}
|
2485
2562
|
return 1;
|
@@ -2602,7 +2679,7 @@ sub Process_gdat($$$)
|
|
2602
2679
|
{
|
2603
2680
|
my ($et, $dirInfo, $tagTbl) = @_;
|
2604
2681
|
unless ($$et{OPTIONS}{ExtractEmbedded}) {
|
2605
|
-
$et->
|
2682
|
+
$et->Warn('Use the ExtractEmbedded option to extract timed GPSData',3);
|
2606
2683
|
return 0;
|
2607
2684
|
}
|
2608
2685
|
my $dataPt = $$dirInfo{DataPt};
|
@@ -2653,13 +2730,220 @@ sub Process_nbmt($$$)
|
|
2653
2730
|
delete $$et{NoMoreTextDecoding};
|
2654
2731
|
delete $$et{DOC_NUM};
|
2655
2732
|
} else {
|
2656
|
-
$et->
|
2733
|
+
$et->Warn('Use the ExtractEmbedded option to extract timed GPSData',3);
|
2734
|
+
}
|
2735
|
+
return 1;
|
2736
|
+
}
|
2737
|
+
|
2738
|
+
#------------------------------------------------------------------------------
|
2739
|
+
# Un-do LIGOGPS fuzzing
|
2740
|
+
# Inputs: 0) fuzzed latitude, 1) fuzzed longitude, 2) scale factor
|
2741
|
+
# Returns: 0) latitude, 1) longitude
|
2742
|
+
sub UnfuzzLigoGPS($$$)
|
2743
|
+
{
|
2744
|
+
my ($lat, $lon, $scl) = @_;
|
2745
|
+
my $lat2 = int($lat / 10) * 10;
|
2746
|
+
my $lon2 = int($lon / 10) * 10;
|
2747
|
+
return($lat2 + ($lon - $lon2) * $scl, $lon2 + ($lat - $lat2) * $scl);
|
2748
|
+
}
|
2749
|
+
|
2750
|
+
#------------------------------------------------------------------------------
|
2751
|
+
# Decrypt LIGOGPSINFO record (starting with "####")
|
2752
|
+
# Inputs: 0) encrypted GPS record incuding 8-byte header
|
2753
|
+
# Returns: decrypted record including 4-byte uint32 header, or undef on error
|
2754
|
+
sub DecryptLigoGPS($)
|
2755
|
+
{
|
2756
|
+
my $str = shift;
|
2757
|
+
my $num = unpack('x4V',$str);
|
2758
|
+
return undef if $num < 4;
|
2759
|
+
$num = 0x84 if $num > 0x84; # (be safe)
|
2760
|
+
my @in = unpack("x8C$num",$str);
|
2761
|
+
my @out;
|
2762
|
+
while (@in) {
|
2763
|
+
my $b = shift @in;
|
2764
|
+
my $steeringBits = $b & 0xe0;
|
2765
|
+
if ($steeringBits >= 0xc0) {
|
2766
|
+
return undef if @in < 4;
|
2767
|
+
push @out, (shift(@in) | $b & 0x01) ^ 0x20,
|
2768
|
+
(shift(@in) | $b & 0x02) ^ 0x20,
|
2769
|
+
(shift(@in) | $b & 0x0c) ^ 0x20,
|
2770
|
+
shift(@in) ^ 0x20 | $b & 0x30;
|
2771
|
+
} elsif ($steeringBits >= 0x40) {
|
2772
|
+
return undef if @in < 3;
|
2773
|
+
if ($steeringBits == 0x40) {
|
2774
|
+
push @out, 0x20,
|
2775
|
+
(shift(@in) | $b & 0x01) ^ 0x20,
|
2776
|
+
(shift(@in) | $b & 0x06) ^ 0x20,
|
2777
|
+
(shift(@in) | $b & 0x18) ^ 0x20;
|
2778
|
+
} elsif ($steeringBits == 0x60) {
|
2779
|
+
push @out, (shift(@in) | $b & 0x03) ^ 0x20,
|
2780
|
+
0x20,
|
2781
|
+
(shift(@in) | $b & 0x04) ^ 0x20,
|
2782
|
+
(shift(@in) | $b & 0x18) ^ 0x20;
|
2783
|
+
} elsif ($steeringBits == 0x80) {
|
2784
|
+
push @out, (shift(@in) | $b & 0x03) ^ 0x20,
|
2785
|
+
(shift(@in) | $b & 0x0c) ^ 0x20,
|
2786
|
+
0x20,
|
2787
|
+
(shift(@in) | $b & 0x10) ^ 0x20;
|
2788
|
+
} else {
|
2789
|
+
push @out, (shift(@in) | $b & 0x01) ^ 0x20,
|
2790
|
+
(shift(@in) | $b & 0x06) ^ 0x20,
|
2791
|
+
(shift(@in) | $b & 0x18) ^ 0x20,
|
2792
|
+
0x20;
|
2793
|
+
}
|
2794
|
+
} elsif ($steeringBits == 0x00) {
|
2795
|
+
return undef if @in < 1;
|
2796
|
+
push @out, shift(@in) | $b & 0x13;
|
2797
|
+
} else {
|
2798
|
+
return undef; # (shouldn't happen)
|
2799
|
+
}
|
2800
|
+
}
|
2801
|
+
return pack 'C*', @out;
|
2802
|
+
}
|
2803
|
+
|
2804
|
+
#------------------------------------------------------------------------------
|
2805
|
+
# Decipher and parse LIGOGPSINFO record (starting with "####")
|
2806
|
+
# Inputs: 0) ExifTool ref, 1) enciphered string, 2) tag table ref
|
2807
|
+
# Returns: true if this looked like an enciphered string
|
2808
|
+
# Notes: handles contained tags, but may defer handling until full cipher is known
|
2809
|
+
sub DecipherLigoGPS($$$)
|
2810
|
+
{
|
2811
|
+
my ($et, $str, $tagTbl) = @_;
|
2812
|
+
|
2813
|
+
# (enciphered characters must be in the range 0x30-0x5f ('0' - '_'))
|
2814
|
+
$str =~ m[^####.{4}([0-_])[0-_]{3}/[0-_]{2}/[0-_]{2} ..([0-_])..([0-_]).([0-_]) ]s or return undef;
|
2815
|
+
return undef unless $2 eq $3; # (colons in time string must be the same)
|
2816
|
+
|
2817
|
+
my $cipherInfo = $$et{LigoCipher};
|
2818
|
+
$cipherInfo or $cipherInfo = $$et{LigoCipher} = { cache => [ ], secs => [ ], two => -1 };
|
2819
|
+
my $decipher = $$cipherInfo{decipher};
|
2820
|
+
my $cache = $$cipherInfo{cache};
|
2821
|
+
|
2822
|
+
# determine the cipher code table based on the advancing 1's digit of seconds
|
2823
|
+
unless ($decipher) {
|
2824
|
+
push @$cache, $str; # cache records until we can decipher them
|
2825
|
+
my ($millennium, $colon) = ($1, $2);
|
2826
|
+
# determine the Caesar cipher lookup table
|
2827
|
+
# (only characters in range 0x30-0x5f are encrypted)
|
2828
|
+
my $secs = $$cipherInfo{secs};
|
2829
|
+
push @$secs, $4 unless @$secs and $${secs}[-1] eq $4;
|
2830
|
+
$$cipherInfo{two} = $#$secs if $4 eq $millennium; # save index of enciphered '2'
|
2831
|
+
return 1 if @$secs < 10; # must cache the data until we know all 10 digits
|
2832
|
+
my $two = $$cipherInfo{two}; # (index of '2' in the array)
|
2833
|
+
my %decipher = ( $colon => ':' ); # (':' is the time separator)
|
2834
|
+
foreach (0..9) {
|
2835
|
+
my $ch = $$secs[($_ + $two - 2 + 10) % 10];
|
2836
|
+
if ($two < 0 or defined $decipher{$ch}) { # (must be a unique code for each digit)
|
2837
|
+
@$cipherInfo{'secs','two'} = ([ ], -1); # reset and try again
|
2838
|
+
$et->Warn('Hiccup while deciphering LIGOGPSINFO');
|
2839
|
+
return 1;
|
2840
|
+
}
|
2841
|
+
$decipher{$ch} = chr($_ + 0x30);
|
2842
|
+
}
|
2843
|
+
# also know the lat/lon quadrant from the signs of the coordinates
|
2844
|
+
if ($str =~ / ([0-_])$colon(-?).*? ([0-_])$colon(-?)/) {
|
2845
|
+
@decipher{$1,$3} = ($2 ? 'S' : 'N', $4 ? 'W' : 'E');
|
2846
|
+
}
|
2847
|
+
# fill in unknown entries with '?' (only chars 0x30-0x5f are enciphered)
|
2848
|
+
defined $decipher{$_} or $decipher{$_} = '?' foreach map(chr, 0x30..0x5f);
|
2849
|
+
$decipher = $$cipherInfo{decipher} = \%decipher;
|
2850
|
+
$str = shift @$cache; # start deciphering at oldest cache entry
|
2851
|
+
}
|
2852
|
+
|
2853
|
+
# apply reverse Caesar cipher and extract GPS information
|
2854
|
+
do {
|
2855
|
+
my $pre = substr($str, 4, 4); # save second 4 bytes of header
|
2856
|
+
($str = substr($str,8)) =~ s/\0+$//; # remove 8-byte header and null padding
|
2857
|
+
$str =~ s/([0-_])/$$decipher{$1}/g; # decipher
|
2858
|
+
if ($$et{OPTIONS}{Verbose} > 1) {
|
2859
|
+
$et->VPrint(1, "$$et{INDENT}\(Deciphered: ".unpack('H8',$pre)." $str)\n");
|
2860
|
+
}
|
2861
|
+
# add back leading 4 bytes (int16u counter plus 2 unknown bytes), and parse
|
2862
|
+
# (not fuzzed in my only sample when found in standard 'skip' atom)
|
2863
|
+
ParseLigoGPS($et, "$pre$str", $tagTbl, $$et{LigoType} eq 'LigoGPSInfo');
|
2864
|
+
} while $str = shift @$cache;
|
2865
|
+
|
2866
|
+
return 1;
|
2867
|
+
}
|
2868
|
+
|
2869
|
+
#------------------------------------------------------------------------------
|
2870
|
+
# Parse decrypted/deciphered (but not defuzzed) LIGOGPSINFO record
|
2871
|
+
# (record starts with 4-byte int32u counter followed by date/time, etc)
|
2872
|
+
# Inputs: 0) ExifTool ref, 1) GPS string, 2) tag table ref, 3) not fuzzed
|
2873
|
+
# Returns: nothing
|
2874
|
+
sub ParseLigoGPS($$$;$)
|
2875
|
+
{
|
2876
|
+
my ($et, $str, $tagTbl, $noFuzz) = @_;
|
2877
|
+
|
2878
|
+
# example string input
|
2879
|
+
# "....2022/09/19 12:45:24 N:31.285065 W:124.759483 46.93 km/h x:-0.000 y:-0.000 z:-0.000"
|
2880
|
+
unless ($str=~ /^.{4}(\S+ \S+)\s+([NS?]):(-?)([.\d]+)\s+([EW?]):(-?)([\.\d]+)\s+([.\d]+)/s) {
|
2881
|
+
$et->Warn('LIGOGPSINFO format error');
|
2882
|
+
return;
|
2657
2883
|
}
|
2884
|
+
my ($time,$latRef,$latNeg,$lat,$lonRef,$lonNeg,$lon,$spd) = ($1,$2,$3,$4,$5,$6,$7,$8);
|
2885
|
+
my %gpsScl = ( 1 => 1.524855137, 2 => 1.456027985, 3 => 1.15368 );
|
2886
|
+
my $spdScl = $noFuzz ? $knotsToKph : 1.85407333;
|
2887
|
+
$$et{DOC_NUM} = ++$$et{DOC_COUNT};
|
2888
|
+
$time =~ tr(/)(:);
|
2889
|
+
# convert from DDMM.MMMMMM to DD.DDDDDD if necessary
|
2890
|
+
# (speed wasn't scaled in my 1 sample with this format)
|
2891
|
+
$lat =~ /^\d{3}/ and ConvertLatLon($lat,$lon), $spdScl = 1;
|
2892
|
+
unless ($noFuzz) { # unfuzz the coordinates if necessary
|
2893
|
+
my $scl = $$et{OPTIONS}{LigoGPSScale} || $$et{LigoGPSScale} || 1;
|
2894
|
+
$scl = $gpsScl{$scl} if $gpsScl{$scl};
|
2895
|
+
($lat, $lon) = UnfuzzLigoGPS($lat, $lon, $scl);
|
2896
|
+
}
|
2897
|
+
# a final sanity check
|
2898
|
+
($lat > 90 or $lon > 180) and $et->Warn('LIGOGPSINFO coordinates out of range'), return;
|
2899
|
+
$$et{SET_GROUP1} = 'LIGO';
|
2900
|
+
$et->HandleTag($tagTbl, 'GPSDateTime', $time);
|
2901
|
+
# (ignore N/S/E/W if coordinate is signed)
|
2902
|
+
$et->HandleTag($tagTbl, 'GPSLatitude', $lat * (($latNeg or $latRef eq 'S') ? -1 : 1));
|
2903
|
+
$et->HandleTag($tagTbl, 'GPSLongitude', $lon * (($lonNeg or $lonRef eq 'W') ? -1 : 1));
|
2904
|
+
$et->HandleTag($tagTbl, 'GPSSpeed', $spd * $spdScl);
|
2905
|
+
$et->HandleTag($tagTbl, 'GPSTrack', $1) if $str =~ /\bA:(\S+)/;
|
2906
|
+
# (have a sample where tab is used to separate acc components)
|
2907
|
+
$et->HandleTag($tagTbl, 'Accelerometer',"$1 $2 $3") if $str =~ /x:(\S+)\sy:(\S+)\sz:(\S+)/;
|
2908
|
+
$et->HandleTag($tagTbl, 'M', $1) if $str =~ /\bM:(\S+)/;
|
2909
|
+
$et->HandleTag($tagTbl, 'H', $1) if $str =~ /\bH:(\S+)/;
|
2910
|
+
delete $$et{SET_GROUP1};
|
2911
|
+
}
|
2912
|
+
|
2913
|
+
#------------------------------------------------------------------------------
|
2914
|
+
# Process LIGOGPSINFO data (non-JSON format)
|
2915
|
+
# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
|
2916
|
+
# 3) 1=LIGOGPS lat/lon/spd weren't fuzzed
|
2917
|
+
# Returns: 1 on success
|
2918
|
+
sub ProcessLigoGPS($$$;$)
|
2919
|
+
{
|
2920
|
+
my ($et, $dirInfo, $tagTbl, $noFuzz) = @_;
|
2921
|
+
my $dataPt = $$dirInfo{DataPt};
|
2922
|
+
my $pos = ($$dirInfo{DirStart} || 0) + 0x14;
|
2923
|
+
my $cipherInfo = $$et{LigoCipher};
|
2924
|
+
return undef if $pos > length $$dataPt;
|
2925
|
+
$$et{LigoType} = $$dirInfo{DirName} || 'LigoGPS';
|
2926
|
+
push @{$$et{PATH}}, $$et{LigoType} unless $$dirInfo{DirID};
|
2927
|
+
# not fuzzed if header is "LIGOGPSINFO\0\0\0\0\x01" (BlueSkySeaDV688)
|
2928
|
+
$noFuzz = 1 if substr($$dataPt, $pos-8, 4) eq "\0\0\0\x01";
|
2929
|
+
$et->VerboseDir($$et{LigoType});
|
2930
|
+
for (; $pos + 0x84 <= length($$dataPt); $pos+=0x84) {
|
2931
|
+
my $dat = substr($$dataPt, $pos, 0x84);
|
2932
|
+
$dat =~ /^####/ or next; # (have seen blank records filled with zeros, so keep trying)
|
2933
|
+
# decipher if we already know the encryption
|
2934
|
+
$cipherInfo and $$cipherInfo{decipher} and DecipherLigoGPS($et, $dat, $tagTbl) and next;
|
2935
|
+
my $str = DecryptLigoGPS($dat);
|
2936
|
+
defined $str or DecipherLigoGPS($et, $dat, $tagTbl), next; # try to decipher
|
2937
|
+
$et->VPrint(1, "$$et{INDENT}\(Decrypted: ",unpack('V',$str),' ',substr($str,4),")\n") if $$et{OPTIONS}{Verbose} > 1;
|
2938
|
+
ParseLigoGPS($et, $str, $tagTbl, $noFuzz);
|
2939
|
+
}
|
2940
|
+
pop @{$$et{PATH}} unless $$dirInfo{DirID};
|
2941
|
+
delete $$et{DOC_NUM};
|
2658
2942
|
return 1;
|
2659
2943
|
}
|
2660
2944
|
|
2661
2945
|
#------------------------------------------------------------------------------
|
2662
|
-
# Process
|
2946
|
+
# Process LIGOGPSINFO JSON-format GPS (Yada RoadCam Pro 4K BT58189)
|
2663
2947
|
# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
|
2664
2948
|
# Returns: 1 on success
|
2665
2949
|
# Sample data (chained 512-byte records starting like this):
|
@@ -2670,13 +2954,14 @@ sub Process_nbmt($$$)
|
|
2670
2954
|
# 0040: 22 3a 20 22 32 30 32 33 22 2c 20 22 4d 6f 6e 74 [": "2023", "Mont]
|
2671
2955
|
# 0050: 68 22 3a 20 22 31 32 22 2c 20 22 44 61 79 22 3a [h": "12", "Day":]
|
2672
2956
|
# 0060: 20 22 32 38 22 2c 20 22 73 74 61 74 75 73 22 3a [ "28", "status":]
|
2673
|
-
sub
|
2957
|
+
sub ProcessLigoJSON($$$)
|
2674
2958
|
{
|
2675
2959
|
my ($et, $dirInfo, $tagTbl) = @_;
|
2676
2960
|
my $dataPt = $$dirInfo{DataPt};
|
2677
2961
|
my $dirLen = $$dirInfo{DirLen};
|
2678
2962
|
require Image::ExifTool::Import;
|
2679
2963
|
$et->VerboseDir('LIGO_JSON', undef, length($$dataPt));
|
2964
|
+
$$et{SET_GROUP1} = 'LIGO';
|
2680
2965
|
while ($$dataPt =~ /LIGOGPSINFO (\{.*?\})/g) {
|
2681
2966
|
my $json = $1;
|
2682
2967
|
my %dbase;
|
@@ -2726,11 +3011,12 @@ sub ProcessLIGO_JSON($$$)
|
|
2726
3011
|
$et->HandleTag($tagTbl, GPSLongitude2 => $lon);
|
2727
3012
|
}
|
2728
3013
|
unless ($et->Options('ExtractEmbedded')) {
|
2729
|
-
$et->
|
3014
|
+
$et->Warn('Use the ExtractEmbedded option to extract all timed GPS',3);
|
2730
3015
|
last;
|
2731
3016
|
}
|
2732
3017
|
}
|
2733
3018
|
delete $$et{DOC_NUM};
|
3019
|
+
delete $$et{SET_GROUP1};
|
2734
3020
|
return 1;
|
2735
3021
|
}
|
2736
3022
|
|
@@ -2773,7 +3059,7 @@ sub ProcessKenwood($$$)
|
|
2773
3059
|
}
|
2774
3060
|
$et->HandleTag($tagTbl, Accelerometer => "@acc") if @acc;
|
2775
3061
|
unless ($et->Options('ExtractEmbedded')) {
|
2776
|
-
$et->
|
3062
|
+
$et->Warn('Use the ExtractEmbedded option to extract all timed GPS',3);
|
2777
3063
|
last;
|
2778
3064
|
}
|
2779
3065
|
}
|
@@ -2896,7 +3182,7 @@ sub ProcessKenwoodTrailer($$$)
|
|
2896
3182
|
$raf->Read($buff, 14) and $buff eq 'CCCCCCCCCCCCCC' or return 0;
|
2897
3183
|
$et->VerboseDir('Kenwood trailer', undef, undef);
|
2898
3184
|
unless ($$et{OPTIONS}{ExtractEmbedded}) {
|
2899
|
-
$et->
|
3185
|
+
$et->Warn('Use the ExtractEmbedded option to extract timed GPSData from Kenwood trailer',3);
|
2900
3186
|
return 1;
|
2901
3187
|
}
|
2902
3188
|
while ($raf->Read($buff, 121) and $buff =~ /^GPSDATA--(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/) {
|
@@ -3119,7 +3405,7 @@ sub ProcessTTAD($$$)
|
|
3119
3405
|
$et->HandleTag($tagTbl, "Unknown0$type" => "@a");
|
3120
3406
|
}
|
3121
3407
|
} else {
|
3122
|
-
$et->
|
3408
|
+
$et->Warn("Unknown TTAD record type $type",1);
|
3123
3409
|
}
|
3124
3410
|
# without -ee, stop after we find types 0,3,5 (ie. bitmask 0x29)
|
3125
3411
|
$eeOpt or ($found & 0x29) != 0x29 or EEWarn($et), last;
|
@@ -3171,7 +3457,7 @@ sub ProcessInsta360($;$)
|
|
3171
3457
|
}
|
3172
3458
|
unless ($et->Options('ExtractEmbedded')) {
|
3173
3459
|
# can arrive here when reading Insta360 trailer on JPEG image (INSP file)
|
3174
|
-
$et->
|
3460
|
+
$et->Warn('Use ExtractEmbedded option to extract timed metadata from Insta360 trailer',3);
|
3175
3461
|
return 1;
|
3176
3462
|
}
|
3177
3463
|
|
@@ -3368,8 +3654,8 @@ sub ProcessCAMM($$$)
|
|
3368
3654
|
my $rtnVal = 0;
|
3369
3655
|
while ($pos + 4 < $end) {
|
3370
3656
|
my $type = Get16u($dataPt, $pos + 2);
|
3371
|
-
my $size = $size{$type} or $et->
|
3372
|
-
$pos + $size > $end and $et->
|
3657
|
+
my $size = $size{$type} or $et->Warn("Unknown camm record type $type"), last;
|
3658
|
+
$pos + $size > $end and $et->Warn("Truncated camm record $type"), last;
|
3373
3659
|
my $tagTbl = GetTagTable("Image::ExifTool::QuickTime::camm$type");
|
3374
3660
|
$$dirInfo{DirStart} = $pos;
|
3375
3661
|
$$dirInfo{DirLen} = $size;
|