exiftool-vendored.pl 12.73.0 → 12.78.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.
Files changed (70) hide show
  1. package/bin/Changes +94 -6
  2. package/bin/MANIFEST +4 -0
  3. package/bin/META.json +1 -1
  4. package/bin/META.yml +1 -1
  5. package/bin/README +46 -45
  6. package/bin/config_files/example.config +10 -2
  7. package/bin/exiftool +143 -94
  8. package/bin/lib/File/RandomAccess.pm +31 -5
  9. package/bin/lib/File/RandomAccess.pod +4 -4
  10. package/bin/lib/Image/ExifTool/7Z.pm +3 -3
  11. package/bin/lib/Image/ExifTool/AFCP.pm +2 -2
  12. package/bin/lib/Image/ExifTool/BZZ.pm +2 -2
  13. package/bin/lib/Image/ExifTool/BuildTagLookup.pm +7 -7
  14. package/bin/lib/Image/ExifTool/Canon.pm +17 -13
  15. package/bin/lib/Image/ExifTool/CanonVRD.pm +8 -2
  16. package/bin/lib/Image/ExifTool/DICOM.pm +2 -2
  17. package/bin/lib/Image/ExifTool/DNG.pm +4 -4
  18. package/bin/lib/Image/ExifTool/Exif.pm +54 -5
  19. package/bin/lib/Image/ExifTool/FLIR.pm +2 -2
  20. package/bin/lib/Image/ExifTool/Fixup.pm +3 -3
  21. package/bin/lib/Image/ExifTool/FlashPix.pm +3 -3
  22. package/bin/lib/Image/ExifTool/FujiFilm.pm +8 -3
  23. package/bin/lib/Image/ExifTool/GPS.pm +5 -3
  24. package/bin/lib/Image/ExifTool/Geolocation.dat +0 -0
  25. package/bin/lib/Image/ExifTool/Geolocation.pm +237 -0
  26. package/bin/lib/Image/ExifTool/Geotag.pm +4 -4
  27. package/bin/lib/Image/ExifTool/HtmlDump.pm +7 -4
  28. package/bin/lib/Image/ExifTool/ID3.pm +2 -2
  29. package/bin/lib/Image/ExifTool/Import.pm +9 -6
  30. package/bin/lib/Image/ExifTool/JSON.pm +11 -11
  31. package/bin/lib/Image/ExifTool/Jpeg2000.pm +51 -12
  32. package/bin/lib/Image/ExifTool/MIE.pm +3 -3
  33. package/bin/lib/Image/ExifTool/MWG.pm +1 -0
  34. package/bin/lib/Image/ExifTool/MacOS.pm +19 -4
  35. package/bin/lib/Image/ExifTool/MinoltaRaw.pm +2 -2
  36. package/bin/lib/Image/ExifTool/Nikon.pm +5 -3
  37. package/bin/lib/Image/ExifTool/NikonCustom.pm +3 -3
  38. package/bin/lib/Image/ExifTool/Ogg.pm +4 -3
  39. package/bin/lib/Image/ExifTool/Olympus.pm +3 -1
  40. package/bin/lib/Image/ExifTool/PDF.pm +59 -9
  41. package/bin/lib/Image/ExifTool/PLIST.pm +3 -3
  42. package/bin/lib/Image/ExifTool/PanasonicRaw.pm +3 -3
  43. package/bin/lib/Image/ExifTool/Pentax.pm +1 -1
  44. package/bin/lib/Image/ExifTool/PhaseOne.pm +2 -2
  45. package/bin/lib/Image/ExifTool/Photoshop.pm +3 -3
  46. package/bin/lib/Image/ExifTool/PostScript.pm +2 -2
  47. package/bin/lib/Image/ExifTool/QuickTime.pm +223 -117
  48. package/bin/lib/Image/ExifTool/QuickTimeStream.pl +260 -242
  49. package/bin/lib/Image/ExifTool/RSRC.pm +2 -2
  50. package/bin/lib/Image/ExifTool/Samsung.pm +4 -4
  51. package/bin/lib/Image/ExifTool/Shift.pl +1 -2
  52. package/bin/lib/Image/ExifTool/SigmaRaw.pm +3 -3
  53. package/bin/lib/Image/ExifTool/Sony.pm +3 -3
  54. package/bin/lib/Image/ExifTool/TagInfoXML.pm +2 -2
  55. package/bin/lib/Image/ExifTool/TagLookup.pm +85 -8
  56. package/bin/lib/Image/ExifTool/TagNames.pod +148 -8
  57. package/bin/lib/Image/ExifTool/WriteCanonRaw.pl +1 -1
  58. package/bin/lib/Image/ExifTool/WriteExif.pl +26 -23
  59. package/bin/lib/Image/ExifTool/WritePDF.pl +1 -1
  60. package/bin/lib/Image/ExifTool/WriteQuickTime.pl +15 -2
  61. package/bin/lib/Image/ExifTool/WriteXMP.pl +4 -2
  62. package/bin/lib/Image/ExifTool/Writer.pl +77 -52
  63. package/bin/lib/Image/ExifTool/XMP.pm +2 -1
  64. package/bin/lib/Image/ExifTool/XMP2.pl +9 -0
  65. package/bin/lib/Image/ExifTool/ZIP.pm +6 -6
  66. package/bin/lib/Image/ExifTool.pm +204 -63
  67. package/bin/lib/Image/ExifTool.pod +118 -94
  68. package/bin/perl-Image-ExifTool.spec +45 -44
  69. package/bin/pp_build_exe.args +4 -230
  70. package/package.json +4 -4
