exiftool_vendored 13.04.0 → 13.06.0

Sign up to get free protection for your applications and to get access to all the features.
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