55 $info['quicktime'][$atomname]['name'] = $atomname; |
55 $info['quicktime'][$atomname]['name'] = $atomname; |
56 $info['quicktime'][$atomname]['size'] = $atomsize; |
56 $info['quicktime'][$atomname]['size'] = $atomsize; |
57 $info['quicktime'][$atomname]['offset'] = $offset; |
57 $info['quicktime'][$atomname]['offset'] = $offset; |
58 |
58 |
59 if (($offset + $atomsize) > $info['avdataend']) { |
59 if (($offset + $atomsize) > $info['avdataend']) { |
60 $info['error'][] = 'Atom at offset '.$offset.' claims to go beyond end-of-file (length: '.$atomsize.' bytes)'; |
60 $this->error('Atom at offset '.$offset.' claims to go beyond end-of-file (length: '.$atomsize.' bytes)'); |
61 return false; |
61 return false; |
62 } |
62 } |
63 |
63 |
64 if ($atomsize == 0) { |
64 if ($atomsize == 0) { |
65 // Furthermore, for historical reasons the list of atoms is optionally |
65 // Furthermore, for historical reasons the list of atoms is optionally |
66 // terminated by a 32-bit integer set to 0. If you are writing a program |
66 // terminated by a 32-bit integer set to 0. If you are writing a program |
67 // to read user data atoms, you should allow for the terminating 0. |
67 // to read user data atoms, you should allow for the terminating 0. |
68 break; |
68 break; |
69 } |
69 } |
70 $atomHierarchy = array(); |
70 $atomHierarchy = array(); |
71 $info['quicktime'][$atomname] = $this->QuicktimeParseAtom($atomname, $atomsize, $this->fread(min($atomsize, round($this->getid3->memory_limit / 2))), $offset, $atomHierarchy, $this->ParseAllPossibleAtoms); |
71 $info['quicktime'][$atomname] = $this->QuicktimeParseAtom($atomname, $atomsize, $this->fread(min($atomsize, $atom_data_read_buffer_size)), $offset, $atomHierarchy, $this->ParseAllPossibleAtoms); |
72 |
72 |
73 $offset += $atomsize; |
73 $offset += $atomsize; |
74 $atomcounter++; |
74 $atomcounter++; |
75 } |
75 } |
76 |
76 |
77 if (!empty($info['avdataend_tmp'])) { |
77 if (!empty($info['avdataend_tmp'])) { |
78 // this value is assigned to a temp value and then erased because |
78 // this value is assigned to a temp value and then erased because |
79 // otherwise any atoms beyond the 'mdat' atom would not get parsed |
79 // otherwise any atoms beyond the 'mdat' atom would not get parsed |
80 $info['avdataend'] = $info['avdataend_tmp']; |
80 $info['avdataend'] = $info['avdataend_tmp']; |
81 unset($info['avdataend_tmp']); |
81 unset($info['avdataend_tmp']); |
|
82 } |
|
83 |
|
84 if (!empty($info['quicktime']['comments']['chapters']) && is_array($info['quicktime']['comments']['chapters']) && (count($info['quicktime']['comments']['chapters']) > 0)) { |
|
85 $durations = $this->quicktime_time_to_sample_table($info); |
|
86 for ($i = 0; $i < count($info['quicktime']['comments']['chapters']); $i++) { |
|
87 $bookmark = array(); |
|
88 $bookmark['title'] = $info['quicktime']['comments']['chapters'][$i]; |
|
89 if (isset($durations[$i])) { |
|
90 $bookmark['duration_sample'] = $durations[$i]['sample_duration']; |
|
91 if ($i > 0) { |
|
92 $bookmark['start_sample'] = $info['quicktime']['bookmarks'][($i - 1)]['start_sample'] + $info['quicktime']['bookmarks'][($i - 1)]['duration_sample']; |
|
93 } else { |
|
94 $bookmark['start_sample'] = 0; |
|
95 } |
|
96 if ($time_scale = $this->quicktime_bookmark_time_scale($info)) { |
|
97 $bookmark['duration_seconds'] = $bookmark['duration_sample'] / $time_scale; |
|
98 $bookmark['start_seconds'] = $bookmark['start_sample'] / $time_scale; |
|
99 } |
|
100 } |
|
101 $info['quicktime']['bookmarks'][] = $bookmark; |
|
102 } |
|
103 } |
|
104 |
|
105 if (isset($info['quicktime']['temp_meta_key_names'])) { |
|
106 unset($info['quicktime']['temp_meta_key_names']); |
|
107 } |
|
108 |
|
109 if (!empty($info['quicktime']['comments']['location.ISO6709'])) { |
|
110 // https://en.wikipedia.org/wiki/ISO_6709 |
|
111 foreach ($info['quicktime']['comments']['location.ISO6709'] as $ISO6709string) { |
|
112 $latitude = false; |
|
113 $longitude = false; |
|
114 $altitude = false; |
|
115 if (preg_match('#^([\\+\\-])([0-9]{2}|[0-9]{4}|[0-9]{6})(\\.[0-9]+)?([\\+\\-])([0-9]{3}|[0-9]{5}|[0-9]{7})(\\.[0-9]+)?(([\\+\\-])([0-9]{3}|[0-9]{5}|[0-9]{7})(\\.[0-9]+)?)?/$#', $ISO6709string, $matches)) { |
|
116 @list($dummy, $lat_sign, $lat_deg, $lat_deg_dec, $lon_sign, $lon_deg, $lon_deg_dec, $dummy, $alt_sign, $alt_deg, $alt_deg_dec) = $matches; |
|
117 |
|
118 if (strlen($lat_deg) == 2) { // [+-]DD.D |
|
119 $latitude = floatval(ltrim($lat_deg, '0').$lat_deg_dec); |
|
120 } elseif (strlen($lat_deg) == 4) { // [+-]DDMM.M |
|
121 $latitude = floatval(ltrim(substr($lat_deg, 0, 2), '0')) + floatval(ltrim(substr($lat_deg, 2, 2), '0').$lat_deg_dec / 60); |
|
122 } elseif (strlen($lat_deg) == 6) { // [+-]DDMMSS.S |
|
123 $latitude = floatval(ltrim(substr($lat_deg, 0, 2), '0')) + floatval(ltrim(substr($lat_deg, 2, 2), '0') / 60) + floatval(ltrim(substr($lat_deg, 4, 2), '0').$lat_deg_dec / 3600); |
|
124 } |
|
125 |
|
126 if (strlen($lon_deg) == 3) { // [+-]DDD.D |
|
127 $longitude = floatval(ltrim($lon_deg, '0').$lon_deg_dec); |
|
128 } elseif (strlen($lon_deg) == 5) { // [+-]DDDMM.M |
|
129 $longitude = floatval(ltrim(substr($lon_deg, 0, 2), '0')) + floatval(ltrim(substr($lon_deg, 2, 2), '0').$lon_deg_dec / 60); |
|
130 } elseif (strlen($lon_deg) == 7) { // [+-]DDDMMSS.S |
|
131 $longitude = floatval(ltrim(substr($lon_deg, 0, 2), '0')) + floatval(ltrim(substr($lon_deg, 2, 2), '0') / 60) + floatval(ltrim(substr($lon_deg, 4, 2), '0').$lon_deg_dec / 3600); |
|
132 } |
|
133 |
|
134 if (strlen($alt_deg) == 3) { // [+-]DDD.D |
|
135 $altitude = floatval(ltrim($alt_deg, '0').$alt_deg_dec); |
|
136 } elseif (strlen($alt_deg) == 5) { // [+-]DDDMM.M |
|
137 $altitude = floatval(ltrim(substr($alt_deg, 0, 2), '0')) + floatval(ltrim(substr($alt_deg, 2, 2), '0').$alt_deg_dec / 60); |
|
138 } elseif (strlen($alt_deg) == 7) { // [+-]DDDMMSS.S |
|
139 $altitude = floatval(ltrim(substr($alt_deg, 0, 2), '0')) + floatval(ltrim(substr($alt_deg, 2, 2), '0') / 60) + floatval(ltrim(substr($alt_deg, 4, 2), '0').$alt_deg_dec / 3600); |
|
140 } |
|
141 |
|
142 if ($latitude !== false) { |
|
143 $info['quicktime']['comments']['gps_latitude'][] = (($lat_sign == '-') ? -1 : 1) * floatval($latitude); |
|
144 } |
|
145 if ($longitude !== false) { |
|
146 $info['quicktime']['comments']['gps_longitude'][] = (($lon_sign == '-') ? -1 : 1) * floatval($longitude); |
|
147 } |
|
148 if ($altitude !== false) { |
|
149 $info['quicktime']['comments']['gps_altitude'][] = (($alt_sign == '-') ? -1 : 1) * floatval($altitude); |
|
150 } |
|
151 } |
|
152 if ($latitude === false) { |
|
153 $this->warning('location.ISO6709 string not parsed correctly: "'.$ISO6709string.'", please submit as a bug'); |
|
154 } |
|
155 break; |
|
156 } |
82 } |
157 } |
83 |
158 |
84 if (!isset($info['bitrate']) && isset($info['playtime_seconds'])) { |
159 if (!isset($info['bitrate']) && isset($info['playtime_seconds'])) { |
85 $info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds']; |
160 $info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds']; |
86 } |
161 } |
220 $info['quicktime']['video']['frame_count'] = $framecount; |
300 $info['quicktime']['video']['frame_count'] = $framecount; |
221 } |
301 } |
222 break; |
302 break; |
223 |
303 |
224 |
304 |
|
305 case "\xA9".'alb': // ALBum |
|
306 case "\xA9".'ART': // |
|
307 case "\xA9".'art': // ARTist |
|
308 case "\xA9".'aut': // |
|
309 case "\xA9".'cmt': // CoMmenT |
|
310 case "\xA9".'com': // COMposer |
|
311 case "\xA9".'cpy': // |
|
312 case "\xA9".'day': // content created year |
|
313 case "\xA9".'dir': // |
|
314 case "\xA9".'ed1': // |
|
315 case "\xA9".'ed2': // |
|
316 case "\xA9".'ed3': // |
|
317 case "\xA9".'ed4': // |
|
318 case "\xA9".'ed5': // |
|
319 case "\xA9".'ed6': // |
|
320 case "\xA9".'ed7': // |
|
321 case "\xA9".'ed8': // |
|
322 case "\xA9".'ed9': // |
|
323 case "\xA9".'enc': // |
|
324 case "\xA9".'fmt': // |
|
325 case "\xA9".'gen': // GENre |
|
326 case "\xA9".'grp': // GRouPing |
|
327 case "\xA9".'hst': // |
|
328 case "\xA9".'inf': // |
|
329 case "\xA9".'lyr': // LYRics |
|
330 case "\xA9".'mak': // |
|
331 case "\xA9".'mod': // |
|
332 case "\xA9".'nam': // full NAMe |
|
333 case "\xA9".'ope': // |
|
334 case "\xA9".'PRD': // |
|
335 case "\xA9".'prf': // |
|
336 case "\xA9".'req': // |
|
337 case "\xA9".'src': // |
|
338 case "\xA9".'swr': // |
|
339 case "\xA9".'too': // encoder |
|
340 case "\xA9".'trk': // TRacK |
|
341 case "\xA9".'url': // |
|
342 case "\xA9".'wrn': // |
|
343 case "\xA9".'wrt': // WRiTer |
|
344 case '----': // itunes specific |
225 case 'aART': // Album ARTist |
345 case 'aART': // Album ARTist |
|
346 case 'akID': // iTunes store account type |
|
347 case 'apID': // Purchase Account |
|
348 case 'atID': // |
226 case 'catg': // CaTeGory |
349 case 'catg': // CaTeGory |
|
350 case 'cmID': // |
|
351 case 'cnID': // |
227 case 'covr': // COVeR artwork |
352 case 'covr': // COVeR artwork |
228 case 'cpil': // ComPILation |
353 case 'cpil': // ComPILation |
229 case 'cprt': // CoPyRighT |
354 case 'cprt': // CoPyRighT |
230 case 'desc': // DESCription |
355 case 'desc': // DESCription |
231 case 'disk': // DISK number |
356 case 'disk': // DISK number |
232 case 'egid': // Episode Global ID |
357 case 'egid': // Episode Global ID |
|
358 case 'geID': // |
233 case 'gnre': // GeNRE |
359 case 'gnre': // GeNRE |
|
360 case 'hdvd': // HD ViDeo |
234 case 'keyw': // KEYWord |
361 case 'keyw': // KEYWord |
235 case 'ldes': |
362 case 'ldes': // Long DEScription |
236 case 'pcst': // PodCaST |
363 case 'pcst': // PodCaST |
237 case 'pgap': // GAPless Playback |
364 case 'pgap': // GAPless Playback |
|
365 case 'plID': // |
238 case 'purd': // PURchase Date |
366 case 'purd': // PURchase Date |
239 case 'purl': // Podcast URL |
367 case 'purl': // Podcast URL |
240 case 'rati': |
368 case 'rati': // |
241 case 'rndu': |
369 case 'rndu': // |
242 case 'rpdu': |
370 case 'rpdu': // |
243 case 'rtng': // RaTiNG |
371 case 'rtng': // RaTiNG |
244 case 'stik': |
372 case 'sfID': // iTunes store country |
|
373 case 'soaa': // SOrt Album Artist |
|
374 case 'soal': // SOrt ALbum |
|
375 case 'soar': // SOrt ARtist |
|
376 case 'soco': // SOrt COmposer |
|
377 case 'sonm': // SOrt NaMe |
|
378 case 'sosn': // SOrt Show Name |
|
379 case 'stik': // |
245 case 'tmpo': // TeMPO (BPM) |
380 case 'tmpo': // TeMPO (BPM) |
246 case 'trkn': // TRacK Number |
381 case 'trkn': // TRacK Number |
|
382 case 'tven': // tvEpisodeID |
247 case 'tves': // TV EpiSode |
383 case 'tves': // TV EpiSode |
248 case 'tvnn': // TV Network Name |
384 case 'tvnn': // TV Network Name |
249 case 'tvsh': // TV SHow Name |
385 case 'tvsh': // TV SHow Name |
250 case 'tvsn': // TV SeasoN |
386 case 'tvsn': // TV SeasoN |
251 case 'akID': // iTunes store account type |
|
252 case 'apID': |
|
253 case 'atID': |
|
254 case 'cmID': |
|
255 case 'cnID': |
|
256 case 'geID': |
|
257 case 'plID': |
|
258 case 'sfID': // iTunes store country |
|
259 case "\xA9".'alb': // ALBum |
|
260 case "\xA9".'art': // ARTist |
|
261 case "\xA9".'ART': |
|
262 case "\xA9".'aut': |
|
263 case "\xA9".'cmt': // CoMmenT |
|
264 case "\xA9".'com': // COMposer |
|
265 case "\xA9".'cpy': |
|
266 case "\xA9".'day': // content created year |
|
267 case "\xA9".'dir': |
|
268 case "\xA9".'ed1': |
|
269 case "\xA9".'ed2': |
|
270 case "\xA9".'ed3': |
|
271 case "\xA9".'ed4': |
|
272 case "\xA9".'ed5': |
|
273 case "\xA9".'ed6': |
|
274 case "\xA9".'ed7': |
|
275 case "\xA9".'ed8': |
|
276 case "\xA9".'ed9': |
|
277 case "\xA9".'enc': |
|
278 case "\xA9".'fmt': |
|
279 case "\xA9".'gen': // GENre |
|
280 case "\xA9".'grp': // GRouPing |
|
281 case "\xA9".'hst': |
|
282 case "\xA9".'inf': |
|
283 case "\xA9".'lyr': // LYRics |
|
284 case "\xA9".'mak': |
|
285 case "\xA9".'mod': |
|
286 case "\xA9".'nam': // full NAMe |
|
287 case "\xA9".'ope': |
|
288 case "\xA9".'PRD': |
|
289 case "\xA9".'prd': |
|
290 case "\xA9".'prf': |
|
291 case "\xA9".'req': |
|
292 case "\xA9".'src': |
|
293 case "\xA9".'swr': |
|
294 case "\xA9".'too': // encoder |
|
295 case "\xA9".'trk': // TRacK |
|
296 case "\xA9".'url': |
|
297 case "\xA9".'wrn': |
|
298 case "\xA9".'wrt': // WRiTer |
|
299 case '----': // itunes specific |
|
300 if ($atom_parent == 'udta') { |
387 if ($atom_parent == 'udta') { |
301 // User data atom handler |
388 // User data atom handler |
302 $atom_structure['data_length'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2)); |
389 $atom_structure['data_length'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2)); |
303 $atom_structure['language_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 2)); |
390 $atom_structure['language_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 2)); |
304 $atom_structure['data'] = substr($atom_data, 4); |
391 $atom_structure['data'] = substr($atom_data, 4); |
359 switch ($atom_structure['flags_raw']) { |
446 switch ($atom_structure['flags_raw']) { |
360 case 0: // data flag |
447 case 0: // data flag |
361 case 21: // tmpo/cpil flag |
448 case 21: // tmpo/cpil flag |
362 switch ($atomname) { |
449 switch ($atomname) { |
363 case 'cpil': |
450 case 'cpil': |
|
451 case 'hdvd': |
364 case 'pcst': |
452 case 'pcst': |
365 case 'pgap': |
453 case 'pgap': |
|
454 // 8-bit integer (boolean) |
366 $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1)); |
455 $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1)); |
367 break; |
456 break; |
368 |
457 |
369 case 'tmpo': |
458 case 'tmpo': |
|
459 // 16-bit integer |
370 $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 2)); |
460 $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 2)); |
371 break; |
461 break; |
372 |
462 |
373 case 'disk': |
463 case 'disk': |
374 case 'trkn': |
464 case 'trkn': |
|
465 // binary |
375 $num = getid3_lib::BigEndian2Int(substr($boxdata, 10, 2)); |
466 $num = getid3_lib::BigEndian2Int(substr($boxdata, 10, 2)); |
376 $num_total = getid3_lib::BigEndian2Int(substr($boxdata, 12, 2)); |
467 $num_total = getid3_lib::BigEndian2Int(substr($boxdata, 12, 2)); |
377 $atom_structure['data'] = empty($num) ? '' : $num; |
468 $atom_structure['data'] = empty($num) ? '' : $num; |
378 $atom_structure['data'] .= empty($num_total) ? '' : '/'.$num_total; |
469 $atom_structure['data'] .= empty($num_total) ? '' : '/'.$num_total; |
379 break; |
470 break; |
380 |
471 |
381 case 'gnre': |
472 case 'gnre': |
|
473 // enum |
382 $GenreID = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4)); |
474 $GenreID = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4)); |
383 $atom_structure['data'] = getid3_id3v1::LookupGenreName($GenreID - 1); |
475 $atom_structure['data'] = getid3_id3v1::LookupGenreName($GenreID - 1); |
384 break; |
476 break; |
385 |
477 |
386 case 'rtng': |
478 case 'rtng': |
|
479 // 8-bit integer |
387 $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1)); |
480 $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1)); |
388 $atom_structure['data'] = $this->QuicktimeContentRatingLookup($atom_structure[$atomname]); |
481 $atom_structure['data'] = $this->QuicktimeContentRatingLookup($atom_structure[$atomname]); |
389 break; |
482 break; |
390 |
483 |
391 case 'stik': |
484 case 'stik': |
|
485 // 8-bit integer (enum) |
392 $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1)); |
486 $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1)); |
393 $atom_structure['data'] = $this->QuicktimeSTIKLookup($atom_structure[$atomname]); |
487 $atom_structure['data'] = $this->QuicktimeSTIKLookup($atom_structure[$atomname]); |
394 break; |
488 break; |
395 |
489 |
396 case 'sfID': |
490 case 'sfID': |
|
491 // 32-bit integer |
397 $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4)); |
492 $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4)); |
398 $atom_structure['data'] = $this->QuicktimeStoreFrontCodeLookup($atom_structure[$atomname]); |
493 $atom_structure['data'] = $this->QuicktimeStoreFrontCodeLookup($atom_structure[$atomname]); |
399 break; |
494 break; |
400 |
495 |
401 case 'egid': |
496 case 'egid': |
402 case 'purl': |
497 case 'purl': |
403 $atom_structure['data'] = substr($boxdata, 8); |
498 $atom_structure['data'] = substr($boxdata, 8); |
404 break; |
499 break; |
405 |
500 |
|
501 case 'plID': |
|
502 // 64-bit integer |
|
503 $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 8)); |
|
504 break; |
|
505 |
|
506 case 'covr': |
|
507 $atom_structure['data'] = substr($boxdata, 8); |
|
508 // not a foolproof check, but better than nothing |
|
509 if (preg_match('#^\\xFF\\xD8\\xFF#', $atom_structure['data'])) { |
|
510 $atom_structure['image_mime'] = 'image/jpeg'; |
|
511 } elseif (preg_match('#^\\x89\\x50\\x4E\\x47\\x0D\\x0A\\x1A\\x0A#', $atom_structure['data'])) { |
|
512 $atom_structure['image_mime'] = 'image/png'; |
|
513 } elseif (preg_match('#^GIF#', $atom_structure['data'])) { |
|
514 $atom_structure['image_mime'] = 'image/gif'; |
|
515 } |
|
516 break; |
|
517 |
|
518 case 'atID': |
|
519 case 'cnID': |
|
520 case 'geID': |
|
521 case 'tves': |
|
522 case 'tvsn': |
406 default: |
523 default: |
|
524 // 32-bit integer |
407 $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4)); |
525 $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4)); |
408 } |
526 } |
409 break; |
527 break; |
410 |
528 |
411 case 1: // text flag |
529 case 1: // text flag |
412 case 13: // image flag |
530 case 13: // image flag |
413 default: |
531 default: |
414 $atom_structure['data'] = substr($boxdata, 8); |
532 $atom_structure['data'] = substr($boxdata, 8); |
415 if ($atomname == 'covr') { |
533 if ($atomname == 'covr') { |
416 // not a foolproof check, but better than nothing |
534 // not a foolproof check, but better than nothing |
417 if (preg_match('#^\xFF\xD8\xFF#', $atom_structure['data'])) { |
535 if (preg_match('#^\\xFF\\xD8\\xFF#', $atom_structure['data'])) { |
418 $atom_structure['image_mime'] = 'image/jpeg'; |
536 $atom_structure['image_mime'] = 'image/jpeg'; |
419 } elseif (preg_match('#^\x89\x50\x4E\x47\x0D\x0A\x1A\x0A#', $atom_structure['data'])) { |
537 } elseif (preg_match('#^\\x89\\x50\\x4E\\x47\\x0D\\x0A\\x1A\\x0A#', $atom_structure['data'])) { |
420 $atom_structure['image_mime'] = 'image/png'; |
538 $atom_structure['image_mime'] = 'image/png'; |
421 } elseif (preg_match('#^GIF#', $atom_structure['data'])) { |
539 } elseif (preg_match('#^GIF#', $atom_structure['data'])) { |
422 $atom_structure['image_mime'] = 'image/gif'; |
540 $atom_structure['image_mime'] = 'image/gif'; |
423 } |
541 } |
424 } |
542 } |
593 $ptv_lookup[3] = 'full'; |
711 $ptv_lookup[3] = 'full'; |
594 $ptv_lookup[4] = 'current'; |
712 $ptv_lookup[4] = 'current'; |
595 if (isset($ptv_lookup[$atom_structure['display_size_raw']])) { |
713 if (isset($ptv_lookup[$atom_structure['display_size_raw']])) { |
596 $atom_structure['display_size'] = $ptv_lookup[$atom_structure['display_size_raw']]; |
714 $atom_structure['display_size'] = $ptv_lookup[$atom_structure['display_size_raw']]; |
597 } else { |
715 } else { |
598 $info['warning'][] = 'unknown "ptv " display constant ('.$atom_structure['display_size_raw'].')'; |
716 $this->warning('unknown "ptv " display constant ('.$atom_structure['display_size_raw'].')'); |
599 } |
717 } |
600 break; |
718 break; |
601 |
719 |
602 |
720 |
603 case 'stsd': // Sample Table Sample Description atom |
721 case 'stsd': // Sample Table Sample Description atom |
604 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); |
722 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); |
605 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 |
723 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 |
606 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); |
724 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); |
|
725 |
|
726 // see: https://github.com/JamesHeinrich/getID3/issues/111 |
|
727 // Some corrupt files have been known to have high bits set in the number_entries field |
|
728 // This field shouldn't really need to be 32-bits, values stores are likely in the range 1-100000 |
|
729 // Workaround: mask off the upper byte and throw a warning if it's nonzero |
|
730 if ($atom_structure['number_entries'] > 0x000FFFFF) { |
|
731 if ($atom_structure['number_entries'] > 0x00FFFFFF) { |
|
732 $this->warning('"stsd" atom contains improbably large number_entries (0x'.getid3_lib::PrintHexBytes(substr($atom_data, 4, 4), true, false).' = '.$atom_structure['number_entries'].'), probably in error. Ignoring upper byte and interpreting this as 0x'.getid3_lib::PrintHexBytes(substr($atom_data, 5, 3), true, false).' = '.($atom_structure['number_entries'] & 0x00FFFFFF)); |
|
733 $atom_structure['number_entries'] = ($atom_structure['number_entries'] & 0x00FFFFFF); |
|
734 } else { |
|
735 $this->warning('"stsd" atom contains improbably large number_entries (0x'.getid3_lib::PrintHexBytes(substr($atom_data, 4, 4), true, false).' = '.$atom_structure['number_entries'].'), probably in error. Please report this to info@getid3.org referencing bug report #111'); |
|
736 } |
|
737 } |
|
738 |
607 $stsdEntriesDataOffset = 8; |
739 $stsdEntriesDataOffset = 8; |
608 for ($i = 0; $i < $atom_structure['number_entries']; $i++) { |
740 for ($i = 0; $i < $atom_structure['number_entries']; $i++) { |
609 $atom_structure['sample_description_table'][$i]['size'] = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 4)); |
741 $atom_structure['sample_description_table'][$i]['size'] = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 4)); |
610 $stsdEntriesDataOffset += 4; |
742 $stsdEntriesDataOffset += 4; |
611 $atom_structure['sample_description_table'][$i]['data_format'] = substr($atom_data, $stsdEntriesDataOffset, 4); |
743 $atom_structure['sample_description_table'][$i]['data_format'] = substr($atom_data, $stsdEntriesDataOffset, 4); |
1395 case 'CNCV': // Canon:CompressorVersion - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Canon.html |
1533 case 'CNCV': // Canon:CompressorVersion - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Canon.html |
1396 $atom_structure['data'] = $atom_data; |
1534 $atom_structure['data'] = $atom_data; |
1397 break; |
1535 break; |
1398 |
1536 |
1399 case "\x00\x00\x00\x00": |
1537 case "\x00\x00\x00\x00": |
|
1538 // some kind of metacontainer, may contain a big data dump such as: |
|
1539 // mdta keys \005 mdtacom.apple.quicktime.make (mdtacom.apple.quicktime.creationdate ,mdtacom.apple.quicktime.location.ISO6709 $mdtacom.apple.quicktime.software !mdtacom.apple.quicktime.model ilst \01D \001 \015data \001DE\010Apple 0 \002 (data \001DE\0102011-05-11T17:54:04+0200 2 \003 *data \001DE\010+52.4936+013.3897+040.247/ \01D \004 \015data \001DE\0104.3.1 \005 \018data \001DE\010iPhone 4 |
|
1540 // http://www.geocities.com/xhelmboyx/quicktime/formats/qti-layout.txt |
|
1541 |
|
1542 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); |
|
1543 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); |
|
1544 $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom(substr($atom_data, 4), $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); |
|
1545 //$atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); |
|
1546 break; |
|
1547 |
1400 case 'meta': // METAdata atom |
1548 case 'meta': // METAdata atom |
1401 // some kind of metacontainer, may contain a big data dump such as: |
1549 // https://developer.apple.com/library/mac/documentation/QuickTime/QTFF/Metadata/Metadata.html |
1402 // mdta keys mdtacom.apple.quicktime.make (mdtacom.apple.quicktime.creationdate ,mdtacom.apple.quicktime.location.ISO6709 $mdtacom.apple.quicktime.software !mdtacom.apple.quicktime.model ilst data DEApple 0 (data DE2011-05-11T17:54:04+0200 2 *data DE+52.4936+013.3897+040.247/ data DE4.3.1 data DEiPhone 4 |
1550 |
1403 // http://www.geocities.com/xhelmboyx/quicktime/formats/qti-layout.txt |
1551 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); |
1404 |
1552 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); |
1405 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); |
1553 $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); |
1406 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); |
|
1407 $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom(substr($atom_data, 4), $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); |
|
1408 //$atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); |
|
1409 break; |
1554 break; |
1410 |
1555 |
1411 case 'data': // metaDATA atom |
1556 case 'data': // metaDATA atom |
|
1557 static $metaDATAkey = 1; // real ugly, but so is the QuickTime structure that stores keys and values in different multinested locations that are hard to relate to each other |
1412 // seems to be 2 bytes language code (ASCII), 2 bytes unknown (set to 0x10B5 in sample I have), remainder is useful data |
1558 // seems to be 2 bytes language code (ASCII), 2 bytes unknown (set to 0x10B5 in sample I have), remainder is useful data |
1413 $atom_structure['language'] = substr($atom_data, 4 + 0, 2); |
1559 $atom_structure['language'] = substr($atom_data, 4 + 0, 2); |
1414 $atom_structure['unknown'] = getid3_lib::BigEndian2Int(substr($atom_data, 4 + 2, 2)); |
1560 $atom_structure['unknown'] = getid3_lib::BigEndian2Int(substr($atom_data, 4 + 2, 2)); |
1415 $atom_structure['data'] = substr($atom_data, 4 + 4); |
1561 $atom_structure['data'] = substr($atom_data, 4 + 4); |
1416 break; |
1562 $atom_structure['key_name'] = @$info['quicktime']['temp_meta_key_names'][$metaDATAkey++]; |
|
1563 |
|
1564 if ($atom_structure['key_name'] && $atom_structure['data']) { |
|
1565 @$info['quicktime']['comments'][str_replace('com.apple.quicktime.', '', $atom_structure['key_name'])][] = $atom_structure['data']; |
|
1566 } |
|
1567 break; |
|
1568 |
|
1569 case 'keys': // KEYS that may be present in the metadata atom. |
|
1570 // https://developer.apple.com/library/mac/documentation/QuickTime/QTFF/Metadata/Metadata.html#//apple_ref/doc/uid/TP40000939-CH1-SW21 |
|
1571 // The metadata item keys atom holds a list of the metadata keys that may be present in the metadata atom. |
|
1572 // This list is indexed starting with 1; 0 is a reserved index value. The metadata item keys atom is a full atom with an atom type of "keys". |
|
1573 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); |
|
1574 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); |
|
1575 $atom_structure['entry_count'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); |
|
1576 $keys_atom_offset = 8; |
|
1577 for ($i = 1; $i <= $atom_structure['entry_count']; $i++) { |
|
1578 $atom_structure['keys'][$i]['key_size'] = getid3_lib::BigEndian2Int(substr($atom_data, $keys_atom_offset + 0, 4)); |
|
1579 $atom_structure['keys'][$i]['key_namespace'] = substr($atom_data, $keys_atom_offset + 4, 4); |
|
1580 $atom_structure['keys'][$i]['key_value'] = substr($atom_data, $keys_atom_offset + 8, $atom_structure['keys'][$i]['key_size'] - 8); |
|
1581 $keys_atom_offset += $atom_structure['keys'][$i]['key_size']; // key_size includes the 4+4 bytes for key_size and key_namespace |
|
1582 |
|
1583 $info['quicktime']['temp_meta_key_names'][$i] = $atom_structure['keys'][$i]['key_value']; |
|
1584 } |
|
1585 break; |
|
1586 |
|
1587 case 'gps ': |
|
1588 // https://dashcamtalk.com/forum/threads/script-to-extract-gps-data-from-novatek-mp4.20808/page-2#post-291730 |
|
1589 // The 'gps ' contains simple look up table made up of 8byte rows, that point to the 'free' atoms that contains the actual GPS data. |
|
1590 // The first row is version/metadata/notsure, I skip that. |
|
1591 // The following rows consist of 4byte address (absolute) and 4byte size (0x1000), these point to the GPS data in the file. |
|
1592 |
|
1593 $GPS_rowsize = 8; // 4 bytes for offset, 4 bytes for size |
|
1594 if (strlen($atom_data) > 0) { |
|
1595 if ((strlen($atom_data) % $GPS_rowsize) == 0) { |
|
1596 $atom_structure['gps_toc'] = array(); |
|
1597 foreach (str_split($atom_data, $GPS_rowsize) as $counter => $datapair) { |
|
1598 $atom_structure['gps_toc'][] = unpack('Noffset/Nsize', substr($atom_data, $counter * $GPS_rowsize, $GPS_rowsize)); |
|
1599 } |
|
1600 |
|
1601 $atom_structure['gps_entries'] = array(); |
|
1602 $previous_offset = $this->ftell(); |
|
1603 foreach ($atom_structure['gps_toc'] as $key => $gps_pointer) { |
|
1604 if ($key == 0) { |
|
1605 // "The first row is version/metadata/notsure, I skip that." |
|
1606 continue; |
|
1607 } |
|
1608 $this->fseek($gps_pointer['offset']); |
|
1609 $GPS_free_data = $this->fread($gps_pointer['size']); |
|
1610 |
|
1611 /* |
|
1612 // 2017-05-10: I see some of the data, notably the Hour-Minute-Second, but cannot reconcile the rest of the data. However, the NMEA "GPRMC" line is there and relatively easy to parse, so I'm using that instead |
|
1613 |
|
1614 // https://dashcamtalk.com/forum/threads/script-to-extract-gps-data-from-novatek-mp4.20808/page-2#post-291730 |
|
1615 // The structure of the GPS data atom (the 'free' atoms mentioned above) is following: |
|
1616 // hour,minute,second,year,month,day,active,latitude_b,longitude_b,unknown2,latitude,longitude,speed = struct.unpack_from('<IIIIIIssssfff',data, 48) |
|
1617 // For those unfamiliar with python struct: |
|
1618 // I = int |
|
1619 // s = is string (size 1, in this case) |
|
1620 // f = float |
|
1621 |
|
1622 //$atom_structure['gps_entries'][$key] = unpack('Vhour/Vminute/Vsecond/Vyear/Vmonth/Vday/Vactive/Vlatitude_b/Vlongitude_b/Vunknown2/flatitude/flongitude/fspeed', substr($GPS_free_data, 48)); |
|
1623 */ |
|
1624 |
|
1625 // $GPRMC,081836,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E*62 |
|
1626 // $GPRMC,183731,A,3907.482,N,12102.436,W,000.0,360.0,080301,015.5,E*67 |
|
1627 // $GPRMC,002454,A,3553.5295,N,13938.6570,E,0.0,43.1,180700,7.1,W,A*3F |
|
1628 // $GPRMC,094347.000,A,5342.0061,N,00737.9908,W,0.01,156.75,140217,,,A*7D |
|
1629 if (preg_match('#\\$GPRMC,([0-9\\.]*),([AV]),([0-9\\.]*),([NS]),([0-9\\.]*),([EW]),([0-9\\.]*),([0-9\\.]*),([0-9]*),([0-9\\.]*),([EW]?)(,[A])?(\\*[0-9A-F]{2})#', $GPS_free_data, $matches)) { |
|
1630 $GPS_this_GPRMC = array(); |
|
1631 list( |
|
1632 $GPS_this_GPRMC['raw']['gprmc'], |
|
1633 $GPS_this_GPRMC['raw']['timestamp'], |
|
1634 $GPS_this_GPRMC['raw']['status'], |
|
1635 $GPS_this_GPRMC['raw']['latitude'], |
|
1636 $GPS_this_GPRMC['raw']['latitude_direction'], |
|
1637 $GPS_this_GPRMC['raw']['longitude'], |
|
1638 $GPS_this_GPRMC['raw']['longitude_direction'], |
|
1639 $GPS_this_GPRMC['raw']['knots'], |
|
1640 $GPS_this_GPRMC['raw']['angle'], |
|
1641 $GPS_this_GPRMC['raw']['datestamp'], |
|
1642 $GPS_this_GPRMC['raw']['variation'], |
|
1643 $GPS_this_GPRMC['raw']['variation_direction'], |
|
1644 $dummy, |
|
1645 $GPS_this_GPRMC['raw']['checksum'], |
|
1646 ) = $matches; |
|
1647 |
|
1648 $hour = substr($GPS_this_GPRMC['raw']['timestamp'], 0, 2); |
|
1649 $minute = substr($GPS_this_GPRMC['raw']['timestamp'], 2, 2); |
|
1650 $second = substr($GPS_this_GPRMC['raw']['timestamp'], 4, 2); |
|
1651 $ms = substr($GPS_this_GPRMC['raw']['timestamp'], 6); // may contain decimal seconds |
|
1652 $day = substr($GPS_this_GPRMC['raw']['datestamp'], 0, 2); |
|
1653 $month = substr($GPS_this_GPRMC['raw']['datestamp'], 2, 2); |
|
1654 $year = substr($GPS_this_GPRMC['raw']['datestamp'], 4, 2); |
|
1655 $year += (($year > 90) ? 1900 : 2000); // complete lack of foresight: datestamps are stored with 2-digit years, take best guess |
|
1656 $GPS_this_GPRMC['timestamp'] = $year.'-'.$month.'-'.$day.' '.$hour.':'.$minute.':'.$second.$ms; |
|
1657 |
|
1658 $GPS_this_GPRMC['active'] = ($GPS_this_GPRMC['raw']['status'] == 'A'); // A=Active,V=Void |
|
1659 |
|
1660 foreach (array('latitude','longitude') as $latlon) { |
|
1661 preg_match('#^([0-9]{1,3})([0-9]{2}\\.[0-9]+)$#', $GPS_this_GPRMC['raw'][$latlon], $matches); |
|
1662 list($dummy, $deg, $min) = $matches; |
|
1663 $GPS_this_GPRMC[$latlon] = $deg + ($min / 60); |
|
1664 } |
|
1665 $GPS_this_GPRMC['latitude'] *= (($GPS_this_GPRMC['raw']['latitude_direction'] == 'S') ? -1 : 1); |
|
1666 $GPS_this_GPRMC['longitude'] *= (($GPS_this_GPRMC['raw']['longitude_direction'] == 'W') ? -1 : 1); |
|
1667 |
|
1668 $GPS_this_GPRMC['heading'] = $GPS_this_GPRMC['raw']['angle']; |
|
1669 $GPS_this_GPRMC['speed_knot'] = $GPS_this_GPRMC['raw']['knots']; |
|
1670 $GPS_this_GPRMC['speed_kmh'] = $GPS_this_GPRMC['raw']['knots'] * 1.852; |
|
1671 if ($GPS_this_GPRMC['raw']['variation']) { |
|
1672 $GPS_this_GPRMC['variation'] = $GPS_this_GPRMC['raw']['variation']; |
|
1673 $GPS_this_GPRMC['variation'] *= (($GPS_this_GPRMC['raw']['variation_direction'] == 'W') ? -1 : 1); |
|
1674 } |
|
1675 |
|
1676 $atom_structure['gps_entries'][$key] = $GPS_this_GPRMC; |
|
1677 |
|
1678 @$info['quicktime']['gps_track'][$GPS_this_GPRMC['timestamp']] = array( |
|
1679 'latitude' => $GPS_this_GPRMC['latitude'], |
|
1680 'longitude' => $GPS_this_GPRMC['longitude'], |
|
1681 'speed_kmh' => $GPS_this_GPRMC['speed_kmh'], |
|
1682 'heading' => $GPS_this_GPRMC['heading'], |
|
1683 ); |
|
1684 |
|
1685 } else { |
|
1686 $this->warning('Unhandled GPS format in "free" atom at offset '.$gps_pointer['offset']); |
|
1687 } |
|
1688 } |
|
1689 $this->fseek($previous_offset); |
|
1690 |
|
1691 } else { |
|
1692 $this->warning('QuickTime atom "'.$atomname.'" is not mod-8 bytes long ('.$atomsize.' bytes) at offset '.$baseoffset); |
|
1693 } |
|
1694 } else { |
|
1695 $this->warning('QuickTime atom "'.$atomname.'" is zero bytes long at offset '.$baseoffset); |
|
1696 } |
|
1697 break; |
|
1698 |
|
1699 case 'loci':// 3GP location (El Loco) |
|
1700 $info['quicktime']['comments']['gps_flags'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); |
|
1701 $info['quicktime']['comments']['gps_lang'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); |
|
1702 $loffset = 0; |
|
1703 $info['quicktime']['comments']['gps_location'] = $this->LociString(substr($atom_data, 6), $loffset); |
|
1704 $loci_data=substr($atom_data, 6 + $loffset); |
|
1705 $info['quicktime']['comments']['gps_role'] = getid3_lib::BigEndian2Int(substr($loci_data, 0, 1)); |
|
1706 $info['quicktime']['comments']['gps_longitude'] = getid3_lib::FixedPoint16_16(substr($loci_data, 1, 4)); |
|
1707 $info['quicktime']['comments']['gps_latitude'] = getid3_lib::FixedPoint16_16(substr($loci_data, 5, 4)); |
|
1708 $info['quicktime']['comments']['gps_altitude'] = getid3_lib::FixedPoint16_16(substr($loci_data, 9, 4)); |
|
1709 $info['quicktime']['comments']['gps_body'] = $this->LociString(substr($loci_data, 13), $loffset); |
|
1710 $info['quicktime']['comments']['gps_notes'] = $this->LociString(substr($loci_data, 13 + $loffset), $loffset); |
|
1711 break; |
1417 |
1712 |
1418 default: |
1713 default: |
1419 $info['warning'][] = 'Unknown QuickTime atom type: "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $atomname).'" ('.trim(getid3_lib::PrintHexBytes($atomname)).') at offset '.$baseoffset; |
1714 $this->warning('Unknown QuickTime atom type: "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $atomname).'" ('.trim(getid3_lib::PrintHexBytes($atomname)).') at offset '.$baseoffset); |
1420 $atom_structure['data'] = $atom_data; |
1715 $atom_structure['data'] = $atom_data; |
1421 break; |
1716 break; |
1422 } |
1717 } |
1423 array_pop($atomHierarchy); |
1718 array_pop($atomHierarchy); |
1424 return $atom_structure; |
1719 return $atom_structure; |
1751 |
2050 |
1752 public function QuicktimeIODSaudioProfileName($audio_profile_id) { |
2051 public function QuicktimeIODSaudioProfileName($audio_profile_id) { |
1753 static $QuicktimeIODSaudioProfileNameLookup = array(); |
2052 static $QuicktimeIODSaudioProfileNameLookup = array(); |
1754 if (empty($QuicktimeIODSaudioProfileNameLookup)) { |
2053 if (empty($QuicktimeIODSaudioProfileNameLookup)) { |
1755 $QuicktimeIODSaudioProfileNameLookup = array( |
2054 $QuicktimeIODSaudioProfileNameLookup = array( |
1756 0x00 => 'ISO Reserved (0x00)', |
2055 0x00 => 'ISO Reserved (0x00)', |
1757 0x01 => 'Main Audio Profile @ Level 1', |
2056 0x01 => 'Main Audio Profile @ Level 1', |
1758 0x02 => 'Main Audio Profile @ Level 2', |
2057 0x02 => 'Main Audio Profile @ Level 2', |
1759 0x03 => 'Main Audio Profile @ Level 3', |
2058 0x03 => 'Main Audio Profile @ Level 3', |
1760 0x04 => 'Main Audio Profile @ Level 4', |
2059 0x04 => 'Main Audio Profile @ Level 4', |
1761 0x05 => 'Scalable Audio Profile @ Level 1', |
2060 0x05 => 'Scalable Audio Profile @ Level 1', |
1762 0x06 => 'Scalable Audio Profile @ Level 2', |
2061 0x06 => 'Scalable Audio Profile @ Level 2', |
1763 0x07 => 'Scalable Audio Profile @ Level 3', |
2062 0x07 => 'Scalable Audio Profile @ Level 3', |
1764 0x08 => 'Scalable Audio Profile @ Level 4', |
2063 0x08 => 'Scalable Audio Profile @ Level 4', |
1765 0x09 => 'Speech Audio Profile @ Level 1', |
2064 0x09 => 'Speech Audio Profile @ Level 1', |
1766 0x0A => 'Speech Audio Profile @ Level 2', |
2065 0x0A => 'Speech Audio Profile @ Level 2', |
1767 0x0B => 'Synthetic Audio Profile @ Level 1', |
2066 0x0B => 'Synthetic Audio Profile @ Level 1', |
1768 0x0C => 'Synthetic Audio Profile @ Level 2', |
2067 0x0C => 'Synthetic Audio Profile @ Level 2', |
1769 0x0D => 'Synthetic Audio Profile @ Level 3', |
2068 0x0D => 'Synthetic Audio Profile @ Level 3', |
1770 0x0E => 'High Quality Audio Profile @ Level 1', |
2069 0x0E => 'High Quality Audio Profile @ Level 1', |
1771 0x0F => 'High Quality Audio Profile @ Level 2', |
2070 0x0F => 'High Quality Audio Profile @ Level 2', |
1772 0x10 => 'High Quality Audio Profile @ Level 3', |
2071 0x10 => 'High Quality Audio Profile @ Level 3', |
1773 0x11 => 'High Quality Audio Profile @ Level 4', |
2072 0x11 => 'High Quality Audio Profile @ Level 4', |
1774 0x12 => 'High Quality Audio Profile @ Level 5', |
2073 0x12 => 'High Quality Audio Profile @ Level 5', |
1775 0x13 => 'High Quality Audio Profile @ Level 6', |
2074 0x13 => 'High Quality Audio Profile @ Level 6', |
1776 0x14 => 'High Quality Audio Profile @ Level 7', |
2075 0x14 => 'High Quality Audio Profile @ Level 7', |
1777 0x15 => 'High Quality Audio Profile @ Level 8', |
2076 0x15 => 'High Quality Audio Profile @ Level 8', |
1778 0x16 => 'Low Delay Audio Profile @ Level 1', |
2077 0x16 => 'Low Delay Audio Profile @ Level 1', |
1779 0x17 => 'Low Delay Audio Profile @ Level 2', |
2078 0x17 => 'Low Delay Audio Profile @ Level 2', |
1780 0x18 => 'Low Delay Audio Profile @ Level 3', |
2079 0x18 => 'Low Delay Audio Profile @ Level 3', |
1781 0x19 => 'Low Delay Audio Profile @ Level 4', |
2080 0x19 => 'Low Delay Audio Profile @ Level 4', |
1782 0x1A => 'Low Delay Audio Profile @ Level 5', |
2081 0x1A => 'Low Delay Audio Profile @ Level 5', |
1783 0x1B => 'Low Delay Audio Profile @ Level 6', |
2082 0x1B => 'Low Delay Audio Profile @ Level 6', |
1784 0x1C => 'Low Delay Audio Profile @ Level 7', |
2083 0x1C => 'Low Delay Audio Profile @ Level 7', |
1785 0x1D => 'Low Delay Audio Profile @ Level 8', |
2084 0x1D => 'Low Delay Audio Profile @ Level 8', |
1786 0x1E => 'Natural Audio Profile @ Level 1', |
2085 0x1E => 'Natural Audio Profile @ Level 1', |
1787 0x1F => 'Natural Audio Profile @ Level 2', |
2086 0x1F => 'Natural Audio Profile @ Level 2', |
1788 0x20 => 'Natural Audio Profile @ Level 3', |
2087 0x20 => 'Natural Audio Profile @ Level 3', |
1789 0x21 => 'Natural Audio Profile @ Level 4', |
2088 0x21 => 'Natural Audio Profile @ Level 4', |
1790 0x22 => 'Mobile Audio Internetworking Profile @ Level 1', |
2089 0x22 => 'Mobile Audio Internetworking Profile @ Level 1', |
1791 0x23 => 'Mobile Audio Internetworking Profile @ Level 2', |
2090 0x23 => 'Mobile Audio Internetworking Profile @ Level 2', |
1792 0x24 => 'Mobile Audio Internetworking Profile @ Level 3', |
2091 0x24 => 'Mobile Audio Internetworking Profile @ Level 3', |
1793 0x25 => 'Mobile Audio Internetworking Profile @ Level 4', |
2092 0x25 => 'Mobile Audio Internetworking Profile @ Level 4', |
1794 0x26 => 'Mobile Audio Internetworking Profile @ Level 5', |
2093 0x26 => 'Mobile Audio Internetworking Profile @ Level 5', |
1795 0x27 => 'Mobile Audio Internetworking Profile @ Level 6', |
2094 0x27 => 'Mobile Audio Internetworking Profile @ Level 6', |
1796 0x28 => 'AAC Profile @ Level 1', |
2095 0x28 => 'AAC Profile @ Level 1', |
1797 0x29 => 'AAC Profile @ Level 2', |
2096 0x29 => 'AAC Profile @ Level 2', |
1798 0x2A => 'AAC Profile @ Level 4', |
2097 0x2A => 'AAC Profile @ Level 4', |
1799 0x2B => 'AAC Profile @ Level 5', |
2098 0x2B => 'AAC Profile @ Level 5', |
1800 0x2C => 'High Efficiency AAC Profile @ Level 2', |
2099 0x2C => 'High Efficiency AAC Profile @ Level 2', |
1801 0x2D => 'High Efficiency AAC Profile @ Level 3', |
2100 0x2D => 'High Efficiency AAC Profile @ Level 3', |
1802 0x2E => 'High Efficiency AAC Profile @ Level 4', |
2101 0x2E => 'High Efficiency AAC Profile @ Level 4', |
1803 0x2F => 'High Efficiency AAC Profile @ Level 5', |
2102 0x2F => 'High Efficiency AAC Profile @ Level 5', |
1804 0xFE => 'Not part of MPEG-4 audio profiles', |
2103 0xFE => 'Not part of MPEG-4 audio profiles', |
1805 0xFF => 'No audio capability required', |
2104 0xFF => 'No audio capability required', |
1806 ); |
2105 ); |
1807 } |
2106 } |
1808 return (isset($QuicktimeIODSaudioProfileNameLookup[$audio_profile_id]) ? $QuicktimeIODSaudioProfileNameLookup[$audio_profile_id] : 'ISO Reserved / User Private'); |
2107 return (isset($QuicktimeIODSaudioProfileNameLookup[$audio_profile_id]) ? $QuicktimeIODSaudioProfileNameLookup[$audio_profile_id] : 'ISO Reserved / User Private'); |
1809 } |
2108 } |
1810 |
2109 |
2109 |
2408 |
2110 |
2409 |
2111 public function CopyToAppropriateCommentsSection($keyname, $data, $boxname='') { |
2410 public function CopyToAppropriateCommentsSection($keyname, $data, $boxname='') { |
2112 static $handyatomtranslatorarray = array(); |
2411 static $handyatomtranslatorarray = array(); |
2113 if (empty($handyatomtranslatorarray)) { |
2412 if (empty($handyatomtranslatorarray)) { |
|
2413 // http://www.geocities.com/xhelmboyx/quicktime/formats/qtm-layout.txt |
|
2414 // http://www.geocities.com/xhelmboyx/quicktime/formats/mp4-layout.txt |
|
2415 // http://atomicparsley.sourceforge.net/mpeg-4files.html |
|
2416 // https://code.google.com/p/mp4v2/wiki/iTunesMetadata |
|
2417 $handyatomtranslatorarray["\xA9".'alb'] = 'album'; // iTunes 4.0 |
|
2418 $handyatomtranslatorarray["\xA9".'ART'] = 'artist'; |
|
2419 $handyatomtranslatorarray["\xA9".'art'] = 'artist'; // iTunes 4.0 |
|
2420 $handyatomtranslatorarray["\xA9".'aut'] = 'author'; |
|
2421 $handyatomtranslatorarray["\xA9".'cmt'] = 'comment'; // iTunes 4.0 |
|
2422 $handyatomtranslatorarray["\xA9".'com'] = 'comment'; |
2114 $handyatomtranslatorarray["\xA9".'cpy'] = 'copyright'; |
2423 $handyatomtranslatorarray["\xA9".'cpy'] = 'copyright'; |
2115 $handyatomtranslatorarray["\xA9".'day'] = 'creation_date'; // iTunes 4.0 |
2424 $handyatomtranslatorarray["\xA9".'day'] = 'creation_date'; // iTunes 4.0 |
2116 $handyatomtranslatorarray["\xA9".'dir'] = 'director'; |
2425 $handyatomtranslatorarray["\xA9".'dir'] = 'director'; |
2117 $handyatomtranslatorarray["\xA9".'ed1'] = 'edit1'; |
2426 $handyatomtranslatorarray["\xA9".'ed1'] = 'edit1'; |
2118 $handyatomtranslatorarray["\xA9".'ed2'] = 'edit2'; |
2427 $handyatomtranslatorarray["\xA9".'ed2'] = 'edit2'; |
2119 $handyatomtranslatorarray["\xA9".'ed3'] = 'edit3'; |
2428 $handyatomtranslatorarray["\xA9".'ed3'] = 'edit3'; |
2120 $handyatomtranslatorarray["\xA9".'ed4'] = 'edit4'; |
2429 $handyatomtranslatorarray["\xA9".'ed4'] = 'edit4'; |
2121 $handyatomtranslatorarray["\xA9".'ed5'] = 'edit5'; |
2430 $handyatomtranslatorarray["\xA9".'ed5'] = 'edit5'; |
2122 $handyatomtranslatorarray["\xA9".'ed6'] = 'edit6'; |
2431 $handyatomtranslatorarray["\xA9".'ed6'] = 'edit6'; |
2123 $handyatomtranslatorarray["\xA9".'ed7'] = 'edit7'; |
2432 $handyatomtranslatorarray["\xA9".'ed7'] = 'edit7'; |
2124 $handyatomtranslatorarray["\xA9".'ed8'] = 'edit8'; |
2433 $handyatomtranslatorarray["\xA9".'ed8'] = 'edit8'; |
2125 $handyatomtranslatorarray["\xA9".'ed9'] = 'edit9'; |
2434 $handyatomtranslatorarray["\xA9".'ed9'] = 'edit9'; |
|
2435 $handyatomtranslatorarray["\xA9".'enc'] = 'encoded_by'; |
2126 $handyatomtranslatorarray["\xA9".'fmt'] = 'format'; |
2436 $handyatomtranslatorarray["\xA9".'fmt'] = 'format'; |
|
2437 $handyatomtranslatorarray["\xA9".'gen'] = 'genre'; // iTunes 4.0 |
|
2438 $handyatomtranslatorarray["\xA9".'grp'] = 'grouping'; // iTunes 4.2 |
|
2439 $handyatomtranslatorarray["\xA9".'hst'] = 'host_computer'; |
2127 $handyatomtranslatorarray["\xA9".'inf'] = 'information'; |
2440 $handyatomtranslatorarray["\xA9".'inf'] = 'information'; |
|
2441 $handyatomtranslatorarray["\xA9".'lyr'] = 'lyrics'; // iTunes 5.0 |
|
2442 $handyatomtranslatorarray["\xA9".'mak'] = 'make'; |
|
2443 $handyatomtranslatorarray["\xA9".'mod'] = 'model'; |
|
2444 $handyatomtranslatorarray["\xA9".'nam'] = 'title'; // iTunes 4.0 |
|
2445 $handyatomtranslatorarray["\xA9".'ope'] = 'composer'; |
2128 $handyatomtranslatorarray["\xA9".'prd'] = 'producer'; |
2446 $handyatomtranslatorarray["\xA9".'prd'] = 'producer'; |
|
2447 $handyatomtranslatorarray["\xA9".'PRD'] = 'product'; |
2129 $handyatomtranslatorarray["\xA9".'prf'] = 'performers'; |
2448 $handyatomtranslatorarray["\xA9".'prf'] = 'performers'; |
2130 $handyatomtranslatorarray["\xA9".'req'] = 'system_requirements'; |
2449 $handyatomtranslatorarray["\xA9".'req'] = 'system_requirements'; |
2131 $handyatomtranslatorarray["\xA9".'src'] = 'source_credit'; |
2450 $handyatomtranslatorarray["\xA9".'src'] = 'source_credit'; |
2132 $handyatomtranslatorarray["\xA9".'wrt'] = 'writer'; |
2451 $handyatomtranslatorarray["\xA9".'swr'] = 'software'; |
2133 |
2452 $handyatomtranslatorarray["\xA9".'too'] = 'encoding_tool'; // iTunes 4.0 |
2134 // http://www.geocities.com/xhelmboyx/quicktime/formats/qtm-layout.txt |
2453 $handyatomtranslatorarray["\xA9".'trk'] = 'track'; |
2135 $handyatomtranslatorarray["\xA9".'nam'] = 'title'; // iTunes 4.0 |
2454 $handyatomtranslatorarray["\xA9".'url'] = 'url'; |
2136 $handyatomtranslatorarray["\xA9".'cmt'] = 'comment'; // iTunes 4.0 |
|
2137 $handyatomtranslatorarray["\xA9".'wrn'] = 'warning'; |
2455 $handyatomtranslatorarray["\xA9".'wrn'] = 'warning'; |
2138 $handyatomtranslatorarray["\xA9".'hst'] = 'host_computer'; |
2456 $handyatomtranslatorarray["\xA9".'wrt'] = 'composer'; |
2139 $handyatomtranslatorarray["\xA9".'mak'] = 'make'; |
|
2140 $handyatomtranslatorarray["\xA9".'mod'] = 'model'; |
|
2141 $handyatomtranslatorarray["\xA9".'PRD'] = 'product'; |
|
2142 $handyatomtranslatorarray["\xA9".'swr'] = 'software'; |
|
2143 $handyatomtranslatorarray["\xA9".'aut'] = 'author'; |
|
2144 $handyatomtranslatorarray["\xA9".'ART'] = 'artist'; |
|
2145 $handyatomtranslatorarray["\xA9".'trk'] = 'track'; |
|
2146 $handyatomtranslatorarray["\xA9".'alb'] = 'album'; // iTunes 4.0 |
|
2147 $handyatomtranslatorarray["\xA9".'com'] = 'comment'; |
|
2148 $handyatomtranslatorarray["\xA9".'gen'] = 'genre'; // iTunes 4.0 |
|
2149 $handyatomtranslatorarray["\xA9".'ope'] = 'composer'; |
|
2150 $handyatomtranslatorarray["\xA9".'url'] = 'url'; |
|
2151 $handyatomtranslatorarray["\xA9".'enc'] = 'encoder'; |
|
2152 |
|
2153 // http://atomicparsley.sourceforge.net/mpeg-4files.html |
|
2154 $handyatomtranslatorarray["\xA9".'art'] = 'artist'; // iTunes 4.0 |
|
2155 $handyatomtranslatorarray['aART'] = 'album_artist'; |
2457 $handyatomtranslatorarray['aART'] = 'album_artist'; |
2156 $handyatomtranslatorarray['trkn'] = 'track_number'; // iTunes 4.0 |
2458 $handyatomtranslatorarray['apID'] = 'purchase_account'; |
2157 $handyatomtranslatorarray['disk'] = 'disc_number'; // iTunes 4.0 |
2459 $handyatomtranslatorarray['catg'] = 'category'; // iTunes 4.9 |
2158 $handyatomtranslatorarray['gnre'] = 'genre'; // iTunes 4.0 |
2460 $handyatomtranslatorarray['covr'] = 'picture'; // iTunes 4.0 |
2159 $handyatomtranslatorarray["\xA9".'too'] = 'encoder'; // iTunes 4.0 |
2461 $handyatomtranslatorarray['cpil'] = 'compilation'; // iTunes 4.0 |
2160 $handyatomtranslatorarray['tmpo'] = 'bpm'; // iTunes 4.0 |
2462 $handyatomtranslatorarray['cprt'] = 'copyright'; // iTunes 4.0? |
2161 $handyatomtranslatorarray['cprt'] = 'copyright'; // iTunes 4.0? |
2463 $handyatomtranslatorarray['desc'] = 'description'; // iTunes 5.0 |
2162 $handyatomtranslatorarray['cpil'] = 'compilation'; // iTunes 4.0 |
2464 $handyatomtranslatorarray['disk'] = 'disc_number'; // iTunes 4.0 |
2163 $handyatomtranslatorarray['covr'] = 'picture'; // iTunes 4.0 |
2465 $handyatomtranslatorarray['egid'] = 'episode_guid'; // iTunes 4.9 |
2164 $handyatomtranslatorarray['rtng'] = 'rating'; // iTunes 4.0 |
2466 $handyatomtranslatorarray['gnre'] = 'genre'; // iTunes 4.0 |
2165 $handyatomtranslatorarray["\xA9".'grp'] = 'grouping'; // iTunes 4.2 |
2467 $handyatomtranslatorarray['hdvd'] = 'hd_video'; // iTunes 4.0 |
2166 $handyatomtranslatorarray['stik'] = 'stik'; // iTunes 4.9 |
2468 $handyatomtranslatorarray['ldes'] = 'description_long'; // |
2167 $handyatomtranslatorarray['pcst'] = 'podcast'; // iTunes 4.9 |
2469 $handyatomtranslatorarray['keyw'] = 'keyword'; // iTunes 4.9 |
2168 $handyatomtranslatorarray['catg'] = 'category'; // iTunes 4.9 |
2470 $handyatomtranslatorarray['pcst'] = 'podcast'; // iTunes 4.9 |
2169 $handyatomtranslatorarray['keyw'] = 'keyword'; // iTunes 4.9 |
2471 $handyatomtranslatorarray['pgap'] = 'gapless_playback'; // iTunes 7.0 |
2170 $handyatomtranslatorarray['purl'] = 'podcast_url'; // iTunes 4.9 |
2472 $handyatomtranslatorarray['purd'] = 'purchase_date'; // iTunes 6.0.2 |
2171 $handyatomtranslatorarray['egid'] = 'episode_guid'; // iTunes 4.9 |
2473 $handyatomtranslatorarray['purl'] = 'podcast_url'; // iTunes 4.9 |
2172 $handyatomtranslatorarray['desc'] = 'description'; // iTunes 5.0 |
2474 $handyatomtranslatorarray['rtng'] = 'rating'; // iTunes 4.0 |
2173 $handyatomtranslatorarray["\xA9".'lyr'] = 'lyrics'; // iTunes 5.0 |
2475 $handyatomtranslatorarray['soaa'] = 'sort_album_artist'; // |
2174 $handyatomtranslatorarray['tvnn'] = 'tv_network_name'; // iTunes 6.0 |
2476 $handyatomtranslatorarray['soal'] = 'sort_album'; // |
2175 $handyatomtranslatorarray['tvsh'] = 'tv_show_name'; // iTunes 6.0 |
2477 $handyatomtranslatorarray['soar'] = 'sort_artist'; // |
2176 $handyatomtranslatorarray['tvsn'] = 'tv_season'; // iTunes 6.0 |
2478 $handyatomtranslatorarray['soco'] = 'sort_composer'; // |
2177 $handyatomtranslatorarray['tves'] = 'tv_episode'; // iTunes 6.0 |
2479 $handyatomtranslatorarray['sonm'] = 'sort_title'; // |
2178 $handyatomtranslatorarray['purd'] = 'purchase_date'; // iTunes 6.0.2 |
2480 $handyatomtranslatorarray['sosn'] = 'sort_show'; // |
2179 $handyatomtranslatorarray['pgap'] = 'gapless_playback'; // iTunes 7.0 |
2481 $handyatomtranslatorarray['stik'] = 'stik'; // iTunes 4.9 |
2180 |
2482 $handyatomtranslatorarray['tmpo'] = 'bpm'; // iTunes 4.0 |
2181 // http://www.geocities.com/xhelmboyx/quicktime/formats/mp4-layout.txt |
2483 $handyatomtranslatorarray['trkn'] = 'track_number'; // iTunes 4.0 |
2182 |
2484 $handyatomtranslatorarray['tven'] = 'tv_episode_id'; // |
2183 |
2485 $handyatomtranslatorarray['tves'] = 'tv_episode'; // iTunes 6.0 |
|
2486 $handyatomtranslatorarray['tvnn'] = 'tv_network_name'; // iTunes 6.0 |
|
2487 $handyatomtranslatorarray['tvsh'] = 'tv_show_name'; // iTunes 6.0 |
|
2488 $handyatomtranslatorarray['tvsn'] = 'tv_season'; // iTunes 6.0 |
2184 |
2489 |
2185 // boxnames: |
2490 // boxnames: |
2186 /* |
2491 /* |
2187 $handyatomtranslatorarray['iTunSMPB'] = 'iTunSMPB'; |
2492 $handyatomtranslatorarray['iTunSMPB'] = 'iTunSMPB'; |
2188 $handyatomtranslatorarray['iTunNORM'] = 'iTunNORM'; |
2493 $handyatomtranslatorarray['iTunNORM'] = 'iTunNORM'; |
2241 public function Pascal2String($pascalstring) { |
2586 public function Pascal2String($pascalstring) { |
2242 // Pascal strings have 1 unsigned byte at the beginning saying how many chars (1-255) are in the string |
2587 // Pascal strings have 1 unsigned byte at the beginning saying how many chars (1-255) are in the string |
2243 return substr($pascalstring, 1); |
2588 return substr($pascalstring, 1); |
2244 } |
2589 } |
2245 |
2590 |
|
2591 |
|
2592 /* |
|
2593 // helper functions for m4b audiobook chapters |
|
2594 // code by Steffen Hartmann 2015-Nov-08 |
|
2595 */ |
|
2596 public function search_tag_by_key($info, $tag, $history, &$result) { |
|
2597 foreach ($info as $key => $value) { |
|
2598 $key_history = $history.'/'.$key; |
|
2599 if ($key === $tag) { |
|
2600 $result[] = array($key_history, $info); |
|
2601 } else { |
|
2602 if (is_array($value)) { |
|
2603 $this->search_tag_by_key($value, $tag, $key_history, $result); |
|
2604 } |
|
2605 } |
|
2606 } |
|
2607 } |
|
2608 |
|
2609 public function search_tag_by_pair($info, $k, $v, $history, &$result) { |
|
2610 foreach ($info as $key => $value) { |
|
2611 $key_history = $history.'/'.$key; |
|
2612 if (($key === $k) && ($value === $v)) { |
|
2613 $result[] = array($key_history, $info); |
|
2614 } else { |
|
2615 if (is_array($value)) { |
|
2616 $this->search_tag_by_pair($value, $k, $v, $key_history, $result); |
|
2617 } |
|
2618 } |
|
2619 } |
|
2620 } |
|
2621 |
|
2622 public function quicktime_time_to_sample_table($info) { |
|
2623 $res = array(); |
|
2624 $this->search_tag_by_pair($info['quicktime']['moov'], 'name', 'stbl', 'quicktime/moov', $res); |
|
2625 foreach ($res as $value) { |
|
2626 $stbl_res = array(); |
|
2627 $this->search_tag_by_pair($value[1], 'data_format', 'text', $value[0], $stbl_res); |
|
2628 if (count($stbl_res) > 0) { |
|
2629 $stts_res = array(); |
|
2630 $this->search_tag_by_key($value[1], 'time_to_sample_table', $value[0], $stts_res); |
|
2631 if (count($stts_res) > 0) { |
|
2632 return $stts_res[0][1]['time_to_sample_table']; |
|
2633 } |
|
2634 } |
|
2635 } |
|
2636 return array(); |
|
2637 } |
|
2638 |
|
2639 function quicktime_bookmark_time_scale($info) { |
|
2640 $time_scale = ''; |
|
2641 $ts_prefix_len = 0; |
|
2642 $res = array(); |
|
2643 $this->search_tag_by_pair($info['quicktime']['moov'], 'name', 'stbl', 'quicktime/moov', $res); |
|
2644 foreach ($res as $value) { |
|
2645 $stbl_res = array(); |
|
2646 $this->search_tag_by_pair($value[1], 'data_format', 'text', $value[0], $stbl_res); |
|
2647 if (count($stbl_res) > 0) { |
|
2648 $ts_res = array(); |
|
2649 $this->search_tag_by_key($info['quicktime']['moov'], 'time_scale', 'quicktime/moov', $ts_res); |
|
2650 foreach ($ts_res as $value) { |
|
2651 $prefix = substr($value[0], 0, -12); |
|
2652 if ((substr($stbl_res[0][0], 0, strlen($prefix)) === $prefix) && ($ts_prefix_len < strlen($prefix))) { |
|
2653 $time_scale = $value[1]['time_scale']; |
|
2654 $ts_prefix_len = strlen($prefix); |
|
2655 } |
|
2656 } |
|
2657 } |
|
2658 } |
|
2659 return $time_scale; |
|
2660 } |
|
2661 /* |
|
2662 // END helper functions for m4b audiobook chapters |
|
2663 */ |
|
2664 |
|
2665 |
2246 } |
2666 } |