@@ -0,0 +1,237 @@
1
+ #------------------------------------------------------------------------------
2
+ # File: Geolocation.pm
3
+ #
4
+ # Description: Look up geolocation information based on a GPS position
5
+ #
6
+ # Revisions: 2024-03-03 - P. Harvey Created
7
+ #
8
+ # References: https://download.geonames.org/export/
9
+ #
10
+ # Notes: Set $Image::ExifTool::Geolocation::databaseFile to override
11
+ # default database file (lib/Image/ExifTool/Geolocation.dat)
12
+ #
13
+ # Based on data from geonames.org Creative Commons databases,
14
+ # reformatted as follows in the Geolocation.dat file:
15
+ #
16
+ # Header: GeolocationV.VV\tNNNN\n - V.VV=version, NNNN=num city entries
17
+ #
18
+ # NNNN City entries:
19
+ # 1. int16u[2] - longitude.latitude (converted to 0-64k range)
20
+ # 2. int8u - low byte of time zone number
21
+ # 3. int8u - 100's=time zone high bit, population: 10's=num zeros, 1's=sig digit
22
+ # 4. UTF8 City name, terminated by tab
23
+ # 5. 2-character country code
24
+ # 6. Region code, terminated by newline
25
+ # End of section marker - "\0\0\0\0\x01"
26
+ # Country entries:
27
+ # 1. 2-character country code
28
+ # 2. Country name, terminated by newline
29
+ # End of section marker - "\0\0\0\0\x02"
30
+ # Region entries:
31
+ # 1. 2-character country code
32
+ # 2. Region code, terminated by tab
33
+ # 3. Region name, terminated by newline
34
+ # End of section marker - "\0\0\0\0\x03"
35
+ # Time zone entries:
36
+ # 1. Time zone name, terminated by newline
37
+ # End of file marker - "\0\0\0\0\0"
38
+ #------------------------------------------------------------------------------
39
+
40
+ package Image::ExifTool::Geolocation;
41
+
42
+ use strict;
43
+ use vars qw($VERSION $databaseFile);
44
+
45
+ $VERSION = '1.00';
46
+
47
+ my (@cityLookup, %countryLookup, %adminLookup, @timezoneLookup);
48
+
49
+ # get path name for database file from lib/Image/ExifTool/Geolocation.dat by default,
50
+ # or according to $Image::ExifTool::Geolocation::databaseFile if specified
51
+ my $datfile = $databaseFile;
52
+ unless ($datfile) {
53
+ $datfile = $INC{'Image/ExifTool/Geolocation.pm'};
54
+ $datfile or $datfile = 'Geolocation.pm', warn("Error getting Geolocation directory\n");
55
+ $datfile =~ s/\.pm$/\.dat/;
56
+ }
57
+
58
+ # open geolocation database and verify header
59
+ open DATFILE, "<$datfile" or warn("Error reading $datfile\n"), return 0;
60
+ binmode DATFILE;
61
+ my $line = <DATFILE>;
62
+ unless ($line =~ /^Geolocation(\d+\.\d+)\t(\d+)/) {
63
+ warn("Bad format Geolocation database\n");
64
+ close(DATFILE);
65
+ return 0;
66
+ }
67
+ my $ncity = $2;
68
+
69
+ # read city database
70
+ for (;;) {
71
+ $line = <DATFILE>;
72
+ last if length($line) == 6 and $line =~ /\0\0\0\0/;
73
+ $line .= <DATFILE> while length($line) < 7;
74
+ chomp $line;
75
+ push @cityLookup, $line;
76
+ }
77
+ @cityLookup == $ncity or warn("Bad number of entries in Geolocation database\n"), return 0;
78
+ # read countries
79
+ for (;;) {
80
+ $line = <DATFILE>;
81
+ last if length($line) == 6 and $line =~ /\0\0\0\0/;
82
+ chomp $line;
83
+ $countryLookup{substr($line,0,2)} = substr($line,2);
84
+ }
85
+ # read regions
86
+ for (;;) {
87
+ $line = <DATFILE>;
88
+ last if length($line) == 6 and $line =~ /\0\0\0\0/;
89
+ chomp $line;
90
+ my ($code, $region) = split /\t/, $line;
91
+ $adminLookup{$code} = $region;
92
+ }
93
+ # read time zones
94
+ for (;;) {
95
+ $line = <DATFILE>;
96
+ last if length($line) == 6 and $line =~ /\0\0\0\0/;
97
+ chomp $line;
98
+ push @timezoneLookup, $line;
99
+ }
100
+ close DATFILE;
101
+
102
+ #------------------------------------------------------------------------------
103
+ # Look up lat/lon in geolocation database
104
+ # Inputs: 0) Latitude, 1) longitude, 2) optional min population,
105
+ # 3) optional max distance (km)
106
+ # Returns: 0) UTF8 city name (or undef if geolocation is unsuccessful),
107
+ # 1) UTF8 state, province or region (or undef),
108
+ # 2) country code, 3) country name (undef is possible),
109
+ # 4) time zone name (empty string possible), 5) approx population,
110
+ # 6) approx distance (km), 7) approximate compass bearing (or undef),
111
+ # 8/9) approx lat/lon
112
+ sub Geolocate($$;$$)
113
+ {
114
+ my ($lat, $lon, $pop, $km) = @_;
115
+ my ($minPop, $maxDist2);
116
+ my $earthCirc = 40000; # earth circumference in km
117
+
118
+ if ($pop) {
119
+ # convert population minimum to a 2-digit code
120
+ my $dig = substr($pop, 0, 1);
121
+ my $zer = length($pop) - 1;
122
+ # round up if necessary
123
+ if (length($pop) > 1 and substr($pop, 1, 1) >= 5) {
124
+ ++$dig > 9 and $dig = 1, ++$zer;
125
+ }
126
+ $minPop = $zer.$dig;
127
+ }
128
+ if ($km) {
129
+ # convert max distance to reduced coordinate units
130
+ my $tmp = $km * 2 * 65536 / $earthCirc;
131
+ $maxDist2 = $tmp * $tmp;
132
+ }
133
+ my $cos = cos($lat * 3.14159 / 180); # cosine factor for longitude distances
134
+ # reduce lat/lon to the range 0-65536
135
+ $lat = int(($lat + 90) / 180 * 65536 + 0.5) & 0xffff;
136
+ $lon = int(($lon + 180) / 360 * 65536 + 0.5) & 0xffff;
137
+ my $coord = pack('n2',$lon,$lat); # pack for comparison with binary database values
138
+ # binary search to find closest longitude
139
+ my ($n0, $n1) = (0, scalar(@cityLookup)-1);
140
+ while ($n1 - $n0 > 1) {
141
+ my $n = int(($n0 + $n1) / 2);
142
+ if ($coord lt $cityLookup[$n]) {
143
+ $n1 = $n;
144
+ } else {
145
+ $n0 = $n;
146
+ }
147
+ }
148
+ # step backward then forward through database to find nearest city
149
+ my ($minDist2, $minN, @dxy);
150
+ my ($inc, $end, $n) = (-1, -1, $n0+1);
151
+ for (;;) {
152
+ if (($n += $inc) == $end) {
153
+ last if $inc == 1;
154
+ ($inc, $end, $n) = (1, scalar(@cityLookup), $n1);
155
+ }
156
+ my ($x,$y) = unpack('n2', $cityLookup[$n]);
157
+ my ($dy,$dx) = ($y-$lat, $x-$lon);
158
+ $dx += 65536 if $dx < -32768; # measure the short way around the world
159
+ $dx -= 65536 if $dx > 32768;
160
+ $dx = 2 * $cos * $dx; # adjust for longitude spacing
161
+ my $dx2 = $dx * $dx;
162
+ my $dist2 = $dy * $dy + $dx2;
163
+ if (defined $minDist2) {
164
+ # searched far enough if longitude alone is further than best distance
165
+ $dx2 > $minDist2 and $n = $end - $inc, next;
166
+ } elsif (defined $maxDist2) {
167
+ $dx2 > $maxDist2 and $n = $end - $inc, next;
168
+ next if $dist2 > $maxDist2; # ignore if distance is too great
169
+ }
170
+ # ignore if population is below threshold
171
+ next if $minPop and $minPop > unpack('x5C', $cityLookup[$n]) % 100;
172
+ if (not defined $minDist2 or $minDist2 > $dist2) {
173
+ $minDist2 = $dist2;
174
+ @dxy = ($dx, $dy);
175
+ $minN = $n;
176
+ }
177
+ }
178
+ return () unless defined $minN;
179
+
180
+ my ($ln,$lt,$tn,$pc) = unpack('n2C2', $cityLookup[$minN]);
181
+ my ($city, $code) = split /\t/, substr($cityLookup[$minN],6);
182
+ my $ctry = substr($code,0,2);
183
+ my $rgn = $adminLookup{$code};
184
+ my $po2 = substr($pc, -1) . (length($pc) > 1 ? '0' x substr($pc, -2, 1) : '');
185
+ $tn += 256 if $pc > 99;
186
+ my $be; # calculate bearing to geolocated city
187
+ if ($dxy[0] or $dxy[1]) {
188
+ $be = atan2($dxy[0],$dxy[1]) * 180 / 3.14159;
189
+ $be += 360 if $be < 0;
190
+ $be = int($be + 0.5);
191
+ }
192
+ $lt = sprintf('%.3f', $lt * 180 / 65536 - 90);
193
+ $ln = sprintf('%.3f', $ln * 360 / 65536 - 180);
194
+ $km = sprintf('%.1f', sqrt($minDist2) * $earthCirc / (2 * 65536));
195
+
196
+ return($city,$rgn,$ctry,$countryLookup{$ctry},$timezoneLookup[$tn],$po2,$km,$be,$lt,$ln);
197
+ }
198
+
199
+ __END__
200
+
201
+ =head1 NAME
202
+
203
+ Image::ExifTool::Geolocation - Look up geolocation based on GPS position
204
+
205
+ =head1 SYNOPSIS
206
+
207
+ This module is used by the Image::ExifTool Geolocation feature.
208
+
209
+ =head1 DESCRIPTION
210
+
211
+ This module contains the code to convert GPS coordinates to city, region,
212
+ country, time zone, etc. It uses a database derived from geonames.org,
213
+ modified to reduce the size as much as possible.
214
+
215
+ =head1 AUTHOR
216
+
217
+ Copyright 2003-2024, Phil Harvey (philharvey66 at gmail.com)
218
+
219
+ This library is free software; you can redistribute it and/or modify it
220
+ under the same terms as Perl itself. Geolocation.dat is based on data
221
+ from geonames.org with a Creative Commons license.
222
+
223
+ =head1 REFERENCES
224
+
225
+ =over 4
226
+
227
+ =item L<https://download.geonames.org/export/>
228
+
229
+ =back
230
+
231
+ =head1 SEE ALSO
232
+
233
+ L<Image::ExifTool(3pm)|Image::ExifTool>
234
+
235
+ =cut
236
+
237
+ 1; #end
@@ -29,7 +29,7 @@ use vars qw($VERSION);
29
29
  use Image::ExifTool qw(:Public);
