exiftool_vendored 13.03.0 → 13.06.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,242 @@
1
+ #------------------------------------------------------------------------------
2
+ # File: Protobuf.pm
3
+ #
4
+ # Description: Decode protocol buffer data
5
+ #
6
+ # Revisions: 2024-12-04 - P. Harvey Created
7
+ #
8
+ # Notes: Tag definitions for Protobuf tags support additional 'signed'
9
+ # and 'unsigned' formats for varInt (type 0) values
10
+ #
11
+ # References: 1) https://protobuf.dev/programming-guides/encoding/
12
+ #------------------------------------------------------------------------------
13
+
14
+ package Image::ExifTool::Protobuf;
15
+
16
+ use strict;
17
+ use vars qw($VERSION);
18
+ use Image::ExifTool qw(:DataAccess :Utils);
19
+
20
+ $VERSION = '1.00';
21
+
22
+ sub ProcessProtobuf($$$;$);
23
+
24
+ #------------------------------------------------------------------------------
25
+ # Read bytes from dirInfo object
26
+ # Inputs: 0) dirInfo ref, 1) number of bytes
27
+ # Returns: binary data or undef on error
28
+ sub GetBytes($$)
29
+ {
30
+ my ($dirInfo, $n) = @_;
31
+ my $dataPt = $$dirInfo{DataPt};
32
+ my $pos = $$dirInfo{Pos};
33
+ return undef if $pos + $n > length $$dataPt;
34
+ $$dirInfo{Pos} += $n;
35
+ return substr($$dataPt, $pos, $n);
36
+ }
37
+
38
+ #------------------------------------------------------------------------------
39
+ # Read variable-length integer
40
+ # Inputs: 0) dirInfo ref
41
+ # Returns: integer value
42
+ sub VarInt($)
43
+ {
44
+ my $dirInfo = shift;
45
+ my $val = 0;
46
+ my $shift = 0;
47
+ for (;;) {
48
+ my $buff = GetBytes($dirInfo, 1);
49
+ defined $buff or return undef;
50
+ $val += (ord($buff) & 0x7f) << $shift;
51
+ last unless ord($buff) & 0x80;
52
+ $shift += 7;
53
+ }
54
+ return $val;
55
+ }
56
+
57
+ #------------------------------------------------------------------------------
58
+ # Read protobuf record
59
+ # Inputs: 0) dirInfo ref
60
+ # Returns: 0) record payload (plus tag id and format type in list context)
61
+ # Notes: Updates dirInfo Pos to start of next record
62
+ sub ReadRecord($)
63
+ {
64
+ my $dirInfo = shift;
65
+ my $val = VarInt($dirInfo);
66
+ return undef unless defined $val;
67
+ my $id = $val >> 3;
68
+ my $type = $val & 0x07;
69
+ my $buff;
70
+
71
+ if ($type == 0) { # varInt
72
+ $buff = VarInt($dirInfo);
73
+ } elsif ($type == 1) { # 64-bit number
74
+ $buff = GetBytes($dirInfo, 8);
75
+ } elsif ($type == 2) { # string, bytes or protobuf
76
+ my $len = VarInt($dirInfo);
77
+ if ($len) {
78
+ $buff = GetBytes($dirInfo, $len);
79
+ } else {
80
+ $buff = '';
81
+ }
82
+ } elsif ($type == 3) { # (deprecated start group)
83
+ $buff = '';
84
+ } elsif ($type == 4) { # (deprecated end group)
85
+ $buff = '';
86
+ } elsif ($type == 5) { # 32-bit number
87
+ $buff = GetBytes($dirInfo, 4);
88
+ }
89
+ return wantarray ? ($buff, $id, $type) : $buff;
90
+ }
91
+
92
+ #------------------------------------------------------------------------------
93
+ # Check to see if this could be a protobuf object
94
+ # Inputs: 0) data reference
95
+ # Retursn: true if this looks like a protobuf
96
+ sub IsProtobuf($)
97
+ {
98
+ my $pt = shift;
99
+ my $dirInfo = { DataPt => $pt, Pos => 0 };
100
+ for (;;) {
101
+ return 0 unless defined ReadRecord($dirInfo);
102
+ return 1 if $$dirInfo{Pos} == length $$pt;
103
+ }
104
+ }
105
+
106
+ #------------------------------------------------------------------------------
107
+ # Process protobuf data (eg. DJI djmd timed data from Action4 videos) (ref 1)
108
+ # Inputs: 0) ExifTool ref, 1) dirInfo ref with DataPt, DirName and Base,
109
+ # 2) tag table ptr, 3) prefix of parent protobuf ID's
110
+ # Returns: true on success
111
+ sub ProcessProtobuf($$$;$)
112
+ {
113
+ my ($et, $dirInfo, $tagTbl, $prefix) = @_;
114
+ my $dataPt = $$dirInfo{DataPt};
115
+ my $dirName = $$dirInfo{DirName};
116
+ my $unknown = $et->Options('Unknown') || $et->Options('Verbose');
117
+
118
+ $$dirInfo{Pos} = $$dirInfo{DirStart} || 0; # initialize buffer Pos
119
+
120
+ unless ($prefix) {
121
+ $prefix = '';
122
+ $$et{ProtocolName}{$dirName} = '*' unless defined $$et{ProtocolName}{$dirName};
123
+ SetByteOrder('II');
124
+ }
125
+ # loop through protobuf records
126
+ for (;;) {
127
+ my $pos = $$dirInfo{Pos};
128
+ last if $pos >= length $$dataPt;
129
+ my ($buff, $id, $type) = ReadRecord($dirInfo);
130
+ defined $buff or $et->WarnOnce('Protobuf format error'), last;
131
+ if ($type == 2 and $buff =~ /\.proto$/) {
132
+ # save protocol name separately for directory type
133
+ $$et{ProtocolName}{$dirName} = substr($buff, 0, -6);
134
+ $et->HandleTag($tagTbl, Protocol => $buff);
135
+ }
136
+ my $tag = "$$et{ProtocolName}{$dirName}_$prefix$id";
137
+ my $tagInfo = $$tagTbl{$tag};
138
+ if ($tagInfo) {
139
+ next if $type != 2 and $$tagInfo{Unknown} and not $unknown;
140
+ } else {
141
+ next unless $type == 2 or $unknown;
142
+ $tagInfo = AddTagToTable($tagTbl, $tag, { Unknown => 1 });
143
+ }
144
+ # set IsProtobuf flag (only for Unknown tags) if necessary
145
+ if ($type == 2 and $$tagInfo{Unknown}) {
146
+ if ($$tagInfo{IsProtobuf}) {
147
+ $$tagInfo{IsProtobuf} = 0 unless IsProtobuf(\$buff);
148
+ } elsif (not defined $$tagInfo{IsProtobuf} and $buff =~ /[^\x20-\x7f]/ and
149
+ IsProtobuf(\$buff))
150
+ {
151
+ $$tagInfo{IsProtobuf} = 1;
152
+ }
153
+ next unless $$tagInfo{IsProtobuf} or $unknown;
154
+ }
155
+ # format binary payload into a useful value
156
+ my $val;
157
+ if ($$tagInfo{Format}) {
158
+ if ($type == 0) {
159
+ $val = $buff;
160
+ $val = ($val & 1) ? -($val >> 1)-1 : ($val >> 1) if $$tagInfo{Format} eq 'signed';
161
+ } else {
162
+ $val = ReadValue(\$buff, 0, $$tagInfo{Format}, undef, length($buff));
163
+ }
164
+ } elsif ($type == 0) {
165
+ $val = $buff;
166
+ my $signed = ($val & 1) ? -($val >> 1)-1 : ($val >> 1);
167
+ $val .= sprintf(" (0x%x, signed $signed)", $val);
168
+ } elsif ($type == 1) {
169
+ $val = '0x' . unpack('H*', $buff) . ' (double ' . GetDouble(\$buff,0) . ')';
170
+ } elsif ($type == 2) {
171
+ if ($$tagInfo{IsProtobuf}) {
172
+ $et->VPrint(1, "+ Protobuf $tag (" . length($buff) . " bytes)\n");
173
+ my $addr = $$dirInfo{Base} + $$dirInfo{Pos} - length($buff);
174
+ $et->VerboseDump(\$buff, Addr => $addr);
175
+ my %subdir = ( DataPt => \$buff, Base => $addr, DirName => $dirName );
176
+ ProcessProtobuf($et, \%subdir, $tagTbl, "$prefix$id-");
177
+ next;
178
+ } elsif ($buff !~ /[^\x20-\x7f]/) {
179
+ $val = $buff; # assume this is an ASCII string
180
+ } elsif (length($buff) % 4) {
181
+ $val = '0x' . unpack('H*', $buff);
182
+ } else {
183
+ $val = '0x' . join(' ', unpack('(H8)*', $buff)); # (group in 4-byte blocks)
184
+ }
185
+ } elsif ($type == 5) {
186
+ $val = '0x' . unpack('H*', $buff) . ' (int32u ' . Get32u(\$buff, 0);
187
+ $val .= ', int32s ' . Get32s(\$buff, 0) if ord(substr($buff,3,1)) & 0x80;
188
+ $val .= ', float ' . GetFloat(\$buff, 0) . ')';
189
+ } else {
190
+ $val = $buff;
191
+ }
192
+ # get length of data in the record
193
+ my $start = $type == 0 ? $pos + 1 : $$dirInfo{Pos} - length $buff;
194
+ $et->HandleTag($tagTbl, $tag, $val,
195
+ DataPt => $dataPt,
196
+ DataPos=> $$dirInfo{Base},
197
+ Start => $start,
198
+ Size => $$dirInfo{Pos} - $start,
199
+ Extra => ", type=$type",
200
+ Format => $$tagInfo{Format},
201
+ );
202
+ }
203
+ # warn if we didn't finish exactly at the end of the buffer
204
+ $et->WarnOnce('Truncated protobuf data') unless $prefix or $$dirInfo{Pos} == length $$dataPt;
205
+ return 1;
206
+ }
207
+
208
+ __END__
209
+
210
+ =head1 NAME
211
+
212
+ Image::ExifTool::Protobuf - Decode protocol buffer information
213
+
214
+ =head1 SYNOPSIS
215
+
216
+ This module is loaded automatically by Image::ExifTool when required.
217
+
218
+ =head1 DESCRIPTION
219
+
220
+ This module contains definitions required by Image::ExifTool to decode
221
+ information in protocol buffer (protobuf) format.
222
+
223
+ =head1 AUTHOR
224
+
225
+ Copyright 2003-2024, Phil Harvey (philharvey66 at gmail.com)
226
+
227
+ This library is free software; you can redistribute it and/or modify it
228
+ under the same terms as Perl itself.
229
+
230
+ =head1 REFERENCES
231
+
232
+ =over 4
233
+
234
+ =item L<https://protobuf.dev/programming-guides/encoding/>
235
+
236
+ =back
237
+
238
+ =head1 SEE ALSO
239
+
240
+ L<Image::ExifTool(3pm)|Image::ExifTool>
241
+
242
+ =cut
@@ -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 78 different formats of timed GPS metadata from video files.
112
+ currently reads 85 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' },
@@ -339,10 +339,16 @@ my %insvLimit = (
339
339
  Groups => { 2 => 'Preview' },
340
340
  RawConv => '$self->ValidateImage(\$val,$tag)',
341
341
  },
