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__ |