30
30
  use Image::ExifTool::GPS;
31
31
 
32
- $VERSION = '1.73';
32
+ $VERSION = '1.74';
33
33
 
34
34
  sub JITTER() { return 2 } # maximum time jitter
35
35
 
@@ -174,7 +174,7 @@ sub LoadTrackLog($$;$)
174
174
  # $val is track file name
175
175
  if ($et->Open(\*EXIFTOOL_TRKFILE, $val)) {
176
176
  $trackFile = $val;
177
- $raf = new File::RandomAccess(\*EXIFTOOL_TRKFILE);
177
+ $raf = File::RandomAccess->new(\*EXIFTOOL_TRKFILE);
178
178
  unless ($raf->Read($_, 256)) {
179
179
  close EXIFTOOL_TRKFILE;
180
180
  return "Empty track file '${val}'";
@@ -202,7 +202,7 @@ sub LoadTrackLog($$;$)
202
202
  }
203
203
  unless ($from) {
204
204
  # set up RAF for reading log file in memory
205
- $raf = new File::RandomAccess(\$val);
205
+ $raf = File::RandomAccess->new(\$val);
206
206
  $from = 'data';
207
207
  }
208
208
 
@@ -1109,7 +1109,7 @@ sub SetGeoValues($$;$)
1109
1109
  $iExt = $i1;
1110
1110
  }