342
- # djmd - DJI AC003 Osmo Action 4 cam
343
- #TODO djmd => { SubDirectory => { TagTable => 'Image::ExifTool::DJI::djmd', ByteOrder => 'Little-Endian' } },
344
- # (also DJI_20240615181302_0006_D.LRF)
345
- # dbgi - DJI AC003 Osmo Action 4 cam -- lots more unknown stuff
342
+ djmd => { # (DJI AC003 Osmo Action 4 cam)
343
+ Name => 'DJIMetadata',
344
+ SubDirectory => { TagTable => 'Image::ExifTool::DJI::Protobuf' },
345
+ },
346
+ dbgi => { # (DJI AC003 Osmo Action 4 cam)
347
+ Name => 'DJIDebug',
348
+ Unknown => 2,
349
+ Notes => 'extracted only if Unknown option is 2 or greater',
350
+ SubDirectory => { TagTable => 'Image::ExifTool::DJI::Protobuf' },
351
+ },
346
352
  Unknown00 => { Unknown => 1 },
347
353
  Unknown01 => { Unknown => 1 },
348
354
  Unknown02 => { Unknown => 1 },
@@ -894,7 +900,7 @@ sub FoundSomething($$;$$)
894
900
  #------------------------------------------------------------------------------
895
901
  # Approximate GPSDateTime value from sample time and CreateDate
