exiftool_vendored 13.04.0 → 13.06.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 374e7e5a3d68eeca0ec6cb6a40919d056e0731958606daa8bed45a7c4c70daba
4
- data.tar.gz: 1263e0b2b35852265b4d5352eae7f35d12cffe0277f916799a8498c75482a9be
3
+ metadata.gz: 277daacb61b45f8ffcf01c45e8a2ae6c7814aa878af57ce5e700433e37a73a89
4
+ data.tar.gz: c27dc0342c61725b98adb52806417396827df205172ae6793f14bb6eb9d3949b
5
5
  SHA512:
6
- metadata.gz: 7b5d47b1383f939d76af17239fb4f1e1a486ae619b0d728a010bde7e8ed044de0bfc93d755f8c0a22dfa098ddf997d84f1d7a3f562f07153d1486eee68744929
7
- data.tar.gz: b8ba0c1b6c02546df4e54c6d5c4fe072cbb98bc4b6c5b1d0f1ac323ded4ca43e06ff5bcaaaf879005d37533df86f53262cba0dd87d1a09684f360a50a6d4834c
6
+ metadata.gz: acb79bdb9f29cb00439ad4006faba198b460abed0c655e1cce6d7a3845ec14c8fe19a778dcbdb5401bb5f77b51329329e9ab8a6932c98a9d04ff63ca04be9af0
7
+ data.tar.gz: 2771ca21af4d53e1d2138b4b94dd187f1e59c9f9c6230f8d3a8d83be61979b320570084a50f5ae5a3d038114a46cca518dca35e517d8304ed98087c73ceeb163
data/bin/Changes CHANGED
@@ -7,6 +7,26 @@ RSS feed: https://exiftool.org/rss.xml
7
7
  Note: The most recent production release is Version 13.00. (Other versions are
8
8
  considered development releases, and are not uploaded to MetaCPAN.)
9
9
 
10
+ Dec. 5, 2024 - Version 13.06
11
+
12
+ - Decode timed metadata from MP4 videos of yet another dashcam model
13
+ - Patched issue where FileSequence could increment twice for each file when a
14
+ -if condition was used
15
+ - API Changes:
16
+ - Revert default WindowsLongPath option until we can solve the pipe
17
+ problem)
18
+
19
+ Dec. 4, 2024 - Version 13.05
20
+
21
+ - Added a new SonyModelID
22
+ - Added support for XMP HDRGainMap and apdi namespaces
23
+ - Decode DJI timed djmd and dbgi protobuf-format metadata
24
+ - Decode APP10 AROT HDRGainCurve and APP2 URN UniformResourceName
25
+ - Decode a couple of new GoPro tags
26
+ - API Changes:
27
+ - Changed default WindowsLongPath option to 1 (please report if this
28
+ causes any problems)
29
+
10
30
  Nov. 26, 2024 - Version 13.04
11
31
 
12
32
  - Added the ability to write GPSDOP and GPSMeasureMode from the -geotag option
@@ -21,6 +41,8 @@ Nov. 26, 2024 - Version 13.04
21
41
  - Renamed an Unknown Photoshop tag
22
42
  - Convert GoPro GPSSpeed and GPSSpeed3D from m/s to km/h
23
43
  - Patched to tolerate XML header in DOCX xml files
44
+ - Fixed incorrect file offsets for tags in some embedded files of -htmldump
45
+ output
24
46
  - Fixed -htmldump output to show the same names for unknown EXIF tags as with
25
47
  the -u option
26
48
 
data/bin/MANIFEST CHANGED
@@ -397,6 +397,7 @@ lib/Image/ExifTool/PhotoMechanic.pm
397
397
  lib/Image/ExifTool/Photoshop.pm
398
398
  lib/Image/ExifTool/PostScript.pm
399
399
  lib/Image/ExifTool/PrintIM.pm
400
+ lib/Image/ExifTool/Protobuf.pm
400
401
  lib/Image/ExifTool/Qualcomm.pm
401
402
  lib/Image/ExifTool/QuickTime.pm
402
403
  lib/Image/ExifTool/QuickTimeStream.pl