1111
1111
  if (abs($time - $tn) > $geoMaxExtSecs) {
1112
- $err or $err = 'Time is too far from nearest GPS fix'.' '.abs($time-$tn).' '.$geoMaxExtSecs;
1112
+ $err or $err = 'Time is too far from nearest GPS fix'.' '.abs($time-$tn).' > '.$geoMaxExtSecs;
1113
1113
  $et->VPrint(2, ' Nearest fix: ', PrintFixTime($tn), "\n") if $verbose > 2;
1114
1114
  $fix = { } if $$geotag{DateTimeOnly};
1115
1115
  } else {
@@ -13,7 +13,7 @@ use vars qw($VERSION);
13
13
  use Image::ExifTool; # only for FinishTiffDump()
14
14
  use Image::ExifTool::HTML qw(EscapeHTML);
15
15
 
16
- $VERSION = '1.39';
16
+ $VERSION = '1.42';
17
17
 
18
18
  sub DumpTable($$$;$$$$$$);
19
19
  sub Open($$$;@);
@@ -314,6 +314,8 @@ sub Print($$;$$$$$)
314
314
  $title = 'HtmlDump' unless $title;
315
315
  $level or $level = 0;
316
316
  my $tell = $raf->Tell();
317
+ $raf->Seek(0,2) or $$self{ERROR} = 'Seek error', return -1;
318
+ my $fileLen = $raf->Tell();
317
319
  my $pos = 0;
318
320
  my $dataEnd = $dataPos + ($dataPt ? length($$dataPt) : 0);
319
321
  # initialize member variables
@@ -352,6 +354,7 @@ sub Print($$;$$$$$)
352
354
  } else {
353
355
  last;
354
356
  }
357
+ $start = $fileLen if $start > $fileLen; # handle case of bad start offset
355
358
  my $len = $start - $pos;
