exiftool_vendored 11.42.5 → 11.43.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 +20 -2
- data/bin/MANIFEST +4 -0
- data/bin/META.json +1 -1
- data/bin/META.yml +1 -1
- data/bin/README +2 -2
- data/bin/exiftool +5 -3
- data/bin/lib/Image/ExifTool.pm +5 -1
- data/bin/lib/Image/ExifTool/Canon.pm +6 -1
- data/bin/lib/Image/ExifTool/QuickTime.pm +41 -11
- data/bin/lib/Image/ExifTool/TagLookup.pm +2356 -2355
- data/bin/lib/Image/ExifTool/TagNames.pod +5 -3
- data/bin/lib/Image/ExifTool/WriteExif.pl +14 -7
- data/bin/lib/Image/ExifTool/WriteQuickTime.pl +456 -34
- data/bin/lib/Image/ExifTool/Writer.pl +31 -3
- data/bin/perl-Image-ExifTool.spec +1 -1
- data/lib/exiftool_vendored/version.rb +1 -1
- data/lib/exiftool_vendored/version_extractor.rb +1 -1
- metadata +16 -2
@@ -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 22584 tags, with 15004 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
|
@@ -24227,6 +24227,7 @@ ItemList tags written by the "mdta" handler. The prefix of
|
|
24227
24227
|
'direction.facing' CameraDirection string
|
24228
24228
|
'direction.motion' CameraMotion string
|
24229
24229
|
'director' Director string
|
24230
|
+
'displayname' DisplayName string
|
24230
24231
|
'genre' Genre string
|
24231
24232
|
'information' Information string
|
24232
24233
|
'keywords' Keywords string
|
@@ -24436,6 +24437,7 @@ added dynamically to this table after processing the Meta Keys information.
|
|
24436
24437
|
'yrrc' Year string
|
24437
24438
|
"\xa9ART" Artist string
|
24438
24439
|
"\xa9alb" Album string
|
24440
|
+
"\xa9aut" Author string
|
24439
24441
|
"\xa9cmt" Comment string
|
24440
24442
|
"\xa9com" Composer string
|
24441
24443
|
"\xa9cpy" Copyright string
|
@@ -24515,9 +24517,9 @@ defined in this table.
|
|
24515
24517
|
'clap' CleanAperture no
|
24516
24518
|
'colr' ICC_Profile ICC_Profile
|
24517
24519
|
'hvcC' HEVCConfiguration? no
|
24518
|
-
'irot' Rotation
|
24520
|
+
'irot' Rotation int8u
|
24519
24521
|
'ispe' ImageSpatialExtent no
|
24520
|
-
'pasp' PixelAspectRatio
|
24522
|
+
'pasp' PixelAspectRatio int32u
|
24521
24523
|
'pixi' ImagePixelDepth no
|
24522
24524
|
'rloc' RelativeLocation no
|
24523
24525
|
|
@@ -412,7 +412,7 @@ sub ExifErr($$$)
|
|
412
412
|
{
|
413
413
|
my ($et, $errStr, $tagTablePtr) = @_;
|
414
414
|
# MakerNote errors are minor by default
|
415
|
-
my $minor = ($$tagTablePtr{GROUPS}{0} eq 'MakerNotes');
|
415
|
+
my $minor = ($$tagTablePtr{GROUPS}{0} eq 'MakerNotes' or $$et{FILE_TYPE} eq 'MOV');
|
416
416
|
if ($$tagTablePtr{VARS} and $$tagTablePtr{VARS}{MINOR_ERRORS}) {
|
417
417
|
$et->Warn("$errStr. IFD dropped.") and return '' if $minor;
|
418
418
|
$minor = 1;
|
@@ -574,7 +574,7 @@ sub WriteExif($$$)
|
|
574
574
|
# only account for nextIFD pointer if we are going to use it
|
575
575
|
$len += 4 if $dataLen==$len+6 and ($$dirInfo{Multi} or $buff =~ /\0{4}$/);
|
576
576
|
UpdateTiffEnd($et, $offset+$base+2+$len);
|
577
|
-
} elsif ($dirLen) {
|
577
|
+
} elsif ($dirLen and $dirStart + 4 >= $dataLen) {
|
578
578
|
# error if we can't load IFD (unless we are creating
|
579
579
|
# from scratch, in which case dirLen will be zero)
|
580
580
|
my $str = $et->Options('IgnoreMinorErrors') ? 'Deleted bad' : 'Bad';
|
@@ -586,7 +586,10 @@ sub WriteExif($$$)
|
|
586
586
|
$numEntries = Get16u($dataPt, $dirStart);
|
587
587
|
$dirEnd = $dirStart + 2 + 12 * $numEntries;
|
588
588
|
if ($dirEnd > $dataLen) {
|
589
|
-
|
589
|
+
my $n = int(($dataLen - $dirStart - 2) / 12);
|
590
|
+
my $rtn = ExifErr($et, "Truncated $name directory", $tagTablePtr);
|
591
|
+
return undef unless $n and defined $rtn;
|
592
|
+
$numEntries = $n; # continue processing the entries we have
|
590
593
|
}
|
591
594
|
# sort entries if necessary (but not in maker notes IFDs)
|
592
595
|
unless ($inMakerNotes) {
|
@@ -1345,11 +1348,15 @@ NoOverwrite: next if $isNew > 0;
|
|
1345
1348
|
if ($$et{DEL_GROUP}{MakerNotes} and
|
1346
1349
|
($$et{DEL_GROUP}{MakerNotes} != 2 or $isNew <= 0))
|
1347
1350
|
{
|
1348
|
-
if ($
|
1349
|
-
|
1350
|
-
|
1351
|
+
if ($et->IsRawType()) {
|
1352
|
+
$et->WarnOnce("Can't delete MakerNotes from $$et{FileType} file",1);
|
1353
|
+
} else {
|
1354
|
+
if ($isNew <= 0) {
|
1355
|
+
++$$et{CHANGED};
|
1356
|
+
$verbose and print $out " Deleting MakerNotes\n";
|
1357
|
+
}
|
1358
|
+
next;
|
1351
1359
|
}
|
1352
|
-
next;
|
1353
1360
|
}
|
1354
1361
|
my $saveOrder = GetByteOrder();
|
1355
1362
|
if ($isNew >= 0 and defined $set{$newID}) {
|
@@ -9,9 +9,9 @@ package Image::ExifTool::QuickTime;
|
|
9
9
|
|
10
10
|
use strict;
|
11
11
|
|
12
|
-
#
|
12
|
+
# maps for adding metadata to various QuickTime-based file types
|
13
13
|
my %movMap = (
|
14
|
-
# MOV (no 'ftyp', or 'ftyp'='qt ') -> 'moov'-'udta'-'XMP_'
|
14
|
+
# MOV (no 'ftyp', or 'ftyp'='qt ') -> XMP in 'moov'-'udta'-'XMP_'
|
15
15
|
QuickTime => 'ItemList',
|
16
16
|
ItemList => 'Meta',
|
17
17
|
Meta => 'UserData',
|
@@ -20,7 +20,7 @@ my %movMap = (
|
|
20
20
|
Movie => 'MOV',
|
21
21
|
);
|
22
22
|
my %mp4Map = (
|
23
|
-
# MP4 ('ftyp' compatible brand 'mp41', 'mp42' or 'f4v ') -> top level
|
23
|
+
# MP4 ('ftyp' compatible brand 'mp41', 'mp42' or 'f4v ') -> XMP at top level
|
24
24
|
QuickTime => 'ItemList',
|
25
25
|
ItemList => 'Meta',
|
26
26
|
Meta => 'UserData',
|
@@ -28,10 +28,39 @@ my %mp4Map = (
|
|
28
28
|
Movie => 'MOV',
|
29
29
|
XMP => 'MOV',
|
30
30
|
);
|
31
|
+
my %heicMap = (
|
32
|
+
# HEIC ('ftyp' compatible brand 'heic' or 'mif1') -> XMP/EXIF in top level 'meta'
|
33
|
+
Meta => 'MOV',
|
34
|
+
ItemInformation => 'Meta',
|
35
|
+
ItemPropertyContainer => 'Meta',
|
36
|
+
XMP => 'ItemInformation',
|
37
|
+
EXIF => 'ItemInformation',
|
38
|
+
ICC_Profile => 'ItemPropertyContainer',
|
39
|
+
IFD0 => 'EXIF',
|
40
|
+
IFD1 => 'IFD0',
|
41
|
+
ExifIFD => 'IFD0',
|
42
|
+
GPS => 'IFD0',
|
43
|
+
SubIFD => 'IFD0',
|
44
|
+
GlobParamIFD => 'IFD0',
|
45
|
+
PrintIM => 'IFD0',
|
46
|
+
InteropIFD => 'ExifIFD',
|
47
|
+
MakerNotes => 'ExifIFD',
|
48
|
+
);
|
49
|
+
my %cr3Map = (
|
50
|
+
# CR3 ('ftyp' compatible brand 'crx ') -> XMP at top level
|
51
|
+
Movie => 'MOV',
|
52
|
+
XMP => 'MOV',
|
53
|
+
'UUID-Canon'=>'Movie',
|
54
|
+
ExifIFD => 'UUID-Canon',
|
55
|
+
IFD0 => 'UUID-Canon',
|
56
|
+
GPS => 'UUID-Canon',
|
57
|
+
#MakerNoteCanon => 'UUID-Canon', # (doesn't yet work -- goes into ExifIFD instead)
|
58
|
+
);
|
31
59
|
my %dirMap = (
|
32
|
-
MOV
|
33
|
-
MP4
|
34
|
-
|
60
|
+
MOV => \%movMap,
|
61
|
+
MP4 => \%mp4Map,
|
62
|
+
CR3 => \%cr3Map,
|
63
|
+
HEIC => \%heicMap,
|
35
64
|
);
|
36
65
|
|
37
66
|
# convert ExifTool Format to QuickTime type
|
@@ -131,7 +160,7 @@ sub IsCurPath($$)
|
|
131
160
|
sub Handle_iloc($$$$)
|
132
161
|
{
|
133
162
|
my ($et, $dirInfo, $dataPt, $outfile) = @_;
|
134
|
-
my ($i, $j, $num, $pos);
|
163
|
+
my ($i, $j, $num, $pos, $id);
|
135
164
|
|
136
165
|
my $off = $$dirInfo{ChunkOffset};
|
137
166
|
my $len = length $$dataPt;
|
@@ -157,11 +186,11 @@ sub Handle_iloc($$$$)
|
|
157
186
|
for ($i=0; $i<$num; ++$i) {
|
158
187
|
if ($ver < 2) {
|
159
188
|
return 0 if $pos + 2 > $len;
|
160
|
-
|
189
|
+
$id = Get16u($dataPt, $pos);
|
161
190
|
$pos += 2;
|
162
191
|
} else {
|
163
192
|
return 0 if $pos + 4 > $len;
|
164
|
-
|
193
|
+
$id = Get32u($dataPt, $pos);
|
165
194
|
$pos += 4;
|
166
195
|
}
|
167
196
|
my ($constOff, @offBase, @offItem, $minOffset);
|
@@ -189,7 +218,7 @@ sub Handle_iloc($$$$)
|
|
189
218
|
my $base_offset = GetVarInt($dataPt, $pos, $nbas);
|
190
219
|
if ($base_offset and not $constOff) {
|
191
220
|
my $tg = ($nbas == 4 ? 'stco' : 'co64') . '_iloc';
|
192
|
-
push @offBase, [ $tg, length($$outfile) + 8 + $pos - $nbas, $nbas ];
|
221
|
+
push @offBase, [ $tg, length($$outfile) + 8 + $pos - $nbas, $nbas, 0, $id ];
|
193
222
|
}
|
194
223
|
return 0 if $pos + 2 > $len;
|
195
224
|
my $ext_num = Get16u($dataPt, $pos);
|
@@ -201,7 +230,7 @@ sub Handle_iloc($$$$)
|
|
201
230
|
my $extent_offset = GetVarInt($dataPt, $pos, $noff);
|
202
231
|
return 0 unless defined $extent_offset;
|
203
232
|
unless ($constOff) {
|
204
|
-
push @offItem, [ $tag, length($$outfile) + 8 + $pos - $noff, $noff ] if $noff;
|
233
|
+
push @offItem, [ $tag, length($$outfile) + 8 + $pos - $noff, $noff, 0, $id ] if $noff;
|
205
234
|
$minOffset = $extent_offset if not defined $minOffset or $minOffset > $extent_offset;
|
206
235
|
}
|
207
236
|
return 0 if $pos + $nlen > length $$dataPt;
|
@@ -274,6 +303,268 @@ sub FormatQTValue($$;$)
|
|
274
303
|
return $flags;
|
275
304
|
}
|
276
305
|
|
306
|
+
#------------------------------------------------------------------------------
|
307
|
+
# Set variable-length integer (used by WriteItemInfo)
|
308
|
+
# Inputs: 0) value, 1) integer size in bytes (0, 4 or 8),
|
309
|
+
# Returns: packed integer
|
310
|
+
sub SetVarInt($$)
|
311
|
+
{
|
312
|
+
my ($val, $n) = @_;
|
313
|
+
if ($n == 4) {
|
314
|
+
return Set32u($val);
|
315
|
+
} elsif ($n == 8) {
|
316
|
+
return Set64u($val);
|
317
|
+
}
|
318
|
+
return '';
|
319
|
+
}
|
320
|
+
|
321
|
+
#------------------------------------------------------------------------------
|
322
|
+
# Write ItemInformation in HEIC files
|
323
|
+
# Inputs: 0) ExifTool ref, 1) dirInfo ref (with BoxPos entry), 2) output buffer ref
|
324
|
+
# Returns: mdat edit list ref (empty if nothing changed)
|
325
|
+
sub WriteItemInfo($$$)
|
326
|
+
{
|
327
|
+
my ($et, $dirInfo, $outfile) = @_;
|
328
|
+
my $boxPos = $$dirInfo{BoxPos};
|
329
|
+
my $raf = $$et{RAF};
|
330
|
+
my $items = $$et{ItemInfo};
|
331
|
+
my (%did, @mdatEdit, $name);
|
332
|
+
|
333
|
+
return () unless $items and $raf;
|
334
|
+
|
335
|
+
# extract information from EXIF/XMP metadata items
|
336
|
+
if ($items and $raf) {
|
337
|
+
my $curPos = $raf->Tell();
|
338
|
+
my $primary = $$et{PrimaryItem} || 0;
|
339
|
+
my $id;
|
340
|
+
foreach $id (sort { $a <=> $b } keys %$items) {
|
341
|
+
my $item = $$items{$id};
|
342
|
+
# only edit primary EXIF/XMP metadata
|
343
|
+
next unless $$item{RefersTo} and $$item{RefersTo}{$primary};
|
344
|
+
my $type = $$item{ContentType} || $$item{Type} || next;
|
345
|
+
# get ExifTool name for this item
|
346
|
+
$name = { Exif => 'EXIF', 'application/rdf+xml' => 'XMP' }->{$type};
|
347
|
+
next unless $name; # only care about EXIF and XMP
|
348
|
+
next unless $$et{EDIT_DIRS}{$name};
|
349
|
+
$did{$name} = 1; # set flag to prevent creating this metadata
|
350
|
+
my ($warn, $extent, $buff, @edit);
|
351
|
+
$warn = 'Missing iloc box' unless $$boxPos{iloc};
|
352
|
+
$warn = "No Extents for $type item" unless $$item{Extents} and @{$$item{Extents}};
|
353
|
+
$warn = "Can't currently decode encoded $type metadata" if $$item{ContentEncoding};
|
354
|
+
$warn = "Can't currently decode protected $type metadata" if $$item{ProtectionIndex};
|
355
|
+
$warn = "Can't currently extract $type with construction method $$item{ConstructionMethod}" if $$item{ConstructionMethod};
|
356
|
+
$warn = "$type metadata is not this file" if $$item{DataReferenceIndex};
|
357
|
+
$warn and $et->Warn($warn), next;
|
358
|
+
my $base = $$item{BaseOffset} || 0;
|
359
|
+
my $val = '';
|
360
|
+
foreach $extent (@{$$item{Extents}}) {
|
361
|
+
$val .= $buff if defined $buff;
|
362
|
+
my $pos = $$extent[1] + $base;
|
363
|
+
if ($$extent[2]) {
|
364
|
+
$raf->Seek($pos, 0) or last;
|
365
|
+
$raf->Read($buff, $$extent[2]) or last;
|
366
|
+
} else {
|
367
|
+
$buff = '';
|
368
|
+
}
|
369
|
+
push @edit, [ $pos, $pos + $$extent[2] ]; # replace or delete this if changed
|
370
|
+
}
|
371
|
+
next unless defined $buff;
|
372
|
+
$buff = $val . $buff if length $val;
|
373
|
+
my ($hdr, $subTable, $proc);
|
374
|
+
if ($name eq 'EXIF') {
|
375
|
+
$hdr = "\0\0\0\x06Exif\0\0";
|
376
|
+
$subTable = GetTagTable('Image::ExifTool::Exif::Main');
|
377
|
+
$proc = \&Image::ExifTool::WriteTIFF;
|
378
|
+
} else {
|
379
|
+
$hdr = '';
|
380
|
+
$subTable = GetTagTable('Image::ExifTool::XMP::Main');
|
381
|
+
}
|
382
|
+
my %dirInfo = (
|
383
|
+
DataPt => \$buff,
|
384
|
+
DataLen => length $buff,
|
385
|
+
DirStart => length $hdr,
|
386
|
+
DirLen => length($buff) - length $hdr,
|
387
|
+
);
|
388
|
+
my $changed = $$et{CHANGED};
|
389
|
+
my $newVal = $et->WriteDirectory(\%dirInfo, $subTable, $proc);
|
390
|
+
if (defined $newVal and $changed ne $$et{CHANGED} and
|
391
|
+
# nothing changed if deleting an empty directory
|
392
|
+
($dirInfo{DirLen} or length $newVal))
|
393
|
+
{
|
394
|
+
$newVal = $hdr . $newVal if length $hdr and length $newVal;
|
395
|
+
$edit[0][2] = \$newVal; # replace the old chunk with the new data
|
396
|
+
$edit[0][3] = $id; # mark this chunk with the item ID
|
397
|
+
push @mdatEdit, @edit;
|
398
|
+
# update item extent_length
|
399
|
+
my $n = length $newVal;
|
400
|
+
foreach $extent (@{$$item{Extents}}) {
|
401
|
+
my ($nlen, $lenPt) = @$extent[3,4];
|
402
|
+
if ($nlen == 8) {
|
403
|
+
Set64u($n, $outfile, $$boxPos{iloc}[0] + 8 + $lenPt);
|
404
|
+
} elsif ($n <= 0xffffffff) {
|
405
|
+
Set32u($n, $outfile, $$boxPos{iloc}[0] + 8 + $lenPt);
|
406
|
+
} else {
|
407
|
+
$et->Error("Can't yet promote iloc offset to 64 bits");
|
408
|
+
return ();
|
409
|
+
}
|
410
|
+
$n = 0;
|
411
|
+
}
|
412
|
+
if (@{$$item{Extents}} != 1) {
|
413
|
+
$et->Error("Can't yet handle $name in multiple parts. Please submit sample for testing");
|
414
|
+
}
|
415
|
+
}
|
416
|
+
$$et{CHANGED} = $changed; # (will set this later if successful in editing mdat)
|
417
|
+
}
|
418
|
+
$raf->Seek($curPos, 0); # seek back to original position
|
419
|
+
}
|
420
|
+
# add necessary metadata types if they didn't already exist
|
421
|
+
my ($countNew, %add, %usedID);
|
422
|
+
foreach $name ('EXIF','XMP') {
|
423
|
+
next if $did{$name} or not $$et{ADD_DIRS}{$name};
|
424
|
+
unless ($$boxPos{iinf} and $$boxPos{iref} and $$boxPos{iloc}) {
|
425
|
+
$et->Warn("Can't create $name. Missing expected box");
|
426
|
+
last;
|
427
|
+
}
|
428
|
+
my $primary = $$et{PrimaryItem};
|
429
|
+
unless (defined $primary) {
|
430
|
+
$et->Warn("Can't create $name. No primary item reference");
|
431
|
+
last;
|
432
|
+
}
|
433
|
+
my $buff = '';
|
434
|
+
my ($hdr, $subTable, $proc);
|
435
|
+
if ($name eq 'EXIF') {
|
436
|
+
$hdr = "\0\0\0\x06Exif\0\0";
|
437
|
+
$subTable = GetTagTable('Image::ExifTool::Exif::Main');
|
438
|
+
$proc = \&Image::ExifTool::WriteTIFF;
|
439
|
+
} else {
|
440
|
+
$hdr = '';
|
441
|
+
$subTable = GetTagTable('Image::ExifTool::XMP::Main');
|
442
|
+
}
|
443
|
+
my %dirInfo = (
|
444
|
+
DataPt => \$buff,
|
445
|
+
DataLen => 0,
|
446
|
+
DirStart => 0,
|
447
|
+
DirLen => 0,
|
448
|
+
);
|
449
|
+
my $changed = $$et{CHANGED};
|
450
|
+
my $newVal = $et->WriteDirectory(\%dirInfo, $subTable, $proc);
|
451
|
+
if (defined $newVal and $changed ne $$et{CHANGED}) {
|
452
|
+
$newVal = $hdr . $newVal if length $hdr;
|
453
|
+
# add new infe to iinf
|
454
|
+
$add{iinf} = $add{iref} = $add{iloc} = '' unless defined $add{iinf};
|
455
|
+
my ($type, $mime);
|
456
|
+
if ($name eq 'XMP') {
|
457
|
+
$type = "mime\0";
|
458
|
+
$mime = "application/rdf+xml\0";
|
459
|
+
} else {
|
460
|
+
$type = "Exif\0";
|
461
|
+
$mime = '';
|
462
|
+
}
|
463
|
+
my $id = 1;
|
464
|
+
++$id while $$items{$id} or $usedID{$id}; # find next unused item ID
|
465
|
+
my $n = length($type) + length($mime) + 16;
|
466
|
+
if ($id < 0x10000) {
|
467
|
+
$add{iinf} .= pack('Na4CCCCnn', $n, 'infe', 2, 0, 0, 1, $id, 0) . $type . $mime;
|
468
|
+
} else {
|
469
|
+
$n += 2;
|
470
|
+
$add{iinf} .= pack('Na4CCCCNn', $n, 'infe', 3, 0, 0, 1, $id, 0) . $type . $mime;
|
471
|
+
}
|
472
|
+
# add new cdsc to iref
|
473
|
+
my $irefVer = Get8u($outfile, $$boxPos{iref}[0] + 8);
|
474
|
+
if ($irefVer) {
|
475
|
+
$add{iref} .= pack('Na4NnN', 18, 'cdsc', $id, 1, $primary);
|
476
|
+
} else {
|
477
|
+
$add{iref} .= pack('Na4nnn', 14, 'cdsc', $id, 1, $primary);
|
478
|
+
}
|
479
|
+
# add new entry to iloc table (see ISO14496-12:2015 pg.79)
|
480
|
+
my $ilocVer = Get8u($outfile, $$boxPos{iloc}[0] + 8);
|
481
|
+
my $siz = Get16u($outfile, $$boxPos{iloc}[0] + 12); # get size information
|
482
|
+
my $noff = ($siz >> 12);
|
483
|
+
my $nlen = ($siz >> 8) & 0x0f;
|
484
|
+
my $nbas = ($siz >> 4) & 0x0f;
|
485
|
+
my $nind = $siz & 0x0f;
|
486
|
+
my $p;
|
487
|
+
if ($ilocVer == 0) {
|
488
|
+
# set offset to 0 as flag that this is a new idat chunk being added
|
489
|
+
$p = length($add{iloc}) + 4 + $nbas + 2;
|
490
|
+
$add{iloc} .= pack('nn',$id,0) . SetVarInt(0,$nbas) . Set16u(1) .
|
491
|
+
SetVarInt(0,$noff) . SetVarInt(length($newVal),$nlen);
|
492
|
+
} elsif ($ilocVer == 1) {
|
493
|
+
$p = length($add{iloc}) + 6 + $nbas + 2 + $nind;
|
494
|
+
$add{iloc} .= pack('nnn',$id,0,0) . SetVarInt(0,$nbas) . Set16u(1) . SetVarInt(0,$nind) .
|
495
|
+
SetVarInt(0,$noff) . SetVarInt(length($newVal),$nlen);
|
496
|
+
} elsif ($ilocVer == 2) {
|
497
|
+
$p = length($add{iloc}) + 8 + $nbas + 2 + $nind;
|
498
|
+
$add{iloc} .= pack('Nnn',$id,0,0) . SetVarInt(0,$nbas) . Set16u(1) . SetVarInt(0,$nind) .
|
499
|
+
SetVarInt(0,$noff) . SetVarInt(length($newVal),$nlen);
|
500
|
+
} else {
|
501
|
+
$et->Warn("Can't create $name. Unsupported iloc version $ilocVer");
|
502
|
+
last;
|
503
|
+
}
|
504
|
+
# add new ChunkOffset entry to update this new offset
|
505
|
+
my $off = $$dirInfo{ChunkOffset} or $et->Warn('Internal error. Missing ChunkOffset'), last;
|
506
|
+
my $newOff;
|
507
|
+
if ($noff == 4) {
|
508
|
+
$newOff = [ 'stco_iloc', $$boxPos{iloc}[0] + $$boxPos{iloc}[1] + $p, $noff, 0, $id ];
|
509
|
+
} elsif ($noff == 8) {
|
510
|
+
$newOff = [ 'co64_iloc', $$boxPos{iloc}[0] + $$boxPos{iloc}[1] + $p, $noff, 0, $id ];
|
511
|
+
} else {
|
512
|
+
$et->Warn("Can't create $name. Invalid iloc offset size");
|
513
|
+
last;
|
514
|
+
}
|
515
|
+
# add directory as a new mdat chunk
|
516
|
+
push @$off, $newOff;
|
517
|
+
push @mdatEdit, [ 0, 0, \$newVal, $id ];
|
518
|
+
$usedID{$id} = 1;
|
519
|
+
$countNew = ($countNew || 0) + 1;
|
520
|
+
$$et{CHANGED} = $changed; # set this later if successful in editing mdat
|
521
|
+
}
|
522
|
+
}
|
523
|
+
if ($countNew) {
|
524
|
+
# insert new entries into iinf, iref and iloc boxes
|
525
|
+
my $added = 0;
|
526
|
+
my $tag;
|
527
|
+
foreach $tag (sort { $$boxPos{$a}[0] <=> $$boxPos{$b}[0] } keys %$boxPos) {
|
528
|
+
next unless $add{$tag};
|
529
|
+
my $pos = $$boxPos{$tag}[0] + $added;
|
530
|
+
my $n = Get32u($outfile, $pos);
|
531
|
+
Set32u($n + length($add{$tag}), $outfile, $pos); # increase box size
|
532
|
+
if ($tag eq 'iinf') {
|
533
|
+
my $iinfVer = Get8u($outfile, $pos + 8);
|
534
|
+
if ($iinfVer == 0) {
|
535
|
+
$n = Get16u($outfile, $pos + 12);
|
536
|
+
Set16u($n + $countNew, $outfile, $pos + 12); # incr count
|
537
|
+
} else {
|
538
|
+
$n = Get32u($outfile, $pos + 12);
|
539
|
+
Set32u($n + $countNew, $outfile, $pos + 12); # incr count
|
540
|
+
}
|
541
|
+
} elsif ($tag eq 'iref') {
|
542
|
+
# nothing more to do
|
543
|
+
} elsif ($tag eq 'iloc') {
|
544
|
+
my $ilocVer = Get8u($outfile, $pos + 8);
|
545
|
+
if ($ilocVer < 2) {
|
546
|
+
$n = Get16u($outfile, $pos + 14);
|
547
|
+
Set16u($n + $countNew, $outfile, $pos + 14); # incr count
|
548
|
+
} else {
|
549
|
+
$n = Get32u($outfile, $pos + 14);
|
550
|
+
Set32u($n + $countNew, $outfile, $pos + 14); # incr count
|
551
|
+
}
|
552
|
+
# must also update pointer locations in this box
|
553
|
+
if ($added) {
|
554
|
+
$$_[1] += $added foreach @{$$dirInfo{ChunkOffset}};
|
555
|
+
}
|
556
|
+
} else {
|
557
|
+
next;
|
558
|
+
}
|
559
|
+
# add new entries to this box
|
560
|
+
substr($$outfile, $pos + $$boxPos{$tag}[1], 0) = $add{$tag};
|
561
|
+
$added += length $add{$tag}; # positions are shifted by length of new entries
|
562
|
+
}
|
563
|
+
}
|
564
|
+
delete $$et{ItemInfo};
|
565
|
+
return @mdatEdit ? \@mdatEdit : undef;
|
566
|
+
}
|
567
|
+
|
277
568
|
#------------------------------------------------------------------------------
|
278
569
|
# Write a series of QuickTime atoms from file or in memory
|
279
570
|
# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
|
@@ -285,8 +576,8 @@ sub WriteQuickTime($$$)
|
|
285
576
|
local $_;
|
286
577
|
my ($et, $dirInfo, $tagTablePtr) = @_;
|
287
578
|
$et or return 1; # allow dummy access to autoload this package
|
288
|
-
my ($mdat, @mdat, $track, $outBuff, $co, $term, $err);
|
289
|
-
my (%langTags, $keysTags, $canCreate, %didTag, $delGrp);
|
579
|
+
my ($mdat, @mdat, @mdatEdit, $edit, $track, $outBuff, $co, $term, $err);
|
580
|
+
my (%langTags, $keysTags, $canCreate, %didTag, $delGrp, %boxPos);
|
290
581
|
my $outfile = $$dirInfo{OutFile} || return 0;
|
291
582
|
my $raf = $$dirInfo{RAF}; # (will be null for lower-level atoms)
|
292
583
|
my $dataPt = $$dirInfo{DataPt}; # (will be null for top-level atoms)
|
@@ -495,8 +786,8 @@ sub WriteQuickTime($$$)
|
|
495
786
|
Multi => $$subdir{Multi}, # necessary?
|
496
787
|
OutFile => $outfile,
|
497
788
|
# initialize array to hold details about chunk offset table
|
498
|
-
# (each entry has 3
|
499
|
-
#
|
789
|
+
# (each entry has 3-5 items: 0=atom type, 1=table offset, 2=table size,
|
790
|
+
# 3=optional base offset, 4=optional item ID)
|
500
791
|
ChunkOffset => \@chunkOffset,
|
501
792
|
);
|
502
793
|
# pass the header pointer if necessary (for EXIF IFD's
|
@@ -515,12 +806,17 @@ sub WriteQuickTime($$$)
|
|
515
806
|
# demote non-QuickTime errors to warnings
|
516
807
|
$$et{DemoteErrors} = 1 unless $$subTable{GROUPS}{0} eq 'QuickTime';
|
517
808
|
my $oldChanged = $$et{CHANGED};
|
518
|
-
$newData = $et->WriteDirectory(\%subdirInfo, $subTable);
|
809
|
+
$newData = $et->WriteDirectory(\%subdirInfo, $subTable, $$subdir{WriteProc});
|
519
810
|
if ($$et{DemoteErrors}) {
|
520
|
-
# just copy existing subdirectory a non-quicktime error occurred
|
811
|
+
# just copy existing subdirectory if a non-quicktime error occurred
|
521
812
|
$$et{CHANGED} = $oldChanged if $$et{DemoteErrors} > 1;
|
522
813
|
delete $$et{DemoteErrors};
|
523
814
|
}
|
815
|
+
if (defined $newData and not length $newData and $$tagTablePtr{PERMANENT}) {
|
816
|
+
# do nothing if trying to delete tag from a PERMANENT table
|
817
|
+
$$et{CHANGED} = $oldChanged;
|
818
|
+
undef $newData;
|
819
|
+
}
|
524
820
|
$$et{CUR_WRITE_GROUP} = $oldWriteGroup;
|
525
821
|
SetByteOrder('MM');
|
526
822
|
# add back header if necessary
|
@@ -711,6 +1007,7 @@ sub WriteQuickTime($$$)
|
|
711
1007
|
$$_[1] += 8 + length $$outfile foreach @chunkOffset;
|
712
1008
|
push @{$$dirInfo{ChunkOffset}}, @chunkOffset;
|
713
1009
|
}
|
1010
|
+
$boxPos{$tag} = [ length($$outfile), length($newData) + 8 ];
|
714
1011
|
# write the updated directory now (unless length is zero, or it is needed as padding)
|
715
1012
|
Write($outfile, Set32u($len+8), $tag, $newData) or $rtnVal=$rtnErr, $err=1, last;
|
716
1013
|
next;
|
@@ -747,6 +1044,8 @@ sub WriteQuickTime($$$)
|
|
747
1044
|
}
|
748
1045
|
$$et{QtDataFlg} = $flg;
|
749
1046
|
}
|
1047
|
+
# save position of this box in the output buffer
|
1048
|
+
$boxPos{$tag} = [ length($$outfile), length($hdr) + length($buff) ];
|
750
1049
|
# copy the existing atom
|
751
1050
|
Write($outfile, $hdr, $buff) or $rtnVal=$rtnErr, $err=1, last;
|
752
1051
|
}
|
@@ -821,7 +1120,7 @@ sub WriteQuickTime($$$)
|
|
821
1120
|
OutFile => $outfile,
|
822
1121
|
);
|
823
1122
|
my $subTable = GetTagTable($$subdir{TagTable});
|
824
|
-
my $newData = $et->WriteDirectory(\%subdirInfo, $subTable);
|
1123
|
+
my $newData = $et->WriteDirectory(\%subdirInfo, $subTable, $$subdir{WriteProc});
|
825
1124
|
if ($newData and length($newData) <= 0x7ffffff7) {
|
826
1125
|
my $prefix = '';
|
827
1126
|
# add atom version or ID if necessary
|
@@ -841,6 +1140,15 @@ sub WriteQuickTime($$$)
|
|
841
1140
|
delete $$addDirs{$subName}; # add only once (must delete _after_ call to WriteDirectory())
|
842
1141
|
}
|
843
1142
|
}
|
1143
|
+
# write HEIC metadata after top-level 'meta' box has been processed if editing this information
|
1144
|
+
if ($dirName eq 'Meta' and $$et{EDIT_DIRS}{ItemInformation} and $curPath eq $writePath) {
|
1145
|
+
$$dirInfo{BoxPos} = \%boxPos;
|
1146
|
+
my $mdatEdit = WriteItemInfo($et, $dirInfo, $outfile);
|
1147
|
+
if ($mdatEdit) {
|
1148
|
+
$et->Error('Multiple top-level Meta containers') if $$et{mdatEdit};
|
1149
|
+
$$et{mdatEdit} = $mdatEdit;
|
1150
|
+
}
|
1151
|
+
}
|
844
1152
|
# write out any necessary terminator
|
845
1153
|
Write($outfile, $term) or $rtnVal=$rtnErr, $err=1 if $term;
|
846
1154
|
|
@@ -857,18 +1165,109 @@ sub WriteQuickTime($$$)
|
|
857
1165
|
$et->Warn('No movie data', 1);
|
858
1166
|
}
|
859
1167
|
|
1168
|
+
# edit mdat blocks as required
|
1169
|
+
# (0=old pos [0 if creating], 1=old end [0 if creating], 2=new data ref or undef to delete,
|
1170
|
+
# 3=new data item id)
|
1171
|
+
if ($$et{mdatEdit}) {
|
1172
|
+
@mdatEdit = @{$$et{mdatEdit}};
|
1173
|
+
delete $$et{mdatEdit};
|
1174
|
+
}
|
1175
|
+
foreach $edit (@mdatEdit) {
|
1176
|
+
my (@thisMdat, @newMdat, $changed);
|
1177
|
+
foreach $mdat (@mdat) {
|
1178
|
+
# keep track of all chunks for the mdat with this header
|
1179
|
+
if (length $$mdat[2]) {
|
1180
|
+
push @newMdat, @thisMdat;
|
1181
|
+
undef @thisMdat;
|
1182
|
+
}
|
1183
|
+
push @thisMdat, $mdat;
|
1184
|
+
# is this edit inside this mdat chunk?
|
1185
|
+
# - $$edit[0] and $$edit[1] will both be zero if we are creating a new chunk
|
1186
|
+
# - $$mdat[1] is zero if mdat runs to end of file
|
1187
|
+
# - $$edit[0] == $$edit[1] == $$mdat[0] if reviving a deleted chunk
|
1188
|
+
# - $$mdat[5] is defined if this was a newly added/edited chunk
|
1189
|
+
next if defined $$mdat[5] or $changed; # don't replace a newly added chunk
|
1190
|
+
if (not $$edit[0] or # (newly created chunk)
|
1191
|
+
# (edit is inside chunk)
|
1192
|
+
((($$edit[0] < $$mdat[1] or not $$mdat[1]) and $$edit[1] > $$mdat[0]) or
|
1193
|
+
# (edit inserted at start or end of chunk)
|
1194
|
+
($$edit[0] == $$edit[1] and ($$edit[0] == $$mdat[0] or $$edit[0] == $$mdat[1]))))
|
1195
|
+
{
|
1196
|
+
if (not $$edit[0]) {
|
1197
|
+
$$edit[0] = $$edit[1] = $$mdat[0]; # insert at start of this mdat
|
1198
|
+
} elsif ($$edit[0] < $$mdat[0] or ($$edit[1] > $$mdat[1] and $$mdat[1])) {
|
1199
|
+
$et->Error('ItemInfo runs across mdat boundary');
|
1200
|
+
return $rtnVal;
|
1201
|
+
}
|
1202
|
+
my $hdrChunk = $thisMdat[0];
|
1203
|
+
$hdrChunk or $et->Error('Internal error finding mdat header'), return $rtnVal;
|
1204
|
+
# calculate difference in mdat size
|
1205
|
+
my $diff = ($$edit[2] ? length(${$$edit[2]}) : 0) - ($$edit[1] - $$edit[0]);
|
1206
|
+
# edit size of mdat in header if necessary
|
1207
|
+
if ($diff) {
|
1208
|
+
if (length($$hdrChunk[2]) == 8) {
|
1209
|
+
my $size = Get32u(\$$hdrChunk[2], 0) + $diff;
|
1210
|
+
$size > 0xffffffff and $et->Error("Can't yet grow mdat across 4GB boundary"), return $rtnVal;
|
1211
|
+
Set32u($size, \$$hdrChunk[2], 0);
|
1212
|
+
} elsif (length($$hdrChunk[2]) == 16) {
|
1213
|
+
my $size = Get64u(\$$hdrChunk[2], 8) + $diff;
|
1214
|
+
Set64u($size, \$$hdrChunk[2], 8);
|
1215
|
+
} else {
|
1216
|
+
$et->Error('Internal error. Invalid mdat header');
|
1217
|
+
return $rtnVal;
|
1218
|
+
}
|
1219
|
+
}
|
1220
|
+
$changed = 1;
|
1221
|
+
# remove the edited section of this chunk (if any) and replace with new data (if any)
|
1222
|
+
if ($$edit[0] > $$mdat[0]) {
|
1223
|
+
push @thisMdat, [ $$edit[0], $$edit[1], '', 0, $$edit[2], $$edit[3] ] if $$edit[2];
|
1224
|
+
# add remaining data after edit (or empty stub in case it is referenced by an offset)
|
1225
|
+
push @thisMdat, [ $$edit[1], $$mdat[1], '' ];
|
1226
|
+
$$mdat[1] = $$edit[0]; # now ends at start of edit
|
1227
|
+
} else {
|
1228
|
+
if ($$edit[2]) {
|
1229
|
+
# insert the new chunk before this chunk, moving the header to the new chunk
|
1230
|
+
splice @thisMdat, -1, 0, [ $$edit[0],$$edit[1],$$mdat[2],0,$$edit[2],$$edit[3] ];
|
1231
|
+
$$mdat[2] = ''; # (header was moved to new chunk)
|
1232
|
+
# initialize ChunkOffset pointer if necessary
|
1233
|
+
if ($$edit[3]) {
|
1234
|
+
my $n = 0;
|
1235
|
+
foreach $co (@$off) {
|
1236
|
+
next unless defined $$co[4] and $$co[4] == $$edit[3];
|
1237
|
+
++$n;
|
1238
|
+
if ($$co[0] eq 'stco_iloc') {
|
1239
|
+
Set32u($$mdat[0], $outfile, $$co[1]);
|
1240
|
+
} else {
|
1241
|
+
Set64u($$mdat[0], $outfile, $$co[1]);
|
1242
|
+
}
|
1243
|
+
}
|
1244
|
+
$n == 1 or $et->Error('Internal error updating chunk offsets');
|
1245
|
+
}
|
1246
|
+
}
|
1247
|
+
$$mdat[0] = $$edit[1]; # remove old data
|
1248
|
+
}
|
1249
|
+
}
|
1250
|
+
}
|
1251
|
+
if ($changed) {
|
1252
|
+
@mdat = ( @newMdat, @thisMdat );
|
1253
|
+
++$$et{CHANGED};
|
1254
|
+
} else {
|
1255
|
+
$et->Error('Internal error modifying mdat');
|
1256
|
+
}
|
1257
|
+
}
|
1258
|
+
|
860
1259
|
# determine our new mdat positions
|
861
|
-
# (0=old pos, 1=old end, 2=mdat header, 3=new pos)
|
1260
|
+
# (0=old pos, 1=old end, 2=mdat header, 3=new pos, 4=new data ref if changed, 5=new item ID)
|
862
1261
|
my $pos = length $$outfile;
|
863
1262
|
foreach $mdat (@mdat) {
|
864
1263
|
$pos += length $$mdat[2];
|
865
1264
|
$$mdat[3] = $pos;
|
866
|
-
$pos += $$mdat[1] - $$mdat[0];
|
1265
|
+
$pos += $$mdat[4] ? length(${$$mdat[4]}) : $$mdat[1] - $$mdat[0];
|
867
1266
|
}
|
868
1267
|
|
869
1268
|
# fix up offsets for new mdat position(s)
|
870
1269
|
foreach $co (@$off) {
|
871
|
-
my ($type, $ptr, $len, $base) = @$co;
|
1270
|
+
my ($type, $ptr, $len, $base, $id) = @$co;
|
872
1271
|
$base = 0 unless $base;
|
873
1272
|
$type =~ /^(stco|co64)_?(.*)$/ or $et->Error('Internal error fixing offsets'), last;
|
874
1273
|
my $siz = $1 eq 'co64' ? 8 : 4;
|
@@ -887,10 +1286,23 @@ sub WriteQuickTime($$$)
|
|
887
1286
|
my $end = $ptr + $n * $siz;
|
888
1287
|
$end > $ptr + $len and $et->Error("Invalid $tag table"), return $rtnVal;
|
889
1288
|
for (; $ptr<$end; $ptr+=$siz) {
|
890
|
-
my $ok;
|
1289
|
+
my ($ok, $i);
|
891
1290
|
my $val = $type eq 'co64' ? Get64u($outfile, $ptr) : Get32u($outfile, $ptr);
|
892
|
-
|
893
|
-
|
1291
|
+
for ($i=0; $i<@mdat; ++$i) {
|
1292
|
+
$mdat = $mdat[$i];
|
1293
|
+
my $pos = $val + $base;
|
1294
|
+
if (defined $$mdat[5]) { # is this chunk associated with an item we edited?
|
1295
|
+
# set offset only for the corresponding new chunk
|
1296
|
+
unless (defined $id and $id == $$mdat[5]) {
|
1297
|
+
# could have pointed to empty chunk before inserted chunk
|
1298
|
+
next unless $pos == $$mdat[0] and $$mdat[0] != $$mdat[1];
|
1299
|
+
}
|
1300
|
+
} else {
|
1301
|
+
# (have seen $pos == $$mdat[1], which is a real PITA)
|
1302
|
+
next unless $pos >= $$mdat[0] and ($pos <= $$mdat[1] or not $$mdat[1]);
|
1303
|
+
# step to next chunk if contiguous and at the end of this one
|
1304
|
+
next if $pos == $$mdat[1] and $i+1 < @mdat and $pos == $mdat[$i+1][0];
|
1305
|
+
}
|
894
1306
|
$val += $$mdat[3] - $$mdat[0];
|
895
1307
|
if ($val < 0) {
|
896
1308
|
$et->Error("Error fixing up $tag offset");
|
@@ -922,17 +1334,21 @@ sub WriteQuickTime($$$)
|
|
922
1334
|
|
923
1335
|
# write the movie data
|
924
1336
|
foreach $mdat (@mdat) {
|
925
|
-
$raf->Seek($$mdat[0], 0) or $et->Error('Seek error'), last;
|
926
1337
|
Write($outfile, $$mdat[2]) or $rtnVal = 0; # write mdat header
|
927
|
-
if ($$mdat[
|
928
|
-
|
929
|
-
defined $result or $rtnVal = 0, last;
|
930
|
-
$result or $et->Error("Truncated mdat atom"), last;
|
1338
|
+
if ($$mdat[4]) {
|
1339
|
+
Write($outfile, ${$$mdat[4]}) or $rtnVal = 0;
|
931
1340
|
} else {
|
932
|
-
|
933
|
-
|
934
|
-
|
935
|
-
|
1341
|
+
$raf->Seek($$mdat[0], 0) or $et->Error('Seek error'), last;
|
1342
|
+
if ($$mdat[1]) {
|
1343
|
+
my $result = Image::ExifTool::CopyBlock($raf, $outfile, $$mdat[1] - $$mdat[0]);
|
1344
|
+
defined $result or $rtnVal = 0, last;
|
1345
|
+
$result or $et->Error("Truncated mdat atom"), last;
|
1346
|
+
} else {
|
1347
|
+
# mdat continues to end of file
|
1348
|
+
my $buff;
|
1349
|
+
while ($raf->Read($buff, 65536)) {
|
1350
|
+
Write($outfile, $buff) or $rtnVal = 0, last;
|
1351
|
+
}
|
936
1352
|
}
|
937
1353
|
}
|
938
1354
|
}
|
@@ -967,7 +1383,13 @@ sub WriteMOV($$)
|
|
967
1383
|
$raf->Read($buff, $size-8) == $size-8 and
|
968
1384
|
$buff !~ /^(....)+(qt )/s)
|
969
1385
|
{
|
970
|
-
|
1386
|
+
if ($buff =~ /^crx /) {
|
1387
|
+
$ftype = 'CR3',
|
1388
|
+
} elsif ($buff =~ /^(heic|mif1|msf1|heix|hevc|hevx)/) {
|
1389
|
+
$ftype = 'HEIC';
|
1390
|
+
} else {
|
1391
|
+
$ftype = 'MP4';
|
1392
|
+
}
|
971
1393
|
} else {
|
972
1394
|
$ftype = 'MOV';
|
973
1395
|
}
|