| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
|
2
|
|
|
|
|
|
|
# File: WriteQuickTime.pl |
|
3
|
|
|
|
|
|
|
# |
|
4
|
|
|
|
|
|
|
# Description: Write XMP to QuickTime (MOV and MP4) files |
|
5
|
|
|
|
|
|
|
# |
|
6
|
|
|
|
|
|
|
# Revisions: 2013-10-29 - P. Harvey Created |
|
7
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
|
8
|
|
|
|
|
|
|
package Image::ExifTool::QuickTime; |
|
9
|
|
|
|
|
|
|
|
|
10
|
22
|
|
|
22
|
|
227
|
use strict; |
|
|
22
|
|
|
|
|
74
|
|
|
|
22
|
|
|
|
|
347397
|
|
|
11
|
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
# maps for adding metadata to various QuickTime-based file types |
|
13
|
|
|
|
|
|
|
my %movMap = ( |
|
14
|
|
|
|
|
|
|
# MOV (no 'ftyp', or 'ftyp'='qt ') -> XMP in 'moov'-'udta'-'XMP_' |
|
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) |
|
18
|
|
|
|
|
|
|
Meta => 'UserData', |
|
19
|
|
|
|
|
|
|
XMP => 'UserData', # MOV-Movie-UserData-XMP |
|
20
|
|
|
|
|
|
|
Microsoft => 'UserData', # MOV-Movie-UserData-Microsoft |
|
21
|
|
|
|
|
|
|
UserData => 'Movie', # MOV-Movie-UserData |
|
22
|
|
|
|
|
|
|
Movie => 'MOV', |
|
23
|
|
|
|
|
|
|
GSpherical => 'SphericalVideoXML', # MOV-Movie-Track-SphericalVideoXML |
|
24
|
|
|
|
|
|
|
SphericalVideoXML => 'Track', # (video track specifically, don't create if it doesn't exist) |
|
25
|
|
|
|
|
|
|
Track => 'Movie', |
|
26
|
|
|
|
|
|
|
); |
|
27
|
|
|
|
|
|
|
my %mp4Map = ( |
|
28
|
|
|
|
|
|
|
# MP4 ('ftyp' compatible brand 'mp41', 'mp42' or 'f4v ') -> XMP at top level |
|
29
|
|
|
|
|
|
|
QuickTime => 'ItemList', # (default location for QuickTime tags) |
|
30
|
|
|
|
|
|
|
ItemList => 'Meta', # MOV-Movie-UserData-Meta-ItemList |
|
31
|
|
|
|
|
|
|
Keys => 'Movie', # MOV-Movie-Meta-Keys !! (hack due to different Meta location) |
|
32
|
|
|
|
|
|
|
Meta => 'UserData', |
|
33
|
|
|
|
|
|
|
UserData => 'Movie', # MOV-Movie-UserData |
|
34
|
|
|
|
|
|
|
Microsoft => 'UserData', # MOV-Movie-UserData-Microsoft |
|
35
|
|
|
|
|
|
|
Movie => 'MOV', |
|
36
|
|
|
|
|
|
|
XMP => 'MOV', # MOV-XMP |
|
37
|
|
|
|
|
|
|
GSpherical => 'SphericalVideoXML', # MOV-Movie-Track-SphericalVideoXML |
|
38
|
|
|
|
|
|
|
SphericalVideoXML => 'Track', # (video track specifically, don't create if it doesn't exist) |
|
39
|
|
|
|
|
|
|
Track => 'Movie', |
|
40
|
|
|
|
|
|
|
); |
|
41
|
|
|
|
|
|
|
my %heicMap = ( |
|
42
|
|
|
|
|
|
|
# HEIC/HEIF/AVIF ('ftyp' compatible brand 'heic','mif1','avif') -> XMP/EXIF in top level 'meta' |
|
43
|
|
|
|
|
|
|
Meta => 'MOV', |
|
44
|
|
|
|
|
|
|
ItemInformation => 'Meta', |
|
45
|
|
|
|
|
|
|
ItemPropertyContainer => 'Meta', |
|
46
|
|
|
|
|
|
|
XMP => 'ItemInformation', |
|
47
|
|
|
|
|
|
|
EXIF => 'ItemInformation', |
|
48
|
|
|
|
|
|
|
ICC_Profile => 'ItemPropertyContainer', |
|
49
|
|
|
|
|
|
|
IFD0 => 'EXIF', |
|
50
|
|
|
|
|
|
|
IFD1 => 'IFD0', |
|
51
|
|
|
|
|
|
|
ExifIFD => 'IFD0', |
|
52
|
|
|
|
|
|
|
GPS => 'IFD0', |
|
53
|
|
|
|
|
|
|
SubIFD => 'IFD0', |
|
54
|
|
|
|
|
|
|
GlobParamIFD => 'IFD0', |
|
55
|
|
|
|
|
|
|
PrintIM => 'IFD0', |
|
56
|
|
|
|
|
|
|
InteropIFD => 'ExifIFD', |
|
57
|
|
|
|
|
|
|
MakerNotes => 'ExifIFD', |
|
58
|
|
|
|
|
|
|
); |
|
59
|
|
|
|
|
|
|
my %cr3Map = ( |
|
60
|
|
|
|
|
|
|
# CR3 ('ftyp' compatible brand 'crx ') -> XMP at top level |
|
61
|
|
|
|
|
|
|
Movie => 'MOV', |
|
62
|
|
|
|
|
|
|
XMP => 'MOV', |
|
63
|
|
|
|
|
|
|
'UUID-Canon'=>'Movie', |
|
64
|
|
|
|
|
|
|
ExifIFD => 'UUID-Canon', |
|
65
|
|
|
|
|
|
|
IFD0 => 'UUID-Canon', |
|
66
|
|
|
|
|
|
|
GPS => 'UUID-Canon', |
|
67
|
|
|
|
|
|
|
#MakerNoteCanon => 'UUID-Canon', # (doesn't yet work -- goes into ExifIFD instead) |
|
68
|
|
|
|
|
|
|
'UUID-Canon2' => 'MOV', |
|
69
|
|
|
|
|
|
|
CanonVRD => 'UUID-Canon2', |
|
70
|
|
|
|
|
|
|
); |
|
71
|
|
|
|
|
|
|
my %dirMap = ( |
|
72
|
|
|
|
|
|
|
MOV => \%movMap, |
|
73
|
|
|
|
|
|
|
MP4 => \%mp4Map, |
|
74
|
|
|
|
|
|
|
CR3 => \%cr3Map, |
|
75
|
|
|
|
|
|
|
HEIC => \%heicMap, |
|
76
|
|
|
|
|
|
|
); |
|
77
|
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
# convert ExifTool Format to QuickTime type |
|
79
|
|
|
|
|
|
|
my %qtFormat = ( |
|
80
|
|
|
|
|
|
|
'undef' => 0x00, string => 0x01, |
|
81
|
|
|
|
|
|
|
int8s => 0x15, int16s => 0x15, int32s => 0x15, int64s => 0x15, |
|
82
|
|
|
|
|
|
|
int8u => 0x16, int16u => 0x16, int32u => 0x16, int64u => 0x16, |
|
83
|
|
|
|
|
|
|
float => 0x17, double => 0x18, |
|
84
|
|
|
|
|
|
|
); |
|
85
|
|
|
|
|
|
|
my $undLang = 0x55c4; # numeric code for default ('und') language |
|
86
|
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
my $maxReadLen = 100000000; # maximum size of atom to read into memory (100 MB) |
|
88
|
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
# boxes that may exist in an "empty" Meta box: |
|
90
|
|
|
|
|
|
|
my %emptyMeta = ( |
|
91
|
|
|
|
|
|
|
hdlr => 'Handler', 'keys' => 'Keys', lang => 'Language', ctry => 'Country', free => 'Free', |
|
92
|
|
|
|
|
|
|
); |
|
93
|
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
# lookup for CTBO ID number based on uuid for Canon CR3 files |
|
95
|
|
|
|
|
|
|
my %ctboID = ( |
|
96
|
|
|
|
|
|
|
"\xbe\x7a\xcf\xcb\x97\xa9\x42\xe8\x9c\x71\x99\x94\x91\xe3\xaf\xac" => 1, # XMP |
|
97
|
|
|
|
|
|
|
"\xea\xf4\x2b\x5e\x1c\x98\x4b\x88\xb9\xfb\xb7\xdc\x40\x6e\x4d\x16" => 2, # PreviewImage |
|
98
|
|
|
|
|
|
|
# ID 3 is used for 'mdat' atom (not a uuid) |
|
99
|
|
|
|
|
|
|
); |
|
100
|
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
# mark UserData tags that don't have ItemList counterparts as Preferred |
|
102
|
|
|
|
|
|
|
# - and set Preferred to 0 for any Avoid-ed tag |
|
103
|
|
|
|
|
|
|
# - also, for now, set Writable to 0 for any tag with a RawConv and no RawConvInv |
|
104
|
|
|
|
|
|
|
{ |
|
105
|
|
|
|
|
|
|
my $itemList = \%Image::ExifTool::QuickTime::ItemList; |
|
106
|
|
|
|
|
|
|
my $userData = \%Image::ExifTool::QuickTime::UserData; |
|
107
|
|
|
|
|
|
|
my (%pref, $tag); |
|
108
|
|
|
|
|
|
|
foreach $tag (TagTableKeys($itemList)) { |
|
109
|
|
|
|
|
|
|
my $tagInfo = $$itemList{$tag}; |
|
110
|
|
|
|
|
|
|
if (ref $tagInfo ne 'HASH') { |
|
111
|
|
|
|
|
|
|
next if ref $tagInfo; |
|
112
|
|
|
|
|
|
|
$tagInfo = $$itemList{$tag} = { Name => $tagInfo }; |
|
113
|
|
|
|
|
|
|
} else { |
|
114
|
|
|
|
|
|
|
$$tagInfo{Writable} = 0 if $$tagInfo{RawConv} and not $$tagInfo{RawConvInv}; |
|
115
|
|
|
|
|
|
|
$$tagInfo{Avoid} and $$tagInfo{Preferred} = 0, next; |
|
116
|
|
|
|
|
|
|
next if defined $$tagInfo{Preferred} and not $$tagInfo{Preferred}; |
|
117
|
|
|
|
|
|
|
} |
|
118
|
|
|
|
|
|
|
$pref{$$tagInfo{Name}} = 1; |
|
119
|
|
|
|
|
|
|
} |
|
120
|
|
|
|
|
|
|
foreach $tag (TagTableKeys($userData)) { |
|
121
|
|
|
|
|
|
|
my $tagInfo = $$userData{$tag}; |
|
122
|
|
|
|
|
|
|
if (ref $tagInfo ne 'HASH') { |
|
123
|
|
|
|
|
|
|
next if ref $tagInfo; |
|
124
|
|
|
|
|
|
|
$tagInfo = $$userData{$tag} = { Name => $tagInfo }; |
|
125
|
|
|
|
|
|
|
} else { |
|
126
|
|
|
|
|
|
|
$$tagInfo{Writable} = 0 if $$tagInfo{RawConv} and not $$tagInfo{RawConvInv}; |
|
127
|
|
|
|
|
|
|
$$tagInfo{Avoid} and $$tagInfo{Preferred} = 0, next; |
|
128
|
|
|
|
|
|
|
next if defined $$tagInfo{Preferred} or $pref{$$tagInfo{Name}}; |
|
129
|
|
|
|
|
|
|
} |
|
130
|
|
|
|
|
|
|
$$tagInfo{Preferred} = 1; |
|
131
|
|
|
|
|
|
|
} |
|
132
|
|
|
|
|
|
|
} |
|
133
|
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
|
135
|
|
|
|
|
|
|
# Format GPSCoordinates for writing |
|
136
|
|
|
|
|
|
|
# Inputs: 0) PrintConv value |
|
137
|
|
|
|
|
|
|
# Returns: ValueConv value |
|
138
|
|
|
|
|
|
|
sub PrintInvGPSCoordinates($) |
|
139
|
|
|
|
|
|
|
{ |
|
140
|
0
|
|
|
0
|
0
|
0
|
my ($val, $et) = @_; |
|
141
|
0
|
|
|
|
|
0
|
my @v = split /, */, $val; |
|
142
|
0
|
0
|
0
|
|
|
0
|
if (@v == 2 or @v == 3) { |
|
143
|
0
|
|
0
|
|
|
0
|
my $below = ($v[2] and $v[2] =~ /below/i); |
|
144
|
0
|
|
|
|
|
0
|
$v[0] = Image::ExifTool::GPS::ToDegrees($v[0], 1); |
|
145
|
0
|
|
|
|
|
0
|
$v[1] = Image::ExifTool::GPS::ToDegrees($v[1], 1); |
|
146
|
0
|
0
|
|
|
|
0
|
$v[2] = Image::ExifTool::ToFloat($v[2]) * ($below ? -1 : 1) if @v == 3; |
|
|
|
0
|
|
|
|
|
|
|
147
|
0
|
|
|
|
|
0
|
return "@v"; |
|
148
|
|
|
|
|
|
|
} |
|
149
|
0
|
0
|
|
|
|
0
|
return $val if $val =~ /^([-+]\d+(\.\d*)?){2,3}(CRS.*)?\/?$/; # already in ISO6709 format? |
|
150
|
0
|
|
|
|
|
0
|
return undef; |
|
151
|
|
|
|
|
|
|
} |
|
152
|
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
|
154
|
|
|
|
|
|
|
# Convert GPS coordinates back to ISO6709 format |
|
155
|
|
|
|
|
|
|
# Inputs: 0) ValueConv value |
|
156
|
|
|
|
|
|
|
# Returns: ISO6709 coordinates |
|
157
|
|
|
|
|
|
|
sub ConvInvISO6709($) |
|
158
|
|
|
|
|
|
|
{ |
|
159
|
0
|
|
|
0
|
0
|
0
|
local $_; |
|
160
|
0
|
|
|
|
|
0
|
my $val = shift; |
|
161
|
0
|
|
|
|
|
0
|
my @a = split ' ', $val; |
|
162
|
0
|
0
|
0
|
|
|
0
|
if (@a == 2 or @a == 3) { |
|
163
|
|
|
|
|
|
|
# latitude must have 2 digits before the decimal, and longitude 3, |
|
164
|
|
|
|
|
|
|
# and all values must start with a "+" or "-", and Google Photos |
|
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 ) |
|
169
|
0
|
|
|
|
|
0
|
my @fmt = ('%s%02d.%s%s','%s%03d.%s%s','%s%d.%s%s'); |
|
170
|
0
|
|
|
|
|
0
|
foreach (@a) { |
|
171
|
0
|
0
|
|
|
|
0
|
return undef unless Image::ExifTool::IsFloat($_); |
|
172
|
0
|
0
|
0
|
|
|
0
|
$_ =~ s/^([-+]?)(\d+)\.?(\d*)/sprintf(shift(@fmt),$1||'+',$2,$3,length($3)<3 ? '0'x(3-length($3)) : '')/e; |
|
|
0
|
|
|
|
|
0
|
|
|
173
|
|
|
|
|
|
|
} |
|
174
|
0
|
|
|
|
|
0
|
return join '', @a, '/'; |
|
175
|
|
|
|
|
|
|
} |
|
176
|
0
|
0
|
|
|
|
0
|
return $val if $val =~ /^([-+]\d+(\.\d*)?){2,3}(CRS.*)?\/?$/; # already in ISO6709 format? |
|
177
|
0
|
|
|
|
|
0
|
return undef; |
|
178
|
|
|
|
|
|
|
} |
|
179
|
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
|
181
|
|
|
|
|
|
|
# Handle offsets in iloc (ItemLocation) atom when writing (ref ISO 14496-12:2015 pg.79) |
|
182
|
|
|
|
|
|
|
# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) data ref, 3) output buffer ref |
|
183
|
|
|
|
|
|
|
# Returns: true on success |
|
184
|
|
|
|
|
|
|
# Notes: see also ParseItemLocation() in QuickTime.pm |
|
185
|
|
|
|
|
|
|
# (variable names with underlines correspond to names in ISO 14496-12) |
|
186
|
|
|
|
|
|
|
sub Handle_iloc($$$$) |
|
187
|
|
|
|
|
|
|
{ |
|
188
|
3
|
|
|
3
|
0
|
15
|
my ($et, $dirInfo, $dataPt, $outfile) = @_; |
|
189
|
3
|
|
|
|
|
10
|
my ($i, $j, $num, $pos, $id); |
|
190
|
|
|
|
|
|
|
|
|
191
|
3
|
|
|
|
|
9
|
my $off = $$dirInfo{ChunkOffset}; |
|
192
|
3
|
|
|
|
|
9
|
my $len = length $$dataPt; |
|
193
|
3
|
50
|
|
|
|
16
|
return 0 if $len < 8; |
|
194
|
3
|
|
|
|
|
15
|
my $ver = Get8u($dataPt, 0); |
|
195
|
3
|
|
|
|
|
15
|
my $siz = Get16u($dataPt, 4); |
|
196
|
3
|
|
|
|
|
12
|
my $noff = ($siz >> 12); |
|
197
|
3
|
|
|
|
|
12
|
my $nlen = ($siz >> 8) & 0x0f; |
|
198
|
3
|
|
|
|
|
8
|
my $nbas = ($siz >> 4) & 0x0f; |
|
199
|
3
|
|
|
|
|
10
|
my $nind = $siz & 0x0f; |
|
200
|
3
|
|
|
|
|
17
|
my %ok = ( 0 => 1, 4 => 1, 8 => 8 ); |
|
201
|
3
|
50
|
33
|
|
|
49
|
return 0 unless $ok{$noff} and $ok{$nlen} and $ok{$nbas} and $ok{$nind}; |
|
|
|
|
33
|
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
202
|
|
|
|
|
|
|
# piggy-back on existing code to fix up stco/co64 4/8-byte offsets |
|
203
|
3
|
50
|
|
|
|
17
|
my $tag = $noff == 4 ? 'stco_iloc' : 'co64_iloc'; |
|
204
|
3
|
50
|
|
|
|
13
|
if ($ver < 2) { |
|
205
|
3
|
|
|
|
|
11
|
$num = Get16u($dataPt, 6); |
|
206
|
3
|
|
|
|
|
14
|
$pos = 8; |
|
207
|
|
|
|
|
|
|
} else { |
|
208
|
0
|
0
|
|
|
|
0
|
return 0 if $len < 10; |
|
209
|
0
|
|
|
|
|
0
|
$num = Get32u($dataPt, 6); |
|
210
|
0
|
|
|
|
|
0
|
$pos = 10; |
|
211
|
|
|
|
|
|
|
} |
|
212
|
3
|
|
|
|
|
16
|
for ($i=0; $i<$num; ++$i) { |
|
213
|
9
|
50
|
|
|
|
27
|
if ($ver < 2) { |
|
214
|
9
|
50
|
|
|
|
24
|
return 0 if $pos + 2 > $len; |
|
215
|
9
|
|
|
|
|
26
|
$id = Get16u($dataPt, $pos); |
|
216
|
9
|
|
|
|
|
15
|
$pos += 2; |
|
217
|
|
|
|
|
|
|
} else { |
|
218
|
0
|
0
|
|
|
|
0
|
return 0 if $pos + 4 > $len; |
|
219
|
0
|
|
|
|
|
0
|
$id = Get32u($dataPt, $pos); |
|
220
|
0
|
|
|
|
|
0
|
$pos += 4; |
|
221
|
|
|
|
|
|
|
} |
|
222
|
9
|
|
|
|
|
23
|
my ($constOff, @offBase, @offItem, $minOffset); |
|
223
|
9
|
50
|
33
|
|
|
42
|
if ($ver == 1 or $ver == 2) { |
|
224
|
0
|
0
|
|
|
|
0
|
return 0 if $pos + 2 > $len; |
|
225
|
|
|
|
|
|
|
# offsets are absolute only if ConstructionMethod is 0, otherwise |
|
226
|
|
|
|
|
|
|
# the relative offsets are constant as far as we are concerned |
|
227
|
0
|
|
|
|
|
0
|
$constOff = Get16u($dataPt, $pos) & 0x0f; |
|
228
|
0
|
|
|
|
|
0
|
$pos += 2; |
|
229
|
|
|
|
|
|
|
} |
|
230
|
9
|
50
|
|
|
|
28
|
return 0 if $pos + 2 > $len; |
|
231
|
9
|
|
|
|
|
23
|
my $drefIdx = Get16u($dataPt, $pos); |
|
232
|
9
|
50
|
|
|
|
22
|
if ($drefIdx) { |
|
233
|
0
|
0
|
0
|
|
|
0
|
if ($$et{QtDataRef} and $$et{QtDataRef}[$drefIdx - 1]) { |
|
234
|
0
|
|
|
|
|
0
|
my $dref = $$et{QtDataRef}[$drefIdx - 1]; |
|
235
|
|
|
|
|
|
|
# these offsets are constant unless the data is in this file |
|
236
|
0
|
0
|
0
|
|
|
0
|
$constOff = 1 unless $$dref[1] == 1 and $$dref[0] ne 'rsrc'; |
|
237
|
|
|
|
|
|
|
} else { |
|
238
|
0
|
|
|
|
|
0
|
$et->Error("No data reference for iloc entry $i"); |
|
239
|
0
|
|
|
|
|
0
|
return 0; |
|
240
|
|
|
|
|
|
|
} |
|
241
|
|
|
|
|
|
|
} |
|
242
|
9
|
|
|
|
|
15
|
$pos += 2; |
|
243
|
|
|
|
|
|
|
# get base offset and save its location if in this file |
|
244
|
9
|
|
|
|
|
31
|
my $base_offset = GetVarInt($dataPt, $pos, $nbas); |
|
245
|
9
|
100
|
66
|
|
|
41
|
if ($base_offset and not $constOff) { |
|
246
|
6
|
50
|
|
|
|
26
|
my $tg = ($nbas == 4 ? 'stco' : 'co64') . '_iloc'; |
|
247
|
6
|
|
|
|
|
26
|
push @offBase, [ $tg, length($$outfile) + 8 + $pos - $nbas, $nbas, 0, $id ]; |
|
248
|
|
|
|
|
|
|
} |
|
249
|
9
|
50
|
|
|
|
27
|
return 0 if $pos + 2 > $len; |
|
250
|
9
|
|
|
|
|
24
|
my $ext_num = Get16u($dataPt, $pos); |
|
251
|
9
|
|
|
|
|
15
|
$pos += 2; |
|
252
|
9
|
|
|
|
|
19
|
my $listStartPos = $pos; |
|
253
|
|
|
|
|
|
|
# run through the item list to get offset locations and the minimum offset in this file |
|
254
|
9
|
|
|
|
|
25
|
for ($j=0; $j<$ext_num; ++$j) { |
|
255
|
9
|
50
|
33
|
|
|
41
|
$pos += $nind if $ver == 1 or $ver == 2; |
|
256
|
9
|
|
|
|
|
26
|
my $extent_offset = GetVarInt($dataPt, $pos, $noff); |
|
257
|
9
|
50
|
|
|
|
27
|
return 0 unless defined $extent_offset; |
|
258
|
9
|
50
|
|
|
|
21
|
unless ($constOff) { |
|
259
|
9
|
50
|
|
|
|
60
|
push @offItem, [ $tag, length($$outfile) + 8 + $pos - $noff, $noff, 0, $id ] if $noff; |
|
260
|
9
|
50
|
33
|
|
|
35
|
$minOffset = $extent_offset if not defined $minOffset or $minOffset > $extent_offset; |
|
261
|
|
|
|
|
|
|
} |
|
262
|
9
|
50
|
|
|
|
27
|
return 0 if $pos + $nlen > length $$dataPt; |
|
263
|
9
|
|
|
|
|
25
|
$pos += $nlen; |
|
264
|
|
|
|
|
|
|
} |
|
265
|
|
|
|
|
|
|
# decide whether to fix up the base offset or individual item offsets |
|
266
|
|
|
|
|
|
|
# (adjust the one that is larger) |
|
267
|
9
|
100
|
66
|
|
|
38
|
if (defined $minOffset and $minOffset > $base_offset) { |
|
268
|
3
|
|
|
|
|
15
|
$$_[3] = $base_offset foreach @offItem; |
|
269
|
3
|
|
|
|
|
21
|
push @$off, @offItem; |
|
270
|
|
|
|
|
|
|
} else { |
|
271
|
6
|
|
|
|
|
20
|
$$_[3] = $minOffset foreach @offBase; |
|
272
|
6
|
|
|
|
|
25
|
push @$off, @offBase; |
|
273
|
|
|
|
|
|
|
} |
|
274
|
|
|
|
|
|
|
} |
|
275
|
3
|
|
|
|
|
18
|
return 1; |
|
276
|
|
|
|
|
|
|
} |
|
277
|
|
|
|
|
|
|
|
|
278
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
|
279
|
|
|
|
|
|
|
# Get localized version of tagInfo hash |
|
280
|
|
|
|
|
|
|
# Inputs: 0) tagInfo hash ref, 1) language code (eg. "fra-FR") |
|
281
|
|
|
|
|
|
|
# Returns: new tagInfo hash ref, or undef if invalid or no language code |
|
282
|
|
|
|
|
|
|
sub GetLangInfo($$) |
|
283
|
|
|
|
|
|
|
{ |
|
284
|
96
|
|
|
96
|
0
|
208
|
my ($tagInfo, $langCode) = @_; |
|
285
|
96
|
50
|
|
|
|
203
|
return undef unless $langCode; |
|
286
|
|
|
|
|
|
|
# only allow alternate language tags in lang-alt lists |
|
287
|
96
|
|
|
|
|
172
|
my $writable = $$tagInfo{Writable}; |
|
288
|
96
|
50
|
|
|
|
269
|
$writable = $$tagInfo{Table}{WRITABLE} unless defined $writable; |
|
289
|
96
|
50
|
|
|
|
209
|
return undef unless $writable; |
|
290
|
96
|
|
|
|
|
154
|
$langCode =~ tr/_/-/; # RFC 3066 specifies '-' as a separator |
|
291
|
96
|
|
|
|
|
248
|
my $langInfo = Image::ExifTool::GetLangInfo($tagInfo, $langCode); |
|
292
|
96
|
|
|
|
|
218
|
return $langInfo; |
|
293
|
|
|
|
|
|
|
} |
|
294
|
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
|
296
|
|
|
|
|
|
|
# validate raw values for writing |
|
297
|
|
|
|
|
|
|
# Inputs: 0) ExifTool ref, 1) tagInfo hash ref, 2) raw value ref |
|
298
|
|
|
|
|
|
|
# Returns: error string or undef (and possibly changes value) on success |
|
299
|
|
|
|
|
|
|
sub CheckQTValue($$$) |
|
300
|
|
|
|
|
|
|
{ |
|
301
|
577
|
|
|
577
|
0
|
1743
|
my ($et, $tagInfo, $valPtr) = @_; |
|
302
|
577
|
|
100
|
|
|
3827
|
my $format = $$tagInfo{Format} || $$tagInfo{Writable} || $$tagInfo{Table}{FORMAT}; |
|
303
|
577
|
100
|
|
|
|
1712
|
return undef unless $format; |
|
304
|
430
|
|
|
|
|
1989
|
return Image::ExifTool::CheckValue($valPtr, $format, $$tagInfo{Count}); |
|
305
|
|
|
|
|
|
|
} |
|
306
|
|
|
|
|
|
|
|
|
307
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
|
308
|
|
|
|
|
|
|
# Format QuickTime value for writing |
|
309
|
|
|
|
|
|
|
# Inputs: 0) ExifTool ref, 1) value ref, 2) tagInfo ref, 3) Format (or undef) |
|
310
|
|
|
|
|
|
|
# Returns: Flags for QT data type, and reformats value as required (sets to undef on error) |
|
311
|
|
|
|
|
|
|
sub FormatQTValue($$;$$) |
|
312
|
|
|
|
|
|
|
{ |
|
313
|
22
|
|
|
22
|
0
|
62
|
my ($et, $valPt, $tagInfo, $format) = @_; |
|
314
|
22
|
|
|
|
|
51
|
my $writable = $$tagInfo{Writable}; |
|
315
|
22
|
|
|
|
|
46
|
my $count = $$tagInfo{Count}; |
|
316
|
22
|
|
|
|
|
38
|
my $flags; |
|
317
|
22
|
50
|
|
|
|
63
|
$format or $format = $$tagInfo{Format}; |
|
318
|
22
|
100
|
33
|
|
|
205
|
if ($format and $format ne 'string' or not $format and $writable and $writable ne 'string') { |
|
|
|
50
|
66
|
|
|
|
|
|
|
|
50
|
66
|
|
|
|
|
|
|
|
50
|
66
|
|
|
|
|
|
319
|
2
|
|
33
|
|
|
16
|
$$valPt = WriteValue($$valPt, $format || $writable, $count); |
|
320
|
2
|
50
|
33
|
|
|
13
|
if ($writable and $qtFormat{$writable}) { |
|
321
|
0
|
|
|
|
|
0
|
$flags = $qtFormat{$writable}; |
|
322
|
|
|
|
|
|
|
} else { |
|
323
|
2
|
|
50
|
|
|
17
|
$flags = $qtFormat{$format || 0} || 0; |
|
324
|
|
|
|
|
|
|
} |
|
325
|
|
|
|
|
|
|
} elsif ($$valPt =~ /^\xff\xd8\xff/) { |
|
326
|
0
|
|
|
|
|
0
|
$flags = 0x0d; # JPG |
|
327
|
|
|
|
|
|
|
} elsif ($$valPt =~ /^(\x89P|\x8aM|\x8bJ)NG\r\n\x1a\n/) { |
|
328
|
0
|
|
|
|
|
0
|
$flags = 0x0e; # PNG |
|
329
|
|
|
|
|
|
|
} elsif ($$valPt =~ /^BM.{15}\0/s) { |
|
330
|
0
|
|
|
|
|
0
|
$flags = 0x1b; # BMP |
|
331
|
|
|
|
|
|
|
} else { |
|
332
|
20
|
|
|
|
|
32
|
$flags = 0x01; # UTF8 |
|
333
|
20
|
|
|
|
|
86
|
$$valPt = $et->Encode($$valPt, 'UTF8'); |
|
334
|
|
|
|
|
|
|
} |
|
335
|
22
|
50
|
|
|
|
58
|
defined $$valPt or $et->WarnOnce("Error converting value for $$tagInfo{Name}"); |
|
336
|
22
|
|
|
|
|
52
|
return $flags; |
|
337
|
|
|
|
|
|
|
} |
|
338
|
|
|
|
|
|
|
|
|
339
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
|
340
|
|
|
|
|
|
|
# Set variable-length integer (used by WriteItemInfo) |
|
341
|
|
|
|
|
|
|
# Inputs: 0) value, 1) integer size in bytes (0, 4 or 8), |
|
342
|
|
|
|
|
|
|
# Returns: packed integer |
|
343
|
|
|
|
|
|
|
sub SetVarInt($$) |
|
344
|
|
|
|
|
|
|
{ |
|
345
|
6
|
|
|
6
|
0
|
15
|
my ($val, $n) = @_; |
|
346
|
6
|
50
|
|
|
|
21
|
if ($n == 4) { |
|
|
|
0
|
|
|
|
|
|
|
347
|
6
|
|
|
|
|
16
|
return Set32u($val); |
|
348
|
|
|
|
|
|
|
} elsif ($n == 8) { |
|
349
|
0
|
|
|
|
|
0
|
return Set64u($val); |
|
350
|
|
|
|
|
|
|
} |
|
351
|
0
|
|
|
|
|
0
|
return ''; |
|
352
|
|
|
|
|
|
|
} |
|
353
|
|
|
|
|
|
|
|
|
354
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
|
355
|
|
|
|
|
|
|
# Write Meta Keys to add/delete entries as necessary ('mdta' handler) (ref PH) |
|
356
|
|
|
|
|
|
|
# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref |
|
357
|
|
|
|
|
|
|
# Returns: updated keys box data |
|
358
|
|
|
|
|
|
|
sub WriteKeys($$$) |
|
359
|
|
|
|
|
|
|
{ |
|
360
|
183
|
|
|
183
|
0
|
619
|
my ($et, $dirInfo, $tagTablePtr) = @_; |
|
361
|
183
|
100
|
|
|
|
1323
|
$et or return 1; # allow dummy access to autoload this package |
|
362
|
9
|
|
|
|
|
34
|
my $dataPt = $$dirInfo{DataPt}; |
|
363
|
9
|
|
|
|
|
27
|
my $dirLen = length $$dataPt; |
|
364
|
9
|
|
|
|
|
21
|
my $outfile = $$dirInfo{OutFile}; |
|
365
|
9
|
|
|
|
|
27
|
my ($tag, %done, %remap, %info, %add, $i); |
|
366
|
|
|
|
|
|
|
|
|
367
|
9
|
50
|
|
|
|
36
|
$dirLen < 8 and $et->Warn('Short Keys box'), $dirLen = 8, $$dataPt = "\0" x 8; |
|
368
|
9
|
100
|
|
|
|
39
|
if ($$et{DEL_GROUP}{Keys}) { |
|
369
|
3
|
|
|
|
|
8
|
$dirLen = 8; # delete all existing keys |
|
370
|
|
|
|
|
|
|
# deleted keys are identified by a zero entry in the Remap lookup |
|
371
|
3
|
|
|
|
|
14
|
my $n = Get32u($dataPt, 4); |
|
372
|
3
|
|
|
|
|
17
|
for ($i=1; $i<=$n; ++$i) { $remap{$i} = 0; } |
|
|
6
|
|
|
|
|
20
|
|
|
373
|
3
|
50
|
|
|
|
30
|
$et->VPrint(0, " [deleting $n Keys entr".($n==1 ? 'y' : 'ies')."]\n"); |
|
374
|
3
|
|
|
|
|
8
|
++$$et{CHANGED}; |
|
375
|
|
|
|
|
|
|
} |
|
376
|
9
|
|
|
|
|
22
|
my $pos = 8; |
|
377
|
9
|
|
|
|
|
39
|
my $newTags = $et->GetNewTagInfoHash($tagTablePtr); |
|
378
|
9
|
|
|
|
|
35
|
my $newData = substr($$dataPt, 0, $pos); |
|
379
|
|
|
|
|
|
|
|
|
380
|
9
|
|
|
|
|
32
|
my $newIndex = 1; |
|
381
|
9
|
|
|
|
|
25
|
my $index = 1; |
|
382
|
9
|
|
|
|
|
40
|
while ($pos < $dirLen - 4) { |
|
383
|
15
|
|
|
|
|
53
|
my $len = unpack("x${pos}N", $$dataPt); |
|
384
|
15
|
50
|
33
|
|
|
79
|
last if $len < 8 or $pos + $len > $dirLen; |
|
385
|
15
|
|
|
|
|
37
|
my $ns = substr($$dataPt, $pos + 4, 4); |
|
386
|
15
|
|
|
|
|
40
|
$tag = substr($$dataPt, $pos + 8, $len - 8); |
|
387
|
15
|
|
|
|
|
41
|
$tag =~ s/\0.*//s; # truncate at null |
|
388
|
15
|
50
|
|
|
|
73
|
$tag =~ s/^com\.apple\.quicktime\.// if $ns eq 'mdta'; # remove apple quicktime domain |
|
389
|
15
|
50
|
|
|
|
45
|
$tag = "Tag_$ns" unless $tag; |
|
390
|
15
|
|
|
|
|
40
|
$done{$tag} = 1; # set flag to avoid creating this tag |
|
391
|
15
|
|
|
|
|
47
|
my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag); |
|
392
|
15
|
50
|
|
|
|
44
|
if ($tagInfo) { |
|
393
|
15
|
|
|
|
|
35
|
$info{$index} = $tagInfo; |
|
394
|
15
|
100
|
|
|
|
47
|
if ($$newTags{$tag}) { |
|
395
|
2
|
|
|
|
|
8
|
my $nvHash = $et->GetNewValueHash($tagInfo); |
|
396
|
|
|
|
|
|
|
# drop this tag if it is being deleted |
|
397
|
2
|
100
|
33
|
|
|
15
|
if ($nvHash and $et->IsOverwriting($nvHash) > 0 and not defined $et->GetNewValue($nvHash)) { |
|
|
|
|
66
|
|
|
|
|
|
398
|
|
|
|
|
|
|
# don't delete this key if we could be writing any alternate-language version of this tag |
|
399
|
1
|
|
|
|
|
3
|
my ($t, $dontDelete); |
|
400
|
1
|
|
|
|
|
6
|
foreach $t (keys %$newTags) { |
|
401
|
6
|
50
|
66
|
|
|
24
|
next unless $$newTags{$t}{SrcTagInfo} and $$newTags{$t}{SrcTagInfo} eq $tagInfo; |
|
402
|
0
|
|
|
|
|
0
|
my $nv = $et->GetNewValueHash($$newTags{$t}); |
|
403
|
0
|
0
|
0
|
|
|
0
|
next unless $et->IsOverwriting($nv) and defined $et->GetNewValue($nv); |
|
404
|
0
|
|
|
|
|
0
|
$dontDelete = 1; |
|
405
|
0
|
|
|
|
|
0
|
last; |
|
406
|
|
|
|
|
|
|
} |
|
407
|
1
|
50
|
|
|
|
5
|
unless ($dontDelete) { |
|
408
|
|
|
|
|
|
|
# delete this key |
|
409
|
1
|
|
|
|
|
12
|
$et->VPrint(1, "$$et{INDENT}\[deleting Keys entry $index '${tag}']\n"); |
|
410
|
1
|
|
|
|
|
3
|
$pos += $len; |
|
411
|
1
|
|
|
|
|
6
|
$remap{$index++} = 0; |
|
412
|
1
|
|
|
|
|
3
|
++$$et{CHANGED}; |
|
413
|
1
|
|
|
|
|
3
|
next; |
|
414
|
|
|
|
|
|
|
} |
|
415
|
|
|
|
|
|
|
} |
|
416
|
|
|
|
|
|
|
} |
|
417
|
|
|
|
|
|
|
} |
|
418
|
|
|
|
|
|
|
# add to the Keys box data |
|
419
|
14
|
|
|
|
|
49
|
$newData .= substr($$dataPt, $pos, $len); |
|
420
|
14
|
|
|
|
|
39
|
$remap{$index++} = $newIndex++; |
|
421
|
14
|
|
|
|
|
43
|
$pos += $len; |
|
422
|
|
|
|
|
|
|
} |
|
423
|
|
|
|
|
|
|
# add keys for any tags we need to create |
|
424
|
9
|
|
|
|
|
47
|
foreach $tag (sort keys %$newTags) { |
|
425
|
12
|
|
|
|
|
25
|
my $tagInfo = $$newTags{$tag}; |
|
426
|
12
|
|
|
|
|
20
|
my $id; |
|
427
|
12
|
100
|
66
|
|
|
49
|
if ($$tagInfo{LangCode} and $$tagInfo{SrcTagInfo}) { |
|
428
|
1
|
|
|
|
|
5
|
$id = $$tagInfo{SrcTagInfo}{TagID}; |
|
429
|
|
|
|
|
|
|
} else { |
|
430
|
11
|
|
|
|
|
23
|
$id = $tag; |
|
431
|
|
|
|
|
|
|
} |
|
432
|
12
|
100
|
|
|
|
30
|
next if $done{$id}; |
|
433
|
9
|
|
|
|
|
29
|
my $nvHash = $et->GetNewValueHash($tagInfo); |
|
434
|
9
|
50
|
66
|
|
|
50
|
next unless $$nvHash{IsCreating} and $et->IsOverwriting($nvHash) and |
|
|
|
|
66
|
|
|
|
|
|
435
|
|
|
|
|
|
|
defined $et->GetNewValue($nvHash); |
|
436
|
|
|
|
|
|
|
# add new entry to 'keys' data |
|
437
|
5
|
50
|
|
|
|
33
|
my $val = $id =~ /^com\./ ? $id : "com.apple.quicktime.$id"; |
|
438
|
5
|
|
|
|
|
37
|
$newData .= Set32u(8 + length($val)) . 'mdta' . $val; |
|
439
|
5
|
|
|
|
|
40
|
$et->VPrint(1, "$$et{INDENT}\[adding Keys entry $newIndex '${id}']\n"); |
|
440
|
5
|
|
|
|
|
18
|
$add{$newIndex++} = $tagInfo; |
|
441
|
5
|
|
|
|
|
14
|
++$$et{CHANGED}; |
|
442
|
|
|
|
|
|
|
} |
|
443
|
9
|
|
|
|
|
27
|
my $num = $newIndex - 1; |
|
444
|
9
|
100
|
|
|
|
26
|
if ($num) { |
|
445
|
8
|
|
|
|
|
97
|
Set32u($num, \$newData, 4); # update count in header |
|
446
|
|
|
|
|
|
|
} else { |
|
447
|
1
|
|
|
|
|
4
|
$newData = ''; # delete empty Keys box |
|
448
|
|
|
|
|
|
|
} |
|
449
|
|
|
|
|
|
|
# save temporary variables for use when writing ItemList: |
|
450
|
|
|
|
|
|
|
# Remap - lookup for remapping Keys ID numbers (0 if item is deleted) |
|
451
|
|
|
|
|
|
|
# Info - Keys tag information, based on old index value |
|
452
|
|
|
|
|
|
|
# Add - Keys items deleted, based on old index value |
|
453
|
|
|
|
|
|
|
# Num - Number of items in edited Keys box |
|
454
|
9
|
|
|
|
|
89
|
$$et{Keys} = { Remap => \%remap, Info => \%info, Add => \%add, Num => $num }; |
|
455
|
|
|
|
|
|
|
|
|
456
|
9
|
|
|
|
|
50
|
return $newData; # return updated Keys box |
|
457
|
|
|
|
|
|
|
} |
|
458
|
|
|
|
|
|
|
|
|
459
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
|
460
|
|
|
|
|
|
|
# Write ItemInformation in HEIC files |
|
461
|
|
|
|
|
|
|
# Inputs: 0) ExifTool ref, 1) dirInfo ref (with BoxPos entry), 2) output buffer ref |
|
462
|
|
|
|
|
|
|
# Returns: mdat edit list ref (empty if nothing changed) |
|
463
|
|
|
|
|
|
|
sub WriteItemInfo($$$) |
|
464
|
|
|
|
|
|
|
{ |
|
465
|
3
|
|
|
3
|
0
|
10
|
my ($et, $dirInfo, $outfile) = @_; |
|
466
|
3
|
|
|
|
|
9
|
my $boxPos = $$dirInfo{BoxPos}; # hash of [length,position] for each box |
|
467
|
3
|
|
|
|
|
9
|
my $raf = $$et{RAF}; |
|
468
|
3
|
|
|
|
|
8
|
my $items = $$et{ItemInfo}; |
|
469
|
3
|
|
|
|
|
7
|
my (%did, @mdatEdit, $name); |
|
470
|
|
|
|
|
|
|
|
|
471
|
3
|
50
|
33
|
|
|
22
|
return () unless $items and $raf; |
|
472
|
|
|
|
|
|
|
|
|
473
|
|
|
|
|
|
|
# extract information from EXIF/XMP metadata items |
|
474
|
3
|
|
|
|
|
11
|
my $primary = $$et{PrimaryItem}; |
|
475
|
3
|
|
|
|
|
13
|
my $curPos = $raf->Tell(); |
|
476
|
3
|
|
|
|
|
7
|
my $id; |
|
477
|
3
|
|
|
|
|
27
|
foreach $id (sort { $a <=> $b } keys %$items) { |
|
|
9
|
|
|
|
|
29
|
|
|
478
|
9
|
50
|
|
|
|
33
|
$primary = $id unless defined $primary; # assume primary is lowest-number item if pitm missing |
|
479
|
9
|
|
|
|
|
19
|
my $item = $$items{$id}; |
|
480
|
|
|
|
|
|
|
# only edit primary EXIF/XMP metadata |
|
481
|
9
|
100
|
66
|
|
|
47
|
next unless $$item{RefersTo} and $$item{RefersTo}{$primary}; |
|
482
|
3
|
|
50
|
|
|
17
|
my $type = $$item{ContentType} || $$item{Type} || next; |
|
483
|
|
|
|
|
|
|
# get ExifTool name for this item |
|
484
|
3
|
|
|
|
|
15
|
$name = { Exif => 'EXIF', 'application/rdf+xml' => 'XMP' }->{$type}; |
|
485
|
3
|
50
|
|
|
|
12
|
next unless $name; # only care about EXIF and XMP |
|
486
|
3
|
50
|
|
|
|
10
|
next unless $$et{EDIT_DIRS}{$name}; |
|
487
|
3
|
|
|
|
|
8
|
$did{$name} = 1; # set flag to prevent creating this metadata |
|
488
|
3
|
|
|
|
|
5
|
my ($warn, $extent, $buff, @edit); |
|
489
|
3
|
50
|
|
|
|
8
|
$warn = 'Missing iloc box' unless $$boxPos{iloc}; |
|
490
|
3
|
50
|
33
|
|
|
12
|
$warn = "No Extents for $type item" unless $$item{Extents} and @{$$item{Extents}}; |
|
|
3
|
|
|
|
|
12
|
|
|
491
|
3
|
50
|
|
|
|
11
|
$warn = "Can't currently decode encoded $type metadata" if $$item{ContentEncoding}; |
|
492
|
3
|
50
|
|
|
|
10
|
$warn = "Can't currently decode protected $type metadata" if $$item{ProtectionIndex}; |
|
493
|
3
|
50
|
|
|
|
10
|
$warn = "Can't currently extract $type with construction method $$item{ConstructionMethod}" if $$item{ConstructionMethod}; |
|
494
|
3
|
50
|
|
|
|
9
|
$warn = "$type metadata is not this file" if $$item{DataReferenceIndex}; |
|
495
|
3
|
50
|
|
|
|
9
|
$warn and $et->Warn($warn), next; |
|
496
|
3
|
|
50
|
|
|
16
|
my $base = $$item{BaseOffset} || 0; |
|
497
|
3
|
|
|
|
|
5
|
my $val = ''; |
|
498
|
3
|
|
|
|
|
6
|
foreach $extent (@{$$item{Extents}}) { |
|
|
3
|
|
|
|
|
9
|
|
|
499
|
3
|
50
|
|
|
|
9
|
$val .= $buff if defined $buff; |
|
500
|
3
|
|
|
|
|
9
|
my $pos = $$extent[1] + $base; |
|
501
|
3
|
100
|
|
|
|
9
|
if ($$extent[2]) { |
|
502
|
2
|
50
|
|
|
|
8
|
$raf->Seek($pos, 0) or last; |
|
503
|
2
|
50
|
|
|
|
10
|
$raf->Read($buff, $$extent[2]) or last; |
|
504
|
|
|
|
|
|
|
} else { |
|
505
|
1
|
|
|
|
|
3
|
$buff = ''; |
|
506
|
|
|
|
|
|
|
} |
|
507
|
3
|
|
|
|
|
14
|
push @edit, [ $pos, $pos + $$extent[2] ]; # replace or delete this if changed |
|
508
|
|
|
|
|
|
|
} |
|
509
|
3
|
50
|
|
|
|
10
|
next unless defined $buff; |
|
510
|
3
|
50
|
|
|
|
11
|
$buff = $val . $buff if length $val; |
|
511
|
3
|
|
|
|
|
7
|
my ($hdr, $subTable, $proc); |
|
512
|
3
|
100
|
|
|
|
11
|
if ($name eq 'EXIF') { |
|
513
|
1
|
50
|
33
|
|
|
17
|
if (not length $buff) { |
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
514
|
|
|
|
|
|
|
# create EXIF from scratch |
|
515
|
0
|
|
|
|
|
0
|
$hdr = "\0\0\0\x06Exif\0\0"; |
|
516
|
|
|
|
|
|
|
} elsif ($buff =~ /^(MM\0\x2a|II\x2a\0)/) { |
|
517
|
0
|
|
|
|
|
0
|
$et->Warn('Missing Exif header'); |
|
518
|
0
|
|
|
|
|
0
|
$hdr = ''; |
|
519
|
|
|
|
|
|
|
} elsif (length($buff) >= 4 and length($buff) >= 4 + unpack('N',$buff)) { |
|
520
|
1
|
|
|
|
|
5
|
$hdr = substr($buff, 0, 4 + unpack('N',$buff)); |
|
521
|
|
|
|
|
|
|
} else { |
|
522
|
0
|
|
|
|
|
0
|
$et->Warn('Invalid Exif header'); |
|
523
|
0
|
|
|
|
|
0
|
next; |
|
524
|
|
|
|
|
|
|
} |
|
525
|
1
|
|
|
|
|
5
|
$subTable = GetTagTable('Image::ExifTool::Exif::Main'); |
|
526
|
1
|
|
|
|
|
3
|
$proc = \&Image::ExifTool::WriteTIFF; |
|
527
|
|
|
|
|
|
|
} else { |
|
528
|
2
|
|
|
|
|
6
|
$hdr = ''; |
|
529
|
2
|
|
|
|
|
18
|
$subTable = GetTagTable('Image::ExifTool::XMP::Main'); |
|
530
|
|
|
|
|
|
|
} |
|
531
|
3
|
|
|
|
|
26
|
my %dirInfo = ( |
|
532
|
|
|
|
|
|
|
DataPt => \$buff, |
|
533
|
|
|
|
|
|
|
DataLen => length $buff, |
|
534
|
|
|
|
|
|
|
DirStart => length $hdr, |
|
535
|
|
|
|
|
|
|
DirLen => length($buff) - length $hdr, |
|
536
|
|
|
|
|
|
|
); |
|
537
|
3
|
|
|
|
|
11
|
my $changed = $$et{CHANGED}; |
|
538
|
3
|
|
|
|
|
17
|
my $newVal = $et->WriteDirectory(\%dirInfo, $subTable, $proc); |
|
539
|
3
|
50
|
33
|
|
|
39
|
if (defined $newVal and $changed ne $$et{CHANGED} and |
|
|
|
|
66
|
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
540
|
|
|
|
|
|
|
# nothing changed if deleting an empty directory |
|
541
|
|
|
|
|
|
|
($dirInfo{DirLen} or length $newVal)) |
|
542
|
|
|
|
|
|
|
{ |
|
543
|
3
|
100
|
66
|
|
|
51
|
$newVal = $hdr . $newVal if length $hdr and length $newVal; |
|
544
|
3
|
|
|
|
|
16
|
$edit[0][2] = \$newVal; # replace the old chunk with the new data |
|
545
|
3
|
|
|
|
|
9
|
$edit[0][3] = $id; # mark this chunk with the item ID |
|
546
|
3
|
|
|
|
|
11
|
push @mdatEdit, @edit; |
|
547
|
|
|
|
|
|
|
# update item extent_length |
|
548
|
3
|
|
|
|
|
6
|
my $n = length $newVal; |
|
549
|
3
|
|
|
|
|
9
|
foreach $extent (@{$$item{Extents}}) { |
|
|
3
|
|
|
|
|
13
|
|
|
550
|
3
|
|
|
|
|
15
|
my ($nlen, $lenPt) = @$extent[3,4]; |
|
551
|
3
|
50
|
|
|
|
16
|
if ($nlen == 8) { |
|
|
|
50
|
|
|
|
|
|
|
552
|
0
|
|
|
|
|
0
|
Set64u($n, $outfile, $$boxPos{iloc}[0] + 8 + $lenPt); |
|
553
|
|
|
|
|
|
|
} elsif ($n <= 0xffffffff) { |
|
554
|
3
|
|
|
|
|
20
|
Set32u($n, $outfile, $$boxPos{iloc}[0] + 8 + $lenPt); |
|
555
|
|
|
|
|
|
|
} else { |
|
556
|
0
|
|
|
|
|
0
|
$et->Error("Can't yet promote iloc length to 64 bits"); |
|
557
|
0
|
|
|
|
|
0
|
return (); |
|
558
|
|
|
|
|
|
|
} |
|
559
|
3
|
|
|
|
|
13
|
$n = 0; |
|
560
|
|
|
|
|
|
|
} |
|
561
|
3
|
50
|
|
|
|
7
|
if (@{$$item{Extents}} != 1) { |
|
|
3
|
|
|
|
|
14
|
|
|
562
|
0
|
|
|
|
|
0
|
$et->Error("Can't yet handle $name in multiple parts. Please submit sample for testing"); |
|
563
|
|
|
|
|
|
|
} |
|
564
|
|
|
|
|
|
|
} |
|
565
|
3
|
|
|
|
|
18
|
$$et{CHANGED} = $changed; # (will set this later if successful in editing mdat) |
|
566
|
|
|
|
|
|
|
} |
|
567
|
3
|
|
|
|
|
29
|
$raf->Seek($curPos, 0); # seek back to original position |
|
568
|
|
|
|
|
|
|
|
|
569
|
|
|
|
|
|
|
# add necessary metadata types if they didn't already exist |
|
570
|
3
|
|
|
|
|
11
|
my ($countNew, %add, %usedID); |
|
571
|
3
|
|
|
|
|
14
|
foreach $name ('EXIF','XMP') { |
|
572
|
6
|
100
|
100
|
|
|
52
|
next if $did{$name} or not $$et{ADD_DIRS}{$name}; |
|
573
|
2
|
|
|
|
|
4
|
my @missing; |
|
574
|
2
|
|
33
|
|
|
19
|
$$boxPos{$_} or push @missing, $_ foreach qw(iinf iloc); |
|
575
|
2
|
50
|
|
|
|
8
|
if (@missing) { |
|
576
|
0
|
0
|
|
|
|
0
|
my $str = @missing > 1 ? join(' and ', @missing) . ' boxes' : "@missing box"; |
|
577
|
0
|
|
|
|
|
0
|
$et->Warn("Can't create $name. Missing expected $str"); |
|
578
|
0
|
|
|
|
|
0
|
last; |
|
579
|
|
|
|
|
|
|
} |
|
580
|
2
|
50
|
|
|
|
8
|
unless (defined $$et{PrimaryItem}) { |
|
581
|
0
|
0
|
|
|
|
0
|
unless (defined $primary) { |
|
582
|
0
|
|
|
|
|
0
|
$et->Warn("Can't create $name. No items to reference"); |
|
583
|
0
|
|
|
|
|
0
|
last; |
|
584
|
|
|
|
|
|
|
} |
|
585
|
|
|
|
|
|
|
# add new primary item reference box after hdrl box |
|
586
|
0
|
0
|
|
|
|
0
|
if ($primary < 0x10000) { |
|
587
|
0
|
|
|
|
|
0
|
$add{hdlr} = pack('Na4Nn', 14, 'pitm', 0, $primary); |
|
588
|
|
|
|
|
|
|
} else { |
|
589
|
0
|
|
|
|
|
0
|
$add{hdlr} = pack('Na4CCCCN', 16, 'pitm', 1, 0, 0, 0, $primary); |
|
590
|
|
|
|
|
|
|
} |
|
591
|
0
|
|
|
|
|
0
|
$et->Warn("Added missing PrimaryItemReference (for item $primary)", 1); |
|
592
|
|
|
|
|
|
|
} |
|
593
|
2
|
|
|
|
|
6
|
my $buff = ''; |
|
594
|
2
|
|
|
|
|
5
|
my ($hdr, $subTable, $proc); |
|
595
|
2
|
100
|
|
|
|
9
|
if ($name eq 'EXIF') { |
|
596
|
1
|
|
|
|
|
3
|
$hdr = "\0\0\0\x06Exif\0\0"; |
|
597
|
1
|
|
|
|
|
13
|
$subTable = GetTagTable('Image::ExifTool::Exif::Main'); |
|
598
|
1
|
|
|
|
|
4
|
$proc = \&Image::ExifTool::WriteTIFF; |
|
599
|
|
|
|
|
|
|
} else { |
|
600
|
1
|
|
|
|
|
3
|
$hdr = ''; |
|
601
|
1
|
|
|
|
|
3
|
$subTable = GetTagTable('Image::ExifTool::XMP::Main'); |
|
602
|
|
|
|
|
|
|
} |
|
603
|
2
|
|
|
|
|
11
|
my %dirInfo = ( |
|
604
|
|
|
|
|
|
|
DataPt => \$buff, |
|
605
|
|
|
|
|
|
|
DataLen => 0, |
|
606
|
|
|
|
|
|
|
DirStart => 0, |
|
607
|
|
|
|
|
|
|
DirLen => 0, |
|
608
|
|
|
|
|
|
|
); |
|
609
|
2
|
|
|
|
|
16
|
my $changed = $$et{CHANGED}; |
|
610
|
2
|
|
|
|
|
12
|
my $newVal = $et->WriteDirectory(\%dirInfo, $subTable, $proc); |
|
611
|
2
|
50
|
33
|
|
|
20
|
if (defined $newVal and $changed ne $$et{CHANGED}) { |
|
612
|
2
|
|
|
|
|
5
|
my $irefVer; |
|
613
|
2
|
50
|
|
|
|
8
|
if ($$boxPos{iref}) { |
|
614
|
2
|
|
|
|
|
13
|
$irefVer = Get8u($outfile, $$boxPos{iref}[0] + 8); |
|
615
|
|
|
|
|
|
|
} else { |
|
616
|
|
|
|
|
|
|
# create iref box after end of iinf box (and save version in boxPos list) |
|
617
|
0
|
0
|
|
|
|
0
|
$irefVer = ($primary < 0x10000 ? 0 : 1); |
|
618
|
0
|
|
|
|
|
0
|
$$boxPos{iref} = [ $$boxPos{iinf}[0] + $$boxPos{iinf}[1], 0, $irefVer ]; |
|
619
|
|
|
|
|
|
|
} |
|
620
|
2
|
100
|
|
|
|
10
|
$newVal = $hdr . $newVal if length $hdr; |
|
621
|
|
|
|
|
|
|
# add new infe to iinf |
|
622
|
2
|
50
|
|
|
|
13
|
$add{iinf} = $add{iref} = $add{iloc} = '' unless defined $add{iinf}; |
|
623
|
2
|
|
|
|
|
6
|
my ($type, $mime); |
|
624
|
2
|
100
|
|
|
|
7
|
if ($name eq 'XMP') { |
|
625
|
1
|
|
|
|
|
3
|
$type = "mime\0"; |
|
626
|
1
|
|
|
|
|
3
|
$mime = "application/rdf+xml\0"; |
|
627
|
|
|
|
|
|
|
} else { |
|
628
|
1
|
|
|
|
|
3
|
$type = "Exif\0"; |
|
629
|
1
|
|
|
|
|
3
|
$mime = ''; |
|
630
|
|
|
|
|
|
|
} |
|
631
|
2
|
|
|
|
|
5
|
my $id = 1; |
|
632
|
2
|
|
66
|
|
|
18
|
++$id while $$items{$id} or $usedID{$id}; # find next unused item ID |
|
633
|
2
|
|
|
|
|
7
|
my $n = length($type) + length($mime) + 16; |
|
634
|
2
|
50
|
|
|
|
9
|
if ($id < 0x10000) { |
|
635
|
2
|
|
|
|
|
18
|
$add{iinf} .= pack('Na4CCCCnn', $n, 'infe', 2, 0, 0, 1, $id, 0) . $type . $mime; |
|
636
|
|
|
|
|
|
|
} else { |
|
637
|
0
|
|
|
|
|
0
|
$n += 2; |
|
638
|
0
|
|
|
|
|
0
|
$add{iinf} .= pack('Na4CCCCNn', $n, 'infe', 3, 0, 0, 1, $id, 0) . $type . $mime; |
|
639
|
|
|
|
|
|
|
} |
|
640
|
|
|
|
|
|
|
# add new cdsc to iref |
|
641
|
2
|
50
|
|
|
|
7
|
if ($irefVer) { |
|
642
|
0
|
|
|
|
|
0
|
$add{iref} .= pack('Na4NnN', 18, 'cdsc', $id, 1, $primary); |
|
643
|
|
|
|
|
|
|
} else { |
|
644
|
2
|
|
|
|
|
12
|
$add{iref} .= pack('Na4nnn', 14, 'cdsc', $id, 1, $primary); |
|
645
|
|
|
|
|
|
|
} |
|
646
|
|
|
|
|
|
|
# add new entry to iloc table (see ISO14496-12:2015 pg.79) |
|
647
|
2
|
|
|
|
|
11
|
my $ilocVer = Get8u($outfile, $$boxPos{iloc}[0] + 8); |
|
648
|
2
|
|
|
|
|
9
|
my $siz = Get16u($outfile, $$boxPos{iloc}[0] + 12); # get size information |
|
649
|
2
|
|
|
|
|
8
|
my $noff = ($siz >> 12); |
|
650
|
2
|
|
|
|
|
7
|
my $nlen = ($siz >> 8) & 0x0f; |
|
651
|
2
|
|
|
|
|
7
|
my $nbas = ($siz >> 4) & 0x0f; |
|
652
|
2
|
|
|
|
|
6
|
my $nind = $siz & 0x0f; |
|
653
|
2
|
|
|
|
|
5
|
my ($pbas, $poff); |
|
654
|
2
|
50
|
|
|
|
30
|
if ($ilocVer == 0) { |
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
655
|
|
|
|
|
|
|
# set offset to 0 as flag that this is a new idat chunk being added |
|
656
|
2
|
|
|
|
|
7
|
$pbas = length($add{iloc}) + 4; |
|
657
|
2
|
|
|
|
|
5
|
$poff = $pbas + $nbas + 2; |
|
658
|
2
|
|
|
|
|
15
|
$add{iloc} .= pack('nn',$id,0) . SetVarInt(0,$nbas) . Set16u(1) . |
|
659
|
|
|
|
|
|
|
SetVarInt(0,$noff) . SetVarInt(length($newVal),$nlen); |
|
660
|
|
|
|
|
|
|
} elsif ($ilocVer == 1) { |
|
661
|
0
|
|
|
|
|
0
|
$pbas = length($add{iloc}) + 6; |
|
662
|
0
|
|
|
|
|
0
|
$poff = $pbas + $nbas + 2 + $nind; |
|
663
|
0
|
|
|
|
|
0
|
$add{iloc} .= pack('nnn',$id,0,0) . SetVarInt(0,$nbas) . Set16u(1) . SetVarInt(0,$nind) . |
|
664
|
|
|
|
|
|
|
SetVarInt(0,$noff) . SetVarInt(length($newVal),$nlen); |
|
665
|
|
|
|
|
|
|
} elsif ($ilocVer == 2) { |
|
666
|
0
|
|
|
|
|
0
|
$pbas = length($add{iloc}) + 8; |
|
667
|
0
|
|
|
|
|
0
|
$poff = $pbas + $nbas + 2 + $nind; |
|
668
|
0
|
|
|
|
|
0
|
$add{iloc} .= pack('Nnn',$id,0,0) . SetVarInt(0,$nbas) . Set16u(1) . SetVarInt(0,$nind) . |
|
669
|
|
|
|
|
|
|
SetVarInt(0,$noff) . SetVarInt(length($newVal),$nlen); |
|
670
|
|
|
|
|
|
|
} else { |
|
671
|
0
|
|
|
|
|
0
|
$et->Warn("Can't create $name. Unsupported iloc version $ilocVer"); |
|
672
|
0
|
|
|
|
|
0
|
last; |
|
673
|
|
|
|
|
|
|
} |
|
674
|
|
|
|
|
|
|
# add new ChunkOffset entry to update this new offset |
|
675
|
2
|
50
|
|
|
|
14
|
my $off = $$dirInfo{ChunkOffset} or $et->Warn('Internal error. Missing ChunkOffset'), last; |
|
676
|
2
|
|
|
|
|
5
|
my $newOff; |
|
677
|
2
|
50
|
|
|
|
8
|
if ($noff == 4) { |
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
678
|
2
|
|
|
|
|
1245
|
$newOff = [ 'stco_iloc', $$boxPos{iloc}[0] + $$boxPos{iloc}[1] + $poff, $noff, 0, $id ]; |
|
679
|
|
|
|
|
|
|
} elsif ($noff == 8) { |
|
680
|
0
|
|
|
|
|
0
|
$newOff = [ 'co64_iloc', $$boxPos{iloc}[0] + $$boxPos{iloc}[1] + $poff, $noff, 0, $id ]; |
|
681
|
|
|
|
|
|
|
} elsif ($noff == 0) { |
|
682
|
|
|
|
|
|
|
# offset_size is zero, so store the offset in base_offset instead |
|
683
|
0
|
0
|
|
|
|
0
|
if ($nbas == 4) { |
|
|
|
0
|
|
|
|
|
|
|
684
|
0
|
|
|
|
|
0
|
$newOff = [ 'stco_iloc', $$boxPos{iloc}[0] + $$boxPos{iloc}[1] + $pbas, $nbas, 0, $id ]; |
|
685
|
|
|
|
|
|
|
} elsif ($nbas == 8) { |
|
686
|
0
|
|
|
|
|
0
|
$newOff = [ 'co64_iloc', $$boxPos{iloc}[0] + $$boxPos{iloc}[1] + $pbas, $nbas, 0, $id ]; |
|
687
|
|
|
|
|
|
|
} else { |
|
688
|
0
|
|
|
|
|
0
|
$et->Warn("Can't create $name. Invalid iloc offset+base size"); |
|
689
|
0
|
|
|
|
|
0
|
last; |
|
690
|
|
|
|
|
|
|
} |
|
691
|
|
|
|
|
|
|
} else { |
|
692
|
0
|
|
|
|
|
0
|
$et->Warn("Can't create $name. Invalid iloc offset size"); |
|
693
|
0
|
|
|
|
|
0
|
last; |
|
694
|
|
|
|
|
|
|
} |
|
695
|
|
|
|
|
|
|
# add directory as a new mdat chunk |
|
696
|
2
|
|
|
|
|
9
|
push @$off, $newOff; |
|
697
|
2
|
|
|
|
|
10
|
push @mdatEdit, [ 0, 0, \$newVal, $id ]; |
|
698
|
2
|
|
|
|
|
7
|
$usedID{$id} = 1; |
|
699
|
2
|
|
50
|
|
|
11
|
$countNew = ($countNew || 0) + 1; |
|
700
|
2
|
|
|
|
|
15
|
$$et{CHANGED} = $changed; # set this later if successful in editing mdat |
|
701
|
|
|
|
|
|
|
} |
|
702
|
|
|
|
|
|
|
} |
|
703
|
3
|
100
|
|
|
|
15
|
if ($countNew) { |
|
704
|
|
|
|
|
|
|
# insert new entries into iinf, iref and iloc boxes, |
|
705
|
|
|
|
|
|
|
# and add new pitm box after hdlr if necessary |
|
706
|
2
|
|
|
|
|
4
|
my $added = 0; |
|
707
|
2
|
|
|
|
|
4
|
my $tag; |
|
708
|
2
|
|
|
|
|
18
|
foreach $tag (sort { $$boxPos{$a}[0] <=> $$boxPos{$b}[0] } keys %$boxPos) { |
|
|
19
|
|
|
|
|
37
|
|
|
709
|
12
|
100
|
|
|
|
30
|
next unless $add{$tag}; |
|
710
|
6
|
|
|
|
|
13
|
my $pos = $$boxPos{$tag}[0] + $added; |
|
711
|
6
|
50
|
33
|
|
|
20
|
unless ($$boxPos{$tag}[1]) { |
|
712
|
0
|
0
|
|
|
|
0
|
$tag eq 'iref' or $et->Error('Internal error adding iref box'), last; |
|
713
|
|
|
|
|
|
|
# create new iref box |
|
714
|
|
|
|
|
|
|
$add{$tag} = Set32u(12 + length $add{$tag}) . $tag . |
|
715
|
0
|
|
|
|
|
0
|
Set8u($$boxPos{$tag}[2]) . "\0\0\0" . $add{$tag}; |
|
716
|
|
|
|
|
|
|
} elsif ($tag ne 'hdlr') { |
|
717
|
|
|
|
|
|
|
my $n = Get32u($outfile, $pos); |
|
718
|
|
|
|
|
|
|
Set32u($n + length($add{$tag}), $outfile, $pos); # increase box size |
|
719
|
|
|
|
|
|
|
} |
|
720
|
6
|
100
|
|
|
|
25
|
if ($tag eq 'iinf') { |
|
|
|
100
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
721
|
2
|
|
|
|
|
7
|
my $iinfVer = Get8u($outfile, $pos + 8); |
|
722
|
2
|
50
|
|
|
|
9
|
if ($iinfVer == 0) { |
|
723
|
0
|
|
|
|
|
0
|
my $n = Get16u($outfile, $pos + 12); |
|
724
|
0
|
|
|
|
|
0
|
Set16u($n + $countNew, $outfile, $pos + 12); # incr count |
|
725
|
|
|
|
|
|
|
} else { |
|
726
|
2
|
|
|
|
|
8
|
my $n = Get32u($outfile, $pos + 12); |
|
727
|
2
|
|
|
|
|
8
|
Set32u($n + $countNew, $outfile, $pos + 12); # incr count |
|
728
|
|
|
|
|
|
|
} |
|
729
|
|
|
|
|
|
|
} elsif ($tag eq 'iref') { |
|
730
|
|
|
|
|
|
|
# nothing more to do |
|
731
|
|
|
|
|
|
|
} elsif ($tag eq 'iloc') { |
|
732
|
2
|
|
|
|
|
7
|
my $ilocVer = Get8u($outfile, $pos + 8); |
|
733
|
2
|
50
|
|
|
|
727
|
if ($ilocVer < 2) { |
|
734
|
2
|
|
|
|
|
13
|
my $n = Get16u($outfile, $pos + 14); |
|
735
|
2
|
|
|
|
|
12
|
Set16u($n + $countNew, $outfile, $pos + 14); # incr count |
|
736
|
|
|
|
|
|
|
} else { |
|
737
|
0
|
|
|
|
|
0
|
my $n = Get32u($outfile, $pos + 14); |
|
738
|
0
|
|
|
|
|
0
|
Set32u($n + $countNew, $outfile, $pos + 14); # incr count |
|
739
|
|
|
|
|
|
|
} |
|
740
|
|
|
|
|
|
|
# must also update pointer locations in this box |
|
741
|
2
|
50
|
|
|
|
9
|
if ($added) { |
|
742
|
0
|
|
|
|
|
0
|
$$_[1] += $added foreach @{$$dirInfo{ChunkOffset}}; |
|
|
0
|
|
|
|
|
0
|
|
|
743
|
|
|
|
|
|
|
} |
|
744
|
|
|
|
|
|
|
} elsif ($tag ne 'hdlr') { |
|
745
|
0
|
|
|
|
|
0
|
next; |
|
746
|
|
|
|
|
|
|
} |
|
747
|
|
|
|
|
|
|
# add new entries to this box (or add pitm after hdlr) |
|
748
|
6
|
|
|
|
|
17
|
substr($$outfile, $pos + $$boxPos{$tag}[1], 0) = $add{$tag}; |
|
749
|
6
|
|
|
|
|
13
|
$added += length $add{$tag}; # positions are shifted by length of new entries |
|
750
|
|
|
|
|
|
|
} |
|
751
|
|
|
|
|
|
|
} |
|
752
|
3
|
|
|
|
|
11
|
delete $$et{ItemInfo}; |
|
753
|
3
|
50
|
|
|
|
59
|
return @mdatEdit ? \@mdatEdit : undef; |
|
754
|
|
|
|
|
|
|
} |
|
755
|
|
|
|
|
|
|
|
|
756
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
|
757
|
|
|
|
|
|
|
# Write a series of QuickTime atoms from file or in memory |
|
758
|
|
|
|
|
|
|
# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref |
|
759
|
|
|
|
|
|
|
# Returns: A) if dirInfo contains DataPt: new directory data |
|
760
|
|
|
|
|
|
|
# B) otherwise: true on success, 0 if a write error occurred |
|
761
|
|
|
|
|
|
|
# (true but sets an Error on a file format error) |
|
762
|
|
|
|
|
|
|
# Notes: Yes, this is a real mess. Just like the QuickTime metadata situation. |
|
763
|
|
|
|
|
|
|
sub WriteQuickTime($$$) |
|
764
|
|
|
|
|
|
|
{ |
|
765
|
809
|
|
|
809
|
0
|
1531
|
local $_; |
|
766
|
809
|
|
|
|
|
1787
|
my ($et, $dirInfo, $tagTablePtr) = @_; |
|
767
|
809
|
100
|
|
|
|
3636
|
$et or return 1; # allow dummy access to autoload this package |
|
768
|
305
|
|
|
|
|
965
|
my ($mdat, @mdat, @mdatEdit, $edit, $track, $outBuff, $co, $term, $delCount); |
|
769
|
305
|
|
|
|
|
0
|
my (%langTags, $canCreate, $delGrp, %boxPos, %didDir, $writeLast, $err, $atomCount); |
|
770
|
305
|
|
50
|
|
|
872
|
my $outfile = $$dirInfo{OutFile} || return 0; |
|
771
|
305
|
|
|
|
|
716
|
my $raf = $$dirInfo{RAF}; # (will be null for lower-level atoms) |
|
772
|
305
|
|
|
|
|
550
|
my $dataPt = $$dirInfo{DataPt}; # (will be null for top-level atoms) |
|
773
|
305
|
|
|
|
|
517
|
my $dirName = $$dirInfo{DirName}; |
|
774
|
305
|
|
100
|
|
|
1013
|
my $dirStart = $$dirInfo{DirStart} || 0; |
|
775
|
305
|
|
|
|
|
532
|
my $parent = $$dirInfo{Parent}; |
|
776
|
305
|
|
|
|
|
517
|
my $addDirs = $$et{ADD_DIRS}; |
|
777
|
305
|
|
|
|
|
542
|
my $didTag = $$et{DidTag}; |
|
778
|
305
|
|
|
|
|
588
|
my $newTags = { }; |
|
779
|
305
|
|
|
|
|
492
|
my $createKeys = 0; |
|
780
|
305
|
100
|
|
|
|
772
|
my ($rtnVal, $rtnErr) = $dataPt ? (undef, undef) : (1, 0); |
|
781
|
|
|
|
|
|
|
|
|
782
|
305
|
100
|
|
|
|
611
|
if ($dataPt) { |
|
783
|
287
|
|
|
|
|
996
|
$raf = new File::RandomAccess($dataPt); |
|
784
|
|
|
|
|
|
|
} else { |
|
785
|
18
|
50
|
|
|
|
59
|
return 0 unless $raf; |
|
786
|
|
|
|
|
|
|
} |
|
787
|
|
|
|
|
|
|
# use buffered output for everything but 'mdat' atoms |
|
788
|
305
|
|
|
|
|
554
|
$outBuff = ''; |
|
789
|
305
|
|
|
|
|
547
|
$outfile = \$outBuff; |
|
790
|
|
|
|
|
|
|
|
|
791
|
305
|
100
|
|
|
|
814
|
$raf->Seek($dirStart, 1) if $dirStart; # skip header if it exists |
|
792
|
|
|
|
|
|
|
|
|
793
|
305
|
|
|
|
|
511
|
my $curPath = join '-', @{$$et{PATH}}; |
|
|
305
|
|
|
|
|
1089
|
|
|
794
|
305
|
|
|
|
|
724
|
my ($dir, $writePath) = ($dirName, $dirName); |
|
795
|
305
|
|
|
|
|
1361
|
$writePath = "$dir-$writePath" while defined($dir = $$et{DirMap}{$dir}); |
|
796
|
|
|
|
|
|
|
# hack to create Keys directories if necessary (its containing Meta is in a different location) |
|
797
|
305
|
100
|
100
|
|
|
1415
|
if ($$addDirs{Keys} and $curPath =~ /^MOV-Movie(-Meta)?$/) { |
|
|
|
100
|
|
|
|
|
|
|
798
|
8
|
|
|
|
|
21
|
$createKeys = 1; # create new Keys directories |
|
799
|
|
|
|
|
|
|
} elsif ($curPath eq 'MOV-Movie-Meta-ItemList') { |
|
800
|
9
|
|
|
|
|
23
|
$createKeys = 2; # create new Keys tags |
|
801
|
9
|
|
|
|
|
22
|
my $keys = $$et{Keys}; |
|
802
|
9
|
50
|
|
|
|
34
|
if ($keys) { |
|
803
|
|
|
|
|
|
|
# add new tag entries for existing Keys tags, now that we know their ID's |
|
804
|
|
|
|
|
|
|
# - first make lookup to convert Keys tagInfo ref to index number |
|
805
|
9
|
|
|
|
|
19
|
my ($index, %keysInfo); |
|
806
|
9
|
|
|
|
|
21
|
foreach $index (keys %{$$keys{Info}}) { |
|
|
9
|
|
|
|
|
40
|
|
|
807
|
15
|
100
|
|
|
|
63
|
$keysInfo{$$keys{Info}{$index}} = $index if $$keys{Remap}{$index}; |
|
808
|
|
|
|
|
|
|
} |
|
809
|
9
|
|
|
|
|
43
|
my $keysTable = GetTagTable('Image::ExifTool::QuickTime::Keys'); |
|
810
|
9
|
|
|
|
|
50
|
my $newKeysTags = $et->GetNewTagInfoHash($keysTable); |
|
811
|
9
|
|
|
|
|
45
|
foreach (keys %$newKeysTags) { |
|
812
|
12
|
|
|
|
|
23
|
my $tagInfo = $$newKeysTags{$_}; |
|
813
|
12
|
|
100
|
|
|
57
|
$index = $keysInfo{$tagInfo} || ($$tagInfo{SrcTagInfo} and $keysInfo{$$tagInfo{SrcTagInfo}}); |
|
814
|
12
|
100
|
|
|
|
51
|
next unless $index; |
|
815
|
2
|
|
|
|
|
27
|
my $id = Set32u($index); |
|
816
|
2
|
100
|
|
|
|
17
|
if ($$tagInfo{LangCode}) { |
|
817
|
|
|
|
|
|
|
# add to lookup of language tags we are writing with this ID |
|
818
|
1
|
50
|
|
|
|
7
|
$langTags{$id} = { } unless $langTags{$id}; |
|
819
|
1
|
|
|
|
|
5
|
$langTags{$id}{$_} = $tagInfo; |
|
820
|
1
|
|
|
|
|
5
|
$id .= '-' . $$tagInfo{LangCode}; |
|
821
|
|
|
|
|
|
|
} |
|
822
|
2
|
|
|
|
|
8
|
$$newTags{$id} = $tagInfo; |
|
823
|
|
|
|
|
|
|
} |
|
824
|
|
|
|
|
|
|
} |
|
825
|
|
|
|
|
|
|
} else { |
|
826
|
|
|
|
|
|
|
# get hash of new tags to edit/create in this directory |
|
827
|
288
|
|
|
|
|
1069
|
$newTags = $et->GetNewTagInfoHash($tagTablePtr); |
|
828
|
|
|
|
|
|
|
# make lookup of language tags for each ID |
|
829
|
288
|
|
|
|
|
872
|
foreach (keys %$newTags) { |
|
830
|
32
|
100
|
66
|
|
|
183
|
next unless $$newTags{$_}{LangCode} and $$newTags{$_}{SrcTagInfo}; |
|
831
|
2
|
|
|
|
|
8
|
my $id = $$newTags{$_}{SrcTagInfo}{TagID}; |
|
832
|
2
|
50
|
|
|
|
9
|
$langTags{$id} = { } unless $langTags{$id}; |
|
833
|
2
|
|
|
|
|
7
|
$langTags{$id}{$_} = $$newTags{$_}; |
|
834
|
|
|
|
|
|
|
} |
|
835
|
|
|
|
|
|
|
} |
|
836
|
305
|
100
|
100
|
|
|
1154
|
if ($curPath eq $writePath or $createKeys) { |
|
837
|
114
|
|
|
|
|
182
|
$canCreate = 1; |
|
838
|
114
|
|
|
|
|
271
|
$delGrp = $$et{DEL_GROUP}{$dirName}; |
|
839
|
|
|
|
|
|
|
} |
|
840
|
305
|
100
|
|
|
|
842
|
$atomCount = $$tagTablePtr{VARS}{ATOM_COUNT} if $$tagTablePtr{VARS}; |
|
841
|
|
|
|
|
|
|
|
|
842
|
305
|
|
|
|
|
470
|
for (;;) { # loop through all atoms at this level |
|
843
|
1347
|
0
|
33
|
|
|
3333
|
if (defined $atomCount and --$atomCount < 0 and $dataPt) { |
|
|
|
|
33
|
|
|
|
|
|
844
|
|
|
|
|
|
|
# stop processing now and just copy the rest of the atom |
|
845
|
0
|
0
|
|
|
|
0
|
Write($outfile, substr($$dataPt, $raf->Tell())) or $rtnVal=$rtnErr, $err=1; |
|
846
|
0
|
|
|
|
|
0
|
last; |
|
847
|
|
|
|
|
|
|
} |
|
848
|
1347
|
|
|
|
|
2396
|
my ($hdr, $buff, $keysIndex); |
|
849
|
1347
|
|
|
|
|
4040
|
my $n = $raf->Read($hdr, 8); |
|
850
|
1347
|
100
|
|
|
|
3272
|
unless ($n == 8) { |
|
851
|
305
|
50
|
33
|
|
|
1068
|
if ($n == 4 and $hdr eq "\0\0\0\0") { |
|
|
|
50
|
|
|
|
|
|
|
852
|
|
|
|
|
|
|
# "for historical reasons" the udta is optionally terminated by 4 zeros (ref 1) |
|
853
|
|
|
|
|
|
|
# --> hold this terminator to the end |
|
854
|
0
|
|
|
|
|
0
|
$term = $hdr; |
|
855
|
|
|
|
|
|
|
} elsif ($n != 0) { |
|
856
|
0
|
|
|
|
|
0
|
$et->Error("Unknown $n bytes at end of file", 1); |
|
857
|
|
|
|
|
|
|
} |
|
858
|
305
|
|
|
|
|
522
|
last; |
|
859
|
|
|
|
|
|
|
} |
|
860
|
1042
|
|
|
|
|
2927
|
my $size = Get32u(\$hdr, 0) - 8; # (atom size without 8-byte header) |
|
861
|
1042
|
|
|
|
|
2329
|
my $tag = substr($hdr, 4, 4); |
|
862
|
1042
|
100
|
|
|
|
3236
|
if ($size == -7) { |
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
863
|
|
|
|
|
|
|
# read the extended size |
|
864
|
3
|
50
|
|
|
|
41
|
$raf->Read($buff, 8) == 8 or $et->Error('Truncated extended atom'), last; |
|
865
|
3
|
|
|
|
|
23
|
$hdr .= $buff; |
|
866
|
3
|
|
|
|
|
21
|
my ($hi, $lo) = unpack('NN', $buff); |
|
867
|
3
|
50
|
33
|
|
|
25
|
if ($hi or $lo > 0x7fffffff) { |
|
868
|
0
|
0
|
|
|
|
0
|
if ($hi > 0x7fffffff) { |
|
|
|
0
|
|
|
|
|
|
|
869
|
0
|
|
|
|
|
0
|
$et->Error('Invalid atom size'); |
|
870
|
0
|
|
|
|
|
0
|
last; |
|
871
|
|
|
|
|
|
|
} elsif (not $et->Options('LargeFileSupport')) { |
|
872
|
0
|
|
|
|
|
0
|
$et->Error('End of processing at large atom (LargeFileSupport not enabled)'); |
|
873
|
0
|
|
|
|
|
0
|
last; |
|
874
|
|
|
|
|
|
|
} |
|
875
|
|
|
|
|
|
|
} |
|
876
|
3
|
|
|
|
|
65
|
$size = $hi * 4294967296 + $lo - 16; |
|
877
|
3
|
50
|
|
|
|
18
|
$size < 0 and $et->Error('Invalid extended atom size'), last; |
|
878
|
|
|
|
|
|
|
} elsif ($size == -8) { |
|
879
|
0
|
0
|
|
|
|
0
|
if ($dataPt) { |
|
880
|
0
|
0
|
|
|
|
0
|
last if $$dirInfo{DirName} eq 'CanonCNTH'; # (this is normal for Canon CNTH atom) |
|
881
|
0
|
|
|
|
|
0
|
my $pos = $raf->Tell() - 4; |
|
882
|
0
|
|
|
|
|
0
|
$raf->Seek(0,2); |
|
883
|
0
|
|
|
|
|
0
|
my $str = $$dirInfo{DirName} . ' with ' . ($raf->Tell() - $pos) . ' bytes'; |
|
884
|
0
|
|
|
|
|
0
|
$et->Error("Terminator found in $str remaining", 1); |
|
885
|
|
|
|
|
|
|
} else { |
|
886
|
|
|
|
|
|
|
# size of zero is only valid for top-level atom, and |
|
887
|
|
|
|
|
|
|
# indicates the atom extends to the end of file |
|
888
|
|
|
|
|
|
|
# (save in mdat list to write later; with zero end position to copy rest of file) |
|
889
|
0
|
|
|
|
|
0
|
push @mdat, [ $raf->Tell(), 0, $hdr ]; |
|
890
|
|
|
|
|
|
|
} |
|
891
|
0
|
|
|
|
|
0
|
last; |
|
892
|
|
|
|
|
|
|
} elsif ($size < 0) { |
|
893
|
0
|
|
|
|
|
0
|
$et->Error('Invalid atom size'); |
|
894
|
0
|
|
|
|
|
0
|
last; |
|
895
|
|
|
|
|
|
|
} |
|
896
|
|
|
|
|
|
|
|
|
897
|
|
|
|
|
|
|
# keep track of 'mdat' atom locations for writing later |
|
898
|
1042
|
100
|
|
|
|
3067
|
if ($tag eq 'mdat') { |
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
899
|
21
|
50
|
|
|
|
75
|
if ($dataPt) { |
|
900
|
0
|
|
|
|
|
0
|
$et->Error("'mdat' not at top level"); |
|
901
|
0
|
|
|
|
|
0
|
last; |
|
902
|
|
|
|
|
|
|
} |
|
903
|
21
|
|
|
|
|
97
|
push @mdat, [ $raf->Tell(), $raf->Tell() + $size, $hdr ]; |
|
904
|
21
|
50
|
|
|
|
99
|
$raf->Seek($size, 1) or $et->Error("Seek error in mdat atom"), return $rtnVal; |
|
905
|
21
|
|
|
|
|
75
|
next; |
|
906
|
|
|
|
|
|
|
} elsif ($tag eq 'cmov') { |
|
907
|
0
|
|
|
|
|
0
|
$et->Error("Can't yet write compressed movie metadata"); |
|
908
|
0
|
|
|
|
|
0
|
return $rtnVal; |
|
909
|
|
|
|
|
|
|
} elsif ($tag eq 'wide') { |
|
910
|
0
|
|
|
|
|
0
|
next; # drop 'wide' tag |
|
911
|
|
|
|
|
|
|
} |
|
912
|
|
|
|
|
|
|
|
|
913
|
|
|
|
|
|
|
# read the atom data |
|
914
|
1021
|
|
|
|
|
1541
|
my $got; |
|
915
|
1021
|
100
|
|
|
|
1873
|
if (not $size) { |
|
916
|
2
|
|
|
|
|
5
|
$buff = ''; |
|
917
|
2
|
|
|
|
|
7
|
$got = 0; |
|
918
|
|
|
|
|
|
|
} else { |
|
919
|
|
|
|
|
|
|
# read the atom data (but only first 64kB if data is huge) |
|
920
|
1019
|
50
|
|
|
|
2970
|
$got = $raf->Read($buff, $size > $maxReadLen ? 0x10000 : $size); |
|
921
|
|
|
|
|
|
|
} |
|
922
|
1021
|
50
|
|
|
|
2251
|
if ($got != $size) { |
|
923
|
|
|
|
|
|
|
# ignore up to 256 bytes of garbage at end of file |
|
924
|
0
|
0
|
0
|
|
|
0
|
if ($got <= 256 and $size >= 1024 and $tag ne 'mdat') { |
|
|
|
|
0
|
|
|
|
|
|
925
|
0
|
|
|
|
|
0
|
my $bytes = $got + length $hdr; |
|
926
|
0
|
0
|
|
|
|
0
|
if ($$et{OPTIONS}{IgnoreMinorErrors}) { |
|
927
|
0
|
|
|
|
|
0
|
$et->Warn("Deleted garbage at end of file ($bytes bytes)"); |
|
928
|
0
|
|
|
|
|
0
|
$buff = $hdr = ''; |
|
929
|
|
|
|
|
|
|
} else { |
|
930
|
0
|
|
|
|
|
0
|
$et->Error("Possible garbage at end of file ($bytes bytes)", 1); |
|
931
|
0
|
|
|
|
|
0
|
return $rtnVal; |
|
932
|
|
|
|
|
|
|
} |
|
933
|
|
|
|
|
|
|
} else { |
|
934
|
0
|
|
|
|
|
0
|
$tag = PrintableTagID($tag,3); |
|
935
|
0
|
0
|
0
|
|
|
0
|
if ($size > $maxReadLen and $got == 0x10000) { |
|
936
|
0
|
|
|
|
|
0
|
my $mb = int($size / 0x100000 + 0.5); |
|
937
|
0
|
|
|
|
|
0
|
$et->Error("'${tag}' atom is too large for rewriting ($mb MB)"); |
|
938
|
|
|
|
|
|
|
} else { |
|
939
|
0
|
|
|
|
|
0
|
$et->Error("Truncated '${tag}' atom"); |
|
940
|
|
|
|
|
|
|
} |
|
941
|
0
|
|
|
|
|
0
|
return $rtnVal; |
|
942
|
|
|
|
|
|
|
} |
|
943
|
|
|
|
|
|
|
} |
|
944
|
|
|
|
|
|
|
# save the handler type for this track |
|
945
|
1021
|
100
|
66
|
|
|
2719
|
if ($tag eq 'hdlr' and length $buff >= 12) { |
|
946
|
75
|
|
|
|
|
177
|
my $hdlr = substr($buff,8,4); |
|
947
|
75
|
100
|
|
|
|
435
|
$$et{HandlerType} = $hdlr if $hdlr =~ /^(vide|soun)$/; |
|
948
|
|
|
|
|
|
|
} |
|
949
|
|
|
|
|
|
|
|
|
950
|
|
|
|
|
|
|
# if this atom stores offsets, save its location so we can fix up offsets later |
|
951
|
|
|
|
|
|
|
# (are there any other atoms that may store absolute file offsets?) |
|
952
|
1021
|
100
|
|
|
|
3837
|
if ($tag =~ /^(stco|co64|iloc|mfra|moof|sidx|saio|gps |CTBO|uuid)$/) { |
|
953
|
|
|
|
|
|
|
# (note that we only need to do this if the media data is stored in this file) |
|
954
|
49
|
|
|
|
|
155
|
my $flg = $$et{QtDataFlg}; |
|
955
|
49
|
50
|
33
|
|
|
688
|
if ($tag eq 'mfra' or $tag eq 'moof') { |
|
|
|
50
|
33
|
|
|
|
|
|
|
|
100
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
956
|
0
|
|
|
|
|
0
|
$et->Error("Can't yet handle movie fragments when writing"); |
|
957
|
0
|
|
|
|
|
0
|
return $rtnVal; |
|
958
|
|
|
|
|
|
|
} elsif ($tag eq 'sidx' or $tag eq 'saio') { |
|
959
|
0
|
|
|
|
|
0
|
$et->Error("Can't yet handle $tag box when writing"); |
|
960
|
0
|
|
|
|
|
0
|
return $rtnVal; |
|
961
|
|
|
|
|
|
|
} elsif ($tag eq 'iloc') { |
|
962
|
3
|
50
|
|
|
|
20
|
Handle_iloc($et, $dirInfo, \$buff, $outfile) or $et->Error('Error parsing iloc atom'); |
|
963
|
|
|
|
|
|
|
} elsif ($tag eq 'gps ') { |
|
964
|
|
|
|
|
|
|
# (only care about the 'gps ' box in 'moov') |
|
965
|
0
|
0
|
0
|
|
|
0
|
if ($$dirInfo{DirID} and $$dirInfo{DirID} eq 'moov' and length $buff > 8) { |
|
|
|
|
0
|
|
|
|
|
|
966
|
0
|
|
|
|
|
0
|
my $off = $$dirInfo{ChunkOffset}; |
|
967
|
0
|
|
|
|
|
0
|
my $num = Get32u(\$buff, 4); |
|
968
|
0
|
0
|
|
|
|
0
|
$num = int((length($buff) - 8) / 8) if $num * 8 + 8 > length($buff); |
|
969
|
0
|
|
|
|
|
0
|
my $i; |
|
970
|
0
|
|
|
|
|
0
|
for ($i=0; $i<$num; ++$i) { |
|
971
|
0
|
|
|
|
|
0
|
push @$off, [ 'stco_gps ', length($$outfile) + length($hdr) + 8 + $i * 8, 4 ]; |
|
972
|
|
|
|
|
|
|
} |
|
973
|
|
|
|
|
|
|
} |
|
974
|
|
|
|
|
|
|
} elsif ($tag eq 'CTBO' or $tag eq 'uuid') { # hack for updating CR3 CTBO offsets |
|
975
|
13
|
|
|
|
|
27
|
push @{$$dirInfo{ChunkOffset}}, [ $tag, length($$outfile), length($hdr) + $size ]; |
|
|
13
|
|
|
|
|
96
|
|
|
976
|
|
|
|
|
|
|
} elsif (not $flg) { |
|
977
|
0
|
|
0
|
|
|
0
|
my $grp = $$et{CUR_WRITE_GROUP} || $parent; |
|
978
|
0
|
|
|
|
|
0
|
$et->Error("Can't locate data reference to update offsets for $grp"); |
|
979
|
0
|
|
|
|
|
0
|
return $rtnVal; |
|
980
|
|
|
|
|
|
|
} elsif ($flg == 3) { |
|
981
|
0
|
|
|
|
|
0
|
$et->Error("Can't write files with mixed internal/external media data"); |
|
982
|
0
|
|
|
|
|
0
|
return $rtnVal; |
|
983
|
|
|
|
|
|
|
} elsif ($flg == 1) { |
|
984
|
|
|
|
|
|
|
# must update offsets since the data is in this file |
|
985
|
33
|
|
|
|
|
60
|
push @{$$dirInfo{ChunkOffset}}, [ $tag, length($$outfile) + length($hdr), $size ]; |
|
|
33
|
|
|
|
|
153
|
|
|
986
|
|
|
|
|
|
|
} |
|
987
|
|
|
|
|
|
|
} |
|
988
|
|
|
|
|
|
|
|
|
989
|
|
|
|
|
|
|
# rewrite this atom |
|
990
|
1021
|
|
|
|
|
3193
|
my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag, \$buff); |
|
991
|
|
|
|
|
|
|
|
|
992
|
|
|
|
|
|
|
# call write hook if it exists |
|
993
|
1021
|
100
|
100
|
|
|
4015
|
&{$$tagInfo{WriteHook}}($buff,$et) if $tagInfo and $$tagInfo{WriteHook}; |
|
|
21
|
|
|
|
|
106
|
|
|
994
|
|
|
|
|
|
|
|
|
995
|
|
|
|
|
|
|
# allow numerical tag ID's (ItemList entries defined by Keys) |
|
996
|
1021
|
50
|
100
|
|
|
2872
|
if (not $tagInfo and $dirName eq 'ItemList' and $$et{Keys}) { |
|
|
|
|
66
|
|
|
|
|
|
997
|
21
|
|
|
|
|
55
|
$keysIndex = unpack('N', $tag); |
|
998
|
21
|
|
|
|
|
56
|
my $newIndex = $$et{Keys}{Remap}{$keysIndex}; |
|
999
|
21
|
50
|
|
|
|
58
|
if (defined $newIndex) { |
|
1000
|
21
|
|
|
|
|
53
|
$tagInfo = $$et{Keys}{Info}{$keysIndex}; |
|
1001
|
21
|
100
|
|
|
|
56
|
unless ($newIndex) { |
|
1002
|
7
|
100
|
|
|
|
19
|
if ($tagInfo) { |
|
1003
|
1
|
|
|
|
|
8
|
$et->VPrint(1," - Keys:$$tagInfo{Name}"); |
|
1004
|
|
|
|
|
|
|
} else { |
|
1005
|
6
|
|
100
|
|
|
23
|
$delCount = ($delCount || 0) + 1; |
|
1006
|
|
|
|
|
|
|
} |
|
1007
|
7
|
|
|
|
|
16
|
++$$et{CHANGED}; |
|
1008
|
7
|
|
|
|
|
16
|
next; |
|
1009
|
|
|
|
|
|
|
} |
|
1010
|
|
|
|
|
|
|
# use the new Keys index of this item if it changed |
|
1011
|
14
|
100
|
|
|
|
40
|
unless ($keysIndex == $newIndex) { |
|
1012
|
2
|
|
|
|
|
6
|
$tag = Set32u($newIndex); |
|
1013
|
2
|
|
|
|
|
8
|
substr($hdr, 4, 4) = $tag; |
|
1014
|
|
|
|
|
|
|
} |
|
1015
|
|
|
|
|
|
|
} else { |
|
1016
|
0
|
|
|
|
|
0
|
undef $keysIndex; |
|
1017
|
|
|
|
|
|
|
} |
|
1018
|
|
|
|
|
|
|
} |
|
1019
|
|
|
|
|
|
|
# delete all ItemList tags when deleting group, but take care not to delete UserData Meta |
|
1020
|
1014
|
100
|
|
|
|
2023
|
if ($delGrp) { |
|
1021
|
77
|
100
|
66
|
|
|
293
|
if ($dirName eq 'ItemList') { |
|
|
|
100
|
100
|
|
|
|
|
|
1022
|
44
|
|
100
|
|
|
94
|
$delCount = ($delCount || 0) + 1; |
|
1023
|
44
|
|
|
|
|
73
|
++$$et{CHANGED}; |
|
1024
|
44
|
|
|
|
|
76
|
next; |
|
1025
|
|
|
|
|
|
|
} elsif ($dirName eq 'UserData' and (not $tagInfo or not $$tagInfo{SubDirectory})) { |
|
1026
|
14
|
|
100
|
|
|
34
|
$delCount = ($delCount || 0) + 1; |
|
1027
|
14
|
|
|
|
|
23
|
++$$et{CHANGED}; |
|
1028
|
14
|
|
|
|
|
28
|
next; |
|
1029
|
|
|
|
|
|
|
} |
|
1030
|
|
|
|
|
|
|
} |
|
1031
|
956
|
50
|
66
|
|
|
3033
|
undef $tagInfo if $tagInfo and $$tagInfo{Unknown}; |
|
1032
|
|
|
|
|
|
|
|
|
1033
|
956
|
50
|
66
|
|
|
3465
|
if ($tagInfo and (not defined $$tagInfo{Writable} or $$tagInfo{Writable})) { |
|
|
|
|
100
|
|
|
|
|
|
1034
|
785
|
|
|
|
|
1728
|
my $subdir = $$tagInfo{SubDirectory}; |
|
1035
|
785
|
|
|
|
|
1327
|
my ($newData, @chunkOffset); |
|
1036
|
|
|
|
|
|
|
|
|
1037
|
785
|
100
|
|
|
|
1540
|
if ($subdir) { # process atoms in this container from a buffer in memory |
|
1038
|
|
|
|
|
|
|
|
|
1039
|
565
|
100
|
|
|
|
1256
|
undef $$et{HandlerType} if $tag eq 'trak'; # init handler type for this track |
|
1040
|
|
|
|
|
|
|
|
|
1041
|
565
|
|
66
|
|
|
2109
|
my $subName = $$subdir{DirName} || $$tagInfo{Name}; |
|
1042
|
565
|
|
100
|
|
|
1801
|
my $start = $$subdir{Start} || 0; |
|
1043
|
565
|
|
100
|
|
|
2158
|
my $base = ($$dirInfo{Base} || 0) + $raf->Tell() - $size; |
|
1044
|
565
|
|
|
|
|
929
|
my $dPos = 0; |
|
1045
|
565
|
|
|
|
|
846
|
my $hdrLen = $start; |
|
1046
|
565
|
50
|
|
|
|
1410
|
if ($$subdir{Base}) { |
|
1047
|
0
|
|
|
|
|
0
|
my $localBase = eval $$subdir{Base}; |
|
1048
|
0
|
|
|
|
|
0
|
$dPos -= $localBase; |
|
1049
|
0
|
|
|
|
|
0
|
$base -= $dPos; |
|
1050
|
|
|
|
|
|
|
# get length of header before base offset |
|
1051
|
0
|
0
|
|
|
|
0
|
$hdrLen -= $localBase if $localBase <= $hdrLen; |
|
1052
|
|
|
|
|
|
|
} |
|
1053
|
|
|
|
|
|
|
my %subdirInfo = ( |
|
1054
|
|
|
|
|
|
|
Parent => $dirName, |
|
1055
|
|
|
|
|
|
|
DirName => $subName, |
|
1056
|
|
|
|
|
|
|
Name => $$tagInfo{Name}, |
|
1057
|
|
|
|
|
|
|
DirID => $tag, |
|
1058
|
|
|
|
|
|
|
DataPt => \$buff, |
|
1059
|
|
|
|
|
|
|
DataLen => $size, |
|
1060
|
|
|
|
|
|
|
DataPos => $dPos, |
|
1061
|
|
|
|
|
|
|
DirStart => $start, |
|
1062
|
|
|
|
|
|
|
DirLen => $size - $start, |
|
1063
|
|
|
|
|
|
|
Base => $base, |
|
1064
|
|
|
|
|
|
|
HasData => $$subdir{HasData}, |
|
1065
|
|
|
|
|
|
|
Multi => $$subdir{Multi}, # necessary? |
|
1066
|
|
|
|
|
|
|
OutFile => $outfile, |
|
1067
|
|
|
|
|
|
|
NoRefTest=> 1, # don't check directory references |
|
1068
|
|
|
|
|
|
|
WriteGroup => $$tagInfo{WriteGroup}, |
|
1069
|
|
|
|
|
|
|
# initialize array to hold details about chunk offset table |
|
1070
|
|
|
|
|
|
|
# (each entry has 3-5 items: 0=atom type, 1=table offset, 2=table size, |
|
1071
|
|
|
|
|
|
|
# 3=optional base offset, 4=optional item ID) |
|
1072
|
565
|
|
|
|
|
5630
|
ChunkOffset => \@chunkOffset, |
|
1073
|
|
|
|
|
|
|
); |
|
1074
|
|
|
|
|
|
|
# set InPlace flag so XMP will be padded properly when |
|
1075
|
|
|
|
|
|
|
# QuickTimePad is used if this is an XMP directory |
|
1076
|
565
|
50
|
|
|
|
2362
|
$subdirInfo{InPlace} = 2 if $et->Options('QuickTimePad'); |
|
1077
|
|
|
|
|
|
|
# pass the header pointer if necessary (for EXIF IFD's |
|
1078
|
|
|
|
|
|
|
# where the Base offset is at the end of the header) |
|
1079
|
565
|
100
|
66
|
|
|
1780
|
if ($hdrLen and $hdrLen < $size) { |
|
1080
|
62
|
|
|
|
|
199
|
my $header = substr($buff,0,$hdrLen); |
|
1081
|
62
|
|
|
|
|
207
|
$subdirInfo{HeaderPtr} = \$header; |
|
1082
|
|
|
|
|
|
|
} |
|
1083
|
565
|
100
|
66
|
|
|
1610
|
SetByteOrder('II') if $$subdir{ByteOrder} and $$subdir{ByteOrder} =~ /^Little/; |
|
1084
|
565
|
|
|
|
|
1060
|
my $oldWriteGroup = $$et{CUR_WRITE_GROUP}; |
|
1085
|
565
|
100
|
|
|
|
1247
|
if ($subName eq 'Track') { |
|
1086
|
33
|
100
|
|
|
|
136
|
$track or $track = 0; |
|
1087
|
33
|
|
|
|
|
172
|
$$et{CUR_WRITE_GROUP} = 'Track' . (++$track); |
|
1088
|
|
|
|
|
|
|
} |
|
1089
|
565
|
|
|
|
|
1626
|
my $subTable = GetTagTable($$subdir{TagTable}); |
|
1090
|
|
|
|
|
|
|
# demote non-QuickTime errors to warnings |
|
1091
|
565
|
100
|
|
|
|
2722
|
$$et{DemoteErrors} = 1 unless $$subTable{GROUPS}{0} eq 'QuickTime'; |
|
1092
|
565
|
|
|
|
|
1022
|
my $oldChanged = $$et{CHANGED}; |
|
1093
|
565
|
|
|
|
|
2977
|
$newData = $et->WriteDirectory(\%subdirInfo, $subTable, $$subdir{WriteProc}); |
|
1094
|
565
|
100
|
|
|
|
2039
|
if ($$et{DemoteErrors}) { |
|
1095
|
|
|
|
|
|
|
# just copy existing subdirectory if a non-quicktime error occurred |
|
1096
|
33
|
50
|
|
|
|
168
|
$$et{CHANGED} = $oldChanged if $$et{DemoteErrors} > 1; |
|
1097
|
33
|
|
|
|
|
77
|
delete $$et{DemoteErrors}; |
|
1098
|
|
|
|
|
|
|
} |
|
1099
|
565
|
50
|
100
|
|
|
2016
|
if (defined $newData and not length $newData and ($$tagInfo{Permanent} or |
|
|
|
|
33
|
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
1100
|
|
|
|
|
|
|
($$tagTablePtr{PERMANENT} and not defined $$tagInfo{Permanent}))) |
|
1101
|
|
|
|
|
|
|
{ |
|
1102
|
|
|
|
|
|
|
# do nothing if trying to delete tag from a PERMANENT table |
|
1103
|
0
|
|
|
|
|
0
|
$$et{CHANGED} = $oldChanged; |
|
1104
|
0
|
|
|
|
|
0
|
undef $newData; |
|
1105
|
|
|
|
|
|
|
} |
|
1106
|
565
|
|
|
|
|
1087
|
$$et{CUR_WRITE_GROUP} = $oldWriteGroup; |
|
1107
|
565
|
|
|
|
|
5542
|
SetByteOrder('MM'); |
|
1108
|
|
|
|
|
|
|
# add back header if necessary |
|
1109
|
565
|
100
|
100
|
|
|
1784
|
if ($start and defined $newData and (length $newData or |
|
|
|
|
66
|
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
1110
|
|
|
|
|
|
|
(defined $$tagInfo{Permanent} and not $$tagInfo{Permanent}))) |
|
1111
|
|
|
|
|
|
|
{ |
|
1112
|
56
|
|
|
|
|
239
|
$newData = substr($buff,0,$start) . $newData; |
|
1113
|
56
|
|
|
|
|
208
|
$$_[1] += $start foreach @chunkOffset; |
|
1114
|
|
|
|
|
|
|
} |
|
1115
|
|
|
|
|
|
|
# the directory exists, so we don't need to add it |
|
1116
|
565
|
100
|
100
|
|
|
2102
|
if ($curPath eq $writePath and $$addDirs{$subName} and $$addDirs{$subName} eq $dirName) { |
|
|
|
|
100
|
|
|
|
|
|
1117
|
34
|
|
|
|
|
98
|
delete $$addDirs{$subName}; |
|
1118
|
|
|
|
|
|
|
} |
|
1119
|
565
|
|
|
|
|
3012
|
$didDir{$tag} = 1; # (note: keyed by tag ID) |
|
1120
|
|
|
|
|
|
|
|
|
1121
|
|
|
|
|
|
|
} else { # modify existing QuickTime tags in various formats |
|
1122
|
|
|
|
|
|
|
|
|
1123
|
220
|
|
|
|
|
850
|
my $nvHash = $et->GetNewValueHash($tagInfo); |
|
1124
|
220
|
100
|
100
|
|
|
1155
|
if ($nvHash or $langTags{$tag} or $delGrp) { |
|
|
|
|
66
|
|
|
|
|
|
1125
|
9
|
|
|
|
|
13
|
my $nvHashNoLang = $nvHash; |
|
1126
|
9
|
|
|
|
|
31
|
my ($val, $len, $lang, $type, $flags, $ctry, $charsetQuickTime); |
|
1127
|
9
|
|
|
|
|
22
|
my $format = $$tagInfo{Format}; |
|
1128
|
9
|
|
66
|
|
|
51
|
my $hasData = ($$dirInfo{HasData} and $buff =~ /\0...data\0/s); |
|
1129
|
9
|
|
|
|
|
17
|
my $langInfo = $tagInfo; |
|
1130
|
9
|
100
|
33
|
|
|
54
|
if ($hasData) { |
|
|
|
50
|
50
|
|
|
|
|
|
|
|
50
|
33
|
|
|
|
|
|
1131
|
5
|
|
|
|
|
9
|
my $pos = 0; |
|
1132
|
5
|
|
|
|
|
11
|
for (;;$pos+=$len) { |
|
1133
|
10
|
100
|
|
|
|
27
|
if ($pos + 16 > $size) { |
|
1134
|
|
|
|
|
|
|
# add any new alternate language tags now |
|
1135
|
5
|
100
|
|
|
|
15
|
if ($langTags{$tag}) { |
|
1136
|
1
|
|
|
|
|
3
|
my $tg; |
|
1137
|
1
|
|
|
|
|
3
|
foreach $tg ('', sort keys %{$langTags{$tag}}) { |
|
|
1
|
|
|
|
|
5
|
|
|
1138
|
2
|
100
|
|
|
|
8
|
my $ti = $tg ? $langTags{$tag}{$tg} : $nvHashNoLang; |
|
1139
|
2
|
|
|
|
|
6
|
$nvHash = $et->GetNewValueHash($ti); |
|
1140
|
2
|
100
|
66
|
|
|
10
|
next unless $nvHash and not $$didTag{$nvHash}; |
|
1141
|
1
|
|
|
|
|
4
|
$$didTag{$nvHash} = 1; |
|
1142
|
1
|
50
|
33
|
|
|
7
|
next unless $$nvHash{IsCreating} and $et->IsOverwriting($nvHash); |
|
1143
|
1
|
|
|
|
|
5
|
my $newVal = $et->GetNewValue($nvHash); |
|
1144
|
1
|
50
|
|
|
|
5
|
next unless defined $newVal; |
|
1145
|
1
|
|
|
|
|
3
|
my $prVal = $newVal; |
|
1146
|
1
|
|
|
|
|
6
|
my $flags = FormatQTValue($et, \$newVal, $tagInfo, $format); |
|
1147
|
1
|
50
|
|
|
|
4
|
next unless defined $newVal; |
|
1148
|
1
|
|
|
|
|
3
|
my ($ctry, $lang) = (0, 0); |
|
1149
|
1
|
50
|
|
|
|
4
|
if ($$ti{LangCode}) { |
|
1150
|
1
|
50
|
|
|
|
9
|
unless ($$ti{LangCode} =~ /^([A-Z]{3})?[-_]?([A-Z]{2})?$/i) { |
|
1151
|
0
|
|
|
|
|
0
|
$et->Warn("Invalid language code for $$ti{Name}"); |
|
1152
|
0
|
|
|
|
|
0
|
next; |
|
1153
|
|
|
|
|
|
|
} |
|
1154
|
|
|
|
|
|
|
# pack language and country codes |
|
1155
|
1
|
50
|
33
|
|
|
11
|
if ($1 and $1 ne 'und') { |
|
1156
|
1
|
|
|
|
|
9
|
$lang = ($lang << 5) | ($_ - 0x60) foreach unpack 'C*', lc($1); |
|
1157
|
|
|
|
|
|
|
} |
|
1158
|
1
|
50
|
33
|
|
|
11
|
$ctry = unpack('n', pack('a2',uc($2))) if $2 and $2 ne 'ZZ'; |
|
1159
|
|
|
|
|
|
|
} |
|
1160
|
1
|
50
|
|
|
|
5
|
$newData = substr($buff, 0, $pos) unless defined $newData; |
|
1161
|
1
|
|
|
|
|
7
|
$newData .= pack('Na4Nnn',16+length($newVal),'data',$flags,$ctry,$lang).$newVal; |
|
1162
|
1
|
|
|
|
|
5
|
my $grp = $et->GetGroup($ti, 1); |
|
1163
|
1
|
|
|
|
|
12
|
$et->VerboseValue("+ $grp:$$ti{Name}", $prVal); |
|
1164
|
1
|
|
|
|
|
5
|
++$$et{CHANGED}; |
|
1165
|
|
|
|
|
|
|
} |
|
1166
|
|
|
|
|
|
|
} |
|
1167
|
5
|
|
|
|
|
11
|
last; |
|
1168
|
|
|
|
|
|
|
} |
|
1169
|
5
|
|
|
|
|
36
|
($len, $type, $flags, $ctry, $lang) = unpack("x${pos}Na4Nnn", $buff); |
|
1170
|
5
|
50
|
|
|
|
19
|
$lang or $lang = $undLang; # treat both 0 and 'und' as 'und' |
|
1171
|
5
|
|
|
|
|
8
|
$langInfo = $tagInfo; |
|
1172
|
5
|
|
|
|
|
16
|
my $delTag = $delGrp; |
|
1173
|
5
|
|
|
|
|
8
|
my $newVal; |
|
1174
|
5
|
|
|
|
|
24
|
my $langCode = GetLangCode($lang, $ctry, 1); |
|
1175
|
5
|
|
|
|
|
11
|
for (;;) { |
|
1176
|
5
|
|
|
|
|
15
|
$langInfo = GetLangInfo($tagInfo, $langCode); |
|
1177
|
5
|
|
|
|
|
22
|
$nvHash = $et->GetNewValueHash($langInfo); |
|
1178
|
5
|
0
|
33
|
|
|
38
|
last if $nvHash or not $ctry or $lang ne $undLang or length($langCode)==2; |
|
|
|
|
33
|
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
1179
|
|
|
|
|
|
|
# check to see if tag was written with a 2-char country code only |
|
1180
|
0
|
|
|
|
|
0
|
$langCode = lc unpack('a2',pack('n',$ctry)); |
|
1181
|
|
|
|
|
|
|
} |
|
1182
|
|
|
|
|
|
|
# set flag to delete language tag when writing default |
|
1183
|
|
|
|
|
|
|
# (except for a default-language Keys entry) |
|
1184
|
5
|
100
|
66
|
|
|
22
|
if (not $nvHash and $nvHashNoLang) { |
|
1185
|
4
|
50
|
33
|
|
|
57
|
if ($lang eq $undLang and not $ctry and not $$didTag{$nvHashNoLang}) { |
|
|
|
|
33
|
|
|
|
|
|
1186
|
4
|
|
|
|
|
12
|
$nvHash = $nvHashNoLang; # write existing default |
|
1187
|
|
|
|
|
|
|
} else { |
|
1188
|
0
|
|
|
|
|
0
|
$delTag = 1; # delete tag |
|
1189
|
|
|
|
|
|
|
} |
|
1190
|
|
|
|
|
|
|
} |
|
1191
|
5
|
50
|
|
|
|
14
|
last if $pos + $len > $size; |
|
1192
|
5
|
50
|
33
|
|
|
23
|
if ($type eq 'data' and $len >= 16) { |
|
|
|
0
|
|
|
|
|
|
|
1193
|
5
|
|
|
|
|
9
|
$pos += 16; |
|
1194
|
5
|
|
|
|
|
9
|
$len -= 16; |
|
1195
|
5
|
|
|
|
|
13
|
$val = substr($buff, $pos, $len); |
|
1196
|
|
|
|
|
|
|
# decode value (see QuickTime.pm for an explanation) |
|
1197
|
5
|
50
|
|
|
|
20
|
if ($stringEncoding{$flags}) { |
|
1198
|
5
|
|
|
|
|
26
|
$val = $et->Decode($val, $stringEncoding{$flags}); |
|
1199
|
5
|
50
|
|
|
|
23
|
$val =~ s/\0$// unless $$tagInfo{Binary}; |
|
1200
|
5
|
|
|
|
|
11
|
$flags = 0x01; # write all strings as UTF-8 |
|
1201
|
|
|
|
|
|
|
} else { |
|
1202
|
0
|
0
|
|
|
|
0
|
if ($format) { |
|
1203
|
|
|
|
|
|
|
# update flags for the format we are writing |
|
1204
|
0
|
0
|
0
|
|
|
0
|
if ($$tagInfo{Writable} and $qtFormat{$$tagInfo{Writable}}) { |
|
|
|
0
|
|
|
|
|
|
|
1205
|
0
|
|
|
|
|
0
|
$flags = $qtFormat{$$tagInfo{Writable}}; |
|
1206
|
|
|
|
|
|
|
} elsif ($qtFormat{$format}) { |
|
1207
|
0
|
|
|
|
|
0
|
$flags = $qtFormat{$format}; |
|
1208
|
|
|
|
|
|
|
} |
|
1209
|
|
|
|
|
|
|
} else { |
|
1210
|
0
|
|
|
|
|
0
|
$format = QuickTimeFormat($flags, $len); |
|
1211
|
|
|
|
|
|
|
} |
|
1212
|
0
|
0
|
|
|
|
0
|
$val = ReadValue(\$val, 0, $format, $$tagInfo{Count}, $len) if $format; |
|
1213
|
|
|
|
|
|
|
} |
|
1214
|
5
|
100
|
100
|
|
|
37
|
if (($nvHash and $et->IsOverwriting($nvHash, $val)) or $delTag) { |
|
|
|
50
|
66
|
|
|
|
|
|
1215
|
3
|
50
|
|
|
|
14
|
$newVal = $et->GetNewValue($nvHash) if defined $nvHash; |
|
1216
|
3
|
50
|
33
|
|
|
36
|
if ($delTag or not defined $newVal or $$didTag{$nvHash}) { |
|
|
|
|
33
|
|
|
|
|
|
1217
|
|
|
|
|
|
|
# delete the tag |
|
1218
|
0
|
|
|
|
|
0
|
my $grp = $et->GetGroup($langInfo, 1); |
|
1219
|
0
|
|
|
|
|
0
|
$et->VerboseValue("- $grp:$$langInfo{Name}", $val); |
|
1220
|
|
|
|
|
|
|
# copy data up to start of this tag to delete this value |
|
1221
|
0
|
0
|
|
|
|
0
|
$newData = substr($buff, 0, $pos-16) unless defined $newData; |
|
1222
|
0
|
|
|
|
|
0
|
++$$et{CHANGED}; |
|
1223
|
0
|
|
|
|
|
0
|
next; |
|
1224
|
|
|
|
|
|
|
} |
|
1225
|
3
|
|
|
|
|
21
|
my $prVal = $newVal; |
|
1226
|
|
|
|
|
|
|
# format new value for writing (and get new flags) |
|
1227
|
3
|
|
|
|
|
13
|
$flags = FormatQTValue($et, \$newVal, $tagInfo, $format); |
|
1228
|
3
|
50
|
|
|
|
8
|
next unless defined $newVal; |
|
1229
|
3
|
|
|
|
|
13
|
my $grp = $et->GetGroup($langInfo, 1); |
|
1230
|
3
|
|
|
|
|
20
|
$et->VerboseValue("- $grp:$$langInfo{Name}", $val); |
|
1231
|
3
|
|
|
|
|
15
|
$et->VerboseValue("+ $grp:$$langInfo{Name}", $prVal); |
|
1232
|
3
|
50
|
|
|
|
13
|
$newData = substr($buff, 0, $pos-16) unless defined $newData; |
|
1233
|
3
|
50
|
|
|
|
9
|
my $wLang = $lang eq $undLang ? 0 : $lang; |
|
1234
|
3
|
|
|
|
|
15
|
$newData .= pack('Na4Nnn', length($newVal)+16, $type, $flags, $ctry, $wLang); |
|
1235
|
3
|
|
|
|
|
8
|
$newData .= $newVal; |
|
1236
|
3
|
|
|
|
|
8
|
++$$et{CHANGED}; |
|
1237
|
|
|
|
|
|
|
} elsif (defined $newData) { |
|
1238
|
0
|
|
|
|
|
0
|
$newData .= substr($buff, $pos-16, $len+16); |
|
1239
|
|
|
|
|
|
|
} |
|
1240
|
|
|
|
|
|
|
} elsif (defined $newData) { |
|
1241
|
0
|
|
|
|
|
0
|
$newData .= substr($buff, $pos, $len); |
|
1242
|
|
|
|
|
|
|
} |
|
1243
|
5
|
100
|
|
|
|
25
|
$$didTag{$nvHash} = 1 if $nvHash; |
|
1244
|
|
|
|
|
|
|
} |
|
1245
|
5
|
50
|
66
|
|
|
38
|
$newData .= substr($buff, $pos) if defined $newData and $pos < $size; |
|
1246
|
5
|
|
|
|
|
13
|
undef $val; # (already constructed $newData) |
|
1247
|
|
|
|
|
|
|
} elsif ($format) { |
|
1248
|
0
|
|
|
|
|
0
|
$val = ReadValue(\$buff, 0, $format, undef, $size); |
|
1249
|
|
|
|
|
|
|
} elsif (($tag =~ /^\xa9/ or $$tagInfo{IText}) and $size >= ($$tagInfo{IText} || 4)) { |
|
1250
|
4
|
|
|
|
|
8
|
my $hdr; |
|
1251
|
4
|
50
|
33
|
|
|
14
|
if ($$tagInfo{IText} and $$tagInfo{IText} >= 6) { |
|
1252
|
0
|
|
|
|
|
0
|
my $iText = $$tagInfo{IText}; |
|
1253
|
0
|
|
|
|
|
0
|
my $pos = $iText - 2; |
|
1254
|
0
|
|
|
|
|
0
|
$lang = unpack("x${pos}n", $buff); |
|
1255
|
0
|
|
|
|
|
0
|
$hdr = substr($buff,4,$iText-6); |
|
1256
|
0
|
|
|
|
|
0
|
$len = $size - $iText; |
|
1257
|
0
|
|
|
|
|
0
|
$val = substr($buff, $iText, $len); |
|
1258
|
|
|
|
|
|
|
} else { |
|
1259
|
4
|
|
|
|
|
12
|
($len, $lang) = unpack('nn', $buff); |
|
1260
|
4
|
50
|
|
|
|
15
|
$len -= 4 if 4 + $len > $size; # (see QuickTime.pm for explanation) |
|
1261
|
4
|
50
|
33
|
|
|
16
|
$len = $size - 4 if $len > $size - 4 or $len < 0; |
|
1262
|
4
|
|
|
|
|
11
|
$val = substr($buff, 4, $len); |
|
1263
|
|
|
|
|
|
|
} |
|
1264
|
4
|
50
|
|
|
|
10
|
$lang or $lang = $undLang; # treat both 0 and 'und' as 'und' |
|
1265
|
4
|
|
|
|
|
7
|
my $enc; |
|
1266
|
4
|
50
|
33
|
|
|
12
|
if ($lang < 0x400 and $val !~ /^\xfe\xff/) { |
|
1267
|
0
|
|
|
|
|
0
|
$charsetQuickTime = $et->Options('CharsetQuickTime'); |
|
1268
|
0
|
|
|
|
|
0
|
$enc = $charsetQuickTime; |
|
1269
|
|
|
|
|
|
|
} else { |
|
1270
|
4
|
50
|
|
|
|
14
|
$enc = $val=~s/^\xfe\xff// ? 'UTF16' : 'UTF8'; |
|
1271
|
|
|
|
|
|
|
} |
|
1272
|
4
|
50
|
|
|
|
10
|
unless ($$tagInfo{NoDecode}) { |
|
1273
|
4
|
|
|
|
|
15
|
$val = $et->Decode($val, $enc); |
|
1274
|
4
|
|
|
|
|
11
|
$val =~ s/\0+$//; # remove trailing nulls if they exist |
|
1275
|
|
|
|
|
|
|
} |
|
1276
|
4
|
50
|
|
|
|
11
|
$val = $hdr . $val if defined $hdr; |
|
1277
|
4
|
|
|
|
|
16
|
my $langCode = UnpackLang($lang, 1); |
|
1278
|
4
|
|
|
|
|
14
|
$langInfo = GetLangInfo($tagInfo, $langCode); |
|
1279
|
4
|
|
|
|
|
15
|
$nvHash = $et->GetNewValueHash($langInfo); |
|
1280
|
4
|
100
|
66
|
|
|
19
|
if (not $nvHash and $nvHashNoLang) { |
|
1281
|
3
|
50
|
33
|
|
|
16
|
if ($lang eq $undLang and not $$didTag{$nvHashNoLang}) { |
|
|
|
0
|
|
|
|
|
|
|
1282
|
3
|
|
|
|
|
7
|
$nvHash = $nvHashNoLang; |
|
1283
|
|
|
|
|
|
|
} elsif ($canCreate) { |
|
1284
|
|
|
|
|
|
|
# delete other languages when writing default |
|
1285
|
0
|
|
|
|
|
0
|
my $grp = $et->GetGroup($langInfo, 1); |
|
1286
|
0
|
|
|
|
|
0
|
$et->VerboseValue("- $grp:$$langInfo{Name}", $val); |
|
1287
|
0
|
|
|
|
|
0
|
++$$et{CHANGED}; |
|
1288
|
0
|
|
|
|
|
0
|
next; |
|
1289
|
|
|
|
|
|
|
} |
|
1290
|
|
|
|
|
|
|
} |
|
1291
|
|
|
|
|
|
|
} else { |
|
1292
|
0
|
|
|
|
|
0
|
$val = $buff; |
|
1293
|
0
|
0
|
0
|
|
|
0
|
if ($tag =~ /^\xa9/ or $$tagInfo{IText}) { |
|
1294
|
0
|
|
|
|
|
0
|
$et->Warn("Corrupted $$tagInfo{Name} value"); |
|
1295
|
|
|
|
|
|
|
} |
|
1296
|
|
|
|
|
|
|
} |
|
1297
|
9
|
100
|
100
|
|
|
46
|
if ($nvHash and defined $val) { |
|
1298
|
3
|
50
|
|
|
|
11
|
if ($et->IsOverwriting($nvHash, $val)) { |
|
1299
|
3
|
|
|
|
|
13
|
$newData = $et->GetNewValue($nvHash); |
|
1300
|
3
|
50
|
33
|
|
|
13
|
$newData = '' unless defined $newData or $canCreate; |
|
1301
|
3
|
|
|
|
|
6
|
++$$et{CHANGED}; |
|
1302
|
3
|
|
|
|
|
25
|
my $grp = $et->GetGroup($langInfo, 1); |
|
1303
|
3
|
|
|
|
|
19
|
$et->VerboseValue("- $grp:$$langInfo{Name}", $val); |
|
1304
|
3
|
50
|
33
|
|
|
16
|
next unless defined $newData and not $$didTag{$nvHash}; |
|
1305
|
3
|
|
|
|
|
13
|
$et->VerboseValue("+ $grp:$$langInfo{Name}", $newData); |
|
1306
|
|
|
|
|
|
|
# add back necessary header and encode as necessary |
|
1307
|
3
|
50
|
0
|
|
|
8
|
if (defined $lang) { |
|
|
|
0
|
0
|
|
|
|
|
|
|
|
0
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
1308
|
3
|
|
50
|
|
|
13
|
my $iText = $$tagInfo{IText} || 0; |
|
1309
|
3
|
|
|
|
|
5
|
my $hdr; |
|
1310
|
3
|
50
|
|
|
|
8
|
if ($iText > 6) { |
|
1311
|
0
|
0
|
|
|
|
0
|
$newData .= ' 'x($iText-6) if length($newData) < $iText-6; |
|
1312
|
0
|
|
|
|
|
0
|
$hdr = substr($newData, 0, $iText-6); |
|
1313
|
0
|
|
|
|
|
0
|
$newData = substr($newData, $iText-6); |
|
1314
|
|
|
|
|
|
|
} |
|
1315
|
3
|
50
|
|
|
|
9
|
unless ($$tagInfo{NoDecode}) { |
|
1316
|
3
|
50
|
|
|
|
14
|
$newData = $et->Encode($newData, $lang < 0x400 ? $charsetQuickTime : 'UTF8'); |
|
1317
|
|
|
|
|
|
|
} |
|
1318
|
3
|
50
|
|
|
|
8
|
my $wLang = $lang eq $undLang ? 0 : $lang; |
|
1319
|
3
|
50
|
|
|
|
7
|
if ($iText < 6) { |
|
|
|
0
|
|
|
|
|
|
|
1320
|
3
|
|
|
|
|
13
|
$newData = pack('nn', length($newData), $wLang) . $newData; |
|
1321
|
|
|
|
|
|
|
} elsif ($iText == 6) { |
|
1322
|
0
|
|
|
|
|
0
|
$newData = pack('Nn', 0, $wLang) . $newData . "\0"; |
|
1323
|
|
|
|
|
|
|
} else { |
|
1324
|
0
|
|
|
|
|
0
|
$newData = "\0\0\0\0" . $hdr . pack('n', $wLang) . $newData . "\0"; |
|
1325
|
|
|
|
|
|
|
} |
|
1326
|
|
|
|
|
|
|
} elsif (not $format or $format =~ /^string/ and |
|
1327
|
|
|
|
|
|
|
not $$tagInfo{Binary} and not $$tagInfo{ValueConv}) |
|
1328
|
|
|
|
|
|
|
{ |
|
1329
|
|
|
|
|
|
|
# write all strings as UTF-8 |
|
1330
|
0
|
|
|
|
|
0
|
$newData = $et->Encode($newData, 'UTF8'); |
|
1331
|
|
|
|
|
|
|
} elsif ($format and not $$tagInfo{Binary}) { |
|
1332
|
|
|
|
|
|
|
# format new value for writing |
|
1333
|
0
|
|
|
|
|
0
|
$newData = WriteValue($newData, $format); |
|
1334
|
|
|
|
|
|
|
} |
|
1335
|
|
|
|
|
|
|
} |
|
1336
|
3
|
|
|
|
|
13
|
$$didTag{$nvHash} = 1; # set flag so we don't add this tag again |
|
1337
|
|
|
|
|
|
|
} |
|
1338
|
|
|
|
|
|
|
} |
|
1339
|
|
|
|
|
|
|
} |
|
1340
|
|
|
|
|
|
|
# write the new atom if it was modified |
|
1341
|
785
|
100
|
|
|
|
1868
|
if (defined $newData) { |
|
1342
|
394
|
|
|
|
|
771
|
my $sizeDiff = length($buff) - length($newData); |
|
1343
|
|
|
|
|
|
|
# pad to original size if specified, otherwise give verbose message about the changed size |
|
1344
|
394
|
50
|
100
|
|
|
1395
|
if ($sizeDiff > 0 and $$tagInfo{PreservePadding} and $et->Options('QuickTimePad')) { |
|
|
|
100
|
66
|
|
|
|
|
|
1345
|
0
|
|
|
|
|
0
|
$newData .= "\0" x $sizeDiff; |
|
1346
|
0
|
|
|
|
|
0
|
$et->VPrint(1, " ($$tagInfo{Name} padded to original size)"); |
|
1347
|
|
|
|
|
|
|
} elsif ($sizeDiff) { |
|
1348
|
67
|
|
|
|
|
355
|
$et->VPrint(1, " ($$tagInfo{Name} changed size)"); |
|
1349
|
|
|
|
|
|
|
} |
|
1350
|
394
|
|
|
|
|
781
|
my $len = length($newData) + 8; |
|
1351
|
394
|
50
|
|
|
|
829
|
$len > 0x7fffffff and $et->Error("$$tagInfo{Name} to large to write"), last; |
|
1352
|
|
|
|
|
|
|
# update size in ChunkOffset list for modified 'uuid' atom |
|
1353
|
394
|
100
|
|
|
|
838
|
$$dirInfo{ChunkOffset}[-1][2] = $len if $tag eq 'uuid'; |
|
1354
|
394
|
100
|
|
|
|
794
|
next unless $len > 8; # don't write empty atom header |
|
1355
|
|
|
|
|
|
|
# maintain pointer to chunk offsets if necessary |
|
1356
|
385
|
100
|
|
|
|
868
|
if (@chunkOffset) { |
|
1357
|
153
|
|
|
|
|
550
|
$$_[1] += 8 + length $$outfile foreach @chunkOffset; |
|
1358
|
153
|
|
|
|
|
257
|
push @{$$dirInfo{ChunkOffset}}, @chunkOffset; |
|
|
153
|
|
|
|
|
394
|
|
|
1359
|
|
|
|
|
|
|
} |
|
1360
|
385
|
100
|
|
|
|
917
|
if ($$tagInfo{WriteLast}) { |
|
1361
|
1
|
|
50
|
|
|
26
|
$writeLast = ($writeLast || '') . Set32u($len) . $tag . $newData; |
|
1362
|
|
|
|
|
|
|
} else { |
|
1363
|
384
|
|
|
|
|
1113
|
$boxPos{$tag} = [ length($$outfile), length($newData) + 8 ]; |
|
1364
|
|
|
|
|
|
|
# write the updated directory with its atom header |
|
1365
|
384
|
50
|
|
|
|
1424
|
Write($outfile, Set32u($len), $tag, $newData) or $rtnVal=$rtnErr, $err=1, last; |
|
1366
|
|
|
|
|
|
|
} |
|
1367
|
385
|
|
|
|
|
1112
|
next; |
|
1368
|
|
|
|
|
|
|
} |
|
1369
|
|
|
|
|
|
|
} |
|
1370
|
|
|
|
|
|
|
# keep track of data references in this track |
|
1371
|
562
|
50
|
66
|
|
|
2798
|
if ($tag eq 'dinf') { |
|
|
|
100
|
66
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
1372
|
0
|
|
|
|
|
0
|
$$et{QtDataRef} = [ ]; # initialize list of data references |
|
1373
|
|
|
|
|
|
|
} elsif ($parent eq 'DataInfo' and length($buff) >= 4) { |
|
1374
|
|
|
|
|
|
|
# save data reference type and version/flags |
|
1375
|
33
|
|
|
|
|
90
|
push @{$$et{QtDataRef}}, [ $tag, Get32u(\$buff,0) ]; |
|
|
33
|
|
|
|
|
146
|
|
|
1376
|
|
|
|
|
|
|
} elsif ($tag eq 'stsd' and length($buff) >= 8) { |
|
1377
|
33
|
|
|
|
|
151
|
my $n = Get32u(\$buff, 4); # get number of sample descriptions in table |
|
1378
|
33
|
|
|
|
|
140
|
my ($pos, $flg) = (8, 0); |
|
1379
|
33
|
|
|
|
|
62
|
my ($i, $msg); |
|
1380
|
33
|
|
|
|
|
123
|
for ($i=0; $i<$n; ++$i) { # loop through sample descriptions |
|
1381
|
33
|
50
|
|
|
|
125
|
$pos + 16 <= length($buff) or $msg = 'Truncated sample table', last; |
|
1382
|
33
|
|
|
|
|
99
|
my $siz = Get32u(\$buff, $pos); |
|
1383
|
33
|
50
|
|
|
|
131
|
$pos + $siz <= length($buff) or $msg = 'Truncated sample table', last; |
|
1384
|
33
|
|
|
|
|
118
|
my $drefIdx = Get16u(\$buff, $pos + 14); |
|
1385
|
33
|
|
|
|
|
113
|
my $drefTbl = $$et{QtDataRef}; |
|
1386
|
33
|
50
|
33
|
|
|
230
|
if (not $drefIdx) { |
|
|
|
50
|
|
|
|
|
|
|
1387
|
0
|
|
|
|
|
0
|
$flg |= 0x01; # in this file if data reference index is 0 (if like iloc) |
|
1388
|
|
|
|
|
|
|
} elsif ($drefTbl and $$drefTbl[$drefIdx-1]) { |
|
1389
|
33
|
|
|
|
|
83
|
my $dref = $$drefTbl[$drefIdx-1]; |
|
1390
|
|
|
|
|
|
|
# $flg = 0x01-in this file, 0x02-in some other file |
|
1391
|
33
|
50
|
33
|
|
|
178
|
$flg |= ($$dref[1] == 1 and $$dref[0] ne 'rsrc') ? 0x01 : 0x02; |
|
1392
|
|
|
|
|
|
|
} else { |
|
1393
|
0
|
|
|
|
|
0
|
$msg = "No data reference for sample description $i"; |
|
1394
|
0
|
|
|
|
|
0
|
last; |
|
1395
|
|
|
|
|
|
|
} |
|
1396
|
33
|
|
|
|
|
105
|
$pos += $siz; |
|
1397
|
|
|
|
|
|
|
} |
|
1398
|
33
|
50
|
|
|
|
100
|
if ($msg) { |
|
1399
|
|
|
|
|
|
|
# (allow empty sample description for non-audio/video handler types, eg. 'url ', 'meta') |
|
1400
|
0
|
0
|
|
|
|
0
|
if ($$et{HandlerType}) { |
|
1401
|
0
|
|
0
|
|
|
0
|
my $grp = $$et{CUR_WRITE_GROUP} || $parent; |
|
1402
|
0
|
|
|
|
|
0
|
$et->Error("$msg for $grp"); |
|
1403
|
0
|
|
|
|
|
0
|
return $rtnErr; |
|
1404
|
|
|
|
|
|
|
} |
|
1405
|
0
|
|
|
|
|
0
|
$flg = 1; # (this seems to be the case) |
|
1406
|
|
|
|
|
|
|
} |
|
1407
|
33
|
|
|
|
|
102
|
$$et{QtDataFlg} = $flg; |
|
1408
|
|
|
|
|
|
|
} |
|
1409
|
562
|
50
|
66
|
|
|
1860
|
if ($tagInfo and $$tagInfo{WriteLast}) { |
|
1410
|
0
|
|
0
|
|
|
0
|
$writeLast = ($writeLast || '') . $hdr . $buff; |
|
1411
|
|
|
|
|
|
|
} else { |
|
1412
|
|
|
|
|
|
|
# save position of this box in the output buffer |
|
1413
|
562
|
|
|
|
|
1916
|
$boxPos{$tag} = [ length($$outfile), length($hdr) + length($buff) ]; |
|
1414
|
|
|
|
|
|
|
# copy the existing atom |
|
1415
|
562
|
50
|
|
|
|
2088
|
Write($outfile, $hdr, $buff) or $rtnVal=$rtnErr, $err=1, last; |
|
1416
|
|
|
|
|
|
|
} |
|
1417
|
|
|
|
|
|
|
} |
|
1418
|
305
|
50
|
|
|
|
737
|
$et->VPrint(0, " [deleting $delCount $dirName tag".($delCount==1 ? '' : 's')."]\n") if $delCount; |
|
|
|
100
|
|
|
|
|
|
|
1419
|
|
|
|
|
|
|
|
|
1420
|
305
|
100
|
|
|
|
849
|
$createKeys &= ~0x01 unless $$addDirs{Keys}; # (Keys may have been written) |
|
1421
|
|
|
|
|
|
|
|
|
1422
|
|
|
|
|
|
|
# add new directories/tags at this level if necessary |
|
1423
|
305
|
100
|
100
|
|
|
1074
|
if ($canCreate and (exists $$et{EDIT_DIRS}{$dirName} or $createKeys)) { |
|
|
|
|
100
|
|
|
|
|
|
1424
|
|
|
|
|
|
|
# get a hash of tagInfo references to add to this directory |
|
1425
|
79
|
|
|
|
|
341
|
my $dirs = $et->GetAddDirHash($tagTablePtr, $dirName); |
|
1426
|
|
|
|
|
|
|
# make sorted list of new tags to be added |
|
1427
|
79
|
|
|
|
|
388
|
my @addTags = sort(keys(%$dirs), keys %$newTags); |
|
1428
|
79
|
|
|
|
|
202
|
my ($tag, $index); |
|
1429
|
|
|
|
|
|
|
# add Keys tags if necessary |
|
1430
|
79
|
100
|
|
|
|
277
|
if ($createKeys) { |
|
1431
|
15
|
100
|
33
|
|
|
119
|
if ($curPath eq 'MOV-Movie') { |
|
|
|
100
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
1432
|
|
|
|
|
|
|
# add Meta for Keys if necessary |
|
1433
|
2
|
50
|
|
|
|
11
|
unless ($didDir{meta}) { |
|
1434
|
2
|
|
|
|
|
8
|
$$dirs{meta} = $Image::ExifTool::QuickTime::Movie{meta}; |
|
1435
|
2
|
|
|
|
|
7
|
push @addTags, 'meta'; |
|
1436
|
|
|
|
|
|
|
} |
|
1437
|
|
|
|
|
|
|
} elsif ($curPath eq 'MOV-Movie-Meta') { |
|
1438
|
|
|
|
|
|
|
# special case for Keys Meta -- reset directories and start again |
|
1439
|
4
|
|
|
|
|
15
|
undef @addTags; |
|
1440
|
4
|
|
|
|
|
12
|
$dirs = { }; |
|
1441
|
4
|
|
|
|
|
11
|
foreach ('keys','ilst') { |
|
1442
|
8
|
50
|
|
|
|
31
|
next if $didDir{$_}; # don't add again |
|
1443
|
0
|
|
|
|
|
0
|
$$dirs{$_} = $Image::ExifTool::QuickTime::Meta{$_}; |
|
1444
|
0
|
|
|
|
|
0
|
push @addTags, $_; |
|
1445
|
|
|
|
|
|
|
} |
|
1446
|
|
|
|
|
|
|
} elsif ($curPath eq 'MOV-Movie-Meta-ItemList' and $$et{Keys}) { |
|
1447
|
9
|
|
|
|
|
25
|
foreach $index (sort { $a <=> $b } keys %{$$et{Keys}{Add}}) { |
|
|
1
|
|
|
|
|
8
|
|
|
|
9
|
|
|
|
|
48
|
|
|
1448
|
5
|
|
|
|
|
18
|
my $id = Set32u($index); |
|
1449
|
5
|
|
|
|
|
19
|
$$newTags{$id} = $$et{Keys}{Add}{$index}; |
|
1450
|
5
|
|
|
|
|
17
|
push @addTags, $id; |
|
1451
|
|
|
|
|
|
|
} |
|
1452
|
|
|
|
|
|
|
} else { |
|
1453
|
0
|
|
|
|
|
0
|
$dirs = $et->GetAddDirHash($tagTablePtr, $dirName); |
|
1454
|
0
|
|
|
|
|
0
|
push @addTags, sort keys %$dirs; |
|
1455
|
|
|
|
|
|
|
} |
|
1456
|
|
|
|
|
|
|
} |
|
1457
|
|
|
|
|
|
|
# (note that $tag may be a binary Keys index here) |
|
1458
|
79
|
|
|
|
|
221
|
foreach $tag (@addTags) { |
|
1459
|
53
|
|
66
|
|
|
221
|
my $tagInfo = $$dirs{$tag} || $$newTags{$tag}; |
|
1460
|
53
|
50
|
33
|
|
|
184
|
next if defined $$tagInfo{CanCreate} and not $$tagInfo{CanCreate}; |
|
1461
|
|
|
|
|
|
|
next if defined $$tagInfo{HandlerType} and |
|
1462
|
53
|
0
|
0
|
|
|
134
|
(not $$et{HandlerType} or $$et{HandlerType} ne $$tagInfo{HandlerType}); |
|
|
|
|
33
|
|
|
|
|
|
1463
|
53
|
|
|
|
|
101
|
my $subdir = $$tagInfo{SubDirectory}; |
|
1464
|
53
|
100
|
|
|
|
127
|
unless ($subdir) { |
|
1465
|
39
|
|
|
|
|
121
|
my $nvHash = $et->GetNewValueHash($tagInfo); |
|
1466
|
39
|
100
|
66
|
|
|
205
|
next unless $nvHash and not $$didTag{$nvHash}; |
|
1467
|
31
|
100
|
66
|
|
|
152
|
next unless $$nvHash{IsCreating} and $et->IsOverwriting($nvHash); |
|
1468
|
18
|
|
|
|
|
69
|
my $newVal = $et->GetNewValue($nvHash); |
|
1469
|
18
|
50
|
|
|
|
49
|
next unless defined $newVal; |
|
1470
|
18
|
|
|
|
|
39
|
my $prVal = $newVal; |
|
1471
|
18
|
|
|
|
|
84
|
my $flags = FormatQTValue($et, \$newVal, $tagInfo); |
|
1472
|
18
|
50
|
|
|
|
47
|
next unless defined $newVal; |
|
1473
|
18
|
|
|
|
|
45
|
my ($ctry, $lang) = (0, 0); |
|
1474
|
|
|
|
|
|
|
# handle alternate languages |
|
1475
|
18
|
50
|
|
|
|
49
|
if ($$tagInfo{LangCode}) { |
|
1476
|
0
|
|
|
|
|
0
|
$tag = substr($tag, 0, 4); # strip language code from tag ID |
|
1477
|
0
|
0
|
|
|
|
0
|
unless ($$tagInfo{LangCode} =~ /^([A-Z]{3})?[-_]?([A-Z]{2})?$/i) { |
|
1478
|
0
|
|
|
|
|
0
|
$et->Warn("Invalid language code for $$tagInfo{Name}"); |
|
1479
|
0
|
|
|
|
|
0
|
next; |
|
1480
|
|
|
|
|
|
|
} |
|
1481
|
|
|
|
|
|
|
# pack language and country codes |
|
1482
|
0
|
0
|
0
|
|
|
0
|
if ($1 and $1 ne 'und') { |
|
1483
|
0
|
|
|
|
|
0
|
$lang = ($lang << 5) | ($_ - 0x60) foreach unpack 'C*', lc($1); |
|
1484
|
|
|
|
|
|
|
} |
|
1485
|
0
|
0
|
0
|
|
|
0
|
$ctry = unpack('n', pack('a2',uc($2))) if $2 and $2 ne 'ZZ'; |
|
1486
|
|
|
|
|
|
|
} |
|
1487
|
18
|
100
|
66
|
|
|
98
|
if ($$dirInfo{HasData}) { |
|
|
|
50
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
1488
|
|
|
|
|
|
|
# add 'data' header |
|
1489
|
11
|
|
|
|
|
86
|
$newVal = pack('Na4Nnn',16+length($newVal),'data',$flags,$ctry,$lang).$newVal; |
|
1490
|
|
|
|
|
|
|
} elsif ($tag =~ /^\xa9/ or $$tagInfo{IText}) { |
|
1491
|
7
|
50
|
66
|
|
|
50
|
if ($ctry) { |
|
|
|
100
|
|
|
|
|
|
|
1492
|
0
|
|
|
|
|
0
|
my $grp = $et->GetGroup($tagInfo,1); |
|
1493
|
0
|
|
|
|
|
0
|
$et->Warn("Can't use country code for $grp:$$tagInfo{Name}"); |
|
1494
|
0
|
|
|
|
|
0
|
next; |
|
1495
|
|
|
|
|
|
|
} elsif ($$tagInfo{IText} and $$tagInfo{IText} >= 6) { |
|
1496
|
|
|
|
|
|
|
# add 6-byte langText header and trailing null |
|
1497
|
|
|
|
|
|
|
# (with extra junk before language code if IText > 6) |
|
1498
|
2
|
|
|
|
|
6
|
my $n = $$tagInfo{IText} - 6; |
|
1499
|
2
|
50
|
|
|
|
8
|
$newVal .= ' ' x $n if length($newVal) < $n; |
|
1500
|
2
|
|
|
|
|
12
|
$newVal = "\0\0\0\0" . substr($newVal,0,$n) . pack('n',0,$lang) . substr($newVal,$n) . "\0"; |
|
1501
|
|
|
|
|
|
|
} else { |
|
1502
|
|
|
|
|
|
|
# add IText header |
|
1503
|
5
|
|
|
|
|
27
|
$newVal = pack('nn',length($newVal),$lang) . $newVal; |
|
1504
|
|
|
|
|
|
|
} |
|
1505
|
|
|
|
|
|
|
} elsif ($ctry or $lang) { |
|
1506
|
0
|
|
|
|
|
0
|
my $grp = $et->GetGroup($tagInfo,1); |
|
1507
|
0
|
|
|
|
|
0
|
$et->Warn("Can't use language code for $grp:$$tagInfo{Name}"); |
|
1508
|
0
|
|
|
|
|
0
|
next; |
|
1509
|
|
|
|
|
|
|
} |
|
1510
|
18
|
50
|
|
|
|
62
|
if ($$tagInfo{WriteLast}) { |
|
1511
|
0
|
|
0
|
|
|
0
|
$writeLast = ($writeLast || '') . Set32u(8+length($newVal)) . $tag . $newVal; |
|
1512
|
|
|
|
|
|
|
} else { |
|
1513
|
18
|
|
|
|
|
62
|
$boxPos{$tag} = [ length($$outfile), 8 + length($newVal) ]; |
|
1514
|
18
|
50
|
|
|
|
81
|
Write($outfile, Set32u(8+length($newVal)), $tag, $newVal) or $rtnVal=$rtnErr, $err=1; |
|
1515
|
|
|
|
|
|
|
} |
|
1516
|
18
|
|
|
|
|
90
|
my $grp = $et->GetGroup($tagInfo, 1); |
|
1517
|
18
|
|
|
|
|
139
|
$et->VerboseValue("+ $grp:$$tagInfo{Name}", $prVal); |
|
1518
|
18
|
|
|
|
|
63
|
$$didTag{$nvHash} = 1; |
|
1519
|
18
|
|
|
|
|
40
|
++$$et{CHANGED}; |
|
1520
|
18
|
|
|
|
|
73
|
next; |
|
1521
|
|
|
|
|
|
|
} |
|
1522
|
14
|
|
33
|
|
|
54
|
my $subName = $$subdir{DirName} || $$tagInfo{Name}; |
|
1523
|
|
|
|
|
|
|
# QuickTime hierarchy is complex, so check full directory path before adding |
|
1524
|
14
|
|
|
|
|
31
|
my $buff; |
|
1525
|
14
|
100
|
66
|
|
|
144
|
if ($createKeys and $curPath eq 'MOV-Movie' and $subName eq 'Meta') { |
|
|
|
50
|
100
|
|
|
|
|
|
|
|
100
|
66
|
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
1526
|
2
|
|
|
|
|
11
|
$et->VPrint(0, " Creating Meta with mdta Handler and Keys\n"); |
|
1527
|
|
|
|
|
|
|
# init Meta box for Keys tags with mdta Handler and empty Keys+ItemList |
|
1528
|
2
|
|
|
|
|
6
|
$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" . |
|
1529
|
|
|
|
|
|
|
"\0\0\0\x10keys\0\0\0\0\0\0\0\0" . |
|
1530
|
|
|
|
|
|
|
"\0\0\0\x08ilst"; |
|
1531
|
|
|
|
|
|
|
} elsif ($createKeys and $curPath eq 'MOV-Movie-Meta') { |
|
1532
|
0
|
0
|
|
|
|
0
|
$buff = ($subName eq 'Keys' ? "\0\0\0\0\0\0\0\0" : ''); |
|
1533
|
|
|
|
|
|
|
} elsif ($subName eq 'Meta' and $$et{OPTIONS}{QuickTimeHandler}) { |
|
1534
|
2
|
|
|
|
|
15
|
$et->VPrint(0, " Creating Meta with mdir Handler\n"); |
|
1535
|
|
|
|
|
|
|
# init Meta box for ItemList tags with mdir Handler |
|
1536
|
2
|
|
|
|
|
7
|
$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"; |
|
1537
|
|
|
|
|
|
|
} else { |
|
1538
|
10
|
50
|
33
|
|
|
109
|
next unless $curPath eq $writePath and $$addDirs{$subName} and $$addDirs{$subName} eq $dirName; |
|
|
|
|
33
|
|
|
|
|
|
1539
|
10
|
|
|
|
|
31
|
$buff = ''; # write from scratch |
|
1540
|
|
|
|
|
|
|
} |
|
1541
|
|
|
|
|
|
|
my %subdirInfo = ( |
|
1542
|
|
|
|
|
|
|
Parent => $dirName, |
|
1543
|
|
|
|
|
|
|
DirName => $subName, |
|
1544
|
|
|
|
|
|
|
DataPt => \$buff, |
|
1545
|
|
|
|
|
|
|
DirStart => 0, |
|
1546
|
|
|
|
|
|
|
HasData => $$subdir{HasData}, |
|
1547
|
|
|
|
|
|
|
OutFile => $outfile, |
|
1548
|
|
|
|
|
|
|
ChunkOffset => [ ], # (just to be safe) |
|
1549
|
|
|
|
|
|
|
WriteGroup => $$tagInfo{WriteGroup}, |
|
1550
|
14
|
|
|
|
|
132
|
); |
|
1551
|
14
|
|
|
|
|
61
|
my $subTable = GetTagTable($$subdir{TagTable}); |
|
1552
|
14
|
|
|
|
|
84
|
my $newData = $et->WriteDirectory(\%subdirInfo, $subTable, $$subdir{WriteProc}); |
|
1553
|
14
|
50
|
33
|
|
|
110
|
if ($newData and length($newData) <= 0x7ffffff7) { |
|
1554
|
14
|
|
|
|
|
35
|
my $prefix = ''; |
|
1555
|
|
|
|
|
|
|
# add atom version or ID if necessary |
|
1556
|
14
|
100
|
|
|
|
45
|
if ($$subdir{Start}) { |
|
1557
|
5
|
100
|
|
|
|
40
|
if ($$subdir{Start} == 4) { |
|
1558
|
2
|
|
|
|
|
5
|
$prefix = "\0\0\0\0"; # a simple version number |
|
1559
|
|
|
|
|
|
|
} else { |
|
1560
|
|
|
|
|
|
|
# get UUID from Condition expression |
|
1561
|
3
|
|
|
|
|
9
|
my $cond = $$tagInfo{Condition}; |
|
1562
|
3
|
50
|
33
|
|
|
294
|
$prefix = eval qq("$1") if $cond and $cond =~ m{=~\s*\/\^(.*)/}; |
|
1563
|
3
|
50
|
|
|
|
28
|
length($prefix) == $$subdir{Start} or $et->Error('Internal UUID error'); |
|
1564
|
|
|
|
|
|
|
} |
|
1565
|
|
|
|
|
|
|
} |
|
1566
|
14
|
|
|
|
|
64
|
my $newHdr = Set32u(8+length($newData)+length($prefix)) . $tag . $prefix; |
|
1567
|
14
|
100
|
|
|
|
61
|
if ($$tagInfo{WriteLast}) { |
|
1568
|
1
|
|
50
|
|
|
18
|
$writeLast = ($writeLast || '') . $newHdr . $newData; |
|
1569
|
|
|
|
|
|
|
} else { |
|
1570
|
13
|
100
|
|
|
|
52
|
if ($tag eq 'uuid') { |
|
1571
|
|
|
|
|
|
|
# add offset for new uuid (needed for CR3 CTBO offsets) |
|
1572
|
2
|
|
|
|
|
5
|
my $off = $$dirInfo{ChunkOffset}; |
|
1573
|
2
|
|
|
|
|
14
|
push @$off, [ $tag, length($$outfile), length($newHdr) + length($newData) ]; |
|
1574
|
|
|
|
|
|
|
} |
|
1575
|
13
|
|
|
|
|
52
|
$boxPos{$tag} = [ length($$outfile), length($newHdr) + length($newData) ]; |
|
1576
|
13
|
50
|
|
|
|
55
|
Write($outfile, $newHdr, $newData) or $rtnVal=$rtnErr, $err=1; |
|
1577
|
|
|
|
|
|
|
} |
|
1578
|
|
|
|
|
|
|
} |
|
1579
|
|
|
|
|
|
|
# add only once (must delete _after_ call to WriteDirectory()) |
|
1580
|
|
|
|
|
|
|
# (Keys is a special case, and will be removed after Meta is processed) |
|
1581
|
14
|
50
|
|
|
|
116
|
delete $$addDirs{$subName} unless $subName eq 'Keys'; |
|
1582
|
|
|
|
|
|
|
} |
|
1583
|
|
|
|
|
|
|
} |
|
1584
|
|
|
|
|
|
|
# write HEIC metadata after top-level 'meta' box has been processed if editing this information |
|
1585
|
305
|
50
|
66
|
|
|
732
|
if ($curPath eq 'MOV-Meta' and $$et{EDIT_DIRS}{ItemInformation}) { |
|
1586
|
3
|
|
|
|
|
11
|
$$dirInfo{BoxPos} = \%boxPos; |
|
1587
|
3
|
|
|
|
|
18
|
my $mdatEdit = WriteItemInfo($et, $dirInfo, $outfile); |
|
1588
|
3
|
50
|
|
|
|
14
|
if ($mdatEdit) { |
|
1589
|
3
|
50
|
|
|
|
15
|
$et->Error('Multiple top-level Meta containers') if $$et{mdatEdit}; |
|
1590
|
3
|
|
|
|
|
11
|
$$et{mdatEdit} = $mdatEdit; |
|
1591
|
|
|
|
|
|
|
} |
|
1592
|
|
|
|
|
|
|
} |
|
1593
|
|
|
|
|
|
|
# write out any necessary terminator |
|
1594
|
305
|
50
|
0
|
|
|
726
|
Write($outfile, $term) or $rtnVal=$rtnErr, $err=1 if $term and length $$outfile; |
|
|
|
|
33
|
|
|
|
|
|
1595
|
|
|
|
|
|
|
|
|
1596
|
|
|
|
|
|
|
# delete temporary Keys variables after Meta is processed |
|
1597
|
305
|
100
|
|
|
|
644
|
if ($dirName eq 'Meta') { |
|
1598
|
|
|
|
|
|
|
# delete any Meta box with no useful information (ie. only 'hdlr','keys','lang','ctry') |
|
1599
|
24
|
|
|
|
|
71
|
my $isEmpty = 1; |
|
1600
|
24
|
|
66
|
|
|
169
|
$emptyMeta{$_} or $isEmpty = 0, last foreach keys %boxPos; |
|
1601
|
24
|
100
|
|
|
|
85
|
if ($isEmpty) { |
|
1602
|
2
|
50
|
|
|
|
12
|
$et->VPrint(0,' Deleting ' . join('+', sort map { $emptyMeta{$_} } keys %boxPos)) if %boxPos; |
|
|
2
|
|
|
|
|
19
|
|
|
1603
|
2
|
|
|
|
|
5
|
$$outfile = ''; |
|
1604
|
2
|
|
|
|
|
5
|
++$$et{CHANGED}; |
|
1605
|
|
|
|
|
|
|
} |
|
1606
|
24
|
100
|
|
|
|
87
|
if ($curPath eq 'MOV-Movie-Meta') { |
|
1607
|
9
|
|
|
|
|
25
|
delete $$addDirs{Keys}; # prevent creation of another Meta for Keys tags |
|
1608
|
9
|
|
|
|
|
49
|
delete $$et{Keys}; |
|
1609
|
|
|
|
|
|
|
} |
|
1610
|
|
|
|
|
|
|
} |
|
1611
|
|
|
|
|
|
|
|
|
1612
|
|
|
|
|
|
|
# return now if writing subdirectory |
|
1613
|
305
|
100
|
|
|
|
653
|
if ($dataPt) { |
|
1614
|
287
|
50
|
|
|
|
629
|
$et->Error("Internal error: WriteLast not on top-level atom!\n") if $writeLast; |
|
1615
|
287
|
50
|
|
|
|
2907
|
return $err ? undef : $$outfile; |
|
1616
|
|
|
|
|
|
|
} |
|
1617
|
|
|
|
|
|
|
|
|
1618
|
|
|
|
|
|
|
# issue minor error if we didn't find an 'mdat' atom |
|
1619
|
18
|
|
|
|
|
88
|
my $off = $$dirInfo{ChunkOffset}; |
|
1620
|
18
|
50
|
|
|
|
79
|
if (not @mdat) { |
|
1621
|
0
|
|
|
|
|
0
|
foreach $co (@$off) { |
|
1622
|
0
|
0
|
|
|
|
0
|
next if $$co[0] eq 'uuid'; |
|
1623
|
0
|
|
|
|
|
0
|
$et->Error('Media data referenced but not found'); |
|
1624
|
0
|
|
|
|
|
0
|
return $rtnVal; |
|
1625
|
|
|
|
|
|
|
} |
|
1626
|
0
|
|
|
|
|
0
|
$et->Warn('No media data', 1); |
|
1627
|
|
|
|
|
|
|
} |
|
1628
|
|
|
|
|
|
|
|
|
1629
|
|
|
|
|
|
|
# edit mdat blocks as required |
|
1630
|
|
|
|
|
|
|
# (0=old pos [0 if creating], 1=old end [0 if creating], 2=new data ref or undef to delete, |
|
1631
|
|
|
|
|
|
|
# 3=new data item id) |
|
1632
|
18
|
100
|
|
|
|
83
|
if ($$et{mdatEdit}) { |
|
1633
|
3
|
|
|
|
|
10
|
@mdatEdit = @{$$et{mdatEdit}}; |
|
|
3
|
|
|
|
|
15
|
|
|
1634
|
3
|
|
|
|
|
10
|
delete $$et{mdatEdit}; |
|
1635
|
|
|
|
|
|
|
} |
|
1636
|
18
|
|
|
|
|
56
|
foreach $edit (@mdatEdit) { |
|
1637
|
5
|
|
|
|
|
13
|
my (@thisMdat, @newMdat, $changed); |
|
1638
|
5
|
|
|
|
|
12
|
foreach $mdat (@mdat) { |
|
1639
|
|
|
|
|
|
|
# keep track of all chunks for the mdat with this header |
|
1640
|
12
|
100
|
|
|
|
34
|
if (length $$mdat[2]) { |
|
1641
|
10
|
|
|
|
|
20
|
push @newMdat, @thisMdat; |
|
1642
|
10
|
|
|
|
|
21
|
undef @thisMdat; |
|
1643
|
|
|
|
|
|
|
} |
|
1644
|
12
|
|
|
|
|
22
|
push @thisMdat, $mdat; |
|
1645
|
|
|
|
|
|
|
# is this edit inside this mdat chunk? |
|
1646
|
|
|
|
|
|
|
# - $$edit[0] and $$edit[1] will both be zero if we are creating a new chunk |
|
1647
|
|
|
|
|
|
|
# - $$mdat[1] is zero if mdat runs to end of file |
|
1648
|
|
|
|
|
|
|
# - $$edit[0] == $$edit[1] == $$mdat[0] if reviving a deleted chunk |
|
1649
|
|
|
|
|
|
|
# - $$mdat[5] is defined if this was a newly added/edited chunk |
|
1650
|
12
|
100
|
100
|
|
|
50
|
next if defined $$mdat[5] or $changed; # don't replace a newly added chunk |
|
1651
|
5
|
50
|
33
|
|
|
49
|
if (not $$edit[0] or # (newly created chunk) |
|
|
|
|
66
|
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
1652
|
|
|
|
|
|
|
# (edit is inside chunk) |
|
1653
|
|
|
|
|
|
|
((($$edit[0] < $$mdat[1] or not $$mdat[1]) and $$edit[1] > $$mdat[0]) or |
|
1654
|
|
|
|
|
|
|
# (edit inserted at start or end of chunk) |
|
1655
|
|
|
|
|
|
|
($$edit[0] == $$edit[1] and ($$edit[0] == $$mdat[0] or $$edit[0] == $$mdat[1])))) |
|
1656
|
|
|
|
|
|
|
{ |
|
1657
|
5
|
100
|
33
|
|
|
37
|
if (not $$edit[0]) { |
|
|
|
50
|
33
|
|
|
|
|
|
1658
|
2
|
|
|
|
|
6
|
$$edit[0] = $$edit[1] = $$mdat[0]; # insert at start of this mdat |
|
1659
|
|
|
|
|
|
|
} elsif ($$edit[0] < $$mdat[0] or ($$edit[1] > $$mdat[1] and $$mdat[1])) { |
|
1660
|
0
|
|
|
|
|
0
|
$et->Error('ItemInfo runs across mdat boundary'); |
|
1661
|
0
|
|
|
|
|
0
|
return $rtnVal; |
|
1662
|
|
|
|
|
|
|
} |
|
1663
|
5
|
|
|
|
|
9
|
my $hdrChunk = $thisMdat[0]; |
|
1664
|
5
|
50
|
|
|
|
19
|
$hdrChunk or $et->Error('Internal error finding mdat header'), return $rtnVal; |
|
1665
|
|
|
|
|
|
|
# calculate difference in mdat size |
|
1666
|
5
|
50
|
|
|
|
19
|
my $diff = ($$edit[2] ? length(${$$edit[2]}) : 0) - ($$edit[1] - $$edit[0]); |
|
|
5
|
|
|
|
|
16
|
|
|
1667
|
|
|
|
|
|
|
# edit size of mdat in header if necessary |
|
1668
|
5
|
50
|
|
|
|
17
|
if ($diff) { |
|
1669
|
5
|
50
|
|
|
|
16
|
if (length($$hdrChunk[2]) == 8) { |
|
|
|
0
|
|
|
|
|
|
|
1670
|
5
|
|
|
|
|
21
|
my $size = Get32u(\$$hdrChunk[2], 0); |
|
1671
|
5
|
50
|
|
|
|
19
|
if ($size) { # (0 size = extends to end of file) |
|
1672
|
5
|
|
|
|
|
12
|
$size += $diff; |
|
1673
|
5
|
50
|
|
|
|
15
|
$size > 0xffffffff and $et->Error("Can't yet grow mdat across 4GB boundary"), return $rtnVal; |
|
1674
|
5
|
|
|
|
|
17
|
Set32u($size, \$$hdrChunk[2], 0); |
|
1675
|
|
|
|
|
|
|
} |
|
1676
|
|
|
|
|
|
|
} elsif (length($$hdrChunk[2]) == 16) { |
|
1677
|
0
|
|
|
|
|
0
|
my $size = Get64u(\$$hdrChunk[2], 8); |
|
1678
|
0
|
0
|
|
|
|
0
|
if ($size) { |
|
1679
|
0
|
|
|
|
|
0
|
$size += $diff; |
|
1680
|
0
|
|
|
|
|
0
|
Set64u($size, \$$hdrChunk[2], 8); |
|
1681
|
|
|
|
|
|
|
} |
|
1682
|
|
|
|
|
|
|
} else { |
|
1683
|
0
|
|
|
|
|
0
|
$et->Error('Internal error. Invalid mdat header'); |
|
1684
|
0
|
|
|
|
|
0
|
return $rtnVal; |
|
1685
|
|
|
|
|
|
|
} |
|
1686
|
|
|
|
|
|
|
} |
|
1687
|
5
|
|
|
|
|
10
|
$changed = 1; |
|
1688
|
|
|
|
|
|
|
# remove the edited section of this chunk (if any) and replace with new data (if any) |
|
1689
|
5
|
50
|
|
|
|
16
|
if ($$edit[0] > $$mdat[0]) { |
|
1690
|
0
|
0
|
|
|
|
0
|
push @thisMdat, [ $$edit[0], $$edit[1], '', 0, $$edit[2], $$edit[3] ] if $$edit[2]; |
|
1691
|
|
|
|
|
|
|
# add remaining data after edit (or empty stub in case it is referenced by an offset) |
|
1692
|
0
|
|
|
|
|
0
|
push @thisMdat, [ $$edit[1], $$mdat[1], '' ]; |
|
1693
|
0
|
|
|
|
|
0
|
$$mdat[1] = $$edit[0]; # now ends at start of edit |
|
1694
|
|
|
|
|
|
|
} else { |
|
1695
|
5
|
50
|
|
|
|
17
|
if ($$edit[2]) { |
|
1696
|
|
|
|
|
|
|
# insert the new chunk before this chunk, moving the header to the new chunk |
|
1697
|
5
|
|
|
|
|
27
|
splice @thisMdat, -1, 0, [ $$edit[0],$$edit[1],$$mdat[2],0,$$edit[2],$$edit[3] ]; |
|
1698
|
5
|
|
|
|
|
13
|
$$mdat[2] = ''; # (header was moved to new chunk) |
|
1699
|
|
|
|
|
|
|
# initialize ChunkOffset pointer if necessary |
|
1700
|
5
|
50
|
|
|
|
17
|
if ($$edit[3]) { |
|
1701
|
5
|
|
|
|
|
10
|
my $n = 0; |
|
1702
|
5
|
|
|
|
|
16
|
foreach $co (@$off) { |
|
1703
|
19
|
100
|
66
|
|
|
72
|
next unless defined $$co[4] and $$co[4] == $$edit[3]; |
|
1704
|
5
|
|
|
|
|
10
|
++$n; |
|
1705
|
5
|
50
|
|
|
|
16
|
if ($$co[0] eq 'stco_iloc') { |
|
1706
|
5
|
|
|
|
|
17
|
Set32u($$mdat[0], $outfile, $$co[1]); |
|
1707
|
|
|
|
|
|
|
} else { |
|
1708
|
0
|
|
|
|
|
0
|
Set64u($$mdat[0], $outfile, $$co[1]); |
|
1709
|
|
|
|
|
|
|
} |
|
1710
|
|
|
|
|
|
|
} |
|
1711
|
5
|
50
|
|
|
|
20
|
$n == 1 or $et->Error('Internal error updating chunk offsets'); |
|
1712
|
|
|
|
|
|
|
} |
|
1713
|
|
|
|
|
|
|
} |
|
1714
|
5
|
|
|
|
|
13
|
$$mdat[0] = $$edit[1]; # remove old data |
|
1715
|
|
|
|
|
|
|
} |
|
1716
|
|
|
|
|
|
|
} |
|
1717
|
|
|
|
|
|
|
} |
|
1718
|
5
|
50
|
|
|
|
21
|
if ($changed) { |
|
1719
|
5
|
|
|
|
|
13
|
@mdat = ( @newMdat, @thisMdat ); |
|
1720
|
5
|
|
|
|
|
14
|
++$$et{CHANGED}; |
|
1721
|
|
|
|
|
|
|
} else { |
|
1722
|
0
|
|
|
|
|
0
|
$et->Error('Internal error modifying mdat'); |
|
1723
|
|
|
|
|
|
|
} |
|
1724
|
|
|
|
|
|
|
} |
|
1725
|
|
|
|
|
|
|
|
|
1726
|
|
|
|
|
|
|
# determine our new mdat positions |
|
1727
|
|
|
|
|
|
|
# (0=old pos, 1=old end, 2=mdat header, 3=new pos, 4=new data ref if changed, 5=new item ID) |
|
1728
|
18
|
|
|
|
|
65
|
my $pos = length $$outfile; |
|
1729
|
18
|
|
|
|
|
54
|
foreach $mdat (@mdat) { |
|
1730
|
26
|
|
|
|
|
76
|
$pos += length $$mdat[2]; |
|
1731
|
26
|
|
|
|
|
74
|
$$mdat[3] = $pos; |
|
1732
|
26
|
100
|
|
|
|
110
|
$pos += $$mdat[4] ? length(${$$mdat[4]}) : $$mdat[1] - $$mdat[0]; |
|
|
5
|
|
|
|
|
12
|
|
|
1733
|
|
|
|
|
|
|
} |
|
1734
|
|
|
|
|
|
|
|
|
1735
|
|
|
|
|
|
|
# fix up offsets for new mdat position(s) (and uuid positions in CR3 images) |
|
1736
|
18
|
|
|
|
|
66
|
foreach $co (@$off) { |
|
1737
|
59
|
|
|
|
|
180
|
my ($type, $ptr, $len, $base, $id) = @$co; |
|
1738
|
59
|
50
|
|
|
|
155
|
$base = 0 unless $base; |
|
1739
|
59
|
100
|
|
|
|
382
|
unless ($type =~ /^(stco|co64)_?(.*)$/) { |
|
1740
|
15
|
100
|
|
|
|
60
|
next if $type eq 'uuid'; |
|
1741
|
3
|
50
|
|
|
|
23
|
$type eq 'CTBO' or $et->Error('Internal error fixing offsets'), last; |
|
1742
|
|
|
|
|
|
|
# update 'CTBO' item offsets/sizes in Canon CR3 images |
|
1743
|
3
|
50
|
|
|
|
13
|
$$co[2] > 12 or $et->Error('Invalid CTBO atom'), last; |
|
1744
|
3
|
50
|
|
|
|
11
|
@mdat or $et->Error('Missing CR3 image data'), last; |
|
1745
|
3
|
|
|
|
|
18
|
my $n = Get32u($outfile, $$co[1] + 8); |
|
1746
|
3
|
50
|
|
|
|
26
|
$$co[2] < $n * 20 + 12 and $et->Error('Truncated CTBO atom'), last; |
|
1747
|
3
|
|
|
|
|
15
|
my (%ctboOff, $i); |
|
1748
|
|
|
|
|
|
|
# determine uuid types, and build an offset lookup based on CTBO ID number |
|
1749
|
3
|
|
|
|
|
15
|
foreach (@$off) { |
|
1750
|
25
|
100
|
66
|
|
|
81
|
next unless $$_[0] eq 'uuid' and $$_[2] >= 24; # (ignore undersized and deleted uuid boxes) |
|
1751
|
10
|
|
|
|
|
16
|
my $pos = $$_[1]; |
|
1752
|
10
|
100
|
|
|
|
37
|
next if $pos + 24 > length $$outfile; # (will happen for WriteLast uuid tags) |
|
1753
|
9
|
|
|
|
|
34
|
my $siz = Get32u($outfile, $pos); # get size of uuid atom |
|
1754
|
9
|
50
|
|
|
|
36
|
if ($siz == 1) { # check for extended (8-byte) size |
|
1755
|
0
|
0
|
|
|
|
0
|
next unless $$_[2] >= 32; |
|
1756
|
0
|
|
|
|
|
0
|
$pos += 8; |
|
1757
|
|
|
|
|
|
|
} |
|
1758
|
|
|
|
|
|
|
# get CTBO entry ID based on 16-byte UUID identifier |
|
1759
|
9
|
|
|
|
|
57
|
my $id = $ctboID{substr($$outfile, $pos+8, 16)}; |
|
1760
|
9
|
100
|
|
|
|
57
|
$ctboOff{$id} = $_ if defined $id; |
|
1761
|
|
|
|
|
|
|
} |
|
1762
|
|
|
|
|
|
|
# calculate new offset for the first mdat (size of -1 indicates it didn't change) |
|
1763
|
3
|
|
|
|
|
60
|
$ctboOff{3} = [ 'mdat', $mdat[0][3] - length $mdat[0][2], -1 ]; |
|
1764
|
3
|
|
|
|
|
26
|
for ($i=0; $i<$n; ++$i) { |
|
1765
|
12
|
|
|
|
|
29
|
my $pos = $$co[1] + 12 + $i * 20; |
|
1766
|
12
|
|
|
|
|
30
|
my $id = Get32u($outfile, $pos); |
|
1767
|
|
|
|
|
|
|
# ignore if size is zero unless we can add this entry |
|
1768
|
|
|
|
|
|
|
# (note: can't yet add/delete PreviewImage, but leave this possibility open) |
|
1769
|
12
|
50
|
66
|
|
|
49
|
next unless Get64u($outfile, $pos + 12) or $id == 1 or $id == 2; |
|
|
|
|
66
|
|
|
|
|
|
1770
|
9
|
50
|
|
|
|
33
|
if (not defined $ctboOff{$id}) { |
|
1771
|
0
|
0
|
0
|
|
|
0
|
$id==1 or $id==2 or $et->Error("Can't handle CR3 CTBO ID number $id"), last; |
|
1772
|
|
|
|
|
|
|
# XMP or PreviewImage was deleted -- set offset and size to zero |
|
1773
|
0
|
|
|
|
|
0
|
$ctboOff{$id} = [ 'uuid', 0, 0 ]; |
|
1774
|
|
|
|
|
|
|
} |
|
1775
|
|
|
|
|
|
|
# update the new offset and size of this entry |
|
1776
|
9
|
|
|
|
|
42
|
Set64u($ctboOff{$id}[1], $outfile, $pos + 4); |
|
1777
|
9
|
100
|
|
|
|
45
|
Set64u($ctboOff{$id}[2], $outfile, $pos + 12) unless $ctboOff{$id}[2] < 0; |
|
1778
|
|
|
|
|
|
|
} |
|
1779
|
3
|
|
|
|
|
21
|
next; |
|
1780
|
|
|
|
|
|
|
} |
|
1781
|
44
|
100
|
|
|
|
243
|
my $siz = $1 eq 'co64' ? 8 : 4; |
|
1782
|
44
|
|
|
|
|
86
|
my ($n, $tag); |
|
1783
|
44
|
100
|
|
|
|
121
|
if ($2) { # is this an offset in an iloc or 'gps ' atom? |
|
1784
|
11
|
|
|
|
|
16
|
$n = 1; |
|
1785
|
11
|
|
|
|
|
23
|
$type = $1; |
|
1786
|
11
|
|
|
|
|
19
|
$tag = $2; |
|
1787
|
|
|
|
|
|
|
} else { # this is an stco or co84 atom |
|
1788
|
33
|
50
|
|
|
|
91
|
next if $len < 8; |
|
1789
|
33
|
|
|
|
|
108
|
$n = Get32u($outfile, $ptr + 4); # get number of entries in table |
|
1790
|
33
|
|
|
|
|
82
|
$ptr += 8; |
|
1791
|
33
|
|
|
|
|
69
|
$len -= 8; |
|
1792
|
33
|
|
|
|
|
89
|
$tag = $1; |
|
1793
|
|
|
|
|
|
|
} |
|
1794
|
44
|
|
|
|
|
88
|
my $end = $ptr + $n * $siz; |
|
1795
|
44
|
50
|
|
|
|
119
|
$end > $ptr + $len and $et->Error("Invalid $tag table"), return $rtnVal; |
|
1796
|
44
|
|
|
|
|
139
|
for (; $ptr<$end; $ptr+=$siz) { |
|
1797
|
23
|
|
|
|
|
46
|
my ($ok, $i); |
|
1798
|
23
|
100
|
|
|
|
84
|
my $val = $type eq 'co64' ? Get64u($outfile, $ptr) : Get32u($outfile, $ptr); |
|
1799
|
23
|
|
|
|
|
116
|
for ($i=0; $i<@mdat; ++$i) { |
|
1800
|
38
|
|
|
|
|
74
|
$mdat = $mdat[$i]; |
|
1801
|
38
|
|
|
|
|
62
|
my $pos = $val + $base; |
|
1802
|
38
|
100
|
|
|
|
90
|
if (defined $$mdat[5]) { # is this chunk associated with an item we edited? |
|
1803
|
|
|
|
|
|
|
# set offset only for the corresponding new chunk |
|
1804
|
17
|
100
|
66
|
|
|
64
|
unless (defined $id and $id == $$mdat[5]) { |
|
1805
|
|
|
|
|
|
|
# could have pointed to empty chunk before inserted chunk |
|
1806
|
12
|
50
|
66
|
|
|
52
|
next unless $pos == $$mdat[0] and $$mdat[0] != $$mdat[1]; |
|
1807
|
|
|
|
|
|
|
} |
|
1808
|
|
|
|
|
|
|
} else { |
|
1809
|
|
|
|
|
|
|
# (have seen $pos == $$mdat[1], which is a real PITA) |
|
1810
|
21
|
100
|
66
|
|
|
112
|
next unless $pos >= $$mdat[0] and ($pos <= $$mdat[1] or not $$mdat[1]); |
|
|
|
|
33
|
|
|
|
|
|
1811
|
|
|
|
|
|
|
# step to next chunk if contiguous and at the end of this one |
|
1812
|
18
|
0
|
33
|
|
|
58
|
next if $pos == $$mdat[1] and $i+1 < @mdat and $pos == $mdat[$i+1][0]; |
|
|
|
|
33
|
|
|
|
|
|
1813
|
|
|
|
|
|
|
} |
|
1814
|
23
|
|
|
|
|
45
|
$val += $$mdat[3] - $$mdat[0]; |
|
1815
|
23
|
50
|
|
|
|
55
|
if ($val < 0) { |
|
1816
|
0
|
|
|
|
|
0
|
$et->Error("Error fixing up $tag offset"); |
|
1817
|
0
|
|
|
|
|
0
|
return $rtnVal; |
|
1818
|
|
|
|
|
|
|
} |
|
1819
|
23
|
100
|
|
|
|
64
|
if ($type eq 'co64') { |
|
|
|
50
|
|
|
|
|
|
|
1820
|
12
|
|
|
|
|
39
|
Set64u($val, $outfile, $ptr); |
|
1821
|
|
|
|
|
|
|
} elsif ($val <= 0xffffffff) { |
|
1822
|
11
|
|
|
|
|
28
|
Set32u($val, $outfile, $ptr); |
|
1823
|
|
|
|
|
|
|
} else { |
|
1824
|
0
|
|
|
|
|
0
|
$et->Error("Can't yet promote $tag offset to 64 bits"); |
|
1825
|
0
|
|
|
|
|
0
|
return $rtnVal; |
|
1826
|
|
|
|
|
|
|
} |
|
1827
|
23
|
|
|
|
|
60
|
$ok = 1; |
|
1828
|
23
|
|
|
|
|
41
|
last; |
|
1829
|
|
|
|
|
|
|
} |
|
1830
|
23
|
50
|
|
|
|
97
|
unless ($ok) { |
|
1831
|
0
|
|
|
|
|
0
|
$et->Error("Chunk offset in $tag atom is outside media data"); |
|
1832
|
0
|
|
|
|
|
0
|
return $rtnVal; |
|
1833
|
|
|
|
|
|
|
} |
|
1834
|
|
|
|
|
|
|
} |
|
1835
|
|
|
|
|
|
|
} |
|
1836
|
|
|
|
|
|
|
|
|
1837
|
|
|
|
|
|
|
# switch back to actual output file |
|
1838
|
18
|
|
|
|
|
74
|
$outfile = $$dirInfo{OutFile}; |
|
1839
|
|
|
|
|
|
|
|
|
1840
|
|
|
|
|
|
|
# write the metadata |
|
1841
|
18
|
50
|
|
|
|
80
|
Write($outfile, $outBuff) or $rtnVal = 0; |
|
1842
|
|
|
|
|
|
|
|
|
1843
|
|
|
|
|
|
|
# write the media data |
|
1844
|
18
|
|
|
|
|
94
|
foreach $mdat (@mdat) { |
|
1845
|
26
|
50
|
|
|
|
105
|
Write($outfile, $$mdat[2]) or $rtnVal = 0; # write mdat header |
|
1846
|
26
|
100
|
|
|
|
112
|
if ($$mdat[4]) { |
|
1847
|
5
|
50
|
|
|
|
11
|
Write($outfile, ${$$mdat[4]}) or $rtnVal = 0; |
|
|
5
|
|
|
|
|
17
|
|
|
1848
|
|
|
|
|
|
|
} else { |
|
1849
|
21
|
50
|
|
|
|
109
|
$raf->Seek($$mdat[0], 0) or $et->Error('Seek error'), last; |
|
1850
|
21
|
50
|
|
|
|
125
|
if ($$mdat[1]) { |
|
1851
|
21
|
|
|
|
|
146
|
my $result = Image::ExifTool::CopyBlock($raf, $outfile, $$mdat[1] - $$mdat[0]); |
|
1852
|
21
|
50
|
|
|
|
91
|
defined $result or $rtnVal = 0, last; |
|
1853
|
21
|
50
|
|
|
|
90
|
$result or $et->Error("Truncated mdat atom"), last; |
|
1854
|
|
|
|
|
|
|
} else { |
|
1855
|
|
|
|
|
|
|
# mdat continues to end of file |
|
1856
|
0
|
|
|
|
|
0
|
my $buff; |
|
1857
|
0
|
|
|
|
|
0
|
while ($raf->Read($buff, 65536)) { |
|
1858
|
0
|
0
|
|
|
|
0
|
Write($outfile, $buff) or $rtnVal = 0, last; |
|
1859
|
|
|
|
|
|
|
} |
|
1860
|
|
|
|
|
|
|
} |
|
1861
|
|
|
|
|
|
|
} |
|
1862
|
|
|
|
|
|
|
} |
|
1863
|
|
|
|
|
|
|
|
|
1864
|
|
|
|
|
|
|
# write the stuff that must come last |
|
1865
|
18
|
100
|
50
|
|
|
72
|
Write($outfile, $writeLast) or $rtnVal = 0 if $writeLast; |
|
1866
|
|
|
|
|
|
|
|
|
1867
|
18
|
|
|
|
|
312
|
return $rtnVal; |
|
1868
|
|
|
|
|
|
|
} |
|
1869
|
|
|
|
|
|
|
|
|
1870
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
|
1871
|
|
|
|
|
|
|
# Write QuickTime-format MOV/MP4 file |
|
1872
|
|
|
|
|
|
|
# Inputs: 0) ExifTool ref, 1) dirInfo ref |
|
1873
|
|
|
|
|
|
|
# Returns: 1 on success, 0 if this wasn't a valid QuickTime file, |
|
1874
|
|
|
|
|
|
|
# or -1 if a write error occurred |
|
1875
|
|
|
|
|
|
|
sub WriteMOV($$) |
|
1876
|
|
|
|
|
|
|
{ |
|
1877
|
18
|
|
|
18
|
0
|
75
|
my ($et, $dirInfo) = @_; |
|
1878
|
18
|
50
|
|
|
|
85
|
$et or return 1; # allow dummy access to autoload this package |
|
1879
|
18
|
50
|
|
|
|
96
|
my $raf = $$dirInfo{RAF} or return 0; |
|
1880
|
18
|
|
|
|
|
64
|
my ($buff, $ftype); |
|
1881
|
|
|
|
|
|
|
|
|
1882
|
|
|
|
|
|
|
# read the first atom header |
|
1883
|
18
|
50
|
|
|
|
87
|
return 0 unless $raf->Read($buff, 8) == 8; |
|
1884
|
18
|
|
|
|
|
187
|
my ($size, $tag) = unpack('Na4', $buff); |
|
1885
|
18
|
50
|
33
|
|
|
107
|
return 0 if $size < 8 and $size != 1; |
|
1886
|
|
|
|
|
|
|
|
|
1887
|
|
|
|
|
|
|
# validate the file format |
|
1888
|
18
|
|
|
|
|
85
|
my $tagTablePtr = GetTagTable('Image::ExifTool::QuickTime::Main'); |
|
1889
|
18
|
50
|
|
|
|
129
|
return 0 unless $$tagTablePtr{$tag}; |
|
1890
|
|
|
|
|
|
|
|
|
1891
|
|
|
|
|
|
|
# determine the file type (by default, assume MP4 if 'ftyp' exists |
|
1892
|
|
|
|
|
|
|
# without 'qt ' as a compatible brand, but HEIC is an exception) |
|
1893
|
18
|
100
|
66
|
|
|
211
|
if ($tag eq 'ftyp' and $size >= 12 and $size < 100000 and |
|
|
|
|
66
|
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
1894
|
|
|
|
|
|
|
$raf->Read($buff, $size-8) == $size-8 and |
|
1895
|
|
|
|
|
|
|
$buff !~ /^(....)+(qt )/s) |
|
1896
|
|
|
|
|
|
|
{ |
|
1897
|
9
|
100
|
|
|
|
87
|
if ($buff =~ /^crx /) { |
|
|
|
100
|
|
|
|
|
|
|
1898
|
3
|
|
|
|
|
10
|
$ftype = 'CR3', |
|
1899
|
|
|
|
|
|
|
} elsif ($buff =~ /^(heic|mif1|msf1|heix|hevc|hevx|avif)/) { |
|
1900
|
3
|
|
|
|
|
13
|
$ftype = 'HEIC'; |
|
1901
|
|
|
|
|
|
|
} else { |
|
1902
|
3
|
|
|
|
|
10
|
$ftype = 'MP4'; |
|
1903
|
|
|
|
|
|
|
} |
|
1904
|
|
|
|
|
|
|
} else { |
|
1905
|
9
|
|
|
|
|
29
|
$ftype = 'MOV'; |
|
1906
|
|
|
|
|
|
|
} |
|
1907
|
18
|
|
|
|
|
126
|
$et->SetFileType($ftype); # need to set "FileType" tag for a Condition |
|
1908
|
18
|
100
|
|
|
|
112
|
if ($ftype eq 'HEIC') { |
|
1909
|
|
|
|
|
|
|
# EXIF is preferred in HEIC files |
|
1910
|
3
|
|
|
|
|
21
|
$et->InitWriteDirs($dirMap{$ftype}, 'EXIF', 'QuickTime'); |
|
1911
|
|
|
|
|
|
|
} else { |
|
1912
|
15
|
|
|
|
|
107
|
$et->InitWriteDirs($dirMap{$ftype}, 'XMP', 'QuickTime'); |
|
1913
|
|
|
|
|
|
|
} |
|
1914
|
18
|
|
|
|
|
70
|
$$et{DirMap} = $dirMap{$ftype}; # need access to directory map when writing |
|
1915
|
|
|
|
|
|
|
# track tags globally to avoid creating multiple tags in the case of duplicate directories |
|
1916
|
18
|
|
|
|
|
67
|
$$et{DidTag} = { }; |
|
1917
|
18
|
|
|
|
|
119
|
SetByteOrder('MM'); |
|
1918
|
18
|
|
|
|
|
122
|
$raf->Seek(0,0); |
|
1919
|
|
|
|
|
|
|
|
|
1920
|
|
|
|
|
|
|
# write the file |
|
1921
|
18
|
|
|
|
|
90
|
$$dirInfo{Parent} = ''; |
|
1922
|
18
|
|
|
|
|
74
|
$$dirInfo{DirName} = 'MOV'; |
|
1923
|
18
|
|
|
|
|
70
|
$$dirInfo{ChunkOffset} = [ ]; # (just to be safe) |
|
1924
|
18
|
50
|
|
|
|
104
|
return WriteQuickTime($et, $dirInfo, $tagTablePtr) ? 1 : -1; |
|
1925
|
|
|
|
|
|
|
} |
|
1926
|
|
|
|
|
|
|
|
|
1927
|
|
|
|
|
|
|
1; # end |
|
1928
|
|
|
|
|
|
|
|
|
1929
|
|
|
|
|
|
|
__END__ |