356
359
  if ($len > 0 and not $wasUnused) {
357
360
  # we have a unused bytes before this data block
@@ -432,8 +435,7 @@ sub Print($$;$$$$$)
432
435
  {
433
436
  $err = $msg;
434
437
  # reset $len to the actual length of available data
435
- $raf->Seek(0, 2);
436
- $len = $raf->Tell() - $start;
438
+ $len = $fileLen - $start;
437
439
  $tip .= "<br>Error: Only $len bytes available!" if $tip;
438
440
  next;
439
441
  }
@@ -767,6 +769,7 @@ sub FinishTiffDump($$$)
767
769
  PreviewImageStart => 'PreviewImageLength',
768
770
  JpgFromRawStart => 'JpgFromRawLength',
769
771
  OtherImageStart => 'OtherImageLength',
772
+ PreviewJXLStart => 'PreviewJXLLength',
770
773
  ImageOffset => 'ImageByteCount',
771
774
  AlphaOffset => 'AlphaByteCount',
772
775
  MPImageStart => 'MPImageLength',
@@ -894,7 +897,7 @@ Image::ExifTool::HtmlDump - Dump information in hex to HTML page
894
897
  =head1 SYNOPSIS
895
898
 
896
899
  use Image::ExifTool::HtmlDump;
897
- my $dump = new Image::ExifTool::HtmlDump;
900
+ my $dump = Image::ExifTool::HtmlDump->new;
898
901
  $dump->Add($start, $size, $comment);
899
902
  $dump->Print($dumpInfo, $raf, $dataPt, $dataPos, $outfile);
900
903
 
@@ -18,7 +18,7 @@ use strict;
18
18
  use vars qw($VERSION);
19
19
  use Image::ExifTool qw(:DataAccess :Utils);
20
20
 
21
- $VERSION = '1.60';
21
+ $VERSION = '1.61';
22
22
 
23
23
  sub ProcessID3v2($$$);
24
24
  sub ProcessPrivate($$$);
@@ -1408,7 +1408,7 @@ sub ProcessID3($$)
1408
1408
  $$et{DoneID3} = 1;
1409
1409
 
1410
1410
  # allow this to be called with either RAF or DataPt
1411
- my $raf = $$dirInfo{RAF} || new File::RandomAccess($$dirInfo{DataPt});
1411
+ my $raf = $$dirInfo{RAF} || File::RandomAccess->new($$dirInfo{DataPt});
1412
1412
  my ($buff, %id3Header, %id3Trailer, $hBuff, $tBuff, $eBuff, $tagTablePtr);
1413
1413
  my $rtnVal = 0;
1414
1414
  my $hdrEnd = 0;
@@ -12,7 +12,7 @@ require Exporter;
12
12
 
13
13
  use vars qw($VERSION @ISA @EXPORT_OK);
14
14
 
15
- $VERSION = '1.10';
15
+ $VERSION = '1.12';
16
16
  @ISA = qw(Exporter);
17
17
  @EXPORT_OK = qw(ReadCSV ReadJSON);
18
18
 
@@ -38,13 +38,13 @@ sub ReadCSV($$;$$)
38
38
  $raf = $file;
39
39
  $file = 'CSV file';
40
40
  } elsif (ref $file eq 'GLOB') {
41
- $raf = new File::RandomAccess($file);
41
+ $raf = File::RandomAccess->new($file);
42
42
  $file = 'CSV file';
43
43
  } else {
44
44
  open CSVFILE, $file or return "Error opening CSV file '${file}'";
45
45
  binmode CSVFILE;
46
46
  $openedFile = 1;
47
- $raf = new File::RandomAccess(\*CSVFILE);
47
+ $raf = File::RandomAccess->new(\*CSVFILE);
48
48
  }
49
49
  $delim = ',' unless defined $delim;
50
50
  # set input record separator by first newline found in the file
@@ -238,7 +238,7 @@ Tok: for (;;) {
238
238
 
239
239
  #------------------------------------------------------------------------------
240
240
  # Read JSON file
241
- # Inputs: 0) JSON file name, file ref or RAF ref, 1) database hash ref,
241
+ # Inputs: 0) JSON file name, file ref, RAF ref or SCALAR ref, 1) database hash ref,
242
242
  # 2) flag to delete "-" tags, 3) character set
243
243
  # Returns: undef on success, or error string
244
244
  sub ReadJSON($$;$$)
@@ -253,13 +253,16 @@ sub ReadJSON($$;$$)
253
253
  $raf = $file;
254
254
  $file = 'JSON file';
255
255
  } elsif (ref $file eq 'GLOB') {
256
- $raf = new File::RandomAccess($file);
256
+ $raf = File::RandomAccess->new($file);
257
257
  $file = 'JSON file';
258
+ } elsif (ref $file eq 'SCALAR') {
259
+ $raf = File::RandomAccess->new($file);
260
+ $file = 'in memory';
258
261
  } else {
259
262
  open JSONFILE, $file or return "Error opening JSON file '${file}'";
260
263
  binmode JSONFILE;
261
264
  $openedFile = 1;
262
- $raf = new File::RandomAccess(\*JSONFILE);
265
+ $raf = File::RandomAccess->new(\*JSONFILE);
263
266
  }
264
267
  my $obj = ReadJSONObject($raf);
265
268
  close JSONFILE if $openedFile;
@@ -14,7 +14,7 @@ use vars qw($VERSION);
14
14
  use Image::ExifTool qw(:DataAccess :Utils);
15
15
  use Image::ExifTool::Import;
16
16
 
17
- $VERSION = '1.06';
17
+ $VERSION = '1.07';
18
18
 
19
19
  sub ProcessJSON($$);
20
20
  sub ProcessTag($$$$%);
@@ -60,15 +60,15 @@ sub FoundTag($$$$%)
60
60
  # avoid conflict with special table entries
61
61
  $tag .= '!' if $Image::ExifTool::specialTags{$tag};
62
62
 
