exiftool_vendored 13.04.0 → 13.08.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/bin/Changes +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;
|