exiftool_vendored 12.17.1 → 12.32.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/Changes +225 -1
- data/bin/MANIFEST +23 -0
- data/bin/META.json +1 -1
- data/bin/META.yml +1 -1
- data/bin/README +45 -43
- data/bin/arg_files/xmp2exif.args +2 -1
- data/bin/config_files/acdsee.config +193 -6
- data/bin/config_files/convert_regions.config +25 -14
- data/bin/config_files/cuepointlist.config +70 -0
- data/bin/config_files/example.config +2 -9
- data/bin/exiftool +142 -87
- data/bin/fmt_files/gpx.fmt +2 -2
- data/bin/fmt_files/gpx_wpt.fmt +2 -2
- data/bin/fmt_files/kml.fmt +1 -1
- data/bin/fmt_files/kml_track.fmt +1 -1
- data/bin/lib/Image/ExifTool/Apple.pm +3 -2
- data/bin/lib/Image/ExifTool/BuildTagLookup.pm +30 -12
- data/bin/lib/Image/ExifTool/CBOR.pm +277 -0
- data/bin/lib/Image/ExifTool/Canon.pm +49 -18
- data/bin/lib/Image/ExifTool/DJI.pm +6 -6
- data/bin/lib/Image/ExifTool/DPX.pm +13 -2
- data/bin/lib/Image/ExifTool/DjVu.pm +6 -5
- data/bin/lib/Image/ExifTool/Exif.pm +28 -11
- data/bin/lib/Image/ExifTool/FITS.pm +13 -2
- data/bin/lib/Image/ExifTool/FlashPix.pm +35 -10
- data/bin/lib/Image/ExifTool/FujiFilm.pm +19 -8
- data/bin/lib/Image/ExifTool/GPS.pm +22 -11
- data/bin/lib/Image/ExifTool/Geotag.pm +13 -2
- data/bin/lib/Image/ExifTool/GoPro.pm +16 -1
- data/bin/lib/Image/ExifTool/ICC_Profile.pm +2 -2
- data/bin/lib/Image/ExifTool/ID3.pm +15 -3
- data/bin/lib/Image/ExifTool/JPEG.pm +74 -4
- data/bin/lib/Image/ExifTool/JSON.pm +27 -4
- data/bin/lib/Image/ExifTool/Jpeg2000.pm +393 -16
- data/bin/lib/Image/ExifTool/LIF.pm +153 -0
- data/bin/lib/Image/ExifTool/Lang/nl.pm +60 -59
- data/bin/lib/Image/ExifTool/M2TS.pm +137 -5
- data/bin/lib/Image/ExifTool/MIE.pm +4 -3
- data/bin/lib/Image/ExifTool/MRC.pm +341 -0
- data/bin/lib/Image/ExifTool/MWG.pm +3 -3
- data/bin/lib/Image/ExifTool/MXF.pm +1 -1
- data/bin/lib/Image/ExifTool/MacOS.pm +1 -1
- data/bin/lib/Image/ExifTool/Microsoft.pm +298 -82
- data/bin/lib/Image/ExifTool/Nikon.pm +19 -8
- data/bin/lib/Image/ExifTool/NikonSettings.pm +28 -11
- data/bin/lib/Image/ExifTool/Olympus.pm +6 -3
- data/bin/lib/Image/ExifTool/Other.pm +93 -0
- data/bin/lib/Image/ExifTool/PDF.pm +9 -12
- data/bin/lib/Image/ExifTool/PNG.pm +8 -7
- data/bin/lib/Image/ExifTool/Panasonic.pm +28 -3
- data/bin/lib/Image/ExifTool/Pentax.pm +28 -5
- data/bin/lib/Image/ExifTool/PhaseOne.pm +4 -3
- data/bin/lib/Image/ExifTool/Photoshop.pm +6 -0
- data/bin/lib/Image/ExifTool/QuickTime.pm +247 -88
- data/bin/lib/Image/ExifTool/QuickTimeStream.pl +283 -141
- data/bin/lib/Image/ExifTool/README +3 -0
- data/bin/lib/Image/ExifTool/RIFF.pm +89 -12
- data/bin/lib/Image/ExifTool/Samsung.pm +48 -10
- data/bin/lib/Image/ExifTool/Shortcuts.pm +9 -0
- data/bin/lib/Image/ExifTool/Sony.pm +237 -78
- data/bin/lib/Image/ExifTool/TagInfoXML.pm +1 -0
- data/bin/lib/Image/ExifTool/TagLookup.pm +4125 -4028
- data/bin/lib/Image/ExifTool/TagNames.pod +644 -286
- data/bin/lib/Image/ExifTool/Torrent.pm +18 -11
- data/bin/lib/Image/ExifTool/WriteExif.pl +1 -1
- data/bin/lib/Image/ExifTool/WriteIPTC.pl +1 -1
- data/bin/lib/Image/ExifTool/WritePDF.pl +1 -0
- data/bin/lib/Image/ExifTool/WritePNG.pl +2 -0
- data/bin/lib/Image/ExifTool/WritePostScript.pl +1 -0
- data/bin/lib/Image/ExifTool/WriteQuickTime.pl +55 -21
- data/bin/lib/Image/ExifTool/WriteXMP.pl +7 -3
- data/bin/lib/Image/ExifTool/Writer.pl +47 -10
- data/bin/lib/Image/ExifTool/XMP.pm +39 -14
- data/bin/lib/Image/ExifTool/XMP2.pl +2 -1
- data/bin/lib/Image/ExifTool/XMPStruct.pl +3 -1
- data/bin/lib/Image/ExifTool/ZISRAW.pm +121 -2
- data/bin/lib/Image/ExifTool.pm +223 -72
- data/bin/lib/Image/ExifTool.pod +114 -93
- data/bin/perl-Image-ExifTool.spec +43 -42
- data/lib/exiftool_vendored/version.rb +1 -1
- metadata +28 -13
@@ -13,10 +13,11 @@ package Image::ExifTool::Torrent;
|
|
13
13
|
use strict;
|
14
14
|
use vars qw($VERSION);
|
15
15
|
use Image::ExifTool qw(:DataAccess :Utils);
|
16
|
+
use Image::ExifTool::XMP;
|
16
17
|
|
17
|
-
$VERSION = '1.
|
18
|
+
$VERSION = '1.05';
|
18
19
|
|
19
|
-
sub ReadBencode(
|
20
|
+
sub ReadBencode($$$);
|
20
21
|
sub ExtractTags($$$;$$@);
|
21
22
|
|
22
23
|
# tags extracted from BitTorrent files
|
@@ -99,12 +100,12 @@ sub ReadMore($$)
|
|
99
100
|
|
100
101
|
#------------------------------------------------------------------------------
|
101
102
|
# Read bencoded value
|
102
|
-
# Inputs: 0) input file,
|
103
|
+
# Inputs: 0) ExifTool ref, 1) input file, 2) buffer (pos must be set to current position)
|
103
104
|
# Returns: HASH ref, ARRAY ref, SCALAR ref, SCALAR, or undef on error or end of data
|
104
105
|
# Notes: Sets BencodeError element of RAF on any error
|
105
|
-
sub ReadBencode(
|
106
|
+
sub ReadBencode($$$)
|
106
107
|
{
|
107
|
-
my ($raf, $dataPt) = @_;
|
108
|
+
my ($et, $raf, $dataPt) = @_;
|
108
109
|
|
109
110
|
# read more if necessary (keep a minimum of 64 bytes in the buffer)
|
110
111
|
my $pos = pos($$dataPt);
|
@@ -123,21 +124,21 @@ sub ReadBencode($$)
|
|
123
124
|
} elsif ($tok eq 'd') { # dictionary
|
124
125
|
$val = { };
|
125
126
|
for (;;) {
|
126
|
-
my $k = ReadBencode($raf, $dataPt);
|
127
|
+
my $k = ReadBencode($et, $raf, $dataPt);
|
127
128
|
last unless defined $k;
|
128
129
|
# the key must be a byte string
|
129
130
|
if (ref $k) {
|
130
131
|
ref $k ne 'SCALAR' and $$raf{BencodeError} = 'Bad dictionary key', last;
|
131
132
|
$k = $$k;
|
132
133
|
}
|
133
|
-
my $v = ReadBencode($raf, $dataPt);
|
134
|
+
my $v = ReadBencode($et, $raf, $dataPt);
|
134
135
|
last unless defined $v;
|
135
136
|
$$val{$k} = $v;
|
136
137
|
}
|
137
138
|
} elsif ($tok eq 'l') { # list
|
138
139
|
$val = [ ];
|
139
140
|
for (;;) {
|
140
|
-
my $v = ReadBencode($raf, $dataPt);
|
141
|
+
my $v = ReadBencode($et, $raf, $dataPt);
|
141
142
|
last unless defined $v;
|
142
143
|
push @$val, $v;
|
143
144
|
}
|
@@ -165,8 +166,14 @@ sub ReadBencode($$)
|
|
165
166
|
}
|
166
167
|
if (defined $value) {
|
167
168
|
# return as binary data unless it is a reasonable-length ASCII string
|
168
|
-
if (length($value) > 256
|
169
|
+
if (length($value) > 256) {
|
169
170
|
$val = \$value;
|
171
|
+
} elsif ($value =~ /[^\t\x20-\x7e]/) {
|
172
|
+
if (Image::ExifTool::XMP::IsUTF8(\$value) >= 0) {
|
173
|
+
$val = $et->Decode($value, 'UTF8');
|
174
|
+
} else {
|
175
|
+
$val = \$value;
|
176
|
+
}
|
170
177
|
} else {
|
171
178
|
$val = $value;
|
172
179
|
}
|
@@ -206,7 +213,7 @@ sub ExtractTags($$$;$$@)
|
|
206
213
|
my $tagInfo = $et->GetTagInfo($tagTablePtr, $id) or next;
|
207
214
|
if (ref $val eq 'ARRAY') {
|
208
215
|
if ($$tagInfo{JoinPath}) {
|
209
|
-
$val = join '/', @$val;
|
216
|
+
$val = join '/', map { ref $_ ? '(Binary data)' : $_ } @$val;
|
210
217
|
} else {
|
211
218
|
push @more, @$val;
|
212
219
|
next if ref $more[0] eq 'ARRAY'; # continue expanding nested lists
|
@@ -273,7 +280,7 @@ sub ProcessTorrent($$)
|
|
273
280
|
my $raf = $$dirInfo{RAF};
|
274
281
|
my $buff = '';
|
275
282
|
pos($buff) = 0;
|
276
|
-
my $dict = ReadBencode($raf, \$buff);
|
283
|
+
my $dict = ReadBencode($et, $raf, \$buff);
|
277
284
|
my $err = $$raf{BencodeError};
|
278
285
|
$et->Warn("Bencode error: $err") if $err;
|
279
286
|
if (ref $dict eq 'HASH' and ($$dict{announce} or $$dict{'created by'})) {
|
@@ -548,7 +548,7 @@ sub WriteExif($$$)
|
|
548
548
|
my $mustRead;
|
549
549
|
if ($dirStart < 0 or $dirStart > $dataLen-2) {
|
550
550
|
$mustRead = 1;
|
551
|
-
} elsif ($dirLen
|
551
|
+
} elsif ($dirLen >= 2) {
|
552
552
|
my $len = 2 + 12 * Get16u($dataPt, $dirStart);
|
553
553
|
$mustRead = 1 if $dirStart + $len > $dataLen;
|
554
554
|
}
|
@@ -170,7 +170,7 @@ sub FormatIPTC($$$$$;$)
|
|
170
170
|
} else {
|
171
171
|
my $len = int(($1 || 0) / 8);
|
172
172
|
if ($len == 1) { # 1 byte
|
173
|
-
$$valPtr = chr($$valPtr);
|
173
|
+
$$valPtr = chr($$valPtr & 0xff);
|
174
174
|
} elsif ($len == 2) { # 2-byte integer
|
175
175
|
$$valPtr = pack('n', $$valPtr);
|
176
176
|
} else { # 4-byte integer
|
@@ -293,6 +293,7 @@ sub WritePDF($$)
|
|
293
293
|
my $newTool = new Image::ExifTool;
|
294
294
|
$newTool->Options(List => 1);
|
295
295
|
$newTool->Options(Password => $et->Options('Password'));
|
296
|
+
$newTool->Options(NoPDFList => $et->Options('NoPDFList'));
|
296
297
|
$$newTool{PDF_CAPTURE} = \%capture;
|
297
298
|
my $info = $newTool->ImageInfo($raf, 'XMP', 'PDF:*', 'Error', 'Warning');
|
298
299
|
# not a valid PDF file unless we got a version number
|
@@ -179,6 +179,8 @@ sub BuildTextChunk($$$$$)
|
|
179
179
|
$tag =~ s/-$lang$//; # remove language code from tagID
|
180
180
|
} elsif ($$et{OPTIONS}{Charset} ne 'Latin' and $val =~ /[\x80-\xff]/) {
|
181
181
|
$iTXt = 1; # write as iTXt if it contains non-Latin special characters
|
182
|
+
} elsif ($$tagInfo{iTXt}) {
|
183
|
+
$iTXt = 1; # write as iTXt if specified in user-defined tag
|
182
184
|
}
|
183
185
|
}
|
184
186
|
if ($comp) {
|
@@ -17,6 +17,7 @@ my %movMap = (
|
|
17
17
|
Keys => 'Movie', # MOV-Movie-Meta-Keys !! (hack due to different Meta location)
|
18
18
|
Meta => 'UserData',
|
19
19
|
XMP => 'UserData', # MOV-Movie-UserData-XMP
|
20
|
+
Microsoft => 'UserData', # MOV-Movie-UserData-Microsoft
|
20
21
|
UserData => 'Movie', # MOV-Movie-UserData
|
21
22
|
Movie => 'MOV',
|
22
23
|
GSpherical => 'SphericalVideoXML', # MOV-Movie-Track-SphericalVideoXML
|
@@ -30,6 +31,7 @@ my %mp4Map = (
|
|
30
31
|
Keys => 'Movie', # MOV-Movie-Meta-Keys !! (hack due to different Meta location)
|
31
32
|
Meta => 'UserData',
|
32
33
|
UserData => 'Movie', # MOV-Movie-UserData
|
34
|
+
Microsoft => 'UserData', # MOV-Movie-UserData-Microsoft
|
33
35
|
Movie => 'MOV',
|
34
36
|
XMP => 'MOV', # MOV-XMP
|
35
37
|
GSpherical => 'SphericalVideoXML', # MOV-Movie-Track-SphericalVideoXML
|
@@ -76,8 +78,8 @@ my %dirMap = (
|
|
76
78
|
# convert ExifTool Format to QuickTime type
|
77
79
|
my %qtFormat = (
|
78
80
|
'undef' => 0x00, string => 0x01,
|
79
|
-
int8s => 0x15, int16s => 0x15, int32s => 0x15,
|
80
|
-
int8u => 0x16, int16u => 0x16, int32u => 0x16,
|
81
|
+
int8s => 0x15, int16s => 0x15, int32s => 0x15, int64s => 0x15,
|
82
|
+
int8u => 0x16, int16u => 0x16, int32u => 0x16, int64u => 0x16,
|
81
83
|
float => 0x17, double => 0x18,
|
82
84
|
);
|
83
85
|
my $undLang = 0x55c4; # numeric code for default ('und') language
|
@@ -161,6 +163,9 @@ sub ConvInvISO6709($)
|
|
161
163
|
# latitude must have 2 digits before the decimal, and longitude 3,
|
162
164
|
# and all values must start with a "+" or "-", and Google Photos
|
163
165
|
# requires at least 3 digits after the decimal point
|
166
|
+
# (and as of Apr 2021, Google Photos doesn't accept coordinats
|
167
|
+
# with more than 5 digits after the decimal place:
|
168
|
+
# https://exiftool.org/forum/index.php?topic=11055.msg67171#msg67171 )
|
164
169
|
my @fmt = ('%s%02d.%s%s','%s%03d.%s%s','%s%d.%s%s');
|
165
170
|
foreach (@a) {
|
166
171
|
return undef unless Image::ExifTool::IsFloat($_);
|
@@ -294,7 +299,7 @@ sub GetLangInfo($$)
|
|
294
299
|
sub CheckQTValue($$$)
|
295
300
|
{
|
296
301
|
my ($et, $tagInfo, $valPtr) = @_;
|
297
|
-
my $format = $$tagInfo{Format} || $$tagInfo{Table}{FORMAT};
|
302
|
+
my $format = $$tagInfo{Format} || $$tagInfo{Writable} || $$tagInfo{Table}{FORMAT};
|
298
303
|
return undef unless $format;
|
299
304
|
return Image::ExifTool::CheckValue($valPtr, $format, $$tagInfo{Count});
|
300
305
|
}
|
@@ -307,12 +312,12 @@ sub FormatQTValue($$;$$)
|
|
307
312
|
{
|
308
313
|
my ($et, $valPt, $format, $writable) = @_;
|
309
314
|
my $flags;
|
310
|
-
if ($format and $format ne 'string') {
|
311
|
-
$$valPt = WriteValue($$valPt, $format);
|
315
|
+
if ($format and $format ne 'string' or not $format and $writable and $writable ne 'string') {
|
316
|
+
$$valPt = WriteValue($$valPt, $format || $writable);
|
312
317
|
if ($writable and $qtFormat{$writable}) {
|
313
318
|
$flags = $qtFormat{$writable};
|
314
319
|
} else {
|
315
|
-
$flags = $qtFormat{$format} || 0;
|
320
|
+
$flags = $qtFormat{$format || 0} || 0;
|
316
321
|
}
|
317
322
|
} elsif ($$valPt =~ /^\xff\xd8\xff/) {
|
318
323
|
$flags = 0x0d; # JPG
|
@@ -844,7 +849,7 @@ sub WriteQuickTime($$$)
|
|
844
849
|
# --> hold this terminator to the end
|
845
850
|
$term = $hdr;
|
846
851
|
} elsif ($n != 0) {
|
847
|
-
$et->Error(
|
852
|
+
$et->Error("Unknown $n bytes at end of file", 1);
|
848
853
|
}
|
849
854
|
last;
|
850
855
|
}
|
@@ -1084,7 +1089,9 @@ sub WriteQuickTime($$$)
|
|
1084
1089
|
$$et{CHANGED} = $oldChanged if $$et{DemoteErrors} > 1;
|
1085
1090
|
delete $$et{DemoteErrors};
|
1086
1091
|
}
|
1087
|
-
if (defined $newData and not length $newData and $$
|
1092
|
+
if (defined $newData and not length $newData and ($$tagInfo{Permanent} or
|
1093
|
+
($$tagTablePtr{PERMANENT} and not defined $$tagInfo{Permanent})))
|
1094
|
+
{
|
1088
1095
|
# do nothing if trying to delete tag from a PERMANENT table
|
1089
1096
|
$$et{CHANGED} = $oldChanged;
|
1090
1097
|
undef $newData;
|
@@ -1092,7 +1099,9 @@ sub WriteQuickTime($$$)
|
|
1092
1099
|
$$et{CUR_WRITE_GROUP} = $oldWriteGroup;
|
1093
1100
|
SetByteOrder('MM');
|
1094
1101
|
# add back header if necessary
|
1095
|
-
if ($start and defined $newData and length $newData
|
1102
|
+
if ($start and defined $newData and (length $newData or
|
1103
|
+
(defined $$tagInfo{Permanent} and not $$tagInfo{Permanent})))
|
1104
|
+
{
|
1096
1105
|
$newData = substr($buff,0,$start) . $newData;
|
1097
1106
|
$$_[1] += $start foreach @chunkOffset;
|
1098
1107
|
}
|
@@ -1230,10 +1239,14 @@ sub WriteQuickTime($$$)
|
|
1230
1239
|
} elsif ($format) {
|
1231
1240
|
$val = ReadValue(\$buff, 0, $format, undef, $size);
|
1232
1241
|
} elsif (($tag =~ /^\xa9/ or $$tagInfo{IText}) and $size >= ($$tagInfo{IText} || 4)) {
|
1233
|
-
|
1234
|
-
|
1235
|
-
$
|
1236
|
-
$
|
1242
|
+
my $hdr;
|
1243
|
+
if ($$tagInfo{IText} and $$tagInfo{IText} >= 6) {
|
1244
|
+
my $iText = $$tagInfo{IText};
|
1245
|
+
my $pos = $iText - 2;
|
1246
|
+
$lang = unpack("x${pos}n", $buff);
|
1247
|
+
$hdr = substr($buff,4,$iText-6);
|
1248
|
+
$len = $size - $iText;
|
1249
|
+
$val = substr($buff, $iText, $len);
|
1237
1250
|
} else {
|
1238
1251
|
($len, $lang) = unpack('nn', $buff);
|
1239
1252
|
$len -= 4 if 4 + $len > $size; # (see QuickTime.pm for explanation)
|
@@ -1241,14 +1254,18 @@ sub WriteQuickTime($$$)
|
|
1241
1254
|
$val = substr($buff, 4, $len);
|
1242
1255
|
}
|
1243
1256
|
$lang or $lang = $undLang; # treat both 0 and 'und' as 'und'
|
1257
|
+
my $enc;
|
1244
1258
|
if ($lang < 0x400 and $val !~ /^\xfe\xff/) {
|
1245
1259
|
$charsetQuickTime = $et->Options('CharsetQuickTime');
|
1246
|
-
$
|
1260
|
+
$enc = $charsetQuickTime;
|
1247
1261
|
} else {
|
1248
|
-
|
1262
|
+
$enc = $val=~s/^\xfe\xff// ? 'UTF16' : 'UTF8';
|
1263
|
+
}
|
1264
|
+
unless ($$tagInfo{NoDecode}) {
|
1249
1265
|
$val = $et->Decode($val, $enc);
|
1266
|
+
$val =~ s/\0+$//; # remove trailing nulls if they exist
|
1250
1267
|
}
|
1251
|
-
$val
|
1268
|
+
$val = $hdr . $val if defined $hdr;
|
1252
1269
|
my $langCode = UnpackLang($lang, 1);
|
1253
1270
|
$langInfo = GetLangInfo($tagInfo, $langCode);
|
1254
1271
|
$nvHash = $et->GetNewValueHash($langInfo);
|
@@ -1265,6 +1282,9 @@ sub WriteQuickTime($$$)
|
|
1265
1282
|
}
|
1266
1283
|
} else {
|
1267
1284
|
$val = $buff;
|
1285
|
+
if ($tag =~ /^\xa9/ or $$tagInfo{IText}) {
|
1286
|
+
$et->Warn("Corrupted $$tagInfo{Name} value");
|
1287
|
+
}
|
1268
1288
|
}
|
1269
1289
|
if ($nvHash and defined $val) {
|
1270
1290
|
if ($et->IsOverwriting($nvHash, $val)) {
|
@@ -1277,12 +1297,23 @@ sub WriteQuickTime($$$)
|
|
1277
1297
|
$et->VerboseValue("+ $grp:$$langInfo{Name}", $newData);
|
1278
1298
|
# add back necessary header and encode as necessary
|
1279
1299
|
if (defined $lang) {
|
1280
|
-
$
|
1300
|
+
my $iText = $$tagInfo{IText} || 0;
|
1301
|
+
my $hdr;
|
1302
|
+
if ($iText > 6) {
|
1303
|
+
$newData .= ' 'x($iText-6) if length($newData) < $iText-6;
|
1304
|
+
$hdr = substr($newData, 0, $iText-6);
|
1305
|
+
$newData = substr($newData, $iText-6);
|
1306
|
+
}
|
1307
|
+
unless ($$tagInfo{NoDecode}) {
|
1308
|
+
$newData = $et->Encode($newData, $lang < 0x400 ? $charsetQuickTime : 'UTF8');
|
1309
|
+
}
|
1281
1310
|
my $wLang = $lang eq $undLang ? 0 : $lang;
|
1282
|
-
if (
|
1311
|
+
if ($iText < 6) {
|
1312
|
+
$newData = pack('nn', length($newData), $wLang) . $newData;
|
1313
|
+
} elsif ($iText == 6) {
|
1283
1314
|
$newData = pack('Nn', 0, $wLang) . $newData . "\0";
|
1284
1315
|
} else {
|
1285
|
-
$newData = pack('
|
1316
|
+
$newData = "\0\0\0\0" . $hdr . pack('n', $wLang) . $newData . "\0";
|
1286
1317
|
}
|
1287
1318
|
} elsif (not $format or $format =~ /^string/ and
|
1288
1319
|
not $$tagInfo{Binary} and not $$tagInfo{ValueConv})
|
@@ -1441,9 +1472,12 @@ sub WriteQuickTime($$$)
|
|
1441
1472
|
my $grp = $et->GetGroup($tagInfo,1);
|
1442
1473
|
$et->Warn("Can't use country code for $grp:$$tagInfo{Name}");
|
1443
1474
|
next;
|
1444
|
-
} elsif ($$tagInfo{IText} and $$tagInfo{IText}
|
1475
|
+
} elsif ($$tagInfo{IText} and $$tagInfo{IText} >= 6) {
|
1445
1476
|
# add 6-byte langText header and trailing null
|
1446
|
-
|
1477
|
+
# (with extra junk before language code if IText > 6)
|
1478
|
+
my $n = $$tagInfo{IText} - 6;
|
1479
|
+
$newVal .= ' ' x $n if length($newVal) < $n;
|
1480
|
+
$newVal = "\0\0\0\0" . substr($newVal,0,$n) . pack('n',0,$lang) . substr($newVal,$n) . "\0";
|
1447
1481
|
} else {
|
1448
1482
|
# add IText header
|
1449
1483
|
$newVal = pack('nn',length($newVal),$lang) . $newVal;
|
@@ -506,7 +506,7 @@ sub ConformPathToNamespace($$)
|
|
506
506
|
my $prop;
|
507
507
|
foreach $prop (@propList) {
|
508
508
|
my ($ns, $tag) = $prop =~ /(.+?):(.*)/;
|
509
|
-
next if $$nsUsed{$ns};
|
509
|
+
next if not defined $ns or $$nsUsed{$ns};
|
510
510
|
my $uri = $nsURI{$ns};
|
511
511
|
unless ($uri) {
|
512
512
|
warn "No URI for namespace prefix $ns!\n";
|
@@ -1417,7 +1417,11 @@ sub WriteXMP($$;$)
|
|
1417
1417
|
my $uri = $nsUsed{$1};
|
1418
1418
|
unless ($uri) {
|
1419
1419
|
$uri = $nsURI{$1}; # we must have added a namespace
|
1420
|
-
$uri
|
1420
|
+
unless ($uri) {
|
1421
|
+
# (namespace may be empty if trying to write empty XMP structure, forum12384)
|
1422
|
+
$xmpErr = "Undefined XMP namespace: $1" if length $uri;
|
1423
|
+
next;
|
1424
|
+
}
|
1421
1425
|
}
|
1422
1426
|
$nsNew{$1} = $uri;
|
1423
1427
|
# need a new description if any new namespaces
|
@@ -1465,7 +1469,7 @@ sub WriteXMP($$;$)
|
|
1465
1469
|
$long[-2] .= "$nl$sp<$prop rdf:about='${about}'";
|
1466
1470
|
# generate et:toolkit attribute if this is an exiftool RDF/XML output file
|
1467
1471
|
if (@ns and $nsCur{$ns[0]} =~ m{^http://ns.exiftool.(?:ca|org)/}) {
|
1468
|
-
$long[-2] .= "\n$sp${sp}xmlns:et='http://ns.exiftool.
|
1472
|
+
$long[-2] .= "\n$sp${sp}xmlns:et='http://ns.exiftool.org/1.0/'" .
|
1469
1473
|
" et:toolkit='Image::ExifTool $Image::ExifTool::VERSION'";
|
1470
1474
|
}
|
1471
1475
|
$long[-2] .= "\n$sp${sp}xmlns:$_='$nsCur{$_}'" foreach @ns;
|
@@ -105,6 +105,7 @@ my %writableType = (
|
|
105
105
|
ICC => [ 'ICC_Profile', 'WriteICC' ],
|
106
106
|
IND => 'InDesign',
|
107
107
|
JP2 => 'Jpeg2000',
|
108
|
+
JXL => 'Jpeg2000',
|
108
109
|
MIE => undef,
|
109
110
|
MOV => [ 'QuickTime', 'WriteMOV' ],
|
110
111
|
MRW => 'MinoltaRaw',
|
@@ -135,10 +136,10 @@ my %rawType = (
|
|
135
136
|
my @delGroups = qw(
|
136
137
|
Adobe AFCP APP0 APP1 APP2 APP3 APP4 APP5 APP6 APP7 APP8 APP9 APP10 APP11
|
137
138
|
APP12 APP13 APP14 APP15 CanonVRD CIFF Ducky EXIF ExifIFD File FlashPix
|
138
|
-
FotoStation GlobParamIFD GPS ICC_Profile IFD0 IFD1 Insta360 InteropIFD
|
139
|
-
ItemList JFIF Jpeg2000 Keys MakerNotes Meta MetaIFD MIE
|
140
|
-
PDF-update PhotoMechanic Photoshop PNG PNG-pHYs
|
141
|
-
SubIFD Trailer UserData XML XML-* XMP XMP-*
|
139
|
+
FotoStation GlobParamIFD GPS ICC_Profile IFD0 IFD1 Insta360 InteropIFD
|
140
|
+
IPTC ItemList JFIF Jpeg2000 Keys MakerNotes Meta MetaIFD Microsoft MIE
|
141
|
+
MPF NikonCapture PDF PDF-update PhotoMechanic Photoshop PNG PNG-pHYs
|
142
|
+
PrintIM QuickTime RMETA RSRC SubIFD Trailer UserData XML XML-* XMP XMP-*
|
142
143
|
);
|
143
144
|
# family 2 group names that we can delete
|
144
145
|
my @delGroup2 = qw(
|
@@ -573,6 +574,9 @@ sub SetNewValue($;$$%)
|
|
573
574
|
my $pre = $wantGroup ? $wantGroup . ':' : '';
|
574
575
|
$err = "Tag '$pre${origTag}' is not defined";
|
575
576
|
$err .= ' or has a bad language code' if $origTag =~ /-/;
|
577
|
+
if (not $pre and uc($origTag) eq 'TAG') {
|
578
|
+
$err .= " (specify a writable tag name, not '${origTag}' literally)"
|
579
|
+
}
|
576
580
|
} else {
|
577
581
|
$err = "Invalid tag name '${tag}'";
|
578
582
|
$err .= " (remove the leading '\$')" if $tag =~ /^\$/;
|
@@ -2639,12 +2643,14 @@ GWTInfo: foreach $tagInfo (@infoArray) {
|
|
2639
2643
|
|
2640
2644
|
#------------------------------------------------------------------------------
|
2641
2645
|
# Get list of all group names
|
2642
|
-
# Inputs: 0) Group family number
|
2646
|
+
# Inputs: 0) [optional] ExifTool ref, 1) Group family number
|
2643
2647
|
# Returns: List of group names (sorted alphabetically)
|
2644
|
-
sub GetAllGroups(
|
2648
|
+
sub GetAllGroups($;$)
|
2645
2649
|
{
|
2646
2650
|
local $_;
|
2647
2651
|
my $family = shift || 0;
|
2652
|
+
my $self;
|
2653
|
+
ref $family and $self = $family, $family = shift || 0;
|
2648
2654
|
|
2649
2655
|
$family == 3 and return('Doc#', 'Main');
|
2650
2656
|
$family == 4 and return('Copy#');
|
@@ -2663,9 +2669,23 @@ sub GetAllGroups($)
|
|
2663
2669
|
$allGroups{$grp} = 1 if ($grps = $$table{GROUPS}) and ($grp = $$grps{$family});
|
2664
2670
|
foreach $tag (TagTableKeys($table)) {
|
2665
2671
|
my @infoArray = GetTagInfoList($table, $tag);
|
2666
|
-
|
2667
|
-
|
2668
|
-
|
2672
|
+
if ($family == 7) {
|
2673
|
+
foreach $tagInfo (@infoArray) {
|
2674
|
+
my $id = $$tagInfo{TagID};
|
2675
|
+
if (not defined $id) {
|
2676
|
+
$id = ''; # (just to be safe)
|
2677
|
+
} elsif ($id =~ /^\d+$/) {
|
2678
|
+
$id = sprintf('0x%x', $id) if $self and $$self{OPTIONS}{HexTagIDs};
|
2679
|
+
} else {
|
2680
|
+
$id =~ s/([^-_A-Za-z0-9])/sprintf('%.2x',ord $1)/ge;
|
2681
|
+
}
|
2682
|
+
$allGroups{'ID-' . $id} = 1;
|
2683
|
+
}
|
2684
|
+
} else {
|
2685
|
+
foreach $tagInfo (@infoArray) {
|
2686
|
+
next unless ($grps = $$tagInfo{Groups}) and ($grp = $$grps{$family});
|
2687
|
+
$allGroups{$grp} = 1;
|
2688
|
+
}
|
2669
2689
|
}
|
2670
2690
|
}
|
2671
2691
|
}
|
@@ -4935,6 +4955,12 @@ sub Set64u(@)
|
|
4935
4955
|
$_[1] and substr(${$_[1]}, $_[2], length($val)) = $val;
|
4936
4956
|
return $val;
|
4937
4957
|
}
|
4958
|
+
sub Set64s(@)
|
4959
|
+
{
|
4960
|
+
my $val = shift;
|
4961
|
+
$val < 0 and $val += 4294967296 * 4294967296; # (temporary hack won't really work due to round-off errors)
|
4962
|
+
return Set64u($val, @_);
|
4963
|
+
}
|
4938
4964
|
sub SetRational64u(@) {
|
4939
4965
|
my ($numer,$denom) = Rationalize($_[0],0xffffffff);
|
4940
4966
|
my $val = Set32u($numer) . Set32u($denom);
|
@@ -4996,6 +5022,7 @@ my %writeValueProc = (
|
|
4996
5022
|
int16uRev => \&Set16uRev,
|
4997
5023
|
int32s => \&Set32s,
|
4998
5024
|
int32u => \&Set32u,
|
5025
|
+
int64s => \&Set64s,
|
4999
5026
|
int64u => \&Set64u,
|
5000
5027
|
rational32s => \&SetRational32s,
|
5001
5028
|
rational32u => \&SetRational32u,
|
@@ -6632,7 +6659,17 @@ sub SetFileTime($$;$$$$)
|
|
6632
6659
|
# open file by name if necessary
|
6633
6660
|
unless (ref $file) {
|
6634
6661
|
# (file will be automatically closed when *FH goes out of scope)
|
6635
|
-
$self->Open(\*FH, $file, '+<')
|
6662
|
+
unless ($self->Open(\*FH, $file, '+<')) {
|
6663
|
+
my $success;
|
6664
|
+
if (defined $atime or defined $mtime) {
|
6665
|
+
my ($a, $m, $c) = $self->GetFileTime($file);
|
6666
|
+
$atime = $a unless defined $atime;
|
6667
|
+
$mtime = $m unless defined $mtime;
|
6668
|
+
$success = eval { utime($atime, $mtime, $file) } if defined $atime and defined $mtime;
|
6669
|
+
}
|
6670
|
+
$self->Warn('Error opening file for update') unless $success;
|
6671
|
+
return $success;
|
6672
|
+
}
|
6636
6673
|
$saveFile = $file;
|
6637
6674
|
$file = \*FH;
|
6638
6675
|
}
|
@@ -50,7 +50,7 @@ use Image::ExifTool::Exif;
|
|
50
50
|
use Image::ExifTool::GPS;
|
51
51
|
require Exporter;
|
52
52
|
|
53
|
-
$VERSION = '3.
|
53
|
+
$VERSION = '3.44';
|
54
54
|
@ISA = qw(Exporter);
|
55
55
|
@EXPORT_OK = qw(EscapeXML UnescapeXML);
|
56
56
|
|
@@ -71,6 +71,13 @@ sub ConvertRational($);
|
|
71
71
|
sub ConvertRationalList($);
|
72
72
|
sub WriteGSpherical($$$);
|
73
73
|
|
74
|
+
# standard path locations for XMP in major file types
|
75
|
+
my %stdPath = (
|
76
|
+
JPEG => 'JPEG-APP1-XMP',
|
77
|
+
TIFF => 'TIFF-IFD0-XMP',
|
78
|
+
PSD => 'PSD-XMP',
|
79
|
+
);
|
80
|
+
|
74
81
|
# lookup for translating to ExifTool namespaces (and family 1 group names)
|
75
82
|
%stdXlatNS = (
|
76
83
|
# shorten ugly namespace prefixes
|
@@ -148,7 +155,7 @@ my %xmpNS = (
|
|
148
155
|
DICOM => 'http://ns.adobe.com/DICOM/',
|
149
156
|
'drone-dji'=> 'http://www.dji.com/drone-dji/1.0/',
|
150
157
|
svg => 'http://www.w3.org/2000/svg',
|
151
|
-
et => 'http://ns.exiftool.
|
158
|
+
et => 'http://ns.exiftool.org/1.0/',
|
152
159
|
#
|
153
160
|
# namespaces defined in XMP2.pl:
|
154
161
|
#
|
@@ -188,7 +195,7 @@ my %xmpNS = (
|
|
188
195
|
);
|
189
196
|
|
190
197
|
# build reverse namespace lookup
|
191
|
-
my %uri2ns = ( 'http://ns.exiftool.
|
198
|
+
my %uri2ns = ( 'http://ns.exiftool.ca/1.0/' => 'et' ); # (allow exiftool.ca as well as exiftool.org)
|
192
199
|
{
|
193
200
|
my $ns;
|
194
201
|
foreach $ns (keys %nsURI) {
|
@@ -201,13 +208,13 @@ my %uri2ns = ( 'http://ns.exiftool.org/1.0/' => 'et' ); # (allow exiftool.org as
|
|
201
208
|
ValueConv => 'Image::ExifTool::GPS::ToDegrees($val, 1)',
|
202
209
|
ValueConvInv => 'Image::ExifTool::GPS::ToDMS($self, $val, 2, "N")',
|
203
210
|
PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")',
|
204
|
-
PrintConvInv => 'Image::ExifTool::GPS::ToDegrees($val, 1)',
|
211
|
+
PrintConvInv => 'Image::ExifTool::GPS::ToDegrees($val, 1, "lat")',
|
205
212
|
);
|
206
213
|
%longConv = (
|
207
214
|
ValueConv => 'Image::ExifTool::GPS::ToDegrees($val, 1)',
|
208
215
|
ValueConvInv => 'Image::ExifTool::GPS::ToDMS($self, $val, 2, "E")',
|
209
216
|
PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")',
|
210
|
-
PrintConvInv => 'Image::ExifTool::GPS::ToDegrees($val, 1)',
|
217
|
+
PrintConvInv => 'Image::ExifTool::GPS::ToDegrees($val, 1, "lon")',
|
211
218
|
);
|
212
219
|
%dateTimeInfo = (
|
213
220
|
# NOTE: Do NOT put "Groups" here because Groups hash must not be common!
|
@@ -2010,6 +2017,11 @@ my %sPantryItem = (
|
|
2010
2017
|
Groups => { 2 => 'Location' },
|
2011
2018
|
Writable => 'integer',
|
2012
2019
|
PrintConv => {
|
2020
|
+
OTHER => sub {
|
2021
|
+
my ($val, $inv) = @_;
|
2022
|
+
return undef unless $inv and $val =~ /^([-+0-9])/;
|
2023
|
+
return($1 eq '-' ? 1 : 0);
|
2024
|
+
},
|
2013
2025
|
0 => 'Above Sea Level',
|
2014
2026
|
1 => 'Below Sea Level',
|
2015
2027
|
},
|
@@ -2280,7 +2292,7 @@ my %sPantryItem = (
|
|
2280
2292
|
Priority => 0,
|
2281
2293
|
# prevent this from getting set from a LensID that has been converted
|
2282
2294
|
ValueConvInv => q{
|
2283
|
-
warn "Expected one or more integer values" if $val =~ /[
|
2295
|
+
warn "Expected one or more integer values" if $val =~ /[^-\d ]/;
|
2284
2296
|
return $val;
|
2285
2297
|
},
|
2286
2298
|
},
|
@@ -3251,8 +3263,14 @@ NoLoop:
|
|
3251
3263
|
}
|
3252
3264
|
}
|
3253
3265
|
# generate a default tagInfo hash if necessary
|
3254
|
-
|
3255
|
-
|
3266
|
+
unless ($tagInfo) {
|
3267
|
+
# shorten tag name if necessary
|
3268
|
+
if ($$et{ShortenXmpTags}) {
|
3269
|
+
my $shorten = $$et{ShortenXmpTags};
|
3270
|
+
$name = &$shorten($name);
|
3271
|
+
}
|
3272
|
+
$tagInfo = { Name => $name, IsDefault => 1, Priority => 0 };
|
3273
|
+
}
|
3256
3274
|
# add tag Namespace entry for tags in variable-namespace tables
|
3257
3275
|
$$tagInfo{Namespace} = $xns if $xns;
|
3258
3276
|
if ($$et{curURI}{$ns} and $$et{curURI}{$ns} =~ m{^http://ns.exiftool.(?:ca|org)/(.*?)/(.*?)/}) {
|
@@ -3771,6 +3789,7 @@ sub ParseXMPElement($$$;$$$$)
|
|
3771
3789
|
# (unless we already extracted shorthand values from this element)
|
3772
3790
|
if (length $val or not $shorthand) {
|
3773
3791
|
my $lastProp = $$propList[-1];
|
3792
|
+
$lastProp = '' unless defined $lastProp;
|
3774
3793
|
if (defined $nodeID) {
|
3775
3794
|
SaveBlankInfo($blankInfo, $propList, $val);
|
3776
3795
|
} elsif ($lastProp eq 'rdf:type' and $wasEmpty) {
|
@@ -3844,6 +3863,7 @@ sub ProcessXMP($$;$)
|
|
3844
3863
|
my ($buff, $fmt, $hasXMP, $isXML, $isRDF, $isSVG);
|
3845
3864
|
my $rtnVal = 0;
|
3846
3865
|
my $bom = 0;
|
3866
|
+
my $path = $et->MetadataPath();
|
3847
3867
|
|
3848
3868
|
# namespaces and prefixes currently in effect while parsing the file,
|
3849
3869
|
# and lookup to translate brain-dead-Microsoft-Photo-software prefixes
|
@@ -3861,11 +3881,7 @@ sub ProcessXMP($$;$)
|
|
3861
3881
|
(($$dirInfo{DirName} || '') eq 'XMP' or $$et{FILE_TYPE} eq 'XMP'))
|
3862
3882
|
{
|
3863
3883
|
$$et{XmpValidate} = { } if $$et{OPTIONS}{Validate};
|
3864
|
-
my $
|
3865
|
-
my $nonStd;
|
3866
|
-
if ($$et{FILE_TYPE} =~ /^(JPEG|TIFF|PSD)$/ and $path !~ /^(JPEG-APP1-XMP|TIFF-IFD0-XMP|PSD-XMP)$/) {
|
3867
|
-
$nonStd = 1;
|
3868
|
-
}
|
3884
|
+
my $nonStd = ($stdPath{$$et{FILE_TYPE}} and $path ne $stdPath{$$et{FILE_TYPE}});
|
3869
3885
|
if ($nonStd and $Image::ExifTool::MWG::strict) {
|
3870
3886
|
$et->Warn("Ignored non-standard XMP at $path");
|
3871
3887
|
return 1;
|
@@ -4063,12 +4079,14 @@ sub ProcessXMP($$;$)
|
|
4063
4079
|
|
4064
4080
|
# extract XMP/XML as a block if specified
|
4065
4081
|
my $blockName = $$dirInfo{BlockInfo} ? $$dirInfo{BlockInfo}{Name} : 'XMP';
|
4082
|
+
my $blockExtract = $et->Options('BlockExtract');
|
4066
4083
|
if (($$et{REQ_TAG_LOOKUP}{lc $blockName} or ($$et{TAGS_FROM_FILE} and
|
4067
|
-
not $$et{EXCL_TAG_LOOKUP}{lc $blockName})) and
|
4084
|
+
not $$et{EXCL_TAG_LOOKUP}{lc $blockName}) or $blockExtract) and
|
4068
4085
|
(($$et{FileType} eq 'XMP' and $blockName eq 'XMP') or
|
4069
4086
|
($$dirInfo{DirName} and $$dirInfo{DirName} eq $blockName)))
|
4070
4087
|
{
|
4071
4088
|
$et->FoundTag($$dirInfo{BlockInfo} || 'XMP', substr($$dataPt, $dirStart, $dirLen));
|
4089
|
+
return 1 if $blockExtract and $blockExtract > 1;
|
4072
4090
|
}
|
4073
4091
|
|
4074
4092
|
$tagTablePtr or $tagTablePtr = GetTagTable('Image::ExifTool::XMP::Main');
|
@@ -4123,6 +4141,13 @@ sub ProcessXMP($$;$)
|
|
4123
4141
|
}
|
4124
4142
|
defined $fmt or $et->Warn('XMP character encoding error');
|
4125
4143
|
}
|
4144
|
+
# warn if standard XMP is missing xpacket wrapper
|
4145
|
+
if ($$et{XMP_NO_XPACKET} and $$et{OPTIONS}{Validate} and
|
4146
|
+
$stdPath{$$et{FILE_TYPE}} and $path eq $stdPath{$$et{FILE_TYPE}} and
|
4147
|
+
not $$dirInfo{IsExtended} and not $$et{DOC_NUM})
|
4148
|
+
{
|
4149
|
+
$et->Warn('XMP is missing xpacket wrapper', 1);
|
4150
|
+
}
|
4126
4151
|
if ($fmt) {
|
4127
4152
|
# trim if necessary to avoid converting non-UTF data
|
4128
4153
|
if ($dirStart or $dirEnd != length($$dataPt)) {
|
@@ -539,7 +539,8 @@ my %sImageRegion = ( # new in 1.5
|
|
539
539
|
NOTES => q{
|
540
540
|
This table contains tags defined by the IPTC Extension schema version 1.5.
|
541
541
|
The actual namespace prefix is "Iptc4xmpExt", but ExifTool shortens this for
|
542
|
-
the family 1 group name. (see
|
542
|
+
the family 1 group name. (see
|
543
|
+
L<http://www.iptc.org/standards/photo-metadata/iptc-standard/>)
|
543
544
|
},
|
544
545
|
AboutCvTerm => {
|
545
546
|
Struct => \%sCVTermDetails,
|
@@ -32,7 +32,9 @@ sub SerializeStruct($;$)
|
|
32
32
|
my ($key, $val, @vals, $rtnVal);
|
33
33
|
|
34
34
|
if (ref $obj eq 'HASH') {
|
35
|
-
|
35
|
+
# support hashes with ordered keys
|
36
|
+
my @keys = $$obj{_ordered_keys_} ? @{$$obj{_ordered_keys_}} : sort keys %$obj;
|
37
|
+
foreach $key (@keys) {
|
36
38
|
push @vals, $key . '=' . SerializeStruct($$obj{$key}, '}');
|
37
39
|
}
|
38
40
|
$rtnVal = '{' . join(',', @vals) . '}';
|