63
- # use underline instead of colon if necessary in tag name
64
- $tag =~ s/([A-Z]):([A-Z]{2})/${1}_$2/g;
65
-
66
- AddTagToTable($tagTablePtr, $tag, {
67
- Name => Image::ExifTool::MakeTagName($tag),
68
- %flags,
69
- Temporary => 1,
70
- }) unless $$tagTablePtr{$tag};
71
-
63
+ unless ($$tagTablePtr{$tag}) {
64
+ my $name = $tag;
65
+ $name =~ tr/:/_/; # use underlines in place of colons in tag name
66
+ AddTagToTable($tagTablePtr, $tag, {
67
+ Name => Image::ExifTool::MakeTagName($name),
68
+ %flags,
69
+ Temporary => 1,
70
+ });
71
+ }
72
72
  $et->HandleTag($tagTablePtr, $tag, $val);
73
73
  }
74
74
 
@@ -120,7 +120,7 @@ sub ProcessJSON($$)
120
120
  my $buff = substr(${$$dirInfo{DataPt}}, $$dirInfo{DirStart}, $$dirInfo{DirLen});
121
121
  $dataPt = \$buff;
122
122
  }
123
- $raf = new File::RandomAccess($dataPt);
123
+ $raf = File::RandomAccess->new($dataPt);
124
124
  # extract as a block if requested
125
125
  my $blockName = $$dirInfo{BlockInfo} ? $$dirInfo{BlockInfo}{Name} : '';
126
126
  my $blockExtract = $et->Options('BlockExtract');
@@ -16,7 +16,7 @@ use strict;
16
16
  use vars qw($VERSION);
17
17
  use Image::ExifTool qw(:DataAccess :Utils);
18
18
 
19
- $VERSION = '1.37';
19
+ $VERSION = '1.39';
20
20
 
21
21
  sub ProcessJpeg2000Box($$$);
22
22
  sub ProcessJUMD($$$);
@@ -141,10 +141,13 @@ my %j2cMarker = (
141
141
  Authenticity Initiative) JUMBF (JPEG Universal Metadata Box Format) metdata
142
142
  is currently extracted from JPEG, PNG, TIFF-based (eg. TIFF, DNG),
143
143
  QuickTime-based (eg. MP4, MOV, HEIF, AVIF), RIFF-based (eg. WAV, AVI, WebP),
144
- GIF files and ID3v2 metadata. The suggested ExifTool command-line arguments
145
- for reading C2PA metadata are C<-jumbf:all -G3 -b -j -u -struct>. This
146
- metadata may be deleted from writable JPEG, PNG, WebP, TIFF-based, and
147
- QuickTime-based files by deleting the JUMBF group with C<-jumbf:all=>.
144
+ PDF, SVG and GIF files, and ID3v2 metadata. The suggested ExifTool
145
+ command-line arguments for reading C2PA metadata are C<-jumbf:all -G3 -b -j
146
+ -u -struct>. This metadata may be deleted from writable JPEG, PNG, WebP,
147
+ TIFF-based, and QuickTime-based files by deleting the JUMBF group with
148
+ C<-jumbf:all=>. The C2PA JUMBF metadata may be extracted as a block via the
149
+ JUMBF tag. See L<https://c2pa.org/specifications/> for the C2PA
150
+ specification.
148
151
  },
149
152
  #
150
153
  # NOTE: ONLY TAGS WITH "Format" DEFINED ARE EXTRACTED!
@@ -419,7 +422,7 @@ my %j2cMarker = (
419
422
  Flags => [ 'Binary', 'Protected' ],
420
423
  SubDirectory => { TagTable => 'Image::ExifTool::CBOR::Main' },
421
424
  },
422
- bfdb => { # used in JUMBF (see # (used when tag is renamed according to JUMDLabel)
425
+ bfdb => { # used in JUMBF
423
426
  Name => 'BinaryDataType',
424
427
  Notes => 'JUMBF, MIME type and optional file name',
425
428
  Format => 'undef',
@@ -748,6 +751,7 @@ my %j2cMarker = (
748
751
  },
749
752
  # seen:
750
753
  # cacb/cast/caas/cacl/casg/json-00110010800000aa00389b71
754
+ # (also brob- but not yet tested)
751
755
  # 6579d6fbdba2446bb2ac1b82feeb89d1 - JPEG image
752
756
  },
753
757
  'label' => { Name => 'JUMDLabel' },
@@ -821,6 +825,7 @@ sub ProcessJUMD($$$)
821
825
  $name =~ tr/-_a-zA-Z0-9//dc; # remove other illegal characters
822
826
  $name =~ s/__/_/; # collapse double underlines
823
827
  $name = ucfirst $name; # capitalize first letter
828
+ $name =~ s/C2pa/C2PA/; # capitalize C2PA
824
829
  $name = "Tag$name" if length($name) < 2; # must at least 2 characters long
825
830
  $$et{JUMBFLabel} = $name;
826
831
  }
@@ -1017,11 +1022,23 @@ sub ProcessJpeg2000Box($$$)
1017
1022
  my $dirStart = $$dirInfo{DirStart} || 0;
1018
1023
  my $base = $$dirInfo{Base} || 0;
1019
1024
  my $outfile = $$dirInfo{OutFile};
1025
+ my $dirName = $$dirInfo{DirName} || '';
1020
1026
  my $dirEnd = $dirStart + $dirLen;
1021
1027
  my ($err, $outBuff, $verbose, $doColour, $hash, $raf);
1022
1028
 