896
902
  # Inputs: 0) ExifTool ref, 1) tag table ptr, 2) sample time (s)
897
- # 3) true if CreateDate is at end of video, 4) flag if CreateDate is UTC
903
+ # 3) true if CreateDate is UTC
898
904
  # Notes: Uses ExifTool CreateDateAtEnd as flag to subtract video duration
899
905
  sub SetGPSDateTime($$$;$)
900
906
  {
@@ -1432,7 +1438,7 @@ Sample: for ($i=0; ; ) {
1432
1438
 
1433
1439
  if ($$tagTbl{$metaFormat}) {
1434
1440
  my $tagInfo = $et->GetTagInfo($tagTbl, $metaFormat, \$buff);
1435
- if ($tagInfo) {
1441
+ if ($tagInfo and (not $$tagInfo{Unknown} or $$et{OPTIONS}{Unknown} >= $$tagInfo{Unknown})) {
1436
1442
  FoundSomething($et, $tagTbl, $time[$i], $dur[$i]);
1437
1443
  $$et{ee} = $ee; # need ee information for 'keys'
1438
1444
  $et->HandleTag($tagTbl, $metaFormat, undef,
@@ -1442,6 +1448,15 @@ Sample: for ($i=0; ; ) {
1442
1448
  TagInfo => $tagInfo,
1443
1449
  );
1444
1450
  delete $$et{ee};
1451
+ # synthesize GPSDateTime if necessary for djmd metadata
1452
+ if ($metaFormat eq 'djmd') {
1453
+ if (defined $$et{GPSLatitude} and defined $$et{GPSLongitude} and not $$et{GPSDateTime}) {
1454
+ SetGPSDateTime($et, $tagTbl, $time[$i], 1); # (NC)
1455
+ }
1456
+ delete $$et{GPSLatitude};
1457
+ delete $$et{GPSLongitude};
1458
+ delete $$et{GPSDateTime};
1459
+ }
1445
1460
  } elsif ($metaFormat eq 'camm' and $buff =~ /^X/) {
1446
1461
  # seen 'camm' metadata in this format (X/Y/Z acceleration and G force? + GPRMC + ?)
1447
1462
  # "X0000.0000Y0000.0000Z0000.0000G0000.0000$GPRMC,000125,V,,,,,000.0,,280908,002.1,N*71~, 794021 \x0a"
@@ -1670,7 +1685,7 @@ sub ProcessFreeGPS($$$)
1670
1685
  }
1671
1686
  if ($notEnc and $notStr) {
1672
1687
 
1673
- $debug and $et->FoundTag(GPSType => '3a');
1688
+ $debug and $et->FoundTag(GPSType => 3);
1674
1689
  # decode freeGPS from ViofoA119v3 dashcam (similar to Novatek GPS format)
1675
1690
  # 0000: 00 00 40 00 66 72 65 65 47 50 53 20 f0 03 00 00 [..@.freeGPS ....]
1676
1691
  # 0010: 05 00 00 00 2f 00 00 00 03 00 00 00 13 00 00 00 [..../...........]
@@ -1698,7 +1713,7 @@ sub ProcessFreeGPS($$$)
1698
1713
 
1699
1714
  } else {
1700
1715
 
1701
- $debug and $et->FoundTag(GPSType => '3b');
1716
+ $debug and $et->FoundTag(GPSType => 4);
1702
1717
  # decode freeGPS from E-ACE B44 dashcam
1703
1718
  # 0000: 00 00 40 00 66 72 65 65 47 50 53 20 f0 03 00 00 [..@.freeGPS ....]
1704
1719
  # 0010: 08 00 00 00 22 00 00 00 01 00 00 00 18 00 00 00 [...."...........]
@@ -1735,7 +1750,7 @@ sub ProcessFreeGPS($$$)
1735
1750
 
1736
1751
  } elsif ($$dataPt =~ /^.{21}\0\0\0A([NS])([EW])/s) {
1737
1752
 
1738
- $debug and $et->FoundTag(GPSType => 4);
1753
+ $debug and $et->FoundTag(GPSType => 5);
1739
1754
  # also decode 'gpmd' chunk from Kingslim D4 dashcam videos
1740
1755
  # 0000: 0a 00 00 00 0b 00 00 00 07 00 00 00 e5 07 00 00 [................]
1741
1756
  # 0010: 06 00 00 00 03 00 00 00 41 4e 57 31 91 52 83 45 [........ANW1.R.E]
@@ -1762,7 +1777,7 @@ sub ProcessFreeGPS($$$)
1762
1777
 
1763
1778
  } elsif ($$dataPt =~ /^.{60}A\0{3}.{4}([NS])\0{3}.{4}([EW])\0{3}/s) {
1764
1779
 
1765
- $debug and $et->FoundTag(GPSType => 5);
1780
+ $debug and $et->FoundTag(GPSType => 6);
1766
1781
  # decode freeGPS from Akaso dashcam
1767
1782
  # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 60 00 00 00 [....freeGPS `...]
1768
1783
  # 0010: 78 2e 78 78 00 00 00 00 00 00 00 00 00 00 00 00 [x.xx............]
@@ -1796,7 +1811,7 @@ sub ProcessFreeGPS($$$)
1796
1811
 
1797
1812
  } elsif ($$dataPt =~ /^.{60}4W`b]S</s and length($$dataPt) >= 140) {
1798
1813
 
1799
- $debug and $et->FoundTag(GPSType => 6);
1814
+ $debug and $et->FoundTag(GPSType => 7);
1800
1815
  # 0000: 00 00 40 00 66 72 65 65 47 50 53 20 f0 01 00 00 [..@.freeGPS ....]
1801
1816
  # 0010: 5a 58 53 42 4e 58 59 53 00 00 00 00 00 00 00 00 [ZXSBNXYS........]
1802
1817
  # 0020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
@@ -1817,7 +1832,7 @@ sub ProcessFreeGPS($$$)
1817
1832
 
1818
1833
  } elsif ($$dataPt =~ /^.{64}[\x01-\x0c]\0{3}[\x01-\x1f]\0{3}A[NS][EW]\0{5}/s) {
1819
1834
 
1820
- $debug and $et->FoundTag(GPSType => 7);
1835
+ $debug and $et->FoundTag(GPSType => 8);
1821
1836
  # Akaso V1 dascham
1822
1837
  # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 78 00 00 00 [....freeGPS x...]
1823
1838
  # 0010: 59 6e 64 41 6b 61 73 6f 43 61 72 00 00 00 00 00 [YndAkasoCar.....]
@@ -1854,7 +1869,7 @@ sub ProcessFreeGPS($$$)
1854
1869
 
1855
1870
  } elsif ($$dataPt =~ /^.{12}\xac\0\0\0.{44}(.{72})/s) {
1856
1871
 
1857
- $debug and $et->FoundTag(GPSType => 8);
1872
+ $debug and $et->FoundTag(GPSType => 9);
1858
1873
  # EACHPAI dash cam
1859
1874
  # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 ac 00 00 00 [....freeGPS ....]
1860
1875
  # 0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
@@ -1878,7 +1893,7 @@ sub ProcessFreeGPS($$$)
1878
1893
 
1879
1894
  } elsif ($$dataPt =~ /^.{64}A([NS])([EW])\0/s) {
1880
1895
 
1881
- $debug and $et->FoundTag(GPSType => 9);
1896
+ $debug and $et->FoundTag(GPSType => 10);
1882
1897
  # Vantrue S1 dashcam
1883
1898
  # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 78 00 00 00 [....freeGPS x...]
1884
1899
  # 0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
@@ -1904,7 +1919,7 @@ sub ProcessFreeGPS($$$)
1904
1919
 
1905
1920
  } elsif (substr($$dataPt,0x45,3) eq 'ATC') {
1906
1921
 
1907
- $debug and $et->FoundTag(GPSType => 10);
1922
+ $debug and $et->FoundTag(GPSType => 11);
1908
1923
  # header looks like this: (sample 1)
1909
1924
  # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 38 06 00 00 [....freeGPS 8...]
1910
1925
  # 0010: 49 51 53 32 30 31 33 30 33 30 36 42 00 00 00 00 [IQS20130306B....]
@@ -2017,7 +2032,7 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
2017
2032
 
2018
2033
  } elsif ($$dataPt =~ /^.{60}A\0.{10}([NS])\0.{14}([EW])\0/s and $dirLen >= 0x88) {
2019
2034
 
2020
- $debug and $et->FoundTag(GPSType => 11);
2035
+ $debug and $et->FoundTag(GPSType => 12);
2021
2036
  # header looks like this in my sample:
2022
2037
  # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 08 01 00 00 [....freeGPS ....]
2023
2038
  # 0010: 32 30 31 33 30 38 31 35 2e 30 31 00 00 00 00 00 [20130815.01.....]
@@ -2048,9 +2063,9 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
2048
2063
 
2049
2064
  } elsif ($$dataPt =~ /^.{16}A([NS])([EW])\0/s) {
2050
2065
 
2051
- $debug and $et->FoundTag(GPSType => 12);
2066
+ $debug and $et->FoundTag(GPSType => 13);
2052
2067
  # INNOVV MP4 video (same format as INNOVV TS)
2053
- while ($$dataPt =~ /(A[NS][EW]\0.{28})/g) {
2068
+ while ($$dataPt =~ /(A[NS][EW]\0.{28})/sg) {
2054
2069
  my $dat = $1;
2055
2070
  $lat = abs(GetFloat(\$dat, 4)); # (abs just to be safe)
2056
2071
  $lon = abs(GetFloat(\$dat, 8)); # (abs just to be safe)
@@ -2068,9 +2083,35 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
2068
2083
  SetByteOrder($oldOrder);
2069
2084
  return 1;
2070
2085
 
2086
+ } elsif ($$dataPt =~ /^.{24}A([NS])([EW])/s) {
2087
+
2088
+ $debug and $et->FoundTag(GPSType => 14);
2089
+ # XBHT motorcycle dashcam Model XB702
2090
+ # 0000: 00 00 40 00 66 72 65 65 47 50 53 20 f0 03 00 00 [..@.freeGPS ....]
2091
+ # 0010: 00 17 05 11 0d 25 18 00 41 4e 45 64 83 3f 00 00 [.....%..ANEd.?..]
2092
+ # 0020: 44 3d c5 02 48 6d ff 07 df 03 00 00 6b 00 00 00 [D=..Hm......k...]
2093
+ # 0030: 00 00 00 00 00 17 05 11 0d 25 18 01 41 4e 45 64 [.........%..ANEd]
2094
+ # 0040: 8b 3f 00 00 30 3d c5 02 50 6d ff 07 df 03 00 00 [.?..0=..Pm......]
2095
+ while ($$dataPt =~ /(.{8}A[NS][EW].{25})/sg) {
2096
+ my $dat = $1;
2097
+ my ($yr,$mon,$day,$hr,$min,$sec,$ss,$latRef,$lonRef,$lat,$lon,$spd) =
2098
+ unpack('xC7xCCx5VVx4v', $dat);
2099
+ $yr += 2000; $lat /= 1e4; $lon /= 1e4;
2100
+ $ss = 0 if $ss > 9; # (just in case)
2101
+ ConvertLatLon($lat, $lon);
2102
+ $$et{DOC_NUM} = ++$$et{DOC_COUNT};
2103
+ my $time = sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%.2d.%d',$yr,$mon,$day,$hr,$min,$sec,$ss);
2104
+ $et->HandleTag($tagTbl, GPSDateTime => $time);
2105
+ $et->HandleTag($tagTbl, GPSLatitude => $lat * ($latRef eq 'S' ? -1 : 1));
2106
+ $et->HandleTag($tagTbl, GPSLongitude => $lon * ($lonRef eq 'W' ? -1 : 1));
2107
+ $et->HandleTag($tagTbl, GPSSpeed => $spd);
2108
+ }
2109
+ SetByteOrder($oldOrder);
2110
+ return 1;
2111
+
2071
2112
  } elsif ($$dataPt =~ /^.{28}A.{11}([NS]).{15}([EW])/s) {
2072
2113
 
2073
- $debug and $et->FoundTag(GPSType => 13);
2114
+ $debug and $et->FoundTag(GPSType => 15);
2074
2115
  # Vantrue N4 dashcam
2075
2116
  # 0000: 00 00 40 00 66 72 65 65 47 50 53 20 f0 03 00 00 [..@.freeGPS ....]
2076
2117
  # 0010: 0d 00 00 00 16 00 00 00 1e 00 00 00 41 00 00 00 [............A...]
@@ -2127,7 +2168,7 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
2127
2168
  ($hr,$min,$sec,$yr,$mon,$day,$stat,$latRef,$lonRef) =
2128
2169
  unpack('x48V6a1a1a1x1V4', $$dataPt);
2129
2170
  if (substr($$dataPt, 16, 3) eq 'IQS') {
2130
- $debug and $et->FoundTag(GPSType => 14);
2171
+ $debug and $et->FoundTag(GPSType => 16);
2131
2172
  # Type 3b (ref PH)
2132
2173
  # header looks like this in my sample:
2133
2174
  # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 4c 00 00 00 [....freeGPS L...]
@@ -2139,7 +2180,7 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
2139
2180
  $spd = Get32s($dataPt, 0x54) / 100 * $mpsToKph;
2140
2181
  $alt = GetFloat($dataPt, 0x58) / 1000; # (NC)
2141
2182
  } else {
2142
- $debug and $et->FoundTag(GPSType => 15);
2183
+ $debug and $et->FoundTag(GPSType => 17);
2143
2184
  $lat = GetFloat($dataPt, 0x4c);
2144
2185
  $lon = GetFloat($dataPt, 0x50);
2145
2186
  $spd = GetFloat($dataPt, 0x54) * $knotsToKph;
@@ -2159,7 +2200,7 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
2159
2200
 
2160
2201
  } elsif ($$dataPt =~ m<^.{23}(\d{4})/(\d{2})/(\d{2}) (\d{2}):(\d{2}):(\d{2}) [N|S]>s) {
2161
2202
 
2162
- $debug and $et->FoundTag(GPSType => 16);
2203
+ $debug and $et->FoundTag(GPSType => 18);
2163
2204
  # XGODY 12" 4K Dashcam
2164
2205
  # 0000: 00 00 00 a8 66 72 65 65 47 50 53 20 98 00 00 00 [....freeGPS ....]
2165
2206
  # 0010: 6e 6f 72 6d 61 6c 3a 32 30 32 34 2f 30 35 2f 32 [normal:2024/05/2]
@@ -2191,7 +2232,7 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
2191
2232
 
2192
2233
  } elsif ($$dataPt =~ m/^.{30}A.{20}VV/) {
2193
2234
 
2194
- $debug and $et->FoundTag(GPSType => 17);
2235
+ $debug and $et->FoundTag(GPSType => 19);
2195
2236
  # 70mai A810 dashcam (note: no timestamps in the samples I have)
2196
2237
  # 0000: 00 00 40 00 66 72 65 65 47 50 53 20 ed 01 00 00 [..@.freeGPS ....]
2197
2238
  # 0010: 03 00 ed 01 00 00 00 0f 00 00 70 08 00 00 41 66 [..........p...Af]
@@ -2208,7 +2249,7 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
2208
2249
 
2209
2250
  } else {
2210
2251
 
2211
- $debug and $et->FoundTag(GPSType => 18);
2252
+ $debug and $et->FoundTag(GPSType => 20);
2212
2253
  # (look for binary GPS as stored by Nextbase 512G, ref PH)
2213
2254
  # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 78 01 00 00 [....freeGPS x...]
2214
2255
  # 0010: 78 2e 78 78 00 00 00 00 00 00 00 00 00 00 00 00 [x.xx............]
@@ -34,7 +34,7 @@ use Image::ExifTool qw(:DataAccess :Utils);
34
34
  use Image::ExifTool::Exif;
35
35
  use Image::ExifTool::Minolta;
36
36
 
37
- $VERSION = '3.70';
37
+ $VERSION = '3.71';
38
38
 
39
39
  sub ProcessSRF($$$);
40
40
  sub ProcessSR2($$$);
@@ -2166,6 +2166,7 @@ my %hidUnk = ( Hidden => 1, Unknown => 1 );
2166
2166
  397 => 'ILCE-7CM2', #JR
2167
2167
  398 => 'ILX-LR1', #JR
2168
2168
  399 => 'ZV-E10M2', #JR
2169
+ 400 => 'ILCE-1M2', #PH
2169
2170
  },
2170
2171
  },
2171
2172
  0xb020 => { #2