1 | #------------------------------------------------------------------------------
|
---|
2 | # File: QuickTime.pm
|
---|
3 | #
|
---|
4 | # Description: Read QuickTime, MP4 and M4A meta information
|
---|
5 | #
|
---|
6 | # Revisions: 10/04/2005 - P. Harvey Created
|
---|
7 | # 12/19/2005 - P. Harvey Added MP4 support
|
---|
8 | # 09/22/2006 - P. Harvey Added M4A support
|
---|
9 | #
|
---|
10 | # References: 1) http://developer.apple.com/documentation/QuickTime/
|
---|
11 | # 2) http://search.cpan.org/dist/MP4-Info-1.04/
|
---|
12 | # 3) http://www.geocities.com/xhelmboyx/quicktime/formats/mp4-layout.txt
|
---|
13 | # 4) http://wiki.multimedia.cx/index.php?title=Apple_QuickTime
|
---|
14 | #------------------------------------------------------------------------------
|
---|
15 |
|
---|
16 | package Image::ExifTool::QuickTime;
|
---|
17 |
|
---|
18 | use strict;
|
---|
19 | use vars qw($VERSION);
|
---|
20 | use Image::ExifTool qw(:DataAccess :Utils);
|
---|
21 | use Image::ExifTool::Exif;
|
---|
22 |
|
---|
23 | $VERSION = '1.12';
|
---|
24 |
|
---|
25 | sub FixWrongFormat($);
|
---|
26 | sub ProcessMOV($$;$);
|
---|
27 |
|
---|
28 | # information for time/date-based tags (time zero is Jan 1, 1904)
|
---|
29 | my %timeInfo = (
|
---|
30 | Groups => { 2 => 'Time' },
|
---|
31 | # Note: This value will be in UTC if generated by a system that is aware of the time zone
|
---|
32 | ValueConv => 'ConvertUnixTime($val - ((66 * 365 + 17) * 24 * 3600))',
|
---|
33 | PrintConv => '$self->ConvertDateTime($val)',
|
---|
34 | );
|
---|
35 | # information for duration tags
|
---|
36 | my %durationInfo = (
|
---|
37 | ValueConv => '$self->{TimeScale} ? $val / $self->{TimeScale} : $val',
|
---|
38 | PrintConv => '$self->{TimeScale} ? sprintf("%.2fs", $val) : $val',
|
---|
39 | );
|
---|
40 |
|
---|
41 | # QuickTime atoms
|
---|
42 | %Image::ExifTool::QuickTime::Main = (
|
---|
43 | PROCESS_PROC => \&Image::ExifTool::QuickTime::ProcessMOV,
|
---|
44 | GROUPS => { 2 => 'Video' },
|
---|
45 | NOTES => q{
|
---|
46 | These tags are used in QuickTime MOV and MP4 videos, and QTIF images. Tags
|
---|
47 | with a question mark after their name are not extracted unless the Unknown
|
---|
48 | option is set.
|
---|
49 | },
|
---|
50 | free => { Unknown => 1, Binary => 1 },
|
---|
51 | skip => { Unknown => 1, Binary => 1 },
|
---|
52 | wide => { Unknown => 1, Binary => 1 },
|
---|
53 | ftyp => { #MP4
|
---|
54 | Name => 'FrameType',
|
---|
55 | Unknown => 1,
|
---|
56 | Notes => 'MP4 only',
|
---|
57 | Binary => 1,
|
---|
58 | },
|
---|
59 | pnot => {
|
---|
60 | Name => 'Preview',
|
---|
61 | SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Preview' },
|
---|
62 | },
|
---|
63 | PICT => {
|
---|
64 | Name => 'PreviewPICT',
|
---|
65 | Binary => 1,
|
---|
66 | },
|
---|
67 | moov => {
|
---|
68 | Name => 'Movie',
|
---|
69 | SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Movie' },
|
---|
70 | },
|
---|
71 | mdat => { Unknown => 1, Binary => 1 },
|
---|
72 | );
|
---|
73 |
|
---|
74 | # atoms used in QTIF files
|
---|
75 | %Image::ExifTool::QuickTime::ImageFile = (
|
---|
76 | PROCESS_PROC => \&Image::ExifTool::QuickTime::ProcessMOV,
|
---|
77 | GROUPS => { 2 => 'Image' },
|
---|
78 | NOTES => 'Tags used in QTIF QuickTime Image Files.',
|
---|
79 | idsc => {
|
---|
80 | Name => 'ImageDescription',
|
---|
81 | SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::ImageDesc' },
|
---|
82 | },
|
---|
83 | idat => {
|
---|
84 | Name => 'ImageData',
|
---|
85 | Binary => 1,
|
---|
86 | },
|
---|
87 | iicc => {
|
---|
88 | Name => 'ICC_Profile',
|
---|
89 | SubDirectory => { TagTable => 'Image::ExifTool::ICC_Profile::Main' },
|
---|
90 | },
|
---|
91 | );
|
---|
92 |
|
---|
93 | # image description data block
|
---|
94 | %Image::ExifTool::QuickTime::ImageDesc = (
|
---|
95 | PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
---|
96 | GROUPS => { 2 => 'Image' },
|
---|
97 | 4 => { Name => 'CompressorID', Format => 'string[4]' },
|
---|
98 | 20 => { Name => 'VendorID', Format => 'string[4]' },
|
---|
99 | 28 => { Name => 'Quality', Format => 'int32u' },
|
---|
100 | 32 => { Name => 'ImageWidth', Format => 'int16u' },
|
---|
101 | 34 => { Name => 'ImageHeight', Format => 'int16u' },
|
---|
102 | 36 => { Name => 'XResolution', Format => 'int32u' },
|
---|
103 | 40 => { Name => 'YResolution', Format => 'int32u' },
|
---|
104 | 48 => { Name => 'FrameCount', Format => 'int16u' },
|
---|
105 | 50 => { Name => 'NameLength', Format => 'int8u' },
|
---|
106 | 51 => { Name => 'Compressor', Format => 'string[$val{46}]' },
|
---|
107 | 82 => { Name => 'BitDepth', Format => 'int16u' },
|
---|
108 | );
|
---|
109 |
|
---|
110 | # preview data block
|
---|
111 | %Image::ExifTool::QuickTime::Preview = (
|
---|
112 | PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
---|
113 | GROUPS => { 2 => 'Image' },
|
---|
114 | FORMAT => 'int16u',
|
---|
115 | 0 => {
|
---|
116 | Name => 'PreviewDate',
|
---|
117 | Format => 'int32u',
|
---|
118 | %timeInfo,
|
---|
119 | },
|
---|
120 | 2 => 'PreviewVersion',
|
---|
121 | 3 => {
|
---|
122 | Name => 'PreviewAtomType',
|
---|
123 | Format => 'string[4]',
|
---|
124 | },
|
---|
125 | 5 => 'PreviewAtomIndex',
|
---|
126 | );
|
---|
127 |
|
---|
128 | # movie atoms
|
---|
129 | %Image::ExifTool::QuickTime::Movie = (
|
---|
130 | PROCESS_PROC => \&Image::ExifTool::QuickTime::ProcessMOV,
|
---|
131 | GROUPS => { 2 => 'Video' },
|
---|
132 | mvhd => {
|
---|
133 | Name => 'MovieHeader',
|
---|
134 | SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::MovieHdr' },
|
---|
135 | },
|
---|
136 | trak => {
|
---|
137 | Name => 'Track',
|
---|
138 | SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Track' },
|
---|
139 | },
|
---|
140 | udta => {
|
---|
141 | Name => 'UserData',
|
---|
142 | SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::UserData' },
|
---|
143 | },
|
---|
144 | );
|
---|
145 |
|
---|
146 | # movie header data block
|
---|
147 | %Image::ExifTool::QuickTime::MovieHdr = (
|
---|
148 | PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
---|
149 | GROUPS => { 2 => 'Video' },
|
---|
150 | FORMAT => 'int32u',
|
---|
151 | 0 => { Name => 'Version', Format => 'int8u' },
|
---|
152 | 1 => {
|
---|
153 | Name => 'CreateDate',
|
---|
154 | %timeInfo,
|
---|
155 | },
|
---|
156 | 2 => {
|
---|
157 | Name => 'ModifyDate',
|
---|
158 | %timeInfo,
|
---|
159 | },
|
---|
160 | 3 => {
|
---|
161 | Name => 'TimeScale',
|
---|
162 | RawConv => '$self->{TimeScale} = $val',
|
---|
163 | },
|
---|
164 | 4 => { Name => 'Duration', %durationInfo },
|
---|
165 | 5 => {
|
---|
166 | Name => 'PreferredRate',
|
---|
167 | ValueConv => '$val / 0x10000',
|
---|
168 | },
|
---|
169 | 6 => {
|
---|
170 | Name => 'PreferredVolume',
|
---|
171 | Format => 'int16u',
|
---|
172 | ValueConv => '$val / 256',
|
---|
173 | PrintConv => 'sprintf("%.2f%%", $val * 100)',
|
---|
174 | },
|
---|
175 | 18 => { Name => 'PreviewTime', %durationInfo },
|
---|
176 | 19 => { Name => 'PreviewDuration', %durationInfo },
|
---|
177 | 20 => { Name => 'PosterTime', %durationInfo },
|
---|
178 | 21 => { Name => 'SelectionTime', %durationInfo },
|
---|
179 | 22 => { Name => 'SelectionDuration',%durationInfo },
|
---|
180 | 23 => { Name => 'CurrentTime', %durationInfo },
|
---|
181 | 24 => 'NextTrackID',
|
---|
182 | );
|
---|
183 |
|
---|
184 | # track atoms
|
---|
185 | %Image::ExifTool::QuickTime::Track = (
|
---|
186 | PROCESS_PROC => \&Image::ExifTool::QuickTime::ProcessMOV,
|
---|
187 | GROUPS => { 2 => 'Video' },
|
---|
188 | tkhd => {
|
---|
189 | Name => 'TrackHeader',
|
---|
190 | SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::TrackHdr' },
|
---|
191 | },
|
---|
192 | udta => {
|
---|
193 | Name => 'UserData',
|
---|
194 | SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::UserData' },
|
---|
195 | },
|
---|
196 | mdia => { #MP4
|
---|
197 | Name => 'Media',
|
---|
198 | SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Media' },
|
---|
199 | },
|
---|
200 | );
|
---|
201 |
|
---|
202 | # track header data block
|
---|
203 | %Image::ExifTool::QuickTime::TrackHdr = (
|
---|
204 | PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
---|
205 | GROUPS => { 1 => 'Track#', 2 => 'Video' },
|
---|
206 | FORMAT => 'int32u',
|
---|
207 | 0 => {
|
---|
208 | Name => 'TrackVersion',
|
---|
209 | Format => 'int8u',
|
---|
210 | Priority => 0,
|
---|
211 | },
|
---|
212 | 1 => {
|
---|
213 | Name => 'TrackCreateDate',
|
---|
214 | Priority => 0,
|
---|
215 | %timeInfo,
|
---|
216 | },
|
---|
217 | 2 => {
|
---|
218 | Name => 'TrackModifyDate',
|
---|
219 | Priority => 0,
|
---|
220 | %timeInfo,
|
---|
221 | },
|
---|
222 | 3 => {
|
---|
223 | Name => 'TrackID',
|
---|
224 | Priority => 0,
|
---|
225 | },
|
---|
226 | 5 => {
|
---|
227 | Name => 'TrackDuration',
|
---|
228 | Priority => 0,
|
---|
229 | %durationInfo,
|
---|
230 | },
|
---|
231 | 8 => {
|
---|
232 | Name => 'TrackLayer',
|
---|
233 | Format => 'int16u',
|
---|
234 | Priority => 0,
|
---|
235 | },
|
---|
236 | 9 => {
|
---|
237 | Name => 'TrackVolume',
|
---|
238 | Format => 'int16u',
|
---|
239 | Priority => 0,
|
---|
240 | ValueConv => '$val / 256',
|
---|
241 | PrintConv => 'sprintf("%.2f%%", $val * 100)',
|
---|
242 | },
|
---|
243 | 19 => {
|
---|
244 | Name => 'ImageWidth',
|
---|
245 | Priority => 0,
|
---|
246 | RawConv => \&FixWrongFormat,
|
---|
247 | },
|
---|
248 | 20 => {
|
---|
249 | Name => 'ImageHeight',
|
---|
250 | Priority => 0,
|
---|
251 | RawConv => \&FixWrongFormat,
|
---|
252 | },
|
---|
253 | );
|
---|
254 |
|
---|
255 | # user data atoms
|
---|
256 | %Image::ExifTool::QuickTime::UserData = (
|
---|
257 | PROCESS_PROC => \&Image::ExifTool::QuickTime::ProcessMOV,
|
---|
258 | GROUPS => { 2 => 'Video' },
|
---|
259 | NOTES => q{
|
---|
260 | Tag ID's beginning with the copyright symbol (hex 0xa9) are multi-language
|
---|
261 | text, but ExifTool only extracts the text from the first language in the
|
---|
262 | record. ExifTool will extract any multi-language user data tags found, even
|
---|
263 | if they don't exist in this table.
|
---|
264 | },
|
---|
265 | "\xa9cpy" => 'Copyright',
|
---|
266 | "\xa9day" => 'CreateDate',
|
---|
267 | "\xa9dir" => 'Director',
|
---|
268 | "\xa9ed1" => 'Edit1',
|
---|
269 | "\xa9ed2" => 'Edit2',
|
---|
270 | "\xa9ed3" => 'Edit3',
|
---|
271 | "\xa9ed4" => 'Edit4',
|
---|
272 | "\xa9ed5" => 'Edit5',
|
---|
273 | "\xa9ed6" => 'Edit6',
|
---|
274 | "\xa9ed7" => 'Edit7',
|
---|
275 | "\xa9ed8" => 'Edit8',
|
---|
276 | "\xa9ed9" => 'Edit9',
|
---|
277 | "\xa9fmt" => 'Format',
|
---|
278 | "\xa9inf" => 'Information',
|
---|
279 | "\xa9prd" => 'Producer',
|
---|
280 | "\xa9prf" => 'Performers',
|
---|
281 | "\xa9req" => 'Requirements',
|
---|
282 | "\xa9src" => 'Source',
|
---|
283 | "\xa9wrt" => 'Writer',
|
---|
284 | name => 'Name',
|
---|
285 | WLOC => {
|
---|
286 | Name => 'WindowLocation',
|
---|
287 | Format => 'int16u',
|
---|
288 | },
|
---|
289 | LOOP => {
|
---|
290 | Name => 'LoopStyle',
|
---|
291 | Format => 'int32u',
|
---|
292 | PrintConv => {
|
---|
293 | 1 => 'Normal',
|
---|
294 | 2 => 'Palindromic',
|
---|
295 | },
|
---|
296 | },
|
---|
297 | SelO => {
|
---|
298 | Name => 'PlaySelection',
|
---|
299 | Format => 'int8u',
|
---|
300 | },
|
---|
301 | AllF => {
|
---|
302 | Name => 'PlayAllFrames',
|
---|
303 | Format => 'int8u',
|
---|
304 | },
|
---|
305 | meta => {
|
---|
306 | Name => 'Meta',
|
---|
307 | SubDirectory => {
|
---|
308 | TagTable => 'Image::ExifTool::QuickTime::Meta',
|
---|
309 | HasVersion => 1, # must skip 4-byte version number header
|
---|
310 | },
|
---|
311 | },
|
---|
312 | 'ptv '=> {
|
---|
313 | Name => 'PrintToVideo',
|
---|
314 | SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Video' },
|
---|
315 | },
|
---|
316 | # hnti => 'HintInfo',
|
---|
317 | # hinf => 'HintTrackInfo',
|
---|
318 | TAGS => [
|
---|
319 | {
|
---|
320 | # these tags were initially discovered in a Pentax movie, but
|
---|
321 | # seem very similar to those used by Nikon
|
---|
322 | Name => 'PentaxTags',
|
---|
323 | Condition => '$$valPt =~ /^PENTAX DIGITAL CAMERA\0/',
|
---|
324 | SubDirectory => {
|
---|
325 | TagTable => 'Image::ExifTool::Pentax::MOV',
|
---|
326 | ByteOrder => 'LittleEndian',
|
---|
327 | },
|
---|
328 | },
|
---|
329 | {
|
---|
330 | Name => 'NikonTags',
|
---|
331 | Condition => '$$valPt =~ /^NIKON DIGITAL CAMERA\0/',
|
---|
332 | SubDirectory => {
|
---|
333 | TagTable => 'Image::ExifTool::Nikon::MOV',
|
---|
334 | ByteOrder => 'LittleEndian',
|
---|
335 | },
|
---|
336 | },
|
---|
337 | {
|
---|
338 | Name => 'SanyoMOV',
|
---|
339 | Condition => q{
|
---|
340 | $$valPt =~ /^SANYO DIGITAL CAMERA\0/ and
|
---|
341 | $self->{VALUE}->{FileType} eq "MOV"
|
---|
342 | },
|
---|
343 | SubDirectory => {
|
---|
344 | TagTable => 'Image::ExifTool::Sanyo::MOV',
|
---|
345 | ByteOrder => 'LittleEndian',
|
---|
346 | },
|
---|
347 | },
|
---|
348 | {
|
---|
349 | Name => 'SanyoMP4',
|
---|
350 | Condition => q{
|
---|
351 | $$valPt =~ /^SANYO DIGITAL CAMERA\0/ and
|
---|
352 | $self->{VALUE}->{FileType} eq "MP4"
|
---|
353 | },
|
---|
354 | SubDirectory => {
|
---|
355 | TagTable => 'Image::ExifTool::Sanyo::MP4',
|
---|
356 | ByteOrder => 'LittleEndian',
|
---|
357 | },
|
---|
358 | },
|
---|
359 | {
|
---|
360 | Name => 'UnknownTags',
|
---|
361 | Unknown => 1,
|
---|
362 | Binary => 1
|
---|
363 | },
|
---|
364 | ],
|
---|
365 | );
|
---|
366 |
|
---|
367 | # meta atoms
|
---|
368 | %Image::ExifTool::QuickTime::Meta = (
|
---|
369 | PROCESS_PROC => \&Image::ExifTool::QuickTime::ProcessMOV,
|
---|
370 | GROUPS => { 2 => 'Video' },
|
---|
371 | ilst => {
|
---|
372 | Name => 'InfoList',
|
---|
373 | SubDirectory => {
|
---|
374 | TagTable => 'Image::ExifTool::QuickTime::InfoList',
|
---|
375 | HasData => 1, # process atoms as containers with 'data' elements
|
---|
376 | },
|
---|
377 | },
|
---|
378 | );
|
---|
379 |
|
---|
380 | # info list atoms
|
---|
381 | # -> these atoms are unique, and contain one or more 'data' atoms
|
---|
382 | %Image::ExifTool::QuickTime::InfoList = (
|
---|
383 | PROCESS_PROC => \&Image::ExifTool::QuickTime::ProcessMOV,
|
---|
384 | GROUPS => { 2 => 'Audio' },
|
---|
385 | "\xa9ART" => 'Artist',
|
---|
386 | "\xa9alb" => 'Album',
|
---|
387 | "\xa9cmt" => 'Comment',
|
---|
388 | "\xa9com" => 'Composer',
|
---|
389 | "\xa9day" => 'Year',
|
---|
390 | "\xa9des" => 'Description', #4
|
---|
391 | "\xa9gen" => 'Genre',
|
---|
392 | "\xa9grp" => 'Grouping',
|
---|
393 | "\xa9lyr" => 'Lyrics',
|
---|
394 | "\xa9nam" => 'Title',
|
---|
395 | "\xa9too" => 'Encoder',
|
---|
396 | "\xa9trk" => 'Track',
|
---|
397 | "\xa9wrt" => 'Composer',
|
---|
398 | '----' => {
|
---|
399 | Name => 'iTunesInfo',
|
---|
400 | SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::iTunesInfo' },
|
---|
401 | },
|
---|
402 | aART => 'AlbumArtist',
|
---|
403 | apid => 'AppleStoreID',
|
---|
404 | auth => 'Author',
|
---|
405 | covr => 'CoverArt',
|
---|
406 | cpil => {
|
---|
407 | Name => 'Compilation',
|
---|
408 | PrintConv => { 0 => 'No', 1 => 'Yes' },
|
---|
409 | },
|
---|
410 | cprt => 'Copyright',
|
---|
411 | disk => {
|
---|
412 | Name => 'DiskNumber',
|
---|
413 | ValueConv => 'length($val) >= 6 ? join(" of ",unpack("x2nn",$val)) : \$val',
|
---|
414 | },
|
---|
415 | dscp => 'Description',
|
---|
416 | gnre => 'Genre',
|
---|
417 | perf => 'Performer',
|
---|
418 | pgap => {
|
---|
419 | Name => 'PlayGap',
|
---|
420 | PrintConv => {
|
---|
421 | 0 => 'Insert Gap',
|
---|
422 | 1 => 'No Gap',
|
---|
423 | },
|
---|
424 | },
|
---|
425 | rtng => 'Rating', # int
|
---|
426 | titl => 'Title',
|
---|
427 | tmpo => 'BeatsPerMinute', # int
|
---|
428 | trkn => {
|
---|
429 | Name => 'TrackNumber',
|
---|
430 | ValueConv => 'length($val) >= 6 ? join(" of ",unpack("x2nn",$val)) : \$val',
|
---|
431 | },
|
---|
432 | );
|
---|
433 |
|
---|
434 | # info list atoms
|
---|
435 | %Image::ExifTool::QuickTime::iTunesInfo = (
|
---|
436 | PROCESS_PROC => \&Image::ExifTool::QuickTime::ProcessMOV,
|
---|
437 | GROUPS => { 2 => 'Audio' },
|
---|
438 | );
|
---|
439 |
|
---|
440 | # print to video data block
|
---|
441 | %Image::ExifTool::QuickTime::Video = (
|
---|
442 | PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
---|
443 | GROUPS => { 2 => 'Video' },
|
---|
444 | 0 => {
|
---|
445 | Name => 'DisplaySize',
|
---|
446 | PrintConv => {
|
---|
447 | 0 => 'Normal',
|
---|
448 | 1 => 'Double Size',
|
---|
449 | 2 => 'Half Size',
|
---|
450 | 3 => 'Full Screen',
|
---|
451 | 4 => 'Current Size',
|
---|
452 | },
|
---|
453 | },
|
---|
454 | 6 => {
|
---|
455 | Name => 'SlideShow',
|
---|
456 | PrintConv => {
|
---|
457 | 0 => 'No',
|
---|
458 | 1 => 'Yes',
|
---|
459 | },
|
---|
460 | },
|
---|
461 | );
|
---|
462 |
|
---|
463 | # MP4 media
|
---|
464 | %Image::ExifTool::QuickTime::Media = (
|
---|
465 | PROCESS_PROC => \&Image::ExifTool::QuickTime::ProcessMOV,
|
---|
466 | GROUPS => { 2 => 'Video' },
|
---|
467 | NOTES => 'MP4 only (most tags unknown because ISO charges for the specification).',
|
---|
468 | minf => {
|
---|
469 | Name => 'Minf',
|
---|
470 | SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Minf' },
|
---|
471 | },
|
---|
472 | );
|
---|
473 |
|
---|
474 | %Image::ExifTool::QuickTime::Minf = (
|
---|
475 | PROCESS_PROC => \&Image::ExifTool::QuickTime::ProcessMOV,
|
---|
476 | GROUPS => { 2 => 'Video' },
|
---|
477 | NOTES => 'MP4 only (most tags unknown because ISO charges for the specification).',
|
---|
478 | dinf => {
|
---|
479 | Name => 'Dinf',
|
---|
480 | SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Dinf' },
|
---|
481 | },
|
---|
482 | stbl => {
|
---|
483 | Name => 'Stbl',
|
---|
484 | SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Stbl' },
|
---|
485 | },
|
---|
486 | );
|
---|
487 |
|
---|
488 | %Image::ExifTool::QuickTime::Stbl = (
|
---|
489 | PROCESS_PROC => \&Image::ExifTool::QuickTime::ProcessMOV,
|
---|
490 | GROUPS => { 2 => 'Video' },
|
---|
491 | NOTES => 'MP4 only (most tags unknown because ISO charges for the specification).',
|
---|
492 | );
|
---|
493 |
|
---|
494 | %Image::ExifTool::QuickTime::Dinf = (
|
---|
495 | PROCESS_PROC => \&Image::ExifTool::QuickTime::ProcessMOV,
|
---|
496 | GROUPS => { 2 => 'Video' },
|
---|
497 | NOTES => 'MP4 only (most tags unknown because ISO charges for the specification).',
|
---|
498 | );
|
---|
499 |
|
---|
500 | #------------------------------------------------------------------------------
|
---|
501 | # Fix incorrect format for ImageWidth/Height as written by Pentax
|
---|
502 | sub FixWrongFormat($)
|
---|
503 | {
|
---|
504 | my $val = shift;
|
---|
505 | return undef unless $val;
|
---|
506 | if ($val & 0xffff0000) {
|
---|
507 | $val = unpack('n',pack('N',$val));
|
---|
508 | }
|
---|
509 | return $val;
|
---|
510 | }
|
---|
511 |
|
---|
512 | #------------------------------------------------------------------------------
|
---|
513 | # Process a QuickTime atom
|
---|
514 | # Inputs: 0) ExifTool object reference, 1) directory information reference
|
---|
515 | # 2) optional tag table reference
|
---|
516 | # Returns: 1 on success
|
---|
517 | sub ProcessMOV($$;$)
|
---|
518 | {
|
---|
519 | my ($exifTool, $dirInfo, $tagTablePtr) = @_;
|
---|
520 | my $raf = $$dirInfo{RAF};
|
---|
521 | my $dataPt = $$dirInfo{DataPt};
|
---|
522 | my $verbose = $exifTool->Options('Verbose');
|
---|
523 | my $dataPos = $$dirInfo{Base} || 0;
|
---|
524 | my ($buff, $tag, $size, $track);
|
---|
525 |
|
---|
526 | # more convenient to package data as a RandomAccess file
|
---|
527 | $raf or $raf = new File::RandomAccess($dataPt);
|
---|
528 | # skip leading 4-byte version number if necessary
|
---|
529 | ($raf->Read($buff,4) == 4 and $dataPos += 4) or return 0 if $$dirInfo{HasVersion};
|
---|
530 | # read size/tag name atom header
|
---|
531 | $raf->Read($buff,8) == 8 or return 0;
|
---|
532 | $dataPos += 8;
|
---|
533 | $tagTablePtr or $tagTablePtr = GetTagTable('Image::ExifTool::QuickTime::Main');
|
---|
534 | ($size, $tag) = unpack('Na4', $buff);
|
---|
535 | if ($dataPt) {
|
---|
536 | $verbose and $exifTool->VerboseDir($$dirInfo{DirName});
|
---|
537 | } else {
|
---|
538 | # check on file type if called with a RAF
|
---|
539 | $$tagTablePtr{$tag} or return 0;
|
---|
540 | if ($tag eq 'ftyp') {
|
---|
541 | # read ahead 4 bytes to see if this is an M4A file
|
---|
542 | my $ftyp = 'MP4';
|
---|
543 | if ($raf->Read($buff, 4) == 4) {
|
---|
544 | $raf->Seek(-4, 1);
|
---|
545 | $ftyp = 'M4A' if $buff eq 'M4A ';
|
---|
546 | }
|
---|
547 | $exifTool->SetFileType($ftyp); # MP4 or M4A
|
---|
548 | } else {
|
---|
549 | $exifTool->SetFileType(); # MOV
|
---|
550 | }
|
---|
551 | SetByteOrder('MM');
|
---|
552 | }
|
---|
553 | for (;;) {
|
---|
554 | if ($size < 8) {
|
---|
555 | last if $size == 0;
|
---|
556 | $size == 1 or $exifTool->Warn('Invalid atom size'), last;
|
---|
557 | $raf->Read($buff, 8) == 8 or last;
|
---|
558 | $dataPos += 8;
|
---|
559 | my ($hi, $lo) = unpack('NN', $buff);
|
---|
560 | if ($hi or $lo > 0x7fffffff) {
|
---|
561 | $exifTool->Warn('End of processing at large atom');
|
---|
562 | last;
|
---|
563 | }
|
---|
564 | $size = $lo;
|
---|
565 | }
|
---|
566 | $size -= 8;
|
---|
567 | my $tagInfo = $exifTool->GetTagInfo($tagTablePtr, $tag);
|
---|
568 | # generate tagInfo if Unknown option set
|
---|
569 | if (not defined $tagInfo and ($exifTool->{OPTIONS}->{Unknown} or
|
---|
570 | $tag =~ /^\xa9/))
|
---|
571 | {
|
---|
572 | my $name = $tag;
|
---|
573 | $name =~ s/([\x00-\x1f\x7f-\xff])/'x'.unpack('H*',$1)/eg;
|
---|
574 | if ($name =~ /^xa9(.*)/) {
|
---|
575 | $tagInfo = {
|
---|
576 | Name => "UserData_$1",
|
---|
577 | Description => "User Data $1",
|
---|
578 | };
|
---|
579 | } else {
|
---|
580 | $tagInfo = {
|
---|
581 | Name => "Unknown_$name",
|
---|
582 | Description => "Unknown $name",
|
---|
583 | Unknown => 1,
|
---|
584 | Binary => 1,
|
---|
585 | };
|
---|
586 | }
|
---|
587 | Image::ExifTool::AddTagToTable($tagTablePtr, $tag, $tagInfo);
|
---|
588 | }
|
---|
589 | # load values only if associated with a tag (or verbose) and < 16MB long
|
---|
590 | if ((defined $tagInfo or $verbose) and $size < 0x1000000) {
|
---|
591 | my $val;
|
---|
592 | unless ($raf->Read($val, $size) == $size) {
|
---|
593 | $exifTool->Warn("Truncated '$tag' data");
|
---|
594 | last;
|
---|
595 | }
|
---|
596 | # use value to get tag info if necessary
|
---|
597 | $tagInfo or $tagInfo = $exifTool->GetTagInfo($tagTablePtr, $tag, \$val);
|
---|
598 | my $hasData = ($$dirInfo{HasData} and $val =~ /^\0...data\0/s);
|
---|
599 | if ($verbose and not $hasData) {
|
---|
600 | $exifTool->VerboseInfo($tag, $tagInfo,
|
---|
601 | Value => $val,
|
---|
602 | DataPt => \$val,
|
---|
603 | DataPos => $dataPos,
|
---|
604 | );
|
---|
605 | }
|
---|
606 | if ($tagInfo) {
|
---|
607 | my $subdir = $$tagInfo{SubDirectory};
|
---|
608 | if ($subdir) {
|
---|
609 | my %dirInfo = (
|
---|
610 | DataPt => \$val,
|
---|
611 | DirStart => 0,
|
---|
612 | DirLen => $size,
|
---|
613 | DirName => $$tagInfo{Name},
|
---|
614 | HasData => $$subdir{HasData},
|
---|
615 | HasVersion => $$subdir{HasVersion},
|
---|
616 | # Base needed for IsOffset tags in binary data
|
---|
617 | Base => $dataPos,
|
---|
618 | );
|
---|
619 | if ($$subdir{ByteOrder} and $$subdir{ByteOrder} =~ /^Little/) {
|
---|
620 | SetByteOrder('II');
|
---|
621 | }
|
---|
622 | if ($$tagInfo{Name} eq 'Track') {
|
---|
623 | $track or $track = 0;
|
---|
624 | $exifTool->{SET_GROUP1} = 'Track' . (++$track);
|
---|
625 | }
|
---|
626 | my $subTable = GetTagTable($$subdir{TagTable});
|
---|
627 | $exifTool->ProcessDirectory(\%dirInfo, $subTable);
|
---|
628 | delete $exifTool->{SET_GROUP1};
|
---|
629 | SetByteOrder('MM');
|
---|
630 | } elsif ($hasData) {
|
---|
631 | # handle atoms containing 'data' tags
|
---|
632 | my $pos = 0;
|
---|
633 | for (;;) {
|
---|
634 | last if $pos + 16 > $size;
|
---|
635 | my ($len, $type, $flags) = unpack("x${pos}Na4N", $val);
|
---|
636 | last if $pos + $len > $size;
|
---|
637 | my $value;
|
---|
638 | if ($type eq 'data' and $len >= 16) {
|
---|
639 | $pos += 16;
|
---|
640 | $len -= 16;
|
---|
641 | $value = substr($val, $pos, $len);
|
---|
642 | # format flags: 0x0=binary, 0x1=text, 0xd=image, 0x15=boolean
|
---|
643 | if ($flags == 0x0015) {
|
---|
644 | $value = $len ? ReadValue(\$value, $len-1, 'int8u', 1, 1) : '';
|
---|
645 | } elsif ($flags != 0x01 and not $$tagInfo{ValueConv}) {
|
---|
646 | # make binary data a scalar reference unless a ValueConv exists
|
---|
647 | my $buf = $value;
|
---|
648 | $value = \$buf;
|
---|
649 | }
|
---|
650 | }
|
---|
651 | $exifTool->VerboseInfo($tag, $tagInfo,
|
---|
652 | Value => ref $value ? $$value : $value,
|
---|
653 | DataPt => \$val,
|
---|
654 | DataPos => $dataPos,
|
---|
655 | Start => $pos,
|
---|
656 | Size => $len,
|
---|
657 | Extra => sprintf(", Type='$type', Flags=0x%x",$flags)
|
---|
658 | ) if $verbose;
|
---|
659 | $exifTool->FoundTag($tagInfo, $value) if defined $value;
|
---|
660 | $pos += $len;
|
---|
661 | }
|
---|
662 | } else {
|
---|
663 | if ($tag =~ /^\xa9/) {
|
---|
664 | # parse international text to extract first string
|
---|
665 | my $len = unpack('n', $val);
|
---|
666 | # $len should include 4 bytes for length and type words,
|
---|
667 | # but Pentax forgets to add these in, so allow for this
|
---|
668 | $len += 4 if $len == $size - 4;
|
---|
669 | $val = substr($val, 4, $len - 4) if $len <= $size;
|
---|
670 | } elsif ($$tagInfo{Format}) {
|
---|
671 | $val = ReadValue(\$val, 0, $$tagInfo{Format}, $$tagInfo{Count}, length($val));
|
---|
672 | }
|
---|
673 | $exifTool->FoundTag($tagInfo, $val);
|
---|
674 | }
|
---|
675 | }
|
---|
676 | } else {
|
---|
677 | $raf->Seek($size, 1) or $exifTool->Warn("Truncated '$tag' data"), last;
|
---|
678 | }
|
---|
679 | $raf->Read($buff, 8) == 8 or last;
|
---|
680 | $dataPos += $size + 8;
|
---|
681 | ($size, $tag) = unpack('Na4', $buff);
|
---|
682 | }
|
---|
683 | return 1;
|
---|
684 | }
|
---|
685 |
|
---|
686 | #------------------------------------------------------------------------------
|
---|
687 | # Process a QuickTime Image File
|
---|
688 | # Inputs: 0) ExifTool object reference, 1) directory information reference
|
---|
689 | # Returns: 1 on success
|
---|
690 | sub ProcessQTIF($$)
|
---|
691 | {
|
---|
692 | my $table = GetTagTable('Image::ExifTool::QuickTime::ImageFile');
|
---|
693 | return ProcessMOV($_[0], $_[1], $table);
|
---|
694 | }
|
---|
695 |
|
---|
696 | 1; # end
|
---|
697 |
|
---|
698 | __END__
|
---|
699 |
|
---|
700 | =head1 NAME
|
---|
701 |
|
---|
702 | Image::ExifTool::QuickTime - Read QuickTime and MP4 meta information
|
---|
703 |
|
---|
704 | =head1 SYNOPSIS
|
---|
705 |
|
---|
706 | This module is used by Image::ExifTool
|
---|
707 |
|
---|
708 | =head1 DESCRIPTION
|
---|
709 |
|
---|
710 | This module contains routines required by Image::ExifTool to extract
|
---|
711 | information from QuickTime and MP4 video, and M4A audio files.
|
---|
712 |
|
---|
713 | =head1 BUGS
|
---|
714 |
|
---|
715 | The MP4 support is rather pathetic since the specification documentation is
|
---|
716 | not freely available (yes, ISO sucks).
|
---|
717 |
|
---|
718 | =head1 AUTHOR
|
---|
719 |
|
---|
720 | Copyright 2003-2007, Phil Harvey (phil at owl.phy.queensu.ca)
|
---|
721 |
|
---|
722 | This library is free software; you can redistribute it and/or modify it
|
---|
723 | under the same terms as Perl itself.
|
---|
724 |
|
---|
725 | =head1 REFERENCES
|
---|
726 |
|
---|
727 | =over 4
|
---|
728 |
|
---|
729 | =item L<http://developer.apple.com/documentation/QuickTime/>
|
---|
730 |
|
---|
731 | =back
|
---|
732 |
|
---|
733 | =head1 SEE ALSO
|
---|
734 |
|
---|
735 | L<Image::ExifTool::TagNames/QuickTime Tags>,
|
---|
736 | L<Image::ExifTool(3pm)|Image::ExifTool>
|
---|
737 |
|
---|
738 | =cut
|
---|
739 |
|
---|