exiftool-vendored.pl 12.89.0 → 12.96.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.
- package/bin/Changes +63 -3
- package/bin/META.json +1 -1
- package/bin/META.yml +1 -1
- package/bin/README +2 -2
- package/bin/build_geolocation +1 -1
- package/bin/build_tag_lookup +29 -11
- package/bin/exiftool +18 -47
- package/bin/lib/Image/ExifTool/Canon.pm +147 -9
- package/bin/lib/Image/ExifTool/Font.pm +15 -4
- package/bin/lib/Image/ExifTool/Geotag.pm +19 -5
- package/bin/lib/Image/ExifTool/Jpeg2000.pm +2 -2
- package/bin/lib/Image/ExifTool/Nikon.pm +35 -1
- package/bin/lib/Image/ExifTool/NikonSettings.pm +56 -1
- package/bin/lib/Image/ExifTool/OpenEXR.pm +3 -1
- package/bin/lib/Image/ExifTool/PNG.pm +10 -2
- package/bin/lib/Image/ExifTool/PostScript.pm +3 -12
- package/bin/lib/Image/ExifTool/QuickTime.pm +40 -15
- package/bin/lib/Image/ExifTool/QuickTimeStream.pl +203 -42
- package/bin/lib/Image/ExifTool/Samsung.pm +31 -20
- package/bin/lib/Image/ExifTool/TagLookup.pm +6923 -6903
- package/bin/lib/Image/ExifTool/TagNames.pod +112 -9
- package/bin/lib/Image/ExifTool/WriteQuickTime.pl +49 -17
- package/bin/lib/Image/ExifTool/Writer.pl +8 -54
- package/bin/lib/Image/ExifTool.pm +66 -4
- package/bin/perl-Image-ExifTool.spec +1 -1
- package/package.json +3 -3
|
@@ -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 77 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' },
|
|
@@ -757,6 +757,15 @@ my %insvLimit = (
|
|
|
757
757
|
10 => { Name => 'FusionYPR', Format => 'float[3]' },
|
|
758
758
|
);
|
|
759
759
|
|
|
760
|
+
#------------------------------------------------------------------------------
|
|
761
|
+
# Convert unsigned 32-bit integer to signed
|
|
762
|
+
# Inputs: <none> (uses value in $_)
|
|
763
|
+
# Returns: signed integer
|
|
764
|
+
sub SignedInt32()
|
|
765
|
+
{
|
|
766
|
+
return $_ < 0x80000000 ? $_ : $_ - 4294967296;
|
|
767
|
+
}
|
|
768
|
+
|
|
760
769
|
#------------------------------------------------------------------------------
|
|
761
770
|
# Save information from keys in OtherSampleDesc directory for processing timed metadata
|
|
762
771
|
# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
|
|
@@ -1420,9 +1429,10 @@ Sample: for ($i=0; ; ) {
|
|
|
1420
1429
|
} elsif ($type eq 'gps ') { # (ie. GPSDataList tag)
|
|
1421
1430
|
|
|
1422
1431
|
if ($buff =~ /^....freeGPS /s) {
|
|
1423
|
-
#
|
|
1424
|
-
# (some videos don't reference all freeGPS info from 'gps ' table, eg. INNOV
|
|
1425
|
-
|
|
1432
|
+
# parse freeGPS data unless done already in brute-force scan
|
|
1433
|
+
# (some videos don't reference all freeGPS info from 'gps ' table, eg. INNOV,
|
|
1434
|
+
# and some videos don't put 'gps ' data in mdat, eg XGODY 12" 4K Dashcam)
|
|
1435
|
+
last if $$et{FoundGPSByScan};
|
|
1426
1436
|
# decode "freeGPS " data (Novatek and others)
|
|
1427
1437
|
ProcessFreeGPS($et, {
|
|
1428
1438
|
DataPt => \$buff,
|
|
@@ -1473,6 +1483,31 @@ sub ConvertLatLon($$)
|
|
|
1473
1483
|
$_[1] = $deg + ($_[1] - $deg * 100) / 60;
|
|
1474
1484
|
}
|
|
1475
1485
|
|
|
1486
|
+
#------------------------------------------------------------------------------
|
|
1487
|
+
# Decrypt Lucky data
|
|
1488
|
+
# Inputs: 0) string to decrypt, 1) encryption key
|
|
1489
|
+
# Returns: decrypted string
|
|
1490
|
+
my @luckyKeys = ('luckychip gps', 'customer ## gps');
|
|
1491
|
+
sub DecryptLucky($$) {
|
|
1492
|
+
my ($str, $key) = @_;
|
|
1493
|
+
my @str = unpack('C*', $str);
|
|
1494
|
+
my @key = unpack('C*', $key);
|
|
1495
|
+
my @enc = (0..255);
|
|
1496
|
+
my ($i, $j, $k) = (0, 0, 0);
|
|
1497
|
+
do {
|
|
1498
|
+
$j = ($j + $enc[$i] + $key[$i % length($key)]) & 0xff;
|
|
1499
|
+
@enc[$i,$j] = @enc[$j,$i];
|
|
1500
|
+
} while (++$i < 256);
|
|
1501
|
+
($i, $j, $k) = (0, 0, 0);
|
|
1502
|
+
do {
|
|
1503
|
+
$j = ($j + 1) & 0xff;
|
|
1504
|
+
$k = ($k + $enc[$j]) & 0xff;
|
|
1505
|
+
@enc[$j,$k] = @enc[$k,$j];
|
|
1506
|
+
$str[$i] ^= $enc[($enc[$j] + $enc[$k]) & 0xff];
|
|
1507
|
+
} while (++$i < @str);
|
|
1508
|
+
return pack('C*', @str);
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1476
1511
|
#------------------------------------------------------------------------------
|
|
1477
1512
|
# Process "freeGPS " data blocks
|
|
1478
1513
|
# Inputs: 0) ExifTool ref, 1) dirInfo ref {DataPt,SampleTime,SampleDuration}, 2) tagTable ref
|
|
@@ -1588,40 +1623,87 @@ sub ProcessFreeGPS($$$)
|
|
|
1588
1623
|
}
|
|
1589
1624
|
if (defined $lat) {
|
|
1590
1625
|
# extract accelerometer readings if GPS was valid
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
map { $_ = $_ - 4294967296 if $_ >= 0x80000000; $_ /= 256 } @acc;
|
|
1626
|
+
# and change to signed integer and divide by 256
|
|
1627
|
+
@acc = map { SignedInt32 / 256 } unpack('x68V3', $$dataPt);
|
|
1594
1628
|
}
|
|
1595
1629
|
|
|
1596
|
-
} elsif ($$dataPt =~ /^.{37}\0\0\0A([NS])([EW])/s) {
|
|
1630
|
+
} elsif ($$dataPt =~ /^.{37}\0\0\0A([NS])([EW])\0/s) {
|
|
1597
1631
|
|
|
1598
|
-
$debug and $et->FoundTag(GPSType => 3);
|
|
1599
|
-
# decode freeGPS from ViofoA119v3 dashcam (similar to Novatek GPS format)
|
|
1600
|
-
# 0000: 00 00 40 00 66 72 65 65 47 50 53 20 f0 03 00 00 [..@.freeGPS ....]
|
|
1601
|
-
# 0010: 05 00 00 00 2f 00 00 00 03 00 00 00 13 00 00 00 [..../...........]
|
|
1602
|
-
# 0020: 09 00 00 00 1b 00 00 00 41 4e 57 00 25 d1 99 45 [........ANW.%..E]
|
|
1603
|
-
# 0030: f1 47 40 46 66 66 d2 41 85 eb 83 41 00 00 00 00 [.G@Fff.A...A....]
|
|
1604
1632
|
($latRef, $lonRef) = ($1, $2);
|
|
1605
1633
|
($hr,$min,$sec,$yr,$mon,$day) = unpack('x16V6', $$dataPt);
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
$
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
}
|
|
1616
|
-
$
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1634
|
+
# test for base64-encoded and encrypted lucky gps strings
|
|
1635
|
+
my ($notEnc, $notStr, $lt, $ln);
|
|
1636
|
+
if (length($$dataPt) < 0x78) {
|
|
1637
|
+
$notEnc = $notStr = 1;
|
|
1638
|
+
} else {
|
|
1639
|
+
$lt = substr($$dataPt, 0x2c, 20), # latitude
|
|
1640
|
+
$ln = substr($$dataPt, 0x40, 20), # longitude
|
|
1641
|
+
/^[A-Za-z0-9+\/]{8,20}={0,2}\0*$/ or $notEnc = 1, last foreach ($lt, $ln);
|
|
1642
|
+
/^\d{1,5}\.\d+\0*$/ or $notStr = 1, last foreach ($lt, $ln);
|
|
1643
|
+
}
|
|
1644
|
+
if ($notEnc and $notStr) {
|
|
1645
|
+
|
|
1646
|
+
$debug and $et->FoundTag(GPSType => '3a');
|
|
1647
|
+
# decode freeGPS from ViofoA119v3 dashcam (similar to Novatek GPS format)
|
|
1648
|
+
# 0000: 00 00 40 00 66 72 65 65 47 50 53 20 f0 03 00 00 [..@.freeGPS ....]
|
|
1649
|
+
# 0010: 05 00 00 00 2f 00 00 00 03 00 00 00 13 00 00 00 [..../...........]
|
|
1650
|
+
# 0020: 09 00 00 00 1b 00 00 00 41 4e 57 00 25 d1 99 45 [........ANW.%..E]
|
|
1651
|
+
# 0030: f1 47 40 46 66 66 d2 41 85 eb 83 41 00 00 00 00 [.G@Fff.A...A....]
|
|
1652
|
+
if ($yr >= 2000) {
|
|
1653
|
+
# Kenwood dashcam sometimes stores absolute year and local time
|
|
1654
|
+
# (but sometimes year since 2000 and UTC time in same video!)
|
|
1655
|
+
require Time::Local;
|
|
1656
|
+
my $time = Image::ExifTool::TimeLocal($sec,$min,$hr,$day,$mon-1,$yr);
|
|
1657
|
+
($sec,$min,$hr,$day,$mon,$yr) = gmtime($time);
|
|
1658
|
+
$yr += 1900;
|
|
1659
|
+
++$mon;
|
|
1660
|
+
$et->WarnOnce('Converting GPSDateTime to UTC based on local time zone',1);
|
|
1661
|
+
}
|
|
1662
|
+
$lat = GetFloat($dataPt, 0x2c);
|
|
1663
|
+
$lon = GetFloat($dataPt, 0x30);
|
|
1664
|
+
$spd = GetFloat($dataPt, 0x34) * $knotsToKph;
|
|
1665
|
+
$trk = GetFloat($dataPt, 0x38);
|
|
1666
|
+
# (may be all zeros or int16u counting from 1 to 6 if not valid)
|
|
1667
|
+
my $tmp = substr($$dataPt, 60, 12);
|
|
1668
|
+
if ($tmp ne "\0\0\0\0\0\0\0\0\0\0\0\0" and $tmp ne "\x01\0\x02\0\x03\0\x04\0\x05\0\x06\0") {
|
|
1669
|
+
@acc = map { SignedInt32 / 256 } unpack('V3', $tmp);
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
} else {
|
|
1673
|
+
|
|
1674
|
+
$debug and $et->FoundTag(GPSType => '3b');
|
|
1675
|
+
# decode freeGPS from E-ACE B44 dashcam
|
|
1676
|
+
# 0000: 00 00 40 00 66 72 65 65 47 50 53 20 f0 03 00 00 [..@.freeGPS ....]
|
|
1677
|
+
# 0010: 08 00 00 00 22 00 00 00 01 00 00 00 18 00 00 00 [...."...........]
|
|
1678
|
+
# 0020: 08 00 00 00 10 00 00 00 41 4e 45 00 67 4e 69 69 [........ANE.gNii]
|
|
1679
|
+
# 0030: 5a 38 4a 54 74 48 63 61 36 74 77 3d 00 00 00 00 [Z8JTtHca6tw=....]
|
|
1680
|
+
# 0040: 68 74 75 69 5a 4d 4a 53 73 58 55 58 37 4e 6f 3d [htuiZMJSsXUX7No=]
|
|
1681
|
+
# 0050: 00 00 00 00 64 3b ac 41 e1 3a 1d 43 2b 01 00 00 [....d;.A.:.C+...]
|
|
1682
|
+
# 0060: fd ff ff ff 43 00 00 00 32 4a 37 31 50 70 55 48 [....C...2J71PpUH]
|
|
1683
|
+
# 0070: 37 69 68 66 00 00 00 00 00 00 00 00 00 00 00 00 [7ihf............]
|
|
1684
|
+
# (16-byte string at 0x68 is base64 encoded and encrypted 'luckychip' string)
|
|
1685
|
+
$spd = GetFloat($dataPt, 0x54) * $knotsToKph;
|
|
1686
|
+
$trk = GetFloat($dataPt, 0x58);
|
|
1687
|
+
@acc = map SignedInt32, unpack('x92V3', $$dataPt);
|
|
1688
|
+
# (accelerometer scaling is roughly 1G=250-300, but it varies depending on the axis,
|
|
1689
|
+
# so leave the values as raw. The axes are positive acceleration up,left,forward)
|
|
1690
|
+
if ($notEnc) { # (not encrypted)
|
|
1691
|
+
($lat = $lt) =~ s/\0+$//;
|
|
1692
|
+
($lon = $ln) =~ s/\0+$//;
|
|
1693
|
+
} else {
|
|
1694
|
+
# decode base64 strings
|
|
1695
|
+
require Image::ExifTool::XMP;
|
|
1696
|
+
$_ = ${Image::ExifTool::XMP::DecodeBase64($_)} foreach ($lt, $ln);
|
|
1697
|
+
# try various keys to decrypt lat/lon
|
|
1698
|
+
my ($i, $ch, $key) = (0, 'a', $luckyKeys[0]);
|
|
1699
|
+
for (; $i<20; ++$i) {
|
|
1700
|
+
$i and ($key = $luckyKeys[1]) =~ s/#/$ch/g, ++$ch;
|
|
1701
|
+
($lat = DecryptLucky($lt, $key)) =~ /^\d{1,4}\.\d+$/ or undef($lat), next;
|
|
1702
|
+
($lon = DecryptLucky($ln, $key)) =~ /^\d{1,5}\.\d+$/ or undef($lon), next;
|
|
1703
|
+
last;
|
|
1704
|
+
}
|
|
1705
|
+
$lon or $et->WarnOnce('Unknown encryption for latitude/longitude');
|
|
1706
|
+
}
|
|
1625
1707
|
}
|
|
1626
1708
|
|
|
1627
1709
|
} elsif ($$dataPt =~ /^.{21}\0\0\0A([NS])([EW])/s) {
|
|
@@ -1682,7 +1764,7 @@ sub ProcessFreeGPS($$$)
|
|
|
1682
1764
|
$trk -= 360 if $trk >= 360;
|
|
1683
1765
|
undef @acc;
|
|
1684
1766
|
} else {
|
|
1685
|
-
|
|
1767
|
+
@acc = map { SignedInt32 / 1000 } @acc; # (NC)
|
|
1686
1768
|
}
|
|
1687
1769
|
|
|
1688
1770
|
} elsif ($$dataPt =~ /^.{60}4W`b]S</s and length($$dataPt) >= 140) {
|
|
@@ -1786,7 +1868,7 @@ sub ProcessFreeGPS($$$)
|
|
|
1786
1868
|
return 0;
|
|
1787
1869
|
}
|
|
1788
1870
|
# (not sure about acc scaling)
|
|
1789
|
-
|
|
1871
|
+
@acc = map { SignedInt32 / 1000 } @acc;
|
|
1790
1872
|
$lon = GetFloat($dataPt, 0x5c);
|
|
1791
1873
|
$lat = GetFloat($dataPt, 0x60);
|
|
1792
1874
|
$spd = GetFloat($dataPt, 0x64) * $knotsToKph;
|
|
@@ -1931,7 +2013,7 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
|
|
|
1931
2013
|
# 0x7c - int32s[3] accelerometer * 1000
|
|
1932
2014
|
($latRef, $lonRef) = ($1, $2);
|
|
1933
2015
|
($hr,$min,$sec,$yr,$mon,$day,@acc) = unpack('x48V3x52V6', $$dataPt);
|
|
1934
|
-
|
|
2016
|
+
@acc = map { SignedInt32 / 1000 } @acc;
|
|
1935
2017
|
$lat = GetDouble($dataPt, 0x40);
|
|
1936
2018
|
$lon = GetDouble($dataPt, 0x50);
|
|
1937
2019
|
$spd = GetDouble($dataPt, 0x60) * $knotsToKph;
|
|
@@ -1947,8 +2029,7 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
|
|
|
1947
2029
|
$lon = abs(GetFloat(\$dat, 8)); # (abs just to be safe)
|
|
1948
2030
|
$spd = GetFloat(\$dat, 12) * $knotsToKph;
|
|
1949
2031
|
$trk = GetFloat(\$dat, 16);
|
|
1950
|
-
@acc = unpack('x20V3', $dat);
|
|
1951
|
-
map { $_ = $_ - 4294967296 if $_ >= 0x80000000 } @acc;
|
|
2032
|
+
@acc = map SignedInt32, unpack('x20V3', $dat);
|
|
1952
2033
|
ConvertLatLon($lat, $lon);
|
|
1953
2034
|
$$et{DOC_NUM} = ++$$et{DOC_COUNT};
|
|
1954
2035
|
$et->HandleTag($tagTbl, GPSLatitude => $lat * (substr($dat,1,1) eq 'S' ? -1 : 1));
|
|
@@ -1982,7 +2063,7 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
|
|
|
1982
2063
|
$lon = abs(GetDouble($dataPt, 48)); # (abs just to be safe)
|
|
1983
2064
|
$spd = GetDouble($dataPt, 64) * $knotsToKph;
|
|
1984
2065
|
$trk = GetDouble($dataPt, 72);
|
|
1985
|
-
|
|
2066
|
+
@acc = map { SignedInt32 / 1000 } @acc; # (NC)
|
|
1986
2067
|
# (not necessary to read RMC sentence because we already have it all)
|
|
1987
2068
|
|
|
1988
2069
|
} elsif ($$dataPt =~ /^.{72}A[NS][EW]\0/s) {
|
|
@@ -2049,9 +2130,41 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
|
|
|
2049
2130
|
}
|
|
2050
2131
|
}
|
|
2051
2132
|
|
|
2052
|
-
}
|
|
2133
|
+
} elsif ($$dataPt =~ m<^.{23}(\d{4})/(\d{2})/(\d{2}) (\d{2}):(\d{2}):(\d{2}) [N|S]>s) {
|
|
2053
2134
|
|
|
2054
2135
|
$debug and $et->FoundTag(GPSType => 16);
|
|
2136
|
+
# XGODY 12" 4K Dashcam
|
|
2137
|
+
# 0000: 00 00 00 a8 66 72 65 65 47 50 53 20 98 00 00 00 [....freeGPS ....]
|
|
2138
|
+
# 0010: 6e 6f 72 6d 61 6c 3a 32 30 32 34 2f 30 35 2f 32 [normal:2024/05/2]
|
|
2139
|
+
# 0020: 32 20 30 32 3a 35 34 3a 32 39 20 4e 3a 34 32 2e [2 02:54:29 N:42.]
|
|
2140
|
+
# 0030: 33 38 32 34 37 30 20 57 3a 38 33 2e 33 38 39 35 [382470 W:83.3895]
|
|
2141
|
+
# 0040: 37 30 20 35 33 2e 36 20 6b 6d 2f 68 20 78 3a 2d [70 53.6 km/h x:-]
|
|
2142
|
+
# 0050: 30 2e 30 32 20 79 3a 30 2e 39 39 20 7a 3a 30 2e [0.02 y:0.99 z:0.]
|
|
2143
|
+
# 0060: 31 30 20 41 3a 32 36 39 2e 32 20 48 3a 32 34 35 [10 A:269.2 H:245]
|
|
2144
|
+
# 0070: 2e 35 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [.5..............]
|
|
2145
|
+
($yr,$mon,$day,$hr,$min,$sec) = ($1,$2,$3,$4,$5,$6);
|
|
2146
|
+
$$dataPt =~ s/\0+$//; # remove trailing nulls
|
|
2147
|
+
my @a = split ' ', substr($$dataPt,43);
|
|
2148
|
+
$ddd = 1;
|
|
2149
|
+
foreach (@a) {
|
|
2150
|
+
unless (/^([A-Z]):([-+]?\d+(\.\d+)?)$/i) {
|
|
2151
|
+
# (the "km/h" after spd is display units? because the value is stored in knots)
|
|
2152
|
+
defined $lon and not defined $spd and /^\d+\.\d+$/ and $spd = $_ * $knotsToKph;
|
|
2153
|
+
next;
|
|
2154
|
+
}
|
|
2155
|
+
($1 eq 'N' or $1 eq 'S') and $lat = $2, $latRef = $1, next;
|
|
2156
|
+
($1 eq 'E' or $1 eq 'W') and $lon = $2, $lonRef = $1, next;
|
|
2157
|
+
($1 eq 'x' or $1 eq 'y' or $1 eq 'z') and push(@acc,$2), next;
|
|
2158
|
+
$1 eq 'A' and $trk = $2, next; # (verified, but why 'A'?)
|
|
2159
|
+
# seen 'H' - one might expect altitude ('H'eight), but it doesn't fit
|
|
2160
|
+
# the sample data, so save all other information as an "Unknown_X" tag
|
|
2161
|
+
$$tagTbl{$1} or AddTagToTable($tagTbl, $1, { Name => "Unknown_$1", Unknown => 1 });
|
|
2162
|
+
push(@xtra, $1 => $2), next;
|
|
2163
|
+
}
|
|
2164
|
+
|
|
2165
|
+
} else {
|
|
2166
|
+
|
|
2167
|
+
$debug and $et->FoundTag(GPSType => 17);
|
|
2055
2168
|
# (look for binary GPS as stored by Nextbase 512G, ref PH)
|
|
2056
2169
|
# 0000: 00 00 80 00 66 72 65 65 47 50 53 20 78 01 00 00 [....freeGPS x...]
|
|
2057
2170
|
# 0010: 78 2e 78 78 00 00 00 00 00 00 00 00 00 00 00 00 [x.xx............]
|
|
@@ -2084,7 +2197,7 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
|
|
|
2084
2197
|
$day < 1 or $day > 31 or
|
|
2085
2198
|
$hr > 59 or $min > 59 or $sec > 600;
|
|
2086
2199
|
# change lat/lon to signed integer and divide by 1e7
|
|
2087
|
-
|
|
2200
|
+
($lat, $lon) = map { SignedInt32 / 1e7 } $lat, $lon;
|
|
2088
2201
|
$trk -= 0x10000 if $trk >= 0x8000; # make it signed
|
|
2089
2202
|
$trk /= 100;
|
|
2090
2203
|
$trk += 360 if $trk < 0;
|
|
@@ -2115,7 +2228,7 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
|
|
|
2115
2228
|
my $time = sprintf('%.2d:%.2d:%sZ',$hr,$min,$sec);
|
|
2116
2229
|
$et->HandleTag($tagTbl, GPSTimeStamp => $time);
|
|
2117
2230
|
}
|
|
2118
|
-
if (defined $lat) {
|
|
2231
|
+
if (defined $lat and defined $lon) {
|
|
2119
2232
|
# lat/long are in DDDMM.MMMM format unless $ddd is set
|
|
2120
2233
|
ConvertLatLon($lat, $lon) unless $ddd;
|
|
2121
2234
|
$et->HandleTag($tagTbl, GPSLatitude => $lat * ($latRef eq 'S' ? -1 : 1));
|
|
@@ -2680,6 +2793,53 @@ sub ProcessRIFFTrailer($$$)
|
|
|
2680
2793
|
return 1;
|
|
2681
2794
|
}
|
|
2682
2795
|
|
|
2796
|
+
#------------------------------------------------------------------------------
|
|
2797
|
+
# Process Kenwood Dashcam trailer (forum16229)
|
|
2798
|
+
# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
|
|
2799
|
+
# Returns: 1 on success
|
|
2800
|
+
# Sample data (chained 512-byte records starting like this):
|
|
2801
|
+
# 0000: 43 43 43 43 43 43 43 43 43 43 43 43 43 43 47 50 [CCCCCCCCCCCCCCGP]
|
|
2802
|
+
# 0010: 53 44 41 54 41 2d 2d 32 30 32 34 30 37 31 31 31 [SDATA--202407111]
|
|
2803
|
+
# 0020: 32 30 34 31 32 4e 35 30 2e 36 31 32 33 38 36 30 [20412N50.6123860]
|
|
2804
|
+
# 0030: 36 37 37 45 38 2e 37 30 32 37 31 38 30 39 38 39 [677E8.7027180989]
|
|
2805
|
+
# 0040: 35 33 33 2e 30 30 30 30 30 30 30 30 30 30 30 30 [533.000000000000]
|
|
2806
|
+
# 0050: 2e 30 30 30 30 30 30 30 30 30 30 30 30 30 2e 30 [.0000000000000.0]
|
|
2807
|
+
# 0060: 31 39 39 39 39 39 39 39 35 35 33 2d 30 2e 30 39 [19999999553-0.09]
|
|
2808
|
+
# 0070: 30 30 30 30 30 30 33 35 37 2d 30 2e 31 34 30 30 [000000357-0.1400]
|
|
2809
|
+
# 0080: 30 30 30 30 30 35 39 47 50 53 44 41 54 41 2d 2d [0000059GPSDATA--]
|
|
2810
|
+
sub ProcessKenwoodTrailer($$$)
|
|
2811
|
+
{
|
|
2812
|
+
my ($et, $dirInfo, $tagTbl) = @_;
|
|
2813
|
+
my $raf = $$dirInfo{RAF};
|
|
2814
|
+
my $buff;
|
|
2815
|
+
# current file position is 8 bytes into the 14 C's, so test the next 6:
|
|
2816
|
+
$raf->Read($buff, 14) and $buff eq 'CCCCCCCCCCCCCC' or return 0;
|
|
2817
|
+
$et->VerboseDir('Kenwood trailer', undef, undef);
|
|
2818
|
+
unless ($$et{OPTIONS}{ExtractEmbedded}) {
|
|
2819
|
+
$et->WarnOnce('Use the ExtractEmbedded option to extract timed GPSData from Kenwood trailer',3);
|
|
2820
|
+
return 1;
|
|
2821
|
+
}
|
|
2822
|
+
while ($raf->Read($buff, 121) and $buff =~ /^GPSDATA--(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/) {
|
|
2823
|
+
FoundSomething($et, $tagTbl);
|
|
2824
|
+
$et->HandleTag($tagTbl, GPSDateTime => "$1:$2:$3 $4:$5:$6");
|
|
2825
|
+
my $i = 9 + 14;
|
|
2826
|
+
my ($val, @acc, $tag);
|
|
2827
|
+
foreach $tag (qw(GPSLatitude GPSLongitude GPSSpeed unk acc acc acc)) {
|
|
2828
|
+
$val = substr($buff, $i, 14); $i += 14;
|
|
2829
|
+
next if $tag eq 'unk';
|
|
2830
|
+
my $hemi;
|
|
2831
|
+
$hemi = $1 if $val =~ s/^([NSEW])//;
|
|
2832
|
+
$val =~ /^[-+]?\d+\.\d+$/ or next;
|
|
2833
|
+
$tag eq 'acc' and push(@acc,$val), next;
|
|
2834
|
+
$val = -$val if $hemi and ($hemi eq 'S' or $hemi eq 'W');
|
|
2835
|
+
$et->HandleTag($tagTbl, $tag => $val);
|
|
2836
|
+
}
|
|
2837
|
+
$et->HandleTag($tagTbl, Accelerometer => "@acc") if @acc == 3;
|
|
2838
|
+
}
|
|
2839
|
+
delete $$et{DOC_NUM};
|
|
2840
|
+
return 1;
|
|
2841
|
+
}
|
|
2842
|
+
|
|
2683
2843
|
#------------------------------------------------------------------------------
|
|
2684
2844
|
# Process 'gps ' atom containing NMEA from Pittasoft Blackvue dashcam (ref PH)
|
|
2685
2845
|
# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
|
|
@@ -3353,6 +3513,7 @@ sub ScanMediaData($)
|
|
|
3353
3513
|
}
|
|
3354
3514
|
my $dirInfo = { DataPt => \$buff, DataPos => $pos + $dataPos, DirLen => $len };
|
|
3355
3515
|
ProcessFreeGPS($et, $dirInfo, $tagTbl);
|
|
3516
|
+
$$et{FoundGPSByScan} = 1;
|
|
3356
3517
|
}
|
|
3357
3518
|
$pos += $len;
|
|
3358
3519
|
$buf2 = substr($buff, $len);
|
|
@@ -22,13 +22,13 @@ use vars qw($VERSION %samsungLensTypes);
|
|
|
22
22
|
use Image::ExifTool qw(:DataAccess :Utils);
|
|
23
23
|
use Image::ExifTool::Exif;
|
|
24
24
|
|
|
25
|
-
$VERSION = '1.
|
|
25
|
+
$VERSION = '1.58';
|
|
26
26
|
|
|
27
27
|
sub WriteSTMN($$$);
|
|
28
28
|
sub ProcessINFO($$$);
|
|
29
29
|
sub ProcessSamsungMeta($$$);
|
|
30
30
|
sub ProcessSamsungIFD($$$);
|
|
31
|
-
sub ProcessSamsung(
|
|
31
|
+
sub ProcessSamsung($$;$);
|
|
32
32
|
|
|
33
33
|
# Samsung LensType lookup
|
|
34
34
|
%samsungLensTypes = (
|
|
@@ -943,25 +943,25 @@ my %formatMinMax = (
|
|
|
943
943
|
);
|
|
944
944
|
|
|
945
945
|
# information extracted from Samsung trailer (ie. Samsung SM-T805 "Sound & Shot" JPEG) (ref PH)
|
|
946
|
+
# NOTE: These tags may use $$self{SamsungTagName} in a Condition statement
|
|
947
|
+
# if necessary to differentiate tags with the same ID but different names
|
|
946
948
|
%Image::ExifTool::Samsung::Trailer = (
|
|
947
949
|
GROUPS => { 0 => 'MakerNotes', 2 => 'Other' },
|
|
948
950
|
VARS => { NO_ID => 1, HEX_ID => 0 },
|
|
949
951
|
PROCESS_PROC => \&ProcessSamsung,
|
|
952
|
+
TAG_PREFIX => 'SamsungTrailer',
|
|
950
953
|
PRIORITY => 0, # (first one takes priority so DepthMapWidth/Height match first DepthMapData)
|
|
951
954
|
NOTES => q{
|
|
952
|
-
Tags extracted from the trailer of JPEG images written when
|
|
953
|
-
features (such as "Sound & Shot" or "Shot & More") from
|
|
954
|
-
as the Galaxy S4 and Tab S, and from the 'sefd' atom in
|
|
955
|
-
|
|
956
|
-
},
|
|
957
|
-
'0x0001-name' => {
|
|
958
|
-
Name => 'EmbeddedImageName', # ("DualShot_1","DualShot_2")
|
|
959
|
-
RawConv => '$$self{EmbeddedImageName} = $val',
|
|
955
|
+
Tags extracted from the SEFT trailer of JPEG and PNG images written when
|
|
956
|
+
using certain features (such as "Sound & Shot" or "Shot & More") from
|
|
957
|
+
Samsung models such as the Galaxy S4 and Tab S, and from the 'sefd' atom in
|
|
958
|
+
HEIC images from models such as the S10+.
|
|
960
959
|
},
|
|
960
|
+
'0x0001-name' => 'EmbeddedImageName', # ("DualShot_1","DualShot_2","SingleShot")
|
|
961
961
|
'0x0001' => [
|
|
962
962
|
{
|
|
963
963
|
Name => 'EmbeddedImage',
|
|
964
|
-
Condition => '$$self{
|
|
964
|
+
Condition => '$$self{SamsungTagName} ne "DualShot_2"',
|
|
965
965
|
Groups => { 2 => 'Preview' },
|
|
966
966
|
Binary => 1,
|
|
967
967
|
},
|
|
@@ -970,6 +970,7 @@ my %formatMinMax = (
|
|
|
970
970
|
Groups => { 2 => 'Preview' },
|
|
971
971
|
Binary => 1,
|
|
972
972
|
},
|
|
973
|
+
# (have also seen the string "BOKEH" here (SM-A226B)
|
|
973
974
|
],
|
|
974
975
|
'0x0100-name' => 'EmbeddedAudioFileName', # ("SoundShot_000")
|
|
975
976
|
'0x0100' => { Name => 'EmbeddedAudioFile', Groups => { 2 => 'Audio' }, Binary => 1 },
|
|
@@ -1265,6 +1266,7 @@ my %formatMinMax = (
|
|
|
1265
1266
|
'0x0b40' => { # (SM-N975X front camera)
|
|
1266
1267
|
Name => 'SingleShotMeta',
|
|
1267
1268
|
SubDirectory => { TagTable => 'Image::ExifTool::Samsung::SingleShotMeta' },
|
|
1269
|
+
# (have also see the string "BOKEH_INFO" here (SM-A226B)
|
|
1268
1270
|
},
|
|
1269
1271
|
# 0x0b41-name - seen 'SingeShot_DepthMap_1' (Yes, "Singe") (SM-N975X front camera)
|
|
1270
1272
|
'0x0b41' => { Name => 'SingleShotDepthMap', Binary => 1 },
|
|
@@ -1277,8 +1279,10 @@ my %formatMinMax = (
|
|
|
1277
1279
|
# 0x0bd0-name - seen 'Dual_Relighting_Bokeh_Info' #forum16086
|
|
1278
1280
|
# 0x0be0-name - seen 'Livefocus_JDM_Info' #forum16086
|
|
1279
1281
|
# 0x0bf0-name - seen 'Remaster_Info' #forum16086
|
|
1282
|
+
'0x0bf0' => 'RemasterInfo', #forum16086/16242
|
|
1280
1283
|
# 0x0c21-name - seen 'Portrait_Effect_Info' #forum16086
|
|
1281
1284
|
# 0x0c51-name - seen 'Samsung_Capture_Info' #forum16086
|
|
1285
|
+
'0x0c51' => 'SamsungCaptureInfo', #forum16086/16242
|
|
1282
1286
|
# 0x0c61-name - seen 'Camera_Capture_Mode_Info' #forum16086
|
|
1283
1287
|
# 0x0c71-name - seen 'Pro_White_Balance_Info' #forum16086
|
|
1284
1288
|
# 0x0c81-name - seen 'Watermark_Info' #forum16086
|
|
@@ -1289,7 +1293,11 @@ my %formatMinMax = (
|
|
|
1289
1293
|
# 0x0d11-name - seen 'Video_Snapshot_Info' #forum16086
|
|
1290
1294
|
# 0x0d21-name - seen 'Camera_Scene_Info' #forum16086
|
|
1291
1295
|
# 0x0d31-name - seen 'Food_Blur_Effect_Info' #forum16086
|
|
1292
|
-
|
|
1296
|
+
'0x0d91' => { #forum16086/16242
|
|
1297
|
+
Name => 'PEg_Info',
|
|
1298
|
+
Description => 'PEg Info',
|
|
1299
|
+
SubDirectory => { TagTable => 'Image::ExifTool::JSON::Main' },
|
|
1300
|
+
},
|
|
1293
1301
|
# 0x0da1-name - seen 'Captured_App_Info' #forum16086
|
|
1294
1302
|
# 0xa050-name - seen 'Jpeg360_2D_Info' (Samsung Gear 360)
|
|
1295
1303
|
# 0xa050 - seen 'Jpeg3602D' (Samsung Gear 360)
|
|
@@ -1487,7 +1495,7 @@ sub ProcessSamsungMeta($$$)
|
|
|
1487
1495
|
my $pos = $$dirInfo{DirStart};
|
|
1488
1496
|
my $end = $$dirInfo{DirLen} + $pos;
|
|
1489
1497
|
unless ($pos + 8 <= $end and substr($$dataPt, $pos, 4) eq 'DOFS') {
|
|
1490
|
-
$et->Warn("Unrecognized $dirName data");
|
|
1498
|
+
$et->Warn("Unrecognized $dirName data", 1);
|
|
1491
1499
|
return 0;
|
|
1492
1500
|
}
|
|
1493
1501
|
my $ver = Get32u($dataPt, $pos + 4);
|
|
@@ -1563,7 +1571,7 @@ sub ProcessSamsungIFD($$$)
|
|
|
1563
1571
|
# Returns: 1 on success, 0 not valid Samsung trailer, or -1 error writing
|
|
1564
1572
|
# - updates DataPos to point to start of Samsung trailer
|
|
1565
1573
|
# - updates DirLen to existing trailer length
|
|
1566
|
-
sub ProcessSamsung(
|
|
1574
|
+
sub ProcessSamsung($$;$)
|
|
1567
1575
|
{
|
|
1568
1576
|
my ($et, $dirInfo) = @_;
|
|
1569
1577
|
my $raf = $$dirInfo{RAF};
|
|
@@ -1653,8 +1661,13 @@ SamBlock:
|
|
|
1653
1661
|
$audioSize = $size - 8 - $len;
|
|
1654
1662
|
next;
|
|
1655
1663
|
}
|
|
1656
|
-
|
|
1664
|
+
last unless $raf->Seek($dirPos-$noff, 0) and $raf->Read($buf2, $size) == $size;
|
|
1665
|
+
# (could validate the first 4 bytes of the block because they
|
|
1666
|
+
# are the same as the first 4 bytes of the directory entry)
|
|
1667
|
+
$len = Get32u(\$buf2, 4);
|
|
1668
|
+
last if $len + 8 > $size;
|
|
1657
1669
|
my $tag = sprintf("0x%.4x", $type);
|
|
1670
|
+
# add unknown tags if necessary
|
|
1658
1671
|
unless ($$tagTablePtr{$tag}) {
|
|
1659
1672
|
next unless $unknown or $verbose;
|
|
1660
1673
|
my %tagInfo = (
|
|
@@ -1673,11 +1686,8 @@ SamBlock:
|
|
|
1673
1686
|
);
|
|
1674
1687
|
AddTagToTable($tagTablePtr, "$tag-name", \%tagInfo2);
|
|
1675
1688
|
}
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
# are the same as the first 4 bytes of the directory entry)
|
|
1679
|
-
$len = Get32u(\$buf2, 4);
|
|
1680
|
-
last if $len + 8 > $size;
|
|
1689
|
+
# set SamsungTagName ExifTool member for use in tag Condition
|
|
1690
|
+
$$et{SamsungTagName} = substr($buf2, 8, $len);
|
|
1681
1691
|
# extract tag name and value
|
|
1682
1692
|
$et->HandleTag($tagTablePtr, "$tag-name", undef,
|
|
1683
1693
|
DataPt => \$buf2,
|
|
@@ -1691,6 +1701,7 @@ SamBlock:
|
|
|
1691
1701
|
Start => 8 + $len,
|
|
1692
1702
|
Size => $size - (8 + $len),
|
|
1693
1703
|
);
|
|
1704
|
+
delete $$et{SamsungTagName};
|
|
1694
1705
|
}
|
|
1695
1706
|
if ($outfile) {
|
|
1696
1707
|
last unless $raf->Seek($dataPos, 0) and $raf->Read($buff, $dirLen) == $dirLen;
|