1023
- # read from RAF unless reading from buffer
1024
- $raf = $$dirInfo{RAF} unless $dataPt;
1029
+ if ($dataPt) {
1030
+ # save C2PA JUMBF as a block if requested
1031
+ if ($dirName eq 'JUMBF' and $$et{REQ_TAG_LOOKUP}{jumbf} and not $$dirInfo{NoBlockSave}) {
1032
+ if ($dirStart or $dirLen ne length($$dataPt)) {
1033
+ my $dat = substr($$dataPt, $dirStart, $dirLen);
1034
+ $et->FoundTag(JUMBF => \$dat);
1035
+ } else {
1036
+ $et->FoundTag(JUMBF => $dataPt);
1037
+ }
1038
+ }
1039
+ } else {
1040
+ $raf = $$dirInfo{RAF}; # read from RAF
1041
+ }
1025
1042
 
1026
1043
  if ($outfile) {
1027
1044
  unless ($raf) {
@@ -1030,7 +1047,7 @@ sub ProcessJpeg2000Box($$$)
1030
1047
  $outfile = \$outBuff;
1031
1048
  }
1032
1049
  # determine if we will be writing colr box
1033
- if ($$dirInfo{DirName} and $$dirInfo{DirName} eq 'JP2Header') {
1050
+ if ($dirName eq 'JP2Header') {
1034
1051
  $doColour = 2 if defined $et->GetNewValue('ColorSpecMethod') or $et->GetNewValue('ICC_Profile') or
1035
1052
  defined $et->GetNewValue('ColorSpecPrecedence') or defined $et->GetNewValue('ColorSpace') or
1036
1053
  defined $et->GetNewValue('ColorSpecApproximation') or defined $et->GetNewValue('ColorSpecData');
@@ -1038,7 +1055,7 @@ sub ProcessJpeg2000Box($$$)
1038
1055
  } else {
1039
1056
  # (must not set verbose flag when writing!)
1040
1057
  $verbose = $$et{OPTIONS}{Verbose};
1041
- $et->VerboseDir($$dirInfo{DirName}) if $verbose;
1058
+ $et->VerboseDir($dirName) if $verbose;
1042
1059
  # do hash if requested, but only for top-level image data
1043
1060
  $hash = $$et{ImageDataHash} if $raf;
1044
1061
  }
@@ -1187,7 +1204,7 @@ sub ProcessJpeg2000Box($$$)
1187
1204
  # create new tag for JUMBF data values with name corresponding to JUMBFLabel
1188
1205
  if ($tagInfo and $$et{JUMBFLabel} and (not $$tagInfo{SubDirectory} or $$tagInfo{BlockExtract})) {
1189
1206
  $tagInfo = { %$tagInfo, Name => $$et{JUMBFLabel} . ($$tagInfo{JUMBF_Suffix} || '') };
1190
- delete $$tagInfo{Description};
1207
+ ($$tagInfo{Description} = Image::ExifTool::MakeDescription($$tagInfo{Name})) =~ s/C2 PA/C2PA/;
1191
1208
  AddTagToTable($tagTablePtr, '_JUMBF_' . $$et{JUMBFLabel}, $tagInfo);
1192
1209
  delete $$tagInfo{Protected}; # (must do this so -j -b returns JUMBF binary data)
1193
1210
  $$tagInfo{TagID} = $boxID;
@@ -1491,6 +1508,28 @@ sub ProcessJXLCodestream($$)
1491
1508
  return 1;
1492
1509
  }
1493
1510
 
1511
+ #------------------------------------------------------------------------------
1512
+ # Read/write meta information from a C2PA/JUMBF file
1513
+ # Inputs: 0) ExifTool object reference, 1) dirInfo reference
1514
+ # Returns: 1 on success, 0 if this wasn't a valid JUMBF file
1515
+ sub ProcessJUMBF($$)
1516
+ {
1517
+ my ($et, $dirInfo) = @_;
1518
+ my $raf = $$dirInfo{RAF};
1519
+ my $hdr;
1520
+
1521
+ # check to be sure this is a valid JPG2000 file
1522
+ return 0 unless $raf->Read($hdr,20) == 20 and $raf->Seek(0,0);
1523
+ return 0 unless $hdr =~ /^.{4}jumb\0.{3}jumd(.{4})/;
1524
+ $et->SetFileType($1 eq 'c2pa' ? 'C2PA' : 'JUMBF');
1525
+ my %dirInfo = (
1526
+ RAF => $raf,
1527
+ DirName => 'JUMBF',
1528
+ );
1529
+ my $tagTablePtr = GetTagTable('Image::ExifTool::Jpeg2000::Main');
1530
+ return $et->ProcessDirectory(\%dirInfo, $tagTablePtr);
1531
+ }
1532
+
1494
1533
  #------------------------------------------------------------------------------
1495
1534
  # Read/write meta information from a JPEG 2000 image
1496
1535
  # Inputs: 0) ExifTool object reference, 1) dirInfo reference
@@ -1583,7 +1622,7 @@ sub ProcessJXL($$)
1583
1622
  $$et{IsJXL} = 2;
1584
1623
  my $buff = "\0\0\0\x0cJXL \x0d\x0a\x87\x0a\0\0\0\x14ftypjxl \0\0\0\0jxl ";
1585
1624
  # add metadata to empty ISO BMFF container
1586
- $$dirInfo{RAF} = new File::RandomAccess(\$buff);
1625
+ $$dirInfo{RAF} = File::RandomAccess->new(\$buff);
1587
1626
  } else {
1588
1627
  $et->SetFileType('JXL Codestream','image/jxl', 'jxl');
1589
1628
  if ($$et{ImageDataHash} and $raf->Seek(0,0)) {
@@ -14,7 +14,7 @@ use Image::ExifTool qw(:DataAccess :Utils);
14
14
  use Image::ExifTool::Exif;
15
15
  use Image::ExifTool::GPS;
16
16
 
17
- $VERSION = '1.52';
17
+ $VERSION = '1.54';
18
18
 
19
19
  sub ProcessMIE($$);
20
20
  sub ProcessMIEGroup($$$);
@@ -1077,7 +1077,7 @@ sub WriteMIEGroup($$$)
1077
1077
  $newVal = '';
1078
1078
  %subdirInfo = (
1079
1079
  OutFile => \$newVal,
1080
- RAF => new File::RandomAccess(\$oldVal),
1080
+ RAF => File::RandomAccess->new(\$oldVal),
1081
1081
  );
1082
1082
  } elsif ($optCompress and not $$dirInfo{IsCompressed}) {
1083
1083
  # write to memory so we can compress the new MIE group
@@ -1585,7 +1585,7 @@ sub ProcessMIEGroup($$$)
1585
1585
  WasCompressed => $wasCompressed,
1586
1586
  );
1587
1587
  # read from uncompressed data instead if necessary
1588
- $subdirInfo{RAF} = new File::RandomAccess(\$value) if $valLen;
1588
+ $subdirInfo{RAF} = File::RandomAccess->new(\$value) if $valLen;
1589
1589
 
1590
1590
  my $oldOrder = GetByteOrder();
1591
1591
  SetByteOrder($format & 0x08 ? 'II' : 'MM');
@@ -441,6 +441,7 @@ my %sRegionStruct = (
441
441
  Writable => 'real',
442
442
  Notes => 'not part of MWG 2.0 spec',
443
443
  },
444
+ # Title - seen in sample XMP of MWG 2.0 specification, but not in spec itself
444
445
  seeAlso => { Namespace => 'rdfs', Resource => 1 },
445
446
  );
446
447
  my %sKeywordStruct;
@@ -12,7 +12,7 @@ use strict;
12
12
  use vars qw($VERSION);
13
13
  use Image::ExifTool qw(:DataAccess :Utils);
14
14
 
15
- $VERSION = '1.12';
15
+ $VERSION = '1.13';
16
16
 
17
17
  sub MDItemLocalTime($);
18
18
  sub ProcessATTR($$$);
@@ -22,6 +22,11 @@ my %mdDateInfo = (
22
22
  PrintConv => '$self->ConvertDateTime($val)',
23
23
  );
24
24
 
25
+ my %delXAttr = (
26
+ XAttrQuarantine => 'com.apple.quarantine',
27
+ XAttrMDItemWhereFroms => 'com.apple.metadata:kMDItemWhereFroms',
28
+ );
29
+
25
30
  # Information decoded from Mac OS sidecar files
26
31
  %Image::ExifTool::MacOS::Main = (
27
32
  GROUPS => { 0 => 'File', 1 => 'MacOS' },
@@ -320,7 +325,17 @@ my %mdDateInfo = (
320
325
  Groups => { 2 => 'Time' },
321
326
  },
322
327
  'com.apple.metadata:kMDItemFinderComment' => { Name => 'XAttrMDItemFinderComment' },
323
- 'com.apple.metadata:kMDItemWhereFroms' => { Name => 'XAttrMDItemWhereFroms' },
328
+ 'com.apple.metadata:kMDItemWhereFroms' => {
329
+ Name => 'XAttrMDItemWhereFroms',
330
+ Writable => 1,
331
+ WritePseudo => 1,
332
+ WriteCheck => '"May only delete this tag"',
333
+ Protected => 1,
334
+ Notes => q{
335
+ information about where the file came from. May only be deleted when
336
+ writing
337
+ },
338
+ },
324
339
  'com.apple.metadata:kMDLabel' => { Name => 'XAttrMDLabel', Binary => 1 },
325
340
  'com.apple.ResourceFork' => { Name => 'XAttrResourceFork', Binary => 1 },
326
341
  'com.apple.lastuseddate#PS' => {
@@ -407,9 +422,9 @@ sub SetMacOSTags($$$)
407
422
  $et->VPrint(1," - $tag = (all)\n") if $overwrite > 0;
408
423
  undef $val if $val eq '';
409
424
  }
410
- } elsif ($tag eq 'XAttrQuarantine') {
425
+ } elsif ($delXAttr{$tag}) {
411
426
  ($f = $file) =~ s/'/'\\''/g;
412
- $cmd = "/usr/bin/xattr -d com.apple.quarantine '${f}'";
427
+ $cmd = "/usr/bin/xattr -d $delXAttr{$tag} '${f}'";
413
428
  $silentErr = 256; # (will get this error if attribute doesn't exist)
414
429
  } else {
415
430
  ($f = $file) =~ s/(["\\])/\\$1/g; # escape necessary characters for script