data/bin/META.json CHANGED
@@ -50,5 +50,5 @@
50
50
  }
51
51
  },
52
52
  "release_status" : "stable",
53
- "version" : "13.04"
53
+ "version" : "13.06"
54
54
  }
data/bin/META.yml CHANGED
@@ -31,4 +31,4 @@ recommends:
31
31
  Time::HiRes: '0'
32
32
  requires:
33
33
  perl: '5.004'
34
- version: '13.04'
34
+ version: '13.06'
data/bin/README CHANGED
@@ -109,8 +109,8 @@ your home directory, then you would type the following commands in a
109
109
  terminal window to extract and run ExifTool:
110
110
 
111
111
  cd ~/Desktop
112
- gzip -dc Image-ExifTool-13.04.tar.gz | tar -xf -
113
- cd Image-ExifTool-13.04
112
+ gzip -dc Image-ExifTool-13.06.tar.gz | tar -xf -
113
+ cd Image-ExifTool-13.06
114
114
  ./exiftool t/images/ExifTool.jpg
115
115
 
116
116
  Note: These commands extract meta information from one of the test images.
data/bin/exiftool CHANGED
@@ -11,7 +11,7 @@ use strict;
11
11
  use warnings;
12
12
  require 5.004;
13
13
 
14
- my $version = '13.04';
14
+ my $version = '13.06';
15
15
 
16
16
  # add our 'lib' directory to the include list BEFORE 'use Image::ExifTool'
17
17
  my $exePath;
@@ -1918,7 +1918,7 @@ if (@dbKeys) {
1918
1918
  print $vout "Imported entry for '${_}' (full path: '${absPath}')\n";
1919
1919
  }
1920
1920
  } elsif ($verbose and $verbose > 1) {
1921
- print $vout "Imported entry for '${_}' (non-existent file)\n";
1921
+ print $vout "Imported entry for '${_}' (no full path)\n";
1922
1922
  }
1923
1923
  }
1924
1924
  }
@@ -2172,7 +2172,10 @@ sub GetImageInfo($$)
2172
2172
  }
2173
2173
  # can't make use of $info if verbose because we must reprocess
2174
2174
  # the file anyway to generate the verbose output
2175
- undef $info if $verbose or defined $fastCondition or defined $diff;
2175
+ if ($verbose or defined $fastCondition or defined $diff) {
2176
+ undef $info;
2177
+ --$$et{FILE_SEQUENCE};
2178
+ }
2176
2179
  } elsif ($file =~ s/^(\@JSON:)(.*)/$1/) {
2177
2180
  # read JSON file from command line
2178
2181
  my $dat = $2;
@@ -4163,11 +4166,12 @@ sub ScanDir($$;$)
4163
4166
  }
4164
4167
  $dir =~ /\/$/ or $dir .= '/'; # make sure directory name ends with '/'
4165
4168
  foreach $file (@fileList) {
4169
+ next if $file eq '.' or $file eq '..';
4166
4170
  my $path = "$dir$file";
4167
4171
  if ($et->IsDirectory($path)) {
4168
4172
  next unless $recurse;
4169
4173
  # ignore directories starting with "." by default
4170
- next if $file =~ /^\./ and ($recurse == 1 or $file eq '.' or $file eq '..');
4174
+ next if $file =~ /^\./ and $recurse == 1;
4171
4175
  next if $ignore{$file} or ($ignore{SYMLINKS} and -l $path);
4172
4176
  ScanDir($et, $path, $list);
4173
4177
  last if $end;
@@ -4315,14 +4319,15 @@ sub AbsPath($)
4315
4319
  {
4316
4320
  my $file = shift;
4317
4321
  my $path;
4318
- if (defined $file and eval { require Cwd }) {
4319
- $path = eval { Cwd::abs_path($file) };
4320
- # make the delimiters and case consistent
4321
- # (abs_path is very inconsistent about what it returns in Windows)
4322
- if (defined $path and Image::ExifTool::IsPC()) {
4323
- $path =~ tr/\\/\//;
4324
- $path = lc $path;
4322
+ if (defined $file) {
4323
+ return undef if $file eq '*'; # (CSV SourceFile may be '*' -- no absolute path for that)
4324
+ if ($^O eq 'MSWin32' and $mt->Options('WindowsLongPath')) {
4325
+ $path = $mt->WindowsLongPath($file);
4326
+ } elsif (eval { require Cwd }) {
4327
+ local $SIG{'__WARN__'} = sub { };
4328
+ $path = eval { Cwd::abs_path($file) };
4325
4329
  }
4330
+ $path =~ tr/\\/\// if $^O eq 'MSWin32' and defined $path; # use forward slashes
4326
4331
  }
4327
4332
  return $path;
4328
4333
  }
@@ -5907,7 +5912,7 @@ with this command:
5907
5912
 
5908
5913
  produces output like this:
5909
5914
 
5910
- -- Generated by ExifTool 13.04 --
5915
+ -- Generated by ExifTool 13.06 --
5911
5916
  File: a.jpg - 2003:10:31 15:44:19
5912
5917
  (f/5.6, 1/60s, ISO 100)
5913
5918
  File: b.jpg - 2006:05:23 11:57:38
@@ -6022,7 +6027,8 @@ import). May be combined with B<-s> to print tag names instead of
6022
6027
  descriptions, or B<-S> to print tag values only, tab-delimited on a single
6023
6028
  line. The B<-t> option may be combined with B<-j>, B<-php> or B<-X> to add
6024
6029
  tag table information (C<table>, tag C<id>, and C<index> for cases where
6025
- multiple conditional tags exist with the same ID).
6030
+ multiple conditional tags exist with the same ID), which allows the
6031
+ corresponding tag to be located in the B<-listx> output.
6026
6032
 
6027
6033
  =item B<-T> (B<-table>)
6028
6034
 
@@ -6781,14 +6787,14 @@ written (provided they can be calculated from the track log, and they are
6781
6787
  supported by the destination metadata format): GPSLatitude, GPSLatitudeRef,
6782
6788
  GPSLongitude, GPSLongitudeRef, GPSAltitude, GPSAltitudeRef, GPSDateStamp,
6783
6789
  GPSTimeStamp, GPSDateTime, GPSTrack, GPSTrackRef, GPSSpeed, GPSSpeedRef,
6784
- GPSImgDirection, GPSImgDirectionRef, GPSPitch, GPSRoll, GPSCoordinates,
6785
- AmbientTemperature and CameraElevationAngle. By default, in image files
6786
- tags are created in EXIF, and updated in XMP only if they already exist. In
6787
- QuickTime-format files GPSCoordinates is created in the preferred location
6788
- (ItemList by default) as well as in XMP. However, C<EXIF:Geotime>,
6789
- C<XMP:Geotime> or C<QuickTime:Geotime> may be specified to write to write
6790
- only to one group. Also, C<ItemList:Geotime>, C<Keys:Geotime> or
6791
- C<UserData:Geotime> may be used to write to a specific location in
6790
+ GPSImgDirection, GPSImgDirectionRef, GPSMeasureMode, GPSDOP, GPSPitch,
6791
+ GPSRoll, GPSCoordinates, AmbientTemperature and CameraElevationAngle. By
6792
+ default, in image files tags are created in EXIF, and updated in XMP only if
6793
+ they already exist. In QuickTime-format files GPSCoordinates is created in
6794
+ the preferred location (ItemList by default) as well as in XMP. However,
6795
+ C<EXIF:Geotime>, C<XMP:Geotime> or C<QuickTime:Geotime> may be specified to
6796
+ write to write only to one group. Also, C<ItemList:Geotime>, C<Keys:Geotime>
6797
+ or C<UserData:Geotime> may be used to write to a specific location in
6792
6798
  QuickTime-format files. Note that GPSPitch and GPSRoll are non-standard,
6793
6799
  and require user-defined tags in order to be written.
6794
6800
 
@@ -15,11 +15,11 @@ use Image::ExifTool qw(:DataAccess :Utils);
15
15
  use Image::ExifTool::Exif;
16
16
  use Image::ExifTool::XMP;
17
17
  use Image::ExifTool::GPS;
18
+ use Image::ExifTool::Protobuf;
18
19
 
19
- $VERSION = '1.09';
20
+ $VERSION = '1.10';
20
21
 
21
22
  sub ProcessDJIInfo($$$);
22
- sub Process_djmd($$$);
23
23
 
24
24
  my %convFloat2 = (
25
25
  PrintConv => 'sprintf("%+.2f", $val)',
@@ -187,34 +187,96 @@ my %convFloat2 = (
187
187
  },
188
188
  );
189
189
 
190
- # TODO - eventually add ability to decode this?
191
- %Image::ExifTool::DJI::djmd = (
192
- PROCESS_PROC => \&Process_djmd,
190
+ # metadata in protobuf format (djmd and dbgi meta types, ref PH)
191
+ %Image::ExifTool::DJI::Protobuf = (
192
+ GROUPS => { 0 => 'Protobuf', 1 => 'DJI', 2 => 'Location' },
193
+ TAG_PREFIX => '',
194
+ PROCESS_PROC => \&Image::ExifTool::Protobuf::ProcessProtobuf,
195
+ NOTES => q{
196
+ Tags found in protobuf-format DJI meta djmd and dbgi timed metadata. Only a
197
+ few tags are currently known, but unknown djmd tags may be extracted by
198
+ setting the Unknown option to 1 (or 2 to also extract unknown dbgi debug
199
+ tags). Tag ID's are composed of the corresponding .proto file name combined
200
+ with the hierarchical protobuf field numbers. The "dvtm_AVATA2.proto" file
201
+ is used by the DJI Avanta 2, and "dvtm_ac203.proto" by the OsmoAction4.
202
+ },
203
+ Protocol => { },
204
+ # dvtm_ac203_1-1-6 - some version number
205
+ 'dvtm_ac203_1-1-10' => 'Model',
206
+ 'dvtm_ac203_2-3-1' => { Name => 'FrameWidth', Format => 'unsigned' },
207
+ 'dvtm_ac203_2-3-2' => { Name => 'FrameHeight', Format => 'unsigned' },
208
+ 'dvtm_ac203_2-3-3' => { Name => 'FrameRate', Format => 'float' },
209
+ # dvtm_ac203_3-4-1-4 - model code?
210
+ 'dvtm_ac203_3-4-2-1-1' => {
211
+ Name => 'CoordinateUnits',
212
+ Format => 'unsigned',
213
+ # don't extract this -- just convert to degrees
214
+ RawConv => '$$self{CoordUnits} = $val; undef',
215
+ Hidden => 1,
216
+ # PrintConv => { 0 => 'Radians', 1 => 'Degrees' },
217
+ },
218
+ 'dvtm_ac203_3-4-2-1-2' => {
219
+ Name => 'GPSLatitude',
220
+ Format => 'double',
221
+ # set ExifTool GPSLatitude/GPSLongitude members so GPSDateTime will be generated if necessary
222
+ RawConv => '$$self{GPSLatitude} = $$self{CoordUnits} ? $val : $val * 180 / 3.141592653589793', # (NC)
223
+ PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")',
224
+ },
225
+ 'dvtm_ac203_3-4-2-1-3' => {
226
+ Name => 'GPSLongitude',
227
+ Format => 'double',
228
+ RawConv => '$$self{GPSLongitude} = $$self{CoordUnits} ? $val : $val * 180 / 3.141592653589793', # (NC)
229
+ PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")',
230
+ },
231
+ 'dvtm_ac203_3-4-2-2' => {
232
+ Name => 'GPSAltitude',
233
+ Format => 'unsigned',
234
+ ValueConv => '$val / 1000',
235
+ },
236
+ 'dvtm_ac203_3-4-2-6-1' => {
237
+ Name => 'GPSDateTime',
238
+ Format => 'string',
239
+ Groups => { 2 => 'Time' },
240
+ RawConv => '$$self{GPSDateTime} = $val',
241
+ ValueConv => '$val =~ tr/-/:/; $val',
242
+ PrintConv => '$self->ConvertDateTime($val)',
243
+ },
244
+ # dvtm_AVATA2_1-1-2 - some version number
245
+ # dvtm_AVATA2_1-1-3 - some version number
246
+ 'dvtm_AVATA2_1-1-10' => 'Model',
247
+ 'dvtm_AVATA2_2-2-3-1' => 'SerialNumber', # (NC)
248
+ 'dvtm_AVATA2_2-3-1' => { Name => 'FrameWidth', Format => 'unsigned' },
249
+ 'dvtm_AVATA2_2-3-2' => { Name => 'FrameHeight', Format => 'unsigned' },
250
+ 'dvtm_AVATA2_2-3-3' => { Name => 'FrameRate', Format => 'float' },
251
+ # dvtm_AVATA2_3-1-1 - frame number (starting at 1)
252
+ 'dvtm_AVATA2_3-1-2' => { # (also 3-2-1-6 and 3-4-1-6)
253
+ Name => 'TimeStamp',
254
+ Format => 'unsigned',
255
+ # milliseconds, but I don't know what the zero is
256
+ ValueConv => '$val / 1e6',
257
+ },
258
+ # dvtm_AVATA2_3-2-1-4 - model code?
259
+ # dvtm_AVATA2_3-4-1-4 - model code?
260
+ 'dvtm_AVATA2_3-4-4-1-1' => { # (NC) (default seems to be radians if missing)
261
+ Name => 'CoordinateDegrees',
262
+ Format => 'unsigned',
263
+ RawConv => '$$self{CoordDegrees} = $val; undef',
264
+ Hidden => 1,
265
+ },
266
+ 'dvtm_AVATA2_3-4-4-1-2' => {
267
+ Name => 'GPSLatitude',
268
+ Format => 'double',
269
+ RawConv => '$$self{GPSLatitude} = $$self{CoordDegrees} ? $val : $val * 180 / 3.141592653589793', # (NC)
270
+ PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")',
271
+ },
272
+ 'dvtm_AVATA2_3-4-4-1-3' => {
273
+ Name => 'GPSLongitude',
274
+ Format => 'double',
275
+ RawConv => '$$self{GPSLongitude} = $$self{CoordDegrees} ? $val : $val * 180 / 3.141592653589793', # (NC)
276
+ PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")',
277
+ },
193
278
  );
194
279
 
195
- #------------------------------------------------------------------------------
196
- # Process DJI djmd timed data from Action4 videos (ref PH)
197
- # Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
198
- # Returns: 1 on success
199
- # TODO: work in progress
200
- sub Process_djmd($$$)
201
- {
202
- my ($et, $dirInfo, $tagTbl) = @_;
203
- my $dataPt = $$dirInfo{DataPt};
204
- my ($pos, $bit, $val) = (6, 0, 0);
205
- for (;;) {
206
- my $v = Get8u($dataPt, $pos);
207
- $val += ($v & 0x7f) << $bit;
208
- last unless $v & 0x80;
209
- ++$pos;
210
- $bit += 7;
211
- }
212
- $pos += 49;
213
- my @a = unpack("x${pos}fxfxfxfx3fxfxf", $$dataPt);
214
- print "$val @a\n";
215
- return 1;
216
- }
217
-
218
280
  #------------------------------------------------------------------------------
219
281
  # Process DJI info (ref PH)
220
282
  # Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
@@ -18,7 +18,8 @@
18
18
  # specifying $Image::ExifTool::Geolocation::altDir. This
19
19
  # database and has entries in the same order as Geolocation.dat,
20
20
  # and each entry is a newline-separated list of alternate names
21
- # terminated by a null byte.
21
+ # terminated by a null byte. These alternate names are used
22
+ # only when searching for a city by name (eg. "Big Apple").
22
23
  #
23
24
  # Databases are based on data from geonames.org with a
24
25
  # Creative Commons license, reformatted as follows in the
@@ -16,7 +16,7 @@ use vars qw($VERSION);
16
16
  use Image::ExifTool qw(:DataAccess :Utils);
17
17
  use Image::ExifTool::QuickTime;
18
18
 
19
- $VERSION = '1.10';
19
+ $VERSION = '1.11';
20
20
 
21
21
  sub ProcessGoPro($$$);
22
22
  sub ProcessString($$$);
@@ -423,7 +423,7 @@ my %addUnits = (
423
423
  },
424
424
  },
425
425
  # VLTA (GPMF) - seen: 78 ('N') (fmt B -- wrong format?)
426
- # VFPS (GPMF) - seen: '24000 1001' (fmt L)
426
+ VFPS => { Name => 'VideoFrameRate', PrintConv => '$val=~s( )(/);$val' }, #PH (GPMF, fmt L)
427
427
  VFRH => { #PH (Karma)
428
428
  Name => 'VisualFlightRulesHUD',
429
429
  BinaryData => 1,
@@ -431,7 +431,7 @@ my %addUnits = (
431
431
  # TYPE=ffffsS
432
432
  },
433
433
  # VLTE (GPMF) - seen: 'Y','N' (fmt c)
434
- # VRES (GPMF) - seen: '3840 3660' (fmt L)
434
+ VRES => { Name => 'VideoFrameSize', PrintConv => '$val=~s/ /x/;$val' }, #PH (GPMF, fmt L)
435
435
  WBAL => 'ColorTemperatures', #PH (gpmd)
436
436
  WRGB => { #PH (gpmd)
437
437
  Name => 'WhiteBalanceRGB',
@@ -11,7 +11,7 @@ use strict;
11
11
  use vars qw($VERSION);
12
12
  use Image::ExifTool qw(:DataAccess :Utils);
13
13
 
14
- $VERSION = '1.37';
14
+ $VERSION = '1.38';
15
15
 
16
16
  sub ProcessOcad($$$);
17
17
  sub ProcessJPEG_HDR($$$);
@@ -87,6 +87,11 @@ sub ProcessJPEG_HDR($$$);
87
87
  Name => 'InfiRayVersion',
88
88
  Condition => '$$valPt =~ /^....IJPEG\0/s',
89
89
  SubDirectory => { TagTable => 'Image::ExifTool::InfiRay::Version' },
90
+ }, {
91
+ Name => 'UniformResourceName',
92
+ Groups => { 1 => 'APP2' },
93
+ Condition => '$$valPt =~ /^urn:/',
94
+ Notes => 'used in Apple HDR images',
90
95
  }, {
91
96
  Name => 'PreviewImage',
92
97
  Condition => '$$valPt =~ /^(|QVGA\0|BGTH)\xff\xd8\xff\xdb/',
@@ -246,16 +251,26 @@ sub ProcessJPEG_HDR($$$);
246
251
  Condition => '$$valPt =~ /^SEAL\0/',
247
252
  SubDirectory => { TagTable => 'Image::ExifTool::XMP::SEAL' },
248
253
  }],
249
- APP10 => {
254
+ APP10 => [{
250
255
  Name => 'Comment',
251
256
  Condition => '$$valPt =~ /^UNICODE\0/',
252
257
  Notes => 'PhotoStudio Unicode comment',
253
- },
258
+ }, {
259
+ Name => 'HDRGainCurve', #PH (NC)
260
+ Condition => '$$valPt =~ /^AROT\0\0.{4}/s',
261
+ Groups => { 1 => 'APP10', 2 => 'Image' },
262
+ ValueConv => q{
263
+ my $n = unpack('x6N', $val);
264
+ return '<truncated AROT data>' if length($val)-6 < $n * 4;
265
+ my $str = join ' ', unpack("x10V$n", $val);
266
+ return \$str;
267
+ },
268
+ }],
254
269
  APP11 => [{
255
270
  Name => 'JPEG-HDR',
256
271
  Condition => '$$valPt =~ /^HDR_RI /',
257
272
  SubDirectory => { TagTable => 'Image::ExifTool::JPEG::HDR' },
258
- },{
273
+ }, {
259
274
  Name => 'JUMBF',
260
275
  Condition => '$$valPt =~ /^JP/',
261
276
  SubDirectory => { TagTable => 'Image::ExifTool::Jpeg2000::Main' },
@@ -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