exiftool_vendored 12.17.1 → 12.32.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 +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) . '}';
|