exiftool_vendored 11.44.0 → 11.47.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of exiftool_vendored might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/bin/Changes +37 -0
- data/bin/MANIFEST +4 -0
- data/bin/META.json +1 -1
- data/bin/META.yml +1 -1
- data/bin/README +3 -2
- data/bin/config_files/example.config +5 -0
- data/bin/config_files/mini0806.config +99 -0
- data/bin/exiftool +2 -2
- data/bin/lib/Image/ExifTool.pm +12 -7
- data/bin/lib/Image/ExifTool.pod +10 -2
- data/bin/lib/Image/ExifTool/Canon.pm +39 -5
- data/bin/lib/Image/ExifTool/CanonVRD.pm +24 -2
- data/bin/lib/Image/ExifTool/ID3.pm +2 -2
- data/bin/lib/Image/ExifTool/Nikon.pm +3 -2
- data/bin/lib/Image/ExifTool/Olympus.pm +2 -1
- data/bin/lib/Image/ExifTool/Pentax.pm +1 -1
- data/bin/lib/Image/ExifTool/QuickTime.pm +117 -69
- data/bin/lib/Image/ExifTool/README +1 -0
- data/bin/lib/Image/ExifTool/TagLookup.pm +3 -0
- data/bin/lib/Image/ExifTool/TagNames.pod +47 -15
- data/bin/lib/Image/ExifTool/WriteIPTC.pl +1 -1
- data/bin/lib/Image/ExifTool/WriteQuickTime.pl +434 -132
- data/bin/lib/Image/ExifTool/Writer.pl +35 -15
- data/bin/perl-Image-ExifTool.spec +1 -1
- data/lib/exiftool_vendored/version.rb +1 -1
- metadata +3 -2
@@ -52,6 +52,7 @@ with tag keys. Below is an explanation of the meaning of each special key:
|
|
52
52
|
FixOffsets - Evaluated for each value pointer to patch maker note offsets
|
53
53
|
LastIFD - Used by WriteExif() to return offset of last IFD written
|
54
54
|
ImageData - Used by WriteExif() to avoid buffering large image data blocks
|
55
|
+
NoRefTest - Flag to bypass "referenced by previous directory" test
|
55
56
|
|
56
57
|
WRITE_PROC : Function reference or name for writing this directory. The
|
57
58
|
function returns the new directory data or undefined on error. It takes the
|
@@ -6772,6 +6772,7 @@ my %tagExists = (
|
|
6772
6772
|
'clonetype' => 1,
|
6773
6773
|
'closedcaptioning' => 1,
|
6774
6774
|
'cmmflags' => 1,
|
6775
|
+
'cmp1' => 1,
|
6775
6776
|
'cmykequivalent' => 1,
|
6776
6777
|
'coarsedata' => 1,
|
6777
6778
|
'coarsemapimage' => 1,
|
@@ -7928,6 +7929,7 @@ my %tagExists = (
|
|
7928
7929
|
'hyperlinkbase' => 1,
|
7929
7930
|
'hyperlinks' => 1,
|
7930
7931
|
'hyperlinkschanged' => 1,
|
7932
|
+
'iad1' => 1,
|
7931
7933
|
'icc_untagged' => 1,
|
7932
7934
|
'iccbased' => 1,
|
7933
7935
|
'iconenvdata' => 1,
|
@@ -10383,6 +10385,7 @@ my %tagExists = (
|
|
10383
10385
|
'usptooriginalcontenttype' => 1,
|
10384
10386
|
'utm' => 1,
|
10385
10387
|
'uuid-canon' => 1,
|
10388
|
+
'uuid-canon2' => 1,
|
10386
10389
|
'uuid-exif' => 1,
|
10387
10390
|
'uuid-exif2' => 1,
|
10388
10391
|
'uuid-exif_bad' => 1,
|
@@ -12,7 +12,7 @@ meta information extracted from or written to a file.
|
|
12
12
|
=head1 TAG TABLES
|
13
13
|
|
14
14
|
The tables listed below give the names of all tags recognized by ExifTool.
|
15
|
-
They contain a total of
|
15
|
+
They contain a total of 22586 tags, with 15003 unique tag names.
|
16
16
|
|
17
17
|
B<Tag ID>, B<Index#> or B<Sequence> is given in the first column of each
|
18
18
|
table. A B<Tag ID> is the computer-readable equivalent of a tag name, and
|
@@ -7042,8 +7042,21 @@ information.
|
|
7042
7042
|
|
7043
7043
|
Tag ID Tag Name Writable
|
7044
7044
|
------ -------- --------
|
7045
|
+
'IAD1' IAD1 Canon IAD1
|
7046
|
+
|
7047
|
+
=head3 Canon IAD1 Tags
|
7048
|
+
|
7049
|
+
Index2 Tag Name Writable
|
7050
|
+
------ -------- --------
|
7045
7051
|
[no tags known]
|
7046
7052
|
|
7053
|
+
=head3 Canon CMP1 Tags
|
7054
|
+
|
7055
|
+
Index2 Tag Name Writable
|
7056
|
+
------ -------- --------
|
7057
|
+
8 ImageWidth no
|
7058
|
+
10 ImageHeight no
|
7059
|
+
|
7047
7060
|
=head3 Canon CNOP Tags
|
7048
7061
|
|
7049
7062
|
Tag ID Tag Name Writable
|
@@ -7090,6 +7103,12 @@ Information found in the "skip" atom of Canon MOV videos.
|
|
7090
7103
|
------ -------- --------
|
7091
7104
|
'CNDB' Unknown_CNDB? no
|
7092
7105
|
|
7106
|
+
=head3 Canon uuid2 Tags
|
7107
|
+
|
7108
|
+
Tag ID Tag Name Writable
|
7109
|
+
------ -------- --------
|
7110
|
+
'CNOP' CanonVRD CanonVRD
|
7111
|
+
|
7093
7112
|
=head2 CanonCustom Tags
|
7094
7113
|
|
7095
7114
|
=head3 CanonCustom Functions1D Tags
|
@@ -23954,22 +23973,29 @@ Frame rate information stored by some Canon video cameras.
|
|
23954
23973
|
|
23955
23974
|
The QuickTime format is used for many different types of audio, video and
|
23956
23975
|
image files (most notably, MOV/MP4 videos and HEIC/CR3 images). Exiftool
|
23957
|
-
extracts standard meta information a variety of audio, video and image
|
23976
|
+
extracts standard meta information and a variety of audio, video and image
|
23958
23977
|
parameters, as well as proprietary information written by many camera
|
23959
23978
|
models. Tags with a question mark after their name are not extracted unless
|
23960
23979
|
the Unknown option is set.
|
23961
23980
|
|
23962
23981
|
When writing, ExifTool creates both QuickTime and XMP tags by default, but
|
23963
|
-
the group may be specified to write one or the other separately.
|
23964
|
-
created QuickTime tags are added in the
|
23965
|
-
otherwise in UserData,
|
23966
|
-
|
23967
|
-
|
23968
|
-
|
23969
|
-
|
23970
|
-
|
23971
|
-
|
23972
|
-
|
23982
|
+
the group may be specified to write one or the other separately. If no
|
23983
|
+
location is specified, newly created QuickTime tags are added in the
|
23984
|
+
ItemList location if possible, otherwise in UserData, and finally in Keys,
|
23985
|
+
but this order may be changed by setting the PREFERRED level of the
|
23986
|
+
appropriate table in the config file (see example.config in the full
|
23987
|
+
distribution for an example). ExifTool currently writes only top-level
|
23988
|
+
metadata in QuickTime-based files; it extracts other track-specific and
|
23989
|
+
timed metadata, but can not yet edit tags in these locations.
|
23990
|
+
|
23991
|
+
Alternate language tags may be accessed for ItemList and Keys tags by adding
|
23992
|
+
a 3-character ISO 639-2 language code and an optional ISO 3166-1 alpha 2
|
23993
|
+
country code to the tag name (eg. "ItemList:Artist-deu" or
|
23994
|
+
"ItemList::Artist-deu-DE"). UserData tags support only a language code
|
23995
|
+
(without a country code). If no language code is specified when writing,
|
23996
|
+
alternate languages for the tag are deleted. Use the "und" language code to
|
23997
|
+
write the default language without deleting alternate languages. Note that
|
23998
|
+
"eng" is treated as a default language when reading, but not when writing.
|
23973
23999
|
|
23974
24000
|
According to the specification, many QuickTime date/time tags should be
|
23975
24001
|
stored as UTC. Unfortunately, digital cameras often store local time values
|
@@ -24016,6 +24042,7 @@ for the official specification.
|
|
24016
24042
|
'uuid' XMP XMP
|
24017
24043
|
UUID-PROF QuickTime Profile
|
24018
24044
|
UUID-Flip QuickTime Flip
|
24045
|
+
UUID-Canon2 Canon uuid2
|
24019
24046
|
PreviewImage no
|
24020
24047
|
UUID-Unknown? no
|
24021
24048
|
'wide' Wide? no
|
@@ -24367,6 +24394,10 @@ MP4 data reference box.
|
|
24367
24394
|
|
24368
24395
|
As well as these tags, the "mdta" handler uses numerical tag ID's which are
|
24369
24396
|
added dynamically to this table after processing the Meta Keys information.
|
24397
|
+
Tags in this table support alternate languages which are accessed by adding
|
24398
|
+
a 3-character ISO 639-2 language code and an optional ISO 3166-1 alpha 2
|
24399
|
+
country code to the tag name (eg. "ItemList:Title-fra" or
|
24400
|
+
"ItemList::Title-fra-FR").
|
24370
24401
|
|
24371
24402
|
Tag ID Tag Name Writable
|
24372
24403
|
------ -------- --------
|
@@ -24821,6 +24852,7 @@ Child atoms found in "sinf" and/or "pinf" atoms.
|
|
24821
24852
|
25 CompressorName no
|
24822
24853
|
41 BitDepth no
|
24823
24854
|
'CDI1' CDI1 Canon CDI1
|
24855
|
+
'CMP1' CMP1 Canon CMP1
|
24824
24856
|
'JPEG' JPEGInfo? no
|
24825
24857
|
'avcC' AVCConfiguration? no
|
24826
24858
|
'btrt' BitrateInfo QuickTime Bitrate
|
@@ -24966,9 +24998,9 @@ MP4 video media header.
|
|
24966
24998
|
=head3 QuickTime UserData Tags
|
24967
24999
|
|
24968
25000
|
Tag ID's beginning with the copyright symbol (hex 0xa9) are multi-language
|
24969
|
-
text. Alternate language tags are accessed by adding a dash followed by
|
24970
|
-
language
|
24971
|
-
multi-language user data tags found, even if they
|
25001
|
+
text. Alternate language tags are accessed by adding a dash followed by a
|
25002
|
+
3-character ISO 639-2 language code to the tag name. ExifTool will extract
|
25003
|
+
any multi-language user data tags found, even if they aren't in this table.
|
24972
25004
|
|
24973
25005
|
Tag ID Tag Name Writable
|
24974
25006
|
------ -------- --------
|
@@ -215,7 +215,7 @@ sub FormatIPTC($$$$$;$)
|
|
215
215
|
sub IptcDate($)
|
216
216
|
{
|
217
217
|
my $val = shift;
|
218
|
-
unless ($val =~ s
|
218
|
+
unless ($val =~ s{^.*(\d{4})[-:/.]?(\d{2})[-:/.]?(\d{2}).*}{$1$2$3}s) {
|
219
219
|
warn "Invalid date format (use YYYY:mm:dd)\n";
|
220
220
|
undef $val;
|
221
221
|
}
|
@@ -12,21 +12,23 @@ use strict;
|
|
12
12
|
# maps for adding metadata to various QuickTime-based file types
|
13
13
|
my %movMap = (
|
14
14
|
# MOV (no 'ftyp', or 'ftyp'='qt ') -> XMP in 'moov'-'udta'-'XMP_'
|
15
|
-
QuickTime => 'ItemList',
|
16
|
-
ItemList => 'Meta',
|
15
|
+
QuickTime => 'ItemList', # (default location for QuickTime tags)
|
16
|
+
ItemList => 'Meta', # MOV-Movie-UserData-Meta-ItemList
|
17
|
+
Keys => 'Movie', # MOV-Movie-Meta-Keys !! (hack due to different Meta location)
|
17
18
|
Meta => 'UserData',
|
18
|
-
XMP => 'UserData',
|
19
|
-
UserData => 'Movie',
|
19
|
+
XMP => 'UserData', # MOV-Movie-UserData-XMP
|
20
|
+
UserData => 'Movie', # MOV-Movie-UserData
|
20
21
|
Movie => 'MOV',
|
21
22
|
);
|
22
23
|
my %mp4Map = (
|
23
24
|
# MP4 ('ftyp' compatible brand 'mp41', 'mp42' or 'f4v ') -> XMP at top level
|
24
|
-
QuickTime => 'ItemList',
|
25
|
-
ItemList => 'Meta',
|
25
|
+
QuickTime => 'ItemList', # (default location for QuickTime tags)
|
26
|
+
ItemList => 'Meta', # MOV-Movie-UserData-Meta-ItemList
|
27
|
+
Keys => 'Movie', # MOV-Movie-Meta-Keys !! (hack due to different Meta location)
|
26
28
|
Meta => 'UserData',
|
27
|
-
UserData => 'Movie',
|
29
|
+
UserData => 'Movie', # MOV-Movie-UserData
|
28
30
|
Movie => 'MOV',
|
29
|
-
XMP => 'MOV',
|
31
|
+
XMP => 'MOV', # MOV-XMP
|
30
32
|
);
|
31
33
|
my %heicMap = (
|
32
34
|
# HEIC ('ftyp' compatible brand 'heic' or 'mif1') -> XMP/EXIF in top level 'meta'
|
@@ -55,6 +57,8 @@ my %cr3Map = (
|
|
55
57
|
IFD0 => 'UUID-Canon',
|
56
58
|
GPS => 'UUID-Canon',
|
57
59
|
#MakerNoteCanon => 'UUID-Canon', # (doesn't yet work -- goes into ExifIFD instead)
|
60
|
+
'UUID-Canon2' => 'MOV',
|
61
|
+
CanonVRD => 'UUID-Canon2',
|
58
62
|
);
|
59
63
|
my %dirMap = (
|
60
64
|
MOV => \%movMap,
|
@@ -72,6 +76,18 @@ my %qtFormat = (
|
|
72
76
|
);
|
73
77
|
my $undLang = 0x55c4; # numeric code for default ('und') language
|
74
78
|
|
79
|
+
# boxes that may exist in an "empty" Meta box:
|
80
|
+
my %emptyMeta = (
|
81
|
+
hdlr => 'Handler', 'keys' => 'Keys', lang => 'Language', ctry => 'Country', free => 'Free',
|
82
|
+
);
|
83
|
+
|
84
|
+
# lookup for CTBO ID number based on uuid for Canon CR3 files
|
85
|
+
my %ctboID = (
|
86
|
+
"\xbe\x7a\xcf\xcb\x97\xa9\x42\xe8\x9c\x71\x99\x94\x91\xe3\xaf\xac" => 1, # XMP
|
87
|
+
"\xea\xf4\x2b\x5e\x1c\x98\x4b\x88\xb9\xfb\xb7\xdc\x40\x6e\x4d\x16" => 2, # PreviewImage
|
88
|
+
# ID 3 is used for 'mdat' atom (not a uuid)
|
89
|
+
);
|
90
|
+
|
75
91
|
# mark UserData tags that don't have ItemList counterparts as Preferred
|
76
92
|
# (and for now, set Writable to 0 for any tag with a RawConv)
|
77
93
|
{
|
@@ -139,18 +155,6 @@ sub ConvInvISO6709($)
|
|
139
155
|
return undef;
|
140
156
|
}
|
141
157
|
|
142
|
-
#------------------------------------------------------------------------------
|
143
|
-
# Check to see if path is current
|
144
|
-
# Inputs: 0) ExifTool ref, 1) directory name
|
145
|
-
# Returns: true if current path is the root of the specified directory
|
146
|
-
sub IsCurPath($$)
|
147
|
-
{
|
148
|
-
local $_;
|
149
|
-
my ($et, $dir) = @_;
|
150
|
-
$dir = $$et{DirMap}{$dir} and $dir eq $_ or last foreach reverse @{$$et{PATH}};
|
151
|
-
return($dir and $dir eq 'MOV');
|
152
|
-
}
|
153
|
-
|
154
158
|
#------------------------------------------------------------------------------
|
155
159
|
# Handle offsets in iloc (ItemLocation) atom when writing (ref ISO 14496-12:2015 pg.79)
|
156
160
|
# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) data ref, 3) output buffer ref
|
@@ -239,7 +243,6 @@ sub Handle_iloc($$$$)
|
|
239
243
|
# decide whether to fix up the base offset or individual item offsets
|
240
244
|
# (adjust the one that is larger)
|
241
245
|
if (defined $minOffset and $minOffset > $base_offset) {
|
242
|
-
$off or $off = $$dirInfo{ChunkOffset} = [ ];
|
243
246
|
$$_[3] = $base_offset foreach @offItem;
|
244
247
|
push @$off, @offItem;
|
245
248
|
} else {
|
@@ -318,6 +321,100 @@ sub SetVarInt($$)
|
|
318
321
|
return '';
|
319
322
|
}
|
320
323
|
|
324
|
+
#------------------------------------------------------------------------------
|
325
|
+
# Write Meta Keys to add/delete entries as necessary ('mdta' handler) (ref PH)
|
326
|
+
# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
|
327
|
+
# Returns: updated keys box data
|
328
|
+
sub WriteKeys($$$)
|
329
|
+
{
|
330
|
+
my ($et, $dirInfo, $tagTablePtr) = @_;
|
331
|
+
$et or return 1; # allow dummy access to autoload this package
|
332
|
+
my $dataPt = $$dirInfo{DataPt};
|
333
|
+
my $dirLen = length $$dataPt;
|
334
|
+
my $outfile = $$dirInfo{OutFile};
|
335
|
+
my ($tag, %done, %remap, %info, %add, $i);
|
336
|
+
|
337
|
+
$dirLen < 8 and $et->Warn('Short Keys box'), $dirLen = 8, $$dataPt = "\0" x 8;
|
338
|
+
if ($$et{DEL_GROUP}{Keys}) {
|
339
|
+
$dirLen = 8; # delete all existing keys
|
340
|
+
# deleted keys are identified by a zero entry in the Remap lookup
|
341
|
+
my $n = Get32u($dataPt, 4);
|
342
|
+
for ($i=1; $i<=$n; ++$i) { $remap{$i} = 0; }
|
343
|
+
$et->VPrint(0, " [deleting $n Keys entr".($n==1 ? 'y' : 'ies')."]\n");
|
344
|
+
++$$et{CHANGED};
|
345
|
+
}
|
346
|
+
my $pos = 8;
|
347
|
+
my $newTags = $et->GetNewTagInfoHash($tagTablePtr);
|
348
|
+
my $newData = substr($$dataPt, 0, $pos);
|
349
|
+
|
350
|
+
my $newIndex = 1;
|
351
|
+
my $index = 1;
|
352
|
+
while ($pos < $dirLen - 4) {
|
353
|
+
my $len = unpack("x${pos}N", $$dataPt);
|
354
|
+
last if $len < 8 or $pos + $len > $dirLen;
|
355
|
+
my $ns = substr($$dataPt, $pos + 4, 4);
|
356
|
+
$tag = substr($$dataPt, $pos + 8, $len - 8);
|
357
|
+
$tag =~ s/\0.*//s; # truncate at null
|
358
|
+
$tag =~ s/^com\.apple\.quicktime\.// if $ns eq 'mdta'; # remove apple quicktime domain
|
359
|
+
$tag = "Tag_$ns" unless $tag;
|
360
|
+
$done{$tag} = 1; # set flag to avoid creating this tag
|
361
|
+
my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag);
|
362
|
+
if ($tagInfo) {
|
363
|
+
$info{$index} = $tagInfo;
|
364
|
+
if ($$newTags{$tag}) {
|
365
|
+
my $nvHash = $et->GetNewValueHash($tagInfo);
|
366
|
+
# drop this tag if it is being deleted
|
367
|
+
if ($nvHash and $et->IsOverwriting($nvHash) > 0 and not defined $et->GetNewValue($nvHash)) {
|
368
|
+
# delete this key
|
369
|
+
$et->VPrint(1, "$$et{INDENT}\[deleting Keys entry $index '${tag}']\n");
|
370
|
+
$pos += $len;
|
371
|
+
$remap{$index++} = 0;
|
372
|
+
++$$et{CHANGED};
|
373
|
+
next;
|
374
|
+
}
|
375
|
+
}
|
376
|
+
}
|
377
|
+
# add to the Keys box data
|
378
|
+
$newData .= substr($$dataPt, $pos, $len);
|
379
|
+
$remap{$index++} = $newIndex++;
|
380
|
+
$pos += $len;
|
381
|
+
}
|
382
|
+
# add keys for any tags we need to create
|
383
|
+
foreach $tag (sort keys %$newTags) {
|
384
|
+
my $tagInfo = $$newTags{$tag};
|
385
|
+
my $id;
|
386
|
+
if ($$tagInfo{LangCode} and $$tagInfo{SrcTagInfo}) {
|
387
|
+
$id = $$tagInfo{SrcTagInfo}{TagID};
|
388
|
+
} else {
|
389
|
+
$id = $tag;
|
390
|
+
}
|
391
|
+
next if $done{$id};
|
392
|
+
my $nvHash = $et->GetNewValueHash($tagInfo);
|
393
|
+
next unless $$nvHash{IsCreating} and $et->IsOverwriting($nvHash) and
|
394
|
+
defined $et->GetNewValue($nvHash);
|
395
|
+
# add new entry to 'keys' data
|
396
|
+
my $val = "com.apple.quicktime.$id";
|
397
|
+
$newData .= Set32u(8 + length($val)) . 'mdta' . $val;
|
398
|
+
$et->VPrint(1, "$$et{INDENT}\[adding Keys entry $newIndex '${id}']\n");
|
399
|
+
$add{$newIndex++} = $tagInfo;
|
400
|
+
++$$et{CHANGED};
|
401
|
+
}
|
402
|
+
my $num = $newIndex - 1;
|
403
|
+
if ($num) {
|
404
|
+
Set32u($num, \$newData, 4); # update count in header
|
405
|
+
} else {
|
406
|
+
$newData = ''; # delete empty Keys box
|
407
|
+
}
|
408
|
+
# save temporary variables for use when writing ItemList:
|
409
|
+
# Remap - lookup for remapping Keys ID numbers (0 if item is deleted)
|
410
|
+
# Info - Keys tag information, based on old index value
|
411
|
+
# Add - Keys items deleted, based on old index value
|
412
|
+
# Num - Number of items in edited Keys box
|
413
|
+
$$et{Keys} = { Remap => \%remap, Info => \%info, Add => \%add, Num => $num };
|
414
|
+
|
415
|
+
return $newData; # return updated Keys box
|
416
|
+
}
|
417
|
+
|
321
418
|
#------------------------------------------------------------------------------
|
322
419
|
# Write ItemInformation in HEIC files
|
323
420
|
# Inputs: 0) ExifTool ref, 1) dirInfo ref (with BoxPos entry), 2) output buffer ref
|
@@ -571,13 +668,14 @@ sub WriteItemInfo($$$)
|
|
571
668
|
# Returns: A) if dirInfo contains DataPt: new directory data
|
572
669
|
# B) otherwise: true on success, 0 if a write error occurred
|
573
670
|
# (true but sets an Error on a file format error)
|
671
|
+
# Notes: Yes, this is a real mess. Just like the QuickTime metadata situation.
|
574
672
|
sub WriteQuickTime($$$)
|
575
673
|
{
|
576
674
|
local $_;
|
577
675
|
my ($et, $dirInfo, $tagTablePtr) = @_;
|
578
676
|
$et or return 1; # allow dummy access to autoload this package
|
579
|
-
my ($mdat, @mdat, @mdatEdit, $edit, $track, $outBuff, $co, $term, $err);
|
580
|
-
my (%langTags, $
|
677
|
+
my ($mdat, @mdat, @mdatEdit, $edit, $track, $outBuff, $co, $term, $err, $delCount);
|
678
|
+
my (%langTags, $canCreate, $delGrp, %boxPos, $createKeys, $newTags, %didDir, $writeLast);
|
581
679
|
my $outfile = $$dirInfo{OutFile} || return 0;
|
582
680
|
my $raf = $$dirInfo{RAF}; # (will be null for lower-level atoms)
|
583
681
|
my $dataPt = $$dirInfo{DataPt}; # (will be null for top-level atoms)
|
@@ -585,6 +683,7 @@ sub WriteQuickTime($$$)
|
|
585
683
|
my $dirStart = $$dirInfo{DirStart} || 0;
|
586
684
|
my $parent = $$dirInfo{Parent};
|
587
685
|
my $addDirs = $$et{ADD_DIRS};
|
686
|
+
my $didTag = $$et{DidTag};
|
588
687
|
my ($rtnVal, $rtnErr) = $dataPt ? (undef, undef) : (1, 0);
|
589
688
|
|
590
689
|
if ($dataPt) {
|
@@ -592,27 +691,47 @@ sub WriteQuickTime($$$)
|
|
592
691
|
} else {
|
593
692
|
return 0 unless $raf;
|
594
693
|
}
|
595
|
-
# initialize ItemList key directory count
|
596
|
-
$$et{KeyCount} = 0 unless defined $$et{KeyCount};
|
597
|
-
|
598
694
|
# use buffered output for everything but 'mdat' atoms
|
599
695
|
$outBuff = '';
|
600
696
|
$outfile = \$outBuff;
|
601
697
|
|
602
698
|
$raf->Seek($dirStart, 1) if $dirStart; # skip header if it exists
|
603
699
|
|
604
|
-
# get hash of new tags to add to this directory if this is the proper place for them
|
605
700
|
my $curPath = join '-', @{$$et{PATH}};
|
606
701
|
my ($dir, $writePath) = ($dirName, $dirName);
|
607
702
|
$writePath = "$dir-$writePath" while defined($dir = $$et{DirMap}{$dir});
|
608
|
-
|
609
|
-
if
|
703
|
+
# hack for Keys write path (its containing Meta is in a different location)
|
704
|
+
$createKeys = 1 if $$addDirs{Keys} and $curPath =~ /^MOV-Movie(-Meta(-ItemList)?)?$/;
|
705
|
+
if ($curPath eq $writePath or $createKeys) {
|
610
706
|
$canCreate = 1;
|
611
|
-
$delGrp =
|
612
|
-
$et->VPrint(0, " Deleting $dirName tags\n") if $delGrp;
|
707
|
+
$delGrp = $$et{DEL_GROUP}{$dirName};
|
613
708
|
}
|
614
|
-
|
615
|
-
|
709
|
+
# edit existing Keys tags in ItemList if we are at the correct path
|
710
|
+
if ($curPath eq 'MOV-Movie-Meta-ItemList') {
|
711
|
+
$newTags = { };
|
712
|
+
my $keys = $$et{Keys};
|
713
|
+
if ($keys) {
|
714
|
+
# add new tag entries for existing Keys tags, now that we know their ID's
|
715
|
+
my ($index, %keysInfo);
|
716
|
+
foreach $index (keys %{$$keys{Info}}) {
|
717
|
+
$keysInfo{$$keys{Info}{$index}} = $index if $$keys{Remap}{$index};
|
718
|
+
}
|
719
|
+
my $keysTable = GetTagTable('Image::ExifTool::QuickTime::Keys');
|
720
|
+
my $newKeysTags = $et->GetNewTagInfoHash($keysTable);
|
721
|
+
foreach (keys %$newKeysTags) {
|
722
|
+
my $tagInfo = $$newKeysTags{$_};
|
723
|
+
$index = $keysInfo{$tagInfo} || ($$tagInfo{SrcTagInfo} and $keysInfo{$$tagInfo{SrcTagInfo}});
|
724
|
+
next unless $index;
|
725
|
+
my $id = Set32u($index);
|
726
|
+
$id .= '-' . $$tagInfo{LangCode} if $$tagInfo{LangCode};
|
727
|
+
$$newTags{$id} = $tagInfo;
|
728
|
+
}
|
729
|
+
}
|
730
|
+
} else {
|
731
|
+
# get hash of new tags to edit/create in this directory
|
732
|
+
$newTags = $et->GetNewTagInfoHash($tagTablePtr);
|
733
|
+
}
|
734
|
+
# make lookup of language tags for each ID
|
616
735
|
foreach (keys %$newTags) {
|
617
736
|
next unless $$newTags{$_}{LangCode} and $$newTags{$_}{SrcTagInfo};
|
618
737
|
my $id = $$newTags{$_}{SrcTagInfo}{TagID};
|
@@ -621,7 +740,7 @@ sub WriteQuickTime($$$)
|
|
621
740
|
}
|
622
741
|
|
623
742
|
for (;;) { # loop through all atoms at this level
|
624
|
-
my ($hdr, $buff);
|
743
|
+
my ($hdr, $buff, $keysIndex);
|
625
744
|
my $n = $raf->Read($hdr, 8);
|
626
745
|
unless ($n == 8) {
|
627
746
|
if ($n == 4 and $hdr eq "\0\0\0\0") {
|
@@ -701,7 +820,7 @@ sub WriteQuickTime($$$)
|
|
701
820
|
|
702
821
|
# if this atom stores offsets, save its location so we can fix up offsets later
|
703
822
|
# (are there any other atoms that may store absolute file offsets?)
|
704
|
-
if ($tag =~ /^(stco|co64|iloc|mfra|gps )$/) {
|
823
|
+
if ($tag =~ /^(stco|co64|iloc|mfra|gps |CTBO|uuid)$/) {
|
705
824
|
# (note that we only need to do this if the movie data is stored in this file)
|
706
825
|
my $flg = $$et{QtDataFlg};
|
707
826
|
if ($tag eq 'mfra') {
|
@@ -713,7 +832,6 @@ sub WriteQuickTime($$$)
|
|
713
832
|
# (only care about the 'gps ' box in 'moov')
|
714
833
|
if ($$dirInfo{DirID} and $$dirInfo{DirID} eq 'moov' and length $buff > 8) {
|
715
834
|
my $off = $$dirInfo{ChunkOffset};
|
716
|
-
$off or $off = $$dirInfo{ChunkOffset} = [ ];
|
717
835
|
my $num = Get32u(\$buff, 4);
|
718
836
|
$num = int((length($buff) - 8) / 8) if $num * 8 + 8 > length($buff);
|
719
837
|
my $i;
|
@@ -721,6 +839,8 @@ sub WriteQuickTime($$$)
|
|
721
839
|
push @$off, [ 'stco_gps ', length($$outfile) + length($hdr) + 8 + $i * 8, 4 ];
|
722
840
|
}
|
723
841
|
}
|
842
|
+
} elsif ($tag eq 'CTBO' or $tag eq 'uuid') { # hack for updating CR3 CTBO offsets
|
843
|
+
push @{$$dirInfo{ChunkOffset}}, [ $tag, length($$outfile), length($hdr) + $size ];
|
724
844
|
} elsif (not $flg) {
|
725
845
|
my $grp = $$et{CUR_WRITE_GROUP} || $parent;
|
726
846
|
$et->Error("Can't locate data reference to update offsets for $grp");
|
@@ -730,9 +850,7 @@ sub WriteQuickTime($$$)
|
|
730
850
|
return $rtnVal;
|
731
851
|
} elsif ($flg == 1) {
|
732
852
|
# must update offsets since the data is in this file
|
733
|
-
|
734
|
-
$off or $off = $$dirInfo{ChunkOffset} = [ ];
|
735
|
-
push @$off, [ $tag, length($$outfile) + length($hdr), $size ];
|
853
|
+
push @{$$dirInfo{ChunkOffset}}, [ $tag, length($$outfile) + length($hdr), $size ];
|
736
854
|
}
|
737
855
|
}
|
738
856
|
|
@@ -743,14 +861,40 @@ sub WriteQuickTime($$$)
|
|
743
861
|
&{$$tagInfo{WriteHook}}($buff,$et) if $tagInfo and $$tagInfo{WriteHook};
|
744
862
|
|
745
863
|
# allow numerical tag ID's (ItemList entries defined by Keys)
|
746
|
-
|
747
|
-
|
748
|
-
$
|
864
|
+
if (not $tagInfo and $dirName eq 'ItemList' and $$et{Keys}) {
|
865
|
+
$keysIndex = unpack('N', $tag);
|
866
|
+
my $newIndex = $$et{Keys}{Remap}{$keysIndex};
|
867
|
+
if (defined $newIndex) {
|
868
|
+
$tagInfo = $$et{Keys}{Info}{$keysIndex};
|
869
|
+
unless ($newIndex) {
|
870
|
+
if ($tagInfo) {
|
871
|
+
$et->VPrint(1," - Keys:$$tagInfo{Name}");
|
872
|
+
} else {
|
873
|
+
$delCount = ($delCount || 0) + 1;
|
874
|
+
}
|
875
|
+
++$$et{CHANGED};
|
876
|
+
next;
|
877
|
+
}
|
878
|
+
# use the new Keys index of this item if it changed
|
879
|
+
unless ($keysIndex == $newIndex) {
|
880
|
+
$tag = Set32u($newIndex);
|
881
|
+
substr($hdr, 4, 4) = $tag;
|
882
|
+
}
|
883
|
+
} else {
|
884
|
+
undef $keysIndex;
|
885
|
+
}
|
749
886
|
}
|
750
|
-
# delete all ItemList
|
751
|
-
if ($delGrp
|
752
|
-
|
753
|
-
|
887
|
+
# delete all ItemList tags when deleting group, but take care not to delete UserData Meta
|
888
|
+
if ($delGrp) {
|
889
|
+
if ($dirName eq 'ItemList') {
|
890
|
+
$delCount = ($delCount || 0) + 1;
|
891
|
+
++$$et{CHANGED};
|
892
|
+
next;
|
893
|
+
} elsif ($dirName eq 'UserData' and (not $tagInfo or not $$tagInfo{SubDirectory})) {
|
894
|
+
$delCount = ($delCount || 0) + 1;
|
895
|
+
++$$et{CHANGED};
|
896
|
+
next;
|
897
|
+
}
|
754
898
|
}
|
755
899
|
undef $tagInfo if $tagInfo and $$tagInfo{Unknown};
|
756
900
|
|
@@ -786,6 +930,7 @@ sub WriteQuickTime($$$)
|
|
786
930
|
HasData => $$subdir{HasData},
|
787
931
|
Multi => $$subdir{Multi}, # necessary?
|
788
932
|
OutFile => $outfile,
|
933
|
+
NoRefTest=> 1, # don't check directory references
|
789
934
|
# initialize array to hold details about chunk offset table
|
790
935
|
# (each entry has 3-5 items: 0=atom type, 1=table offset, 2=table size,
|
791
936
|
# 3=optional base offset, 4=optional item ID)
|
@@ -826,26 +971,15 @@ sub WriteQuickTime($$$)
|
|
826
971
|
$$_[1] += $start foreach @chunkOffset;
|
827
972
|
}
|
828
973
|
# the directory exists, so we don't need to add it
|
829
|
-
|
974
|
+
if ($curPath eq $writePath and $$addDirs{$subName} and $$addDirs{$subName} eq $dirName) {
|
975
|
+
delete $$addDirs{$subName};
|
976
|
+
}
|
977
|
+
$didDir{$tag} = 1; # (note: keyed by tag ID)
|
830
978
|
|
831
979
|
} else { # modify existing QuickTime tags in various formats
|
832
980
|
|
833
981
|
my $nvHash = $et->GetNewValueHash($tagInfo);
|
834
|
-
|
835
|
-
if (not $nvHash and $$tagInfo{KeysInfo}) {
|
836
|
-
$nvHash = $et->GetNewValueHash($$tagInfo{KeysInfo});
|
837
|
-
# may be writing this as a language tag, so fill in $langTags for this ID
|
838
|
-
unless ($keysTags) {
|
839
|
-
$keysTags = $et->GetNewTagInfoHash(GetTagTable('Image::ExifTool::QuickTime::Keys'));
|
840
|
-
}
|
841
|
-
foreach (keys %$keysTags) {
|
842
|
-
next unless $$keysTags{$_}{SrcTagInfo};
|
843
|
-
next unless $$keysTags{$_}{SrcTagInfo} eq $$tagInfo{KeysInfo};
|
844
|
-
$langTags{$tag} = { } unless $langTags{$tag};
|
845
|
-
$langTags{$tag}{$_} = $$keysTags{$_};
|
846
|
-
}
|
847
|
-
}
|
848
|
-
if ($nvHash or $langTags{$tag} or $delQt) {
|
982
|
+
if ($nvHash or $langTags{$tag} or $delGrp) {
|
849
983
|
my $nvHashNoLang = $nvHash;
|
850
984
|
my ($val, $len, $lang, $type, $flags, $ctry, $charsetQuickTime);
|
851
985
|
my $format = $$tagInfo{Format};
|
@@ -854,19 +988,50 @@ sub WriteQuickTime($$$)
|
|
854
988
|
if ($hasData) {
|
855
989
|
my $pos = 0;
|
856
990
|
for (;;$pos+=$len) {
|
857
|
-
|
991
|
+
if ($pos + 16 > $size) {
|
992
|
+
# add any new alternate language tags now
|
993
|
+
if ($langTags{$tag}) {
|
994
|
+
my $tg;
|
995
|
+
foreach $tg ('', sort keys %{$langTags{$tag}}) {
|
996
|
+
my $ti = $tg ? $langTags{$tag}{$tg} : $nvHashNoLang;
|
997
|
+
$nvHash = $et->GetNewValueHash($ti);
|
998
|
+
next unless $nvHash and not $$didTag{$nvHash};
|
999
|
+
$$didTag{$nvHash} = 1;
|
1000
|
+
next unless $$nvHash{IsCreating} and $et->IsOverwriting($nvHash);
|
1001
|
+
my $newVal = $et->GetNewValue($nvHash);
|
1002
|
+
next unless defined $newVal;
|
1003
|
+
my $prVal = $newVal;
|
1004
|
+
my $flags = FormatQTValue($et, \$newVal, $$tagInfo{Format});
|
1005
|
+
next unless defined $newVal;
|
1006
|
+
my ($ctry, $lang) = (0,0);
|
1007
|
+
if ($$ti{LangCode}) {
|
1008
|
+
unless ($$ti{LangCode} =~ /^([A-Z]{3})?[-_]?([A-Z]{2})?$/i) {
|
1009
|
+
$et->Warn("Invalid language code for $$ti{Name}");
|
1010
|
+
next;
|
1011
|
+
}
|
1012
|
+
# pack language and country codes
|
1013
|
+
if ($1 and $1 ne 'und') {
|
1014
|
+
$lang = ($lang << 5) | ($_ - 0x60) foreach unpack 'C*', lc($1);
|
1015
|
+
}
|
1016
|
+
$ctry = unpack('n', pack('a2',uc($2))) if $2 and $2 ne 'ZZ';
|
1017
|
+
}
|
1018
|
+
$newData = substr($buff, 0, $pos) unless defined $newData;
|
1019
|
+
$newData .= pack('Na4Nnn',16+length($newVal),'data',$flags,$ctry,$lang).$newVal;
|
1020
|
+
my $grp = $et->GetGroup($ti, 1);
|
1021
|
+
$et->VerboseValue("+ $grp:$$ti{Name}", $prVal);
|
1022
|
+
++$$et{CHANGED};
|
1023
|
+
}
|
1024
|
+
}
|
1025
|
+
last;
|
1026
|
+
}
|
858
1027
|
($len, $type, $flags, $ctry, $lang) = unpack("x${pos}Na4Nnn", $buff);
|
859
1028
|
$lang or $lang = $undLang; # treat both 0 and 'und' as 'und'
|
860
1029
|
$langInfo = $tagInfo;
|
861
|
-
my $delTag = $
|
1030
|
+
my $delTag = $delGrp;
|
862
1031
|
my $newVal;
|
863
1032
|
my $langCode = GetLangCode($lang, $ctry, 1);
|
864
1033
|
for (;;) {
|
865
|
-
|
866
|
-
$langInfo = GetLangInfo($$tagInfo{KeysInfo}, $langCode);
|
867
|
-
} else {
|
868
|
-
$langInfo = GetLangInfo($tagInfo, $langCode);
|
869
|
-
}
|
1034
|
+
$langInfo = GetLangInfo($tagInfo, $langCode);
|
870
1035
|
$nvHash = $et->GetNewValueHash($langInfo);
|
871
1036
|
last if $nvHash or not $ctry or $lang ne $undLang or length($langCode)==2;
|
872
1037
|
# check to see if tag was written with a 2-char country code only
|
@@ -875,7 +1040,7 @@ sub WriteQuickTime($$$)
|
|
875
1040
|
# set flag to delete language tag when writing default
|
876
1041
|
# (except for a default-language Keys entry)
|
877
1042
|
if (not $nvHash and $nvHashNoLang) {
|
878
|
-
if ($lang eq $undLang and not $ctry and not
|
1043
|
+
if ($lang eq $undLang and not $ctry and not $$didTag{$nvHashNoLang}) {
|
879
1044
|
$nvHash = $nvHashNoLang; # write existing default
|
880
1045
|
} else {
|
881
1046
|
$delTag = 1; # delete tag
|
@@ -903,34 +1068,28 @@ sub WriteQuickTime($$$)
|
|
903
1068
|
}
|
904
1069
|
if (($nvHash and $et->IsOverwriting($nvHash, $val)) or $delTag) {
|
905
1070
|
$newVal = $et->GetNewValue($nvHash) if defined $nvHash;
|
906
|
-
if ($delTag or
|
907
|
-
|
908
|
-
my $grp = $et->GetGroup($langInfo, 1);
|
909
|
-
$et->VerboseValue("- $grp:$$langInfo{Name}", $val);
|
910
|
-
$newData = substr($buff, 0, $pos-16) unless defined $newData;
|
911
|
-
++$$et{CHANGED};
|
912
|
-
$pos += $len;
|
913
|
-
next;
|
914
|
-
}
|
915
|
-
$newVal = '';
|
916
|
-
}
|
917
|
-
my $prVal = $newVal;
|
918
|
-
# format new value for writing (and get new flags)
|
919
|
-
$flags = FormatQTValue($et, \$newVal, $$tagInfo{Format});
|
920
|
-
if (defined $newVal and not ($nvHash and $didTag{$nvHash})) {
|
921
|
-
next if $newVal eq '' and $val eq '';
|
922
|
-
++$$et{CHANGED};
|
1071
|
+
if ($delTag or not defined $newVal or $$didTag{$nvHash}) {
|
1072
|
+
# delete the tag
|
923
1073
|
my $grp = $et->GetGroup($langInfo, 1);
|
924
1074
|
$et->VerboseValue("- $grp:$$langInfo{Name}", $val);
|
925
|
-
$et->VerboseValue("+ $grp:$$langInfo{Name}", $prVal);
|
926
|
-
$didTag{$nvHash} = 1 if $nvHash;
|
927
|
-
$newData = substr($buff, 0, $pos-16) unless defined $newData;
|
928
|
-
$newData .= pack('Na4Nnn', length($newVal)+16, $type, $flags, $ctry, $lang);
|
929
|
-
$newData .= $newVal;
|
930
|
-
} elsif (defined $newData) {
|
931
1075
|
# copy data up to start of this tag to delete this value
|
932
|
-
$newData
|
1076
|
+
$newData = substr($buff, 0, $pos-16) unless defined $newData;
|
1077
|
+
++$$et{CHANGED};
|
1078
|
+
next;
|
933
1079
|
}
|
1080
|
+
my $prVal = $newVal;
|
1081
|
+
# format new value for writing (and get new flags)
|
1082
|
+
$flags = FormatQTValue($et, \$newVal, $$tagInfo{Format});
|
1083
|
+
my $grp = $et->GetGroup($langInfo, 1);
|
1084
|
+
$et->VerboseValue("- $grp:$$langInfo{Name}", $val);
|
1085
|
+
$et->VerboseValue("+ $grp:$$langInfo{Name}", $prVal);
|
1086
|
+
$$didTag{$nvHash} = 1 if $nvHash;
|
1087
|
+
$newData = substr($buff, 0, $pos-16) unless defined $newData;
|
1088
|
+
$newData .= pack('Na4Nnn', length($newVal)+16, $type, $flags, $ctry, $lang);
|
1089
|
+
$newData .= $newVal;
|
1090
|
+
++$$et{CHANGED};
|
1091
|
+
} elsif (defined $newData) {
|
1092
|
+
$newData .= substr($buff, $pos-16, $len+16);
|
934
1093
|
}
|
935
1094
|
} elsif (defined $newData) {
|
936
1095
|
$newData .= substr($buff, $pos, $len);
|
@@ -962,10 +1121,9 @@ sub WriteQuickTime($$$)
|
|
962
1121
|
$val =~ s/\0+$//; # remove trailing nulls if they exist
|
963
1122
|
my $langCode = UnpackLang($lang, 1);
|
964
1123
|
$langInfo = GetLangInfo($tagInfo, $langCode);
|
965
|
-
# (no need to check $$tagInfo{KeysInfo} because Keys won't get here)
|
966
1124
|
$nvHash = $et->GetNewValueHash($langInfo);
|
967
1125
|
if (not $nvHash and $nvHashNoLang) {
|
968
|
-
if ($lang eq $undLang and not
|
1126
|
+
if ($lang eq $undLang and not $$didTag{$nvHashNoLang}) {
|
969
1127
|
$nvHash = $nvHashNoLang;
|
970
1128
|
} elsif ($canCreate) {
|
971
1129
|
# delete other languages when writing default
|
@@ -982,9 +1140,9 @@ sub WriteQuickTime($$$)
|
|
982
1140
|
++$$et{CHANGED};
|
983
1141
|
my $grp = $et->GetGroup($langInfo, 1);
|
984
1142
|
$et->VerboseValue("- $grp:$$langInfo{Name}", $val);
|
985
|
-
next unless defined $newData and not
|
1143
|
+
next unless defined $newData and not $$didTag{$nvHash};
|
986
1144
|
$et->VerboseValue("+ $grp:$$langInfo{Name}", $newData);
|
987
|
-
|
1145
|
+
$$didTag{$nvHash} = 1; # set flag so we don't add this tag again
|
988
1146
|
# add back necessary header and encode as necessary
|
989
1147
|
if (defined $lang) {
|
990
1148
|
$newData = $et->Encode($newData, $lang < 0x400 ? $charsetQuickTime : 'UTF8');
|
@@ -999,18 +1157,23 @@ sub WriteQuickTime($$$)
|
|
999
1157
|
}
|
1000
1158
|
# write the new atom if it was modified
|
1001
1159
|
if (defined $newData) {
|
1002
|
-
my $len = length
|
1003
|
-
$len >
|
1004
|
-
|
1160
|
+
my $len = length($newData) + 8;
|
1161
|
+
$len > 0x7fffffff and $et->Error("$$tagInfo{Name} to large to write"), last;
|
1162
|
+
# update size in ChunkOffset list for modified 'uuid' atom
|
1163
|
+
$$dirInfo{ChunkOffset}[-1][2] = $len if $tag eq 'uuid';
|
1164
|
+
next unless $len > 8; # don't write empty atom header
|
1005
1165
|
# maintain pointer to chunk offsets if necessary
|
1006
1166
|
if (@chunkOffset) {
|
1007
|
-
$$dirInfo{ChunkOffset} or $$dirInfo{ChunkOffset} = [ ];
|
1008
1167
|
$$_[1] += 8 + length $$outfile foreach @chunkOffset;
|
1009
1168
|
push @{$$dirInfo{ChunkOffset}}, @chunkOffset;
|
1010
1169
|
}
|
1011
|
-
|
1012
|
-
|
1013
|
-
|
1170
|
+
if ($$tagInfo{WriteLast}) {
|
1171
|
+
$writeLast = ($writeLast || '') . Set32u($len) . $tag . $newData;
|
1172
|
+
} else {
|
1173
|
+
$boxPos{$tag} = [ length($$outfile), length($newData) + 8 ];
|
1174
|
+
# write the updated directory with its atom header
|
1175
|
+
Write($outfile, Set32u($len), $tag, $newData) or $rtnVal=$rtnErr, $err=1, last;
|
1176
|
+
}
|
1014
1177
|
next;
|
1015
1178
|
}
|
1016
1179
|
}
|
@@ -1045,25 +1208,61 @@ sub WriteQuickTime($$$)
|
|
1045
1208
|
}
|
1046
1209
|
$$et{QtDataFlg} = $flg;
|
1047
1210
|
}
|
1048
|
-
|
1049
|
-
|
1050
|
-
|
1051
|
-
|
1211
|
+
if ($tagInfo and $$tagInfo{WriteLast}) {
|
1212
|
+
$writeLast = ($writeLast || '') . $hdr . $buff;
|
1213
|
+
} else {
|
1214
|
+
# save position of this box in the output buffer
|
1215
|
+
$boxPos{$tag} = [ length($$outfile), length($hdr) + length($buff) ];
|
1216
|
+
# copy the existing atom
|
1217
|
+
Write($outfile, $hdr, $buff) or $rtnVal=$rtnErr, $err=1, last;
|
1218
|
+
}
|
1052
1219
|
}
|
1220
|
+
$et->VPrint(0, " [deleting $delCount $dirName tag".($delCount==1 ? '' : 's')."]\n") if $delCount;
|
1221
|
+
|
1222
|
+
undef $createKeys unless $$addDirs{Keys}; # (Keys may have been written)
|
1223
|
+
|
1053
1224
|
# add new directories/tags at this level if necessary
|
1054
|
-
if (exists $$et{EDIT_DIRS}{$dirName}
|
1225
|
+
if ($canCreate and (exists $$et{EDIT_DIRS}{$dirName} or $createKeys)) {
|
1055
1226
|
# get a hash of tagInfo references to add to this directory
|
1056
1227
|
my $dirs = $et->GetAddDirHash($tagTablePtr, $dirName);
|
1057
1228
|
# make sorted list of new tags to be added
|
1058
1229
|
my @addTags = sort(keys(%$dirs), keys %$newTags);
|
1059
|
-
my $tag;
|
1230
|
+
my ($tag, $index);
|
1231
|
+
# add Keys tags if necessary
|
1232
|
+
if ($createKeys) {
|
1233
|
+
if ($curPath eq 'MOV-Movie') {
|
1234
|
+
# add Meta for Keys if necessary
|
1235
|
+
unless ($didDir{meta}) {
|
1236
|
+
$$dirs{meta} = $Image::ExifTool::QuickTime::Movie{meta};
|
1237
|
+
push @addTags, 'meta';
|
1238
|
+
}
|
1239
|
+
} elsif ($curPath eq 'MOV-Movie-Meta') {
|
1240
|
+
# special case for Keys Meta -- reset directories and start again
|
1241
|
+
undef @addTags;
|
1242
|
+
$dirs = { };
|
1243
|
+
foreach ('keys','ilst') {
|
1244
|
+
next if $didDir{$_}; # don't add again
|
1245
|
+
$$dirs{$_} = $Image::ExifTool::QuickTime::Meta{$_};
|
1246
|
+
push @addTags, $_;
|
1247
|
+
}
|
1248
|
+
} elsif ($curPath eq 'MOV-Movie-Meta-ItemList' and $$et{Keys}) {
|
1249
|
+
foreach $index (sort { $a <=> $b } keys %{$$et{Keys}{Add}}) {
|
1250
|
+
my $id = Set32u($index);
|
1251
|
+
$$newTags{$id} = $$et{Keys}{Add}{$index};
|
1252
|
+
push @addTags, $id;
|
1253
|
+
}
|
1254
|
+
} else {
|
1255
|
+
$dirs = $et->GetAddDirHash($tagTablePtr, $dirName);
|
1256
|
+
push @addTags, sort keys %$dirs;
|
1257
|
+
}
|
1258
|
+
}
|
1259
|
+
# (note that $tag may be a binary Keys index here)
|
1060
1260
|
foreach $tag (@addTags) {
|
1061
1261
|
my $tagInfo = $$dirs{$tag} || $$newTags{$tag};
|
1062
|
-
next if $$tagInfo{KeysInfo}; # don't try to add keys tags (yet)
|
1063
1262
|
my $subdir = $$tagInfo{SubDirectory};
|
1064
1263
|
unless ($subdir) {
|
1065
1264
|
my $nvHash = $et->GetNewValueHash($tagInfo);
|
1066
|
-
next unless $nvHash and not
|
1265
|
+
next unless $nvHash and not $$didTag{$nvHash};
|
1067
1266
|
next unless $$nvHash{IsCreating} and $et->IsOverwriting($nvHash);
|
1068
1267
|
my $newVal = $et->GetNewValue($nvHash);
|
1069
1268
|
next unless defined $newVal;
|
@@ -1071,16 +1270,18 @@ sub WriteQuickTime($$$)
|
|
1071
1270
|
my $flags = FormatQTValue($et, \$newVal, $$tagInfo{Format});
|
1072
1271
|
next unless defined $newVal;
|
1073
1272
|
my ($ctry, $lang) = (0,0);
|
1074
|
-
|
1075
|
-
|
1273
|
+
# handle alternate languages
|
1274
|
+
if ($$tagInfo{LangCode}) {
|
1275
|
+
$tag = substr($tag, 0, 4); # strip language code from tag ID
|
1276
|
+
unless ($$tagInfo{LangCode} =~ /^([A-Z]{3})?[-_]?([A-Z]{2})?$/i) {
|
1076
1277
|
$et->Warn("Invalid language code for $$tagInfo{Name}");
|
1077
1278
|
next;
|
1078
1279
|
}
|
1079
1280
|
# pack language and country codes
|
1080
|
-
if ($
|
1081
|
-
$lang = ($lang << 5) | ($_ - 0x60) foreach unpack 'C*', lc($
|
1281
|
+
if ($1 and $1 ne 'und') {
|
1282
|
+
$lang = ($lang << 5) | ($_ - 0x60) foreach unpack 'C*', lc($1);
|
1082
1283
|
}
|
1083
|
-
$ctry = unpack('n', pack('a2',uc($
|
1284
|
+
$ctry = unpack('n', pack('a2',uc($2))) if $2 and $2 ne 'ZZ';
|
1084
1285
|
}
|
1085
1286
|
if ($$dirInfo{HasData}) {
|
1086
1287
|
# add 'data' header
|
@@ -1102,16 +1303,37 @@ sub WriteQuickTime($$$)
|
|
1102
1303
|
$et->Warn("Can't use language code for $grp:$$tagInfo{Name}");
|
1103
1304
|
next;
|
1104
1305
|
}
|
1105
|
-
|
1106
|
-
|
1107
|
-
|
1306
|
+
if ($$tagInfo{WriteLast}) {
|
1307
|
+
$writeLast = ($writeLast || '') . Set32u(8+length($newVal)) . $tag . $newVal;
|
1308
|
+
} else {
|
1309
|
+
$boxPos{$tag} = [ length($$outfile), 8 + length($newVal) ];
|
1310
|
+
Write($outfile, Set32u(8+length($newVal)), $tag, $newVal) or $rtnVal=$rtnErr, $err=1;
|
1311
|
+
}
|
1312
|
+
my $grp = $et->GetGroup($tagInfo, 1);
|
1313
|
+
$et->VerboseValue("+ $grp:$$tagInfo{Name}", $prVal);
|
1314
|
+
$$didTag{$nvHash} = 1;
|
1108
1315
|
++$$et{CHANGED};
|
1109
1316
|
next;
|
1110
1317
|
}
|
1111
1318
|
my $subName = $$subdir{DirName} || $$tagInfo{Name};
|
1112
1319
|
# QuickTime hierarchy is complex, so check full directory path before adding
|
1113
|
-
|
1114
|
-
|
1320
|
+
my $buff;
|
1321
|
+
if ($createKeys and $curPath eq 'MOV-Movie' and $subName eq 'Meta') {
|
1322
|
+
$et->VPrint(0, " Creating Meta with mdta Handler and Keys\n");
|
1323
|
+
# init Meta box for Keys tags with mdta Handler and empty Keys+ItemList
|
1324
|
+
$buff = "\0\0\0\x20hdlr\0\0\0\0\0\0\0\0mdta\0\0\0\0\0\0\0\0\0\0\0\0" .
|
1325
|
+
"\0\0\0\x10keys\0\0\0\0\0\0\0\0" .
|
1326
|
+
"\0\0\0\x08ilst";
|
1327
|
+
} elsif ($createKeys and $curPath eq 'MOV-Movie-Meta') {
|
1328
|
+
$buff = ($subName eq 'Keys' ? "\0\0\0\0\0\0\0\0" : '');
|
1329
|
+
} elsif ($subName eq 'Meta' and $$et{OPTIONS}{QuickTimeHandler}) {
|
1330
|
+
$et->VPrint(0, " Creating Meta with mdir Handler\n");
|
1331
|
+
# init Meta box for ItemList tags with mdir Handler
|
1332
|
+
$buff = "\0\0\0\x20hdlr\0\0\0\0\0\0\0\0mdir\0\0\0\0\0\0\0\0\0\0\0\0";
|
1333
|
+
} else {
|
1334
|
+
next unless $curPath eq $writePath and $$addDirs{$subName} and $$addDirs{$subName} eq $dirName;
|
1335
|
+
$buff = ''; # write from scratch
|
1336
|
+
}
|
1115
1337
|
my %subdirInfo = (
|
1116
1338
|
Parent => $dirName,
|
1117
1339
|
DirName => $subName,
|
@@ -1119,6 +1341,7 @@ sub WriteQuickTime($$$)
|
|
1119
1341
|
DirStart => 0,
|
1120
1342
|
HasData => $$subdir{HasData},
|
1121
1343
|
OutFile => $outfile,
|
1344
|
+
ChunkOffset => [ ], # (just to be safe)
|
1122
1345
|
);
|
1123
1346
|
my $subTable = GetTagTable($$subdir{TagTable});
|
1124
1347
|
my $newData = $et->WriteDirectory(\%subdirInfo, $subTable, $$subdir{WriteProc});
|
@@ -1136,13 +1359,25 @@ sub WriteQuickTime($$$)
|
|
1136
1359
|
}
|
1137
1360
|
}
|
1138
1361
|
my $newHdr = Set32u(8+length($newData)+length($prefix)) . $tag . $prefix;
|
1139
|
-
|
1362
|
+
if ($$tagInfo{WriteLast}) {
|
1363
|
+
$writeLast = ($writeLast || '') . $newHdr . $newData;
|
1364
|
+
} else {
|
1365
|
+
if ($tag eq 'uuid') {
|
1366
|
+
# add offset for new uuid (needed for CR3 CTBO offsets)
|
1367
|
+
my $off = $$dirInfo{ChunkOffset};
|
1368
|
+
push @$off, [ $tag, length($$outfile), length($newHdr) + length($newData) ];
|
1369
|
+
}
|
1370
|
+
$boxPos{$tag} = [ length($$outfile), length($newHdr) + length($newData) ];
|
1371
|
+
Write($outfile, $newHdr, $newData) or $rtnVal=$rtnErr, $err=1;
|
1372
|
+
}
|
1140
1373
|
}
|
1141
|
-
|
1374
|
+
# add only once (must delete _after_ call to WriteDirectory())
|
1375
|
+
# (Keys is a special case, and will be removed after Meta is processed)
|
1376
|
+
delete $$addDirs{$subName} unless $subName eq 'Keys';
|
1142
1377
|
}
|
1143
1378
|
}
|
1144
1379
|
# write HEIC metadata after top-level 'meta' box has been processed if editing this information
|
1145
|
-
if ($
|
1380
|
+
if ($curPath eq 'MOV-Meta' and $$et{EDIT_DIRS}{ItemInformation}) {
|
1146
1381
|
$$dirInfo{BoxPos} = \%boxPos;
|
1147
1382
|
my $mdatEdit = WriteItemInfo($et, $dirInfo, $outfile);
|
1148
1383
|
if ($mdatEdit) {
|
@@ -1151,15 +1386,35 @@ sub WriteQuickTime($$$)
|
|
1151
1386
|
}
|
1152
1387
|
}
|
1153
1388
|
# write out any necessary terminator
|
1154
|
-
Write($outfile, $term) or $rtnVal=$rtnErr, $err=1 if $term;
|
1389
|
+
Write($outfile, $term) or $rtnVal=$rtnErr, $err=1 if $term and length $$outfile;
|
1390
|
+
|
1391
|
+
# delete temporary Keys variables after Meta is processed
|
1392
|
+
if ($dirName eq 'Meta') {
|
1393
|
+
# delete any Meta box with no useful information (ie. only 'hdlr','keys','lang','ctry')
|
1394
|
+
my $isEmpty = 1;
|
1395
|
+
$emptyMeta{$_} or $isEmpty = 0, last foreach keys %boxPos;
|
1396
|
+
if ($isEmpty) {
|
1397
|
+
$et->VPrint(0,' Deleting ' . join('+', sort map { $emptyMeta{$_} } keys %boxPos)) if %boxPos;
|
1398
|
+
$$outfile = '';
|
1399
|
+
++$$et{CHANGED};
|
1400
|
+
}
|
1401
|
+
if ($curPath eq 'MOV-Movie-Meta') {
|
1402
|
+
delete $$addDirs{Keys}; # prevent creation of another Meta for Keys tags
|
1403
|
+
delete $$et{Keys};
|
1404
|
+
}
|
1405
|
+
}
|
1155
1406
|
|
1156
1407
|
# return now if writing subdirectory
|
1157
|
-
|
1408
|
+
if ($dataPt) {
|
1409
|
+
$et->Error("Internal error: WriteLast not on top-level atom!\n") if $writeLast;
|
1410
|
+
return $err ? undef : $$outfile;
|
1411
|
+
}
|
1158
1412
|
|
1159
1413
|
# issue minor error if we didn't find an 'mdat' atom
|
1160
|
-
my $off = $$dirInfo{ChunkOffset}
|
1414
|
+
my $off = $$dirInfo{ChunkOffset};
|
1161
1415
|
if (not @mdat) {
|
1162
|
-
|
1416
|
+
foreach $co (@$off) {
|
1417
|
+
next if $$co[0] eq 'uuid';
|
1163
1418
|
$et->Error('Movie data referenced but not found');
|
1164
1419
|
return $rtnVal;
|
1165
1420
|
}
|
@@ -1266,11 +1521,52 @@ sub WriteQuickTime($$$)
|
|
1266
1521
|
$pos += $$mdat[4] ? length(${$$mdat[4]}) : $$mdat[1] - $$mdat[0];
|
1267
1522
|
}
|
1268
1523
|
|
1269
|
-
# fix up offsets for new mdat position(s)
|
1524
|
+
# fix up offsets for new mdat position(s) (and uuid positions in CR3 images)
|
1270
1525
|
foreach $co (@$off) {
|
1271
1526
|
my ($type, $ptr, $len, $base, $id) = @$co;
|
1272
1527
|
$base = 0 unless $base;
|
1273
|
-
$type =~ /^(stco|co64)_?(.*)$/
|
1528
|
+
unless ($type =~ /^(stco|co64)_?(.*)$/) {
|
1529
|
+
next if $type eq 'uuid';
|
1530
|
+
$type eq 'CTBO' or $et->Error('Internal error fixing offsets'), last;
|
1531
|
+
# update 'CTBO' item offsets/sizes in Canon CR3 images
|
1532
|
+
$$co[2] > 12 or $et->Error('Invalid CTBO atom'), last;
|
1533
|
+
@mdat or $et->Error('Missing CR3 image data'), last;
|
1534
|
+
my $n = Get32u($outfile, $$co[1] + 8);
|
1535
|
+
$$co[2] < $n * 20 + 12 and $et->Error('Truncated CTBO atom'), last;
|
1536
|
+
my (%ctboOff, $i);
|
1537
|
+
# determine uuid types, and build an offset lookup based on CTBO ID number
|
1538
|
+
foreach (@$off) {
|
1539
|
+
next unless $$_[0] eq 'uuid' and $$_[2] >= 24; # (ignore undersized and deleted uuid boxes)
|
1540
|
+
my $pos = $$_[1];
|
1541
|
+
next if $pos + 24 > length $$outfile; # (will happen for WriteLast uuid tags)
|
1542
|
+
my $siz = Get32u($outfile, $pos); # get size of uuid atom
|
1543
|
+
if ($siz == 1) { # check for extended (8-byte) size
|
1544
|
+
next unless $$_[2] >= 32;
|
1545
|
+
$pos += 8;
|
1546
|
+
}
|
1547
|
+
# get CTBO entry ID based on 16-byte UUID identifier
|
1548
|
+
my $id = $ctboID{substr($$outfile, $pos+8, 16)};
|
1549
|
+
$ctboOff{$id} = $_ if defined $id;
|
1550
|
+
}
|
1551
|
+
# calculate new offset for the first mdat (size of -1 indicates it didn't change)
|
1552
|
+
$ctboOff{3} = [ 'mdat', $mdat[0][3] - length $mdat[0][2], -1 ];
|
1553
|
+
for ($i=0; $i<$n; ++$i) {
|
1554
|
+
my $pos = $$co[1] + 12 + $i * 20;
|
1555
|
+
my $id = Get32u($outfile, $pos);
|
1556
|
+
# ignore if size is zero unless we can add this entry
|
1557
|
+
# (note: can't yet add/delete PreviewImage, but leave this possibility open)
|
1558
|
+
next unless Get64u($outfile, $pos + 12) or $id == 1 or $id == 2;
|
1559
|
+
if (not defined $ctboOff{$id}) {
|
1560
|
+
$id==1 or $id==2 or $et->Error("Can't handle CR3 CTBO ID number $id"), last;
|
1561
|
+
# XMP or PreviewImage was deleted -- set offset and size to zero
|
1562
|
+
$ctboOff{$id} = [ 'uuid', 0, 0 ];
|
1563
|
+
}
|
1564
|
+
# update the new offset and size of this entry
|
1565
|
+
Set64u($ctboOff{$id}[1], $outfile, $pos + 4);
|
1566
|
+
Set64u($ctboOff{$id}[2], $outfile, $pos + 12) unless $ctboOff{$id}[2] < 0;
|
1567
|
+
}
|
1568
|
+
next;
|
1569
|
+
}
|
1274
1570
|
my $siz = $1 eq 'co64' ? 8 : 4;
|
1275
1571
|
my ($n, $tag);
|
1276
1572
|
if ($2) { # is this an offset in an iloc or 'gps ' atom?
|
@@ -1354,6 +1650,9 @@ sub WriteQuickTime($$$)
|
|
1354
1650
|
}
|
1355
1651
|
}
|
1356
1652
|
|
1653
|
+
# write the stuff that must come last
|
1654
|
+
Write($outfile, $writeLast) or $rtnVal = 0 if $writeLast;
|
1655
|
+
|
1357
1656
|
return $rtnVal;
|
1358
1657
|
}
|
1359
1658
|
|
@@ -1397,12 +1696,15 @@ sub WriteMOV($$)
|
|
1397
1696
|
$et->SetFileType($ftype); # need to set "FileType" tag for a Condition
|
1398
1697
|
$et->InitWriteDirs($dirMap{$ftype}, 'XMP');
|
1399
1698
|
$$et{DirMap} = $dirMap{$ftype}; # need access to directory map when writing
|
1699
|
+
# track tags globally to avoid creating multiple tags in the case of duplicate directories
|
1700
|
+
$$et{DidTag} = { };
|
1400
1701
|
SetByteOrder('MM');
|
1401
1702
|
$raf->Seek(0,0);
|
1402
1703
|
|
1403
1704
|
# write the file
|
1404
1705
|
$$dirInfo{Parent} = '';
|
1405
1706
|
$$dirInfo{DirName} = 'MOV';
|
1707
|
+
$$dirInfo{ChunkOffset} = [ ]; # (just to be safe)
|
1406
1708
|
return WriteQuickTime($et, $dirInfo, $tagTablePtr) ? 1 : -1;
|
1407
1709
|
}
|
1408
1710
|
|