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.
@@ -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 74 different formats of timed GPS metadata from video files.
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
- # process by brute scan instead if ExtractEmbedded >= 3
1424
- # (some videos don't reference all freeGPS info from 'gps ' table, eg. INNOV)
1425
- last if $eeOpt >= 3;
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
- @acc = unpack('x68V3', $$dataPt);
1592
- # change to signed integer and divide by 256
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
- if ($yr >= 2000) {
1607
- # Kenwood dashcam sometimes stores absolute year and local time
1608
- # (but sometimes year since 2000 and UTC time in same video!)
1609
- require Time::Local;
1610
- my $time = Image::ExifTool::TimeLocal($sec,$min,$hr,$day,$mon-1,$yr);
1611
- ($sec,$min,$hr,$day,$mon,$yr) = gmtime($time);
1612
- $yr += 1900;
1613
- ++$mon;
1614
- $et->WarnOnce('Converting GPSDateTime to UTC based on local time zone',1);
1615
- }
1616
- $lat = GetFloat($dataPt, 0x2c);
1617
- $lon = GetFloat($dataPt, 0x30);
1618
- $spd = GetFloat($dataPt, 0x34) * $knotsToKph; # (convert knots to km/h)
1619
- $trk = GetFloat($dataPt, 0x38);
1620
- # (may be all zeros or int16u counting from 1 to 6 if not valid)
1621
- my $tmp = substr($$dataPt, 60, 12);
1622
- 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") {
1623
- @acc = unpack('V3', $tmp);
1624
- map { $_ = $_ - 4294967296 if $_ >= 0x80000000; $_ /= 256 } @acc;
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
- map { $_ = $_ - 4294967296 if $_ >= 0x80000000; $_ /= 1000 } @acc; # (NC)
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
- map { $_ = $_ - 4294967296 if $_ >= 0x80000000; $_ /= 1000 } @acc;
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
- map { $_ = $_ - 4294967296 if $_ >= 0x80000000; $_ /= 1000 } @acc;
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
- map { $_ = $_ - 4294967296 if $_ >= 0x80000000; $_ /= 1000 } @acc; # (NC)
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
- } else {
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
- map { $_ = $_ - 4294967296 if $_ >= 0x80000000; $_ /= 1e7 } $lat, $lon;
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.56';
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 using certain
953
- features (such as "Sound & Shot" or "Shot & More") from Samsung models such
954
- as the Galaxy S4 and Tab S, and from the 'sefd' atom in HEIC images from the
955
- Samsung S10+.
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{EmbeddedImageName} eq "DualShot_1"',
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
- # 0x0d91-name - seen 'PEg_Info' #forum16086
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
- # add unknown tags if necessary
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
- last unless $raf->Seek($dirPos-$noff, 0) and $raf->Read($buf2, $size) == $size;
1677
- # (could validate the first 4 bytes of the block because they
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;