- Timestamp:
- 07/31/2017 07:49:31 PM (8 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-includes/ID3/module.audio-video.quicktime.php
r32979 r41196 36 36 $offset = 0; 37 37 $atomcounter = 0; 38 $atom_data_read_buffer_size = ($info['php_memory_limit'] ? round($info['php_memory_limit'] / 2) : $this->getid3->option_fread_buffer_size * 1024); // allow [default: 32MB] if PHP configured with no memory_limit38 $atom_data_read_buffer_size = max($this->getid3->option_fread_buffer_size * 1024, ($info['php_memory_limit'] ? round($info['php_memory_limit'] / 4) : 1024)); // set read buffer to 25% of PHP memory limit (if one is specified), otherwise use option_fread_buffer_size [default: 32MB] 39 39 while ($offset < $info['avdataend']) { 40 40 if (!getid3_lib::intValueSupported($offset)) { 41 $ info['error'][] = 'Unable to parse atom at offset '.$offset.' because beyond '.round(PHP_INT_MAX / 1073741824).'GB limit of PHP filesystem functions';41 $this->error('Unable to parse atom at offset '.$offset.' because beyond '.round(PHP_INT_MAX / 1073741824).'GB limit of PHP filesystem functions'); 42 42 break; 43 43 } … … 58 58 59 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 61 return false; 62 62 } … … 80 80 $info['avdataend'] = $info['avdataend_tmp']; 81 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 … … 99 174 } 100 175 } 101 if ( ($info['audio']['dataformat'] == 'mp4') && empty($info['video']['resolution_x'])) {176 if ($info['audio']['dataformat'] == 'mp4') { 102 177 $info['fileformat'] = 'mp4'; 103 $info['mime_type'] = 'audio/mp4'; 104 unset($info['video']['dataformat']); 178 if (empty($info['video']['resolution_x'])) { 179 $info['mime_type'] = 'audio/mp4'; 180 unset($info['video']['dataformat']); 181 } else { 182 $info['mime_type'] = 'video/mp4'; 183 } 105 184 } 106 185 … … 121 200 public function QuicktimeParseAtom($atomname, $atomsize, $atom_data, $baseoffset, &$atomHierarchy, $ParseAllPossibleAtoms) { 122 201 // http://developer.apple.com/techpubs/quicktime/qtdevdocs/APIREF/INDEX/atomalphaindex.htm 202 // https://code.google.com/p/mp4v2/wiki/iTunesMetadata 123 203 124 204 $info = &$this->getid3->info; … … 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 345 case 'aART': // Album ARTist 346 case 'akID': // iTunes store account type 347 case 'apID': // Purchase Account 348 case 'atID': // 226 349 case 'catg': // CaTeGory 350 case 'cmID': // 351 case 'cnID': // 227 352 case 'covr': // COVeR artwork 228 353 case 'cpil': // ComPILation … … 231 356 case 'disk': // DISK number 232 357 case 'egid': // Episode Global ID 358 case 'geID': // 233 359 case 'gnre': // GeNRE 360 case 'hdvd': // HD ViDeo 234 361 case 'keyw': // KEYWord 235 case 'ldes': 362 case 'ldes': // Long DEScription 236 363 case 'pcst': // PodCaST 237 364 case 'pgap': // GAPless Playback 365 case 'plID': // 238 366 case 'purd': // PURchase Date 239 367 case 'purl': // Podcast URL 240 case 'rati': 241 case 'rndu': 242 case 'rpdu': 368 case 'rati': // 369 case 'rndu': // 370 case 'rpdu': // 243 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 380 case 'tmpo': // TeMPO (BPM) 246 381 case 'trkn': // TRacK Number 382 case 'tven': // tvEpisodeID 247 383 case 'tves': // TV EpiSode 248 384 case 'tvnn': // TV Network Name 249 385 case 'tvsh': // TV SHow Name 250 386 case 'tvsn': // TV SeasoN 251 case 'akID': // iTunes store account type252 case 'apID':253 case 'atID':254 case 'cmID':255 case 'cnID':256 case 'geID':257 case 'plID':258 case 'sfID': // iTunes store country259 case "\xA9".'alb': // ALBum260 case "\xA9".'art': // ARTist261 case "\xA9".'ART':262 case "\xA9".'aut':263 case "\xA9".'cmt': // CoMmenT264 case "\xA9".'com': // COMposer265 case "\xA9".'cpy':266 case "\xA9".'day': // content created year267 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': // GENre280 case "\xA9".'grp': // GRouPing281 case "\xA9".'hst':282 case "\xA9".'inf':283 case "\xA9".'lyr': // LYRics284 case "\xA9".'mak':285 case "\xA9".'mod':286 case "\xA9".'nam': // full NAMe287 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': // encoder295 case "\xA9".'trk': // TRacK296 case "\xA9".'url':297 case "\xA9".'wrn':298 case "\xA9".'wrt': // WRiTer299 case '----': // itunes specific300 387 if ($atom_parent == 'udta') { 301 388 // User data atom handler … … 319 406 $boxsmalldata = substr($atom_data, $atomoffset + 4, $boxsmallsize); 320 407 if ($boxsmallsize <= 1) { 321 $ info['warning'][] = 'Invalid QuickTime atom smallbox size "'.$boxsmallsize.'" in atom "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $atomname).'" at offset: '.($atom_structure['offset'] + $atomoffset);408 $this->warning('Invalid QuickTime atom smallbox size "'.$boxsmallsize.'" in atom "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $atomname).'" at offset: '.($atom_structure['offset'] + $atomoffset)); 322 409 $atom_structure['data'] = null; 323 410 $atomoffset = strlen($atom_data); … … 329 416 break; 330 417 default: 331 $ info['warning'][] = 'Unknown QuickTime smallbox type: "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $boxsmalltype).'" ('.trim(getid3_lib::PrintHexBytes($boxsmalltype)).') at offset '.$baseoffset;418 $this->warning('Unknown QuickTime smallbox type: "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $boxsmalltype).'" ('.trim(getid3_lib::PrintHexBytes($boxsmalltype)).') at offset '.$baseoffset); 332 419 $atom_structure['data'] = $atom_data; 333 420 break; … … 341 428 $boxdata = substr($atom_data, $atomoffset + 8, $boxsize - 8); 342 429 if ($boxsize <= 1) { 343 $ info['warning'][] = 'Invalid QuickTime atom box size "'.$boxsize.'" in atom "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $atomname).'" at offset: '.($atom_structure['offset'] + $atomoffset);430 $this->warning('Invalid QuickTime atom box size "'.$boxsize.'" in atom "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $atomname).'" at offset: '.($atom_structure['offset'] + $atomoffset)); 344 431 $atom_structure['data'] = null; 345 432 $atomoffset = strlen($atom_data); … … 362 449 switch ($atomname) { 363 450 case 'cpil': 451 case 'hdvd': 364 452 case 'pcst': 365 453 case 'pgap': 454 // 8-bit integer (boolean) 366 455 $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1)); 367 456 break; 368 457 369 458 case 'tmpo': 459 // 16-bit integer 370 460 $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 2)); 371 461 break; … … 373 463 case 'disk': 374 464 case 'trkn': 465 // binary 375 466 $num = getid3_lib::BigEndian2Int(substr($boxdata, 10, 2)); 376 467 $num_total = getid3_lib::BigEndian2Int(substr($boxdata, 12, 2)); … … 380 471 381 472 case 'gnre': 473 // enum 382 474 $GenreID = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4)); 383 475 $atom_structure['data'] = getid3_id3v1::LookupGenreName($GenreID - 1); … … 385 477 386 478 case 'rtng': 479 // 8-bit integer 387 480 $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1)); 388 481 $atom_structure['data'] = $this->QuicktimeContentRatingLookup($atom_structure[$atomname]); … … 390 483 391 484 case 'stik': 485 // 8-bit integer (enum) 392 486 $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1)); 393 487 $atom_structure['data'] = $this->QuicktimeSTIKLookup($atom_structure[$atomname]); … … 395 489 396 490 case 'sfID': 491 // 32-bit integer 397 492 $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4)); 398 493 $atom_structure['data'] = $this->QuicktimeStoreFrontCodeLookup($atom_structure[$atomname]); … … 404 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 523 default: 524 // 32-bit integer 407 525 $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4)); 408 526 } … … 415 533 if ($atomname == 'covr') { 416 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 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 538 $atom_structure['image_mime'] = 'image/png'; 421 539 } elseif (preg_match('#^GIF#', $atom_structure['data'])) { … … 429 547 430 548 default: 431 $ info['warning'][] = 'Unknown QuickTime box type: "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $boxtype).'" ('.trim(getid3_lib::PrintHexBytes($boxtype)).') at offset '.$baseoffset;549 $this->warning('Unknown QuickTime box type: "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $boxtype).'" ('.trim(getid3_lib::PrintHexBytes($boxtype)).') at offset '.$baseoffset); 432 550 $atom_structure['data'] = $atom_data; 433 551 … … 477 595 $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($UncompressedHeader, 0, $atomHierarchy, $ParseAllPossibleAtoms); 478 596 } else { 479 $ info['warning'][] = 'Error decompressing compressed MOV atom at offset '.$atom_structure['offset'];597 $this->warning('Error decompressing compressed MOV atom at offset '.$atom_structure['offset']); 480 598 } 481 599 break; … … 596 714 $atom_structure['display_size'] = $ptv_lookup[$atom_structure['display_size_raw']]; 597 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 718 break; … … 605 723 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 606 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 739 $stsdEntriesDataOffset = 8; 608 740 for ($i = 0; $i < $atom_structure['number_entries']; $i++) { … … 802 934 $max_stts_entries_to_scan = ($info['php_memory_limit'] ? min(floor($this->getid3->memory_limit / 10000), $atom_structure['number_entries']) : $atom_structure['number_entries']); 803 935 if ($max_stts_entries_to_scan < $atom_structure['number_entries']) { 804 $ info['warning'][] = 'QuickTime atom "stts" has '.$atom_structure['number_entries'].' but only scanning the first '.$max_stts_entries_to_scan.' entries due to limited PHP memory available ('.floor($atom_structure['number_entries'] / 1048576).'MB).';936 $this->warning('QuickTime atom "stts" has '.$atom_structure['number_entries'].' but only scanning the first '.$max_stts_entries_to_scan.' entries due to limited PHP memory available ('.floor($atom_structure['number_entries'] / 1048576).'MB).'); 805 937 } 806 938 for ($i = 0; $i < $max_stts_entries_to_scan; $i++) { … … 929 1061 $atom_structure['data_references'][$i]['size'] = getid3_lib::BigEndian2Int(substr($atom_data, $drefDataOffset, 4)); 930 1062 $drefDataOffset += 4; 931 $atom_structure['data_references'][$i]['type'] = substr($atom_data, $drefDataOffset, 4);1063 $atom_structure['data_references'][$i]['type'] = substr($atom_data, $drefDataOffset, 4); 932 1064 $drefDataOffset += 4; 933 1065 $atom_structure['data_references'][$i]['version'] = getid3_lib::BigEndian2Int(substr($atom_data, $drefDataOffset, 1)); … … 935 1067 $atom_structure['data_references'][$i]['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, $drefDataOffset, 3)); // hardcoded: 0x0000 936 1068 $drefDataOffset += 3; 937 $atom_structure['data_references'][$i]['data'] = substr($atom_data, $drefDataOffset, ($atom_structure['data_references'][$i]['size'] - 4 - 4 - 1 - 3));1069 $atom_structure['data_references'][$i]['data'] = substr($atom_data, $drefDataOffset, ($atom_structure['data_references'][$i]['size'] - 4 - 4 - 1 - 3)); 938 1070 $drefDataOffset += ($atom_structure['data_references'][$i]['size'] - 4 - 4 - 1 - 3); 939 1071 … … 1002 1134 1003 1135 if ($atom_structure['time_scale'] == 0) { 1004 $ info['error'][] = 'Corrupt Quicktime file: mdhd.time_scale == zero';1136 $this->error('Corrupt Quicktime file: mdhd.time_scale == zero'); 1005 1137 return false; 1006 1138 } 1007 $info['quicktime']['time_scale'] = ( isset($info['quicktime']['time_scale']) ? max($info['quicktime']['time_scale'], $atom_structure['time_scale']) : $atom_structure['time_scale']);1139 $info['quicktime']['time_scale'] = ((isset($info['quicktime']['time_scale']) && ($info['quicktime']['time_scale'] < 1000)) ? max($info['quicktime']['time_scale'], $atom_structure['time_scale']) : $atom_structure['time_scale']); 1008 1140 1009 1141 $atom_structure['creation_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['creation_time']); … … 1020 1152 $atom_structure['modification_date'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); // "standard Macintosh format" 1021 1153 $atom_structure['version_number'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); // hardcoded: 0x00 1022 $atom_structure['atom_type'] = substr($atom_data, 6, 4); // usually: 'PICT'1154 $atom_structure['atom_type'] = substr($atom_data, 6, 4); // usually: 'PICT' 1023 1155 $atom_structure['atom_index'] = getid3_lib::BigEndian2Int(substr($atom_data, 10, 2)); // usually: 0x01 1024 1156 … … 1030 1162 $atom_structure['region_size'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2)); // The Region size, Region boundary box, 1031 1163 $atom_structure['boundary_box'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 8)); // and Clipping region data fields 1032 $atom_structure['clipping_data'] = substr($atom_data, 10); // constitute a QuickDraw region.1164 $atom_structure['clipping_data'] = substr($atom_data, 10); // constitute a QuickDraw region. 1033 1165 break; 1034 1166 … … 1116 1248 1117 1249 if ($atom_structure['time_scale'] == 0) { 1118 $ info['error'][] = 'Corrupt Quicktime file: mvhd.time_scale == zero';1250 $this->error('Corrupt Quicktime file: mvhd.time_scale == zero'); 1119 1251 return false; 1120 1252 } 1121 1253 $atom_structure['creation_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['creation_time']); 1122 1254 $atom_structure['modify_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['modify_time']); 1123 $info['quicktime']['time_scale'] = ( isset($info['quicktime']['time_scale']) ? max($info['quicktime']['time_scale'], $atom_structure['time_scale']) : $atom_structure['time_scale']);1255 $info['quicktime']['time_scale'] = ((isset($info['quicktime']['time_scale']) && ($info['quicktime']['time_scale'] < 1000)) ? max($info['quicktime']['time_scale'], $atom_structure['time_scale']) : $atom_structure['time_scale']); 1124 1256 $info['quicktime']['display_scale'] = $atom_structure['matrix_a']; 1125 1257 $info['playtime_seconds'] = $atom_structure['duration'] / $atom_structure['time_scale']; … … 1241 1373 1242 1374 // check to see if it looks like chapter titles, in the form of unterminated strings with a leading 16-bit size field 1243 while (($chapter_string_length = getid3_lib::BigEndian2Int(substr($atom_data, $mdat_offset, 2))) 1375 while (($mdat_offset < (strlen($atom_data) - 8)) 1376 && ($chapter_string_length = getid3_lib::BigEndian2Int(substr($atom_data, $mdat_offset, 2))) 1244 1377 && ($chapter_string_length < 1000) 1245 1378 && ($chapter_string_length <= (strlen($atom_data) - $mdat_offset - 2)) 1246 && preg_match('#^[\x20-\xFF]+$#', substr($atom_data, $mdat_offset + 2, $chapter_string_length), $chapter_matches)) { 1379 && preg_match('#^([\x00-\xFF]{2})([\x20-\xFF]+)$#', substr($atom_data, $mdat_offset, $chapter_string_length + 2), $chapter_matches)) { 1380 list($dummy, $chapter_string_length_hex, $chapter_string) = $chapter_matches; 1247 1381 $mdat_offset += (2 + $chapter_string_length); 1248 @$info['quicktime']['comments']['chapters'][] = $chapter_matches[0]; 1249 } 1250 1382 @$info['quicktime']['comments']['chapters'][] = $chapter_string; 1383 1384 // "encd" atom specifies encoding. In theory could be anything, almost always UTF-8, but may be UTF-16 with BOM (not currently handled) 1385 if (substr($atom_data, $mdat_offset, 12) == "\x00\x00\x00\x0C\x65\x6E\x63\x64\x00\x00\x01\x00") { // UTF-8 1386 $mdat_offset += 12; 1387 } 1388 } 1251 1389 1252 1390 … … 1266 1404 if (!empty($getid3_temp->info['warning'])) { 1267 1405 foreach ($getid3_temp->info['warning'] as $value) { 1268 $ info['warning'][] = $value;1406 $this->warning($value); 1269 1407 } 1270 1408 } … … 1369 1507 } 1370 1508 } else { 1371 $ info['warning'][] = 'QuickTime atom "©xyz" data does not match expected data pattern at offset '.$baseoffset.'. Please report as getID3() bug.';1509 $this->warning('QuickTime atom "©xyz" data does not match expected data pattern at offset '.$baseoffset.'. Please report as getID3() bug.'); 1372 1510 } 1373 1511 break; … … 1398 1536 1399 1537 case "\x00\x00\x00\x00": 1400 case 'meta': // METAdata atom1401 1538 // some kind of metacontainer, may contain a big data dump such as: 1402 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 1403 1540 // http://www.geocities.com/xhelmboyx/quicktime/formats/qti-layout.txt 1404 1541 1405 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));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);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); 1408 1545 //$atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); 1409 1546 break; 1410 1547 1548 case 'meta': // METAdata atom 1549 // https://developer.apple.com/library/mac/documentation/QuickTime/QTFF/Metadata/Metadata.html 1550 1551 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1552 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); 1553 $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); 1554 break; 1555 1411 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 1558 // seems to be 2 bytes language code (ASCII), 2 bytes unknown (set to 0x10B5 in sample I have), remainder is useful data 1413 1559 $atom_structure['language'] = substr($atom_data, 4 + 0, 2); 1414 1560 $atom_structure['unknown'] = getid3_lib::BigEndian2Int(substr($atom_data, 4 + 2, 2)); 1415 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 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 1715 $atom_structure['data'] = $atom_data; 1421 1716 break; … … 1441 1736 // terminated by a 32-bit integer set to 0. If you are writing a program 1442 1737 // to read user data atoms, you should allow for the terminating 0. 1738 if (strlen($atom_data) > 12) { 1739 $subatomoffset += 4; 1740 continue; 1741 } 1443 1742 return $atom_structure; 1444 1743 } … … 1754 2053 if (empty($QuicktimeIODSaudioProfileNameLookup)) { 1755 2054 $QuicktimeIODSaudioProfileNameLookup = array( 1756 0x00 => 'ISO Reserved (0x00)',1757 0x01 => 'Main Audio Profile @ Level 1',1758 0x02 => 'Main Audio Profile @ Level 2',1759 0x03 => 'Main Audio Profile @ Level 3',1760 0x04 => 'Main Audio Profile @ Level 4',1761 0x05 => 'Scalable Audio Profile @ Level 1',1762 0x06 => 'Scalable Audio Profile @ Level 2',1763 0x07 => 'Scalable Audio Profile @ Level 3',1764 0x08 => 'Scalable Audio Profile @ Level 4',1765 0x09 => 'Speech Audio Profile @ Level 1',1766 0x0A => 'Speech Audio Profile @ Level 2',1767 0x0B => 'Synthetic Audio Profile @ Level 1',1768 0x0C => 'Synthetic Audio Profile @ Level 2',1769 0x0D => 'Synthetic Audio Profile @ Level 3',1770 0x0E => 'High Quality Audio Profile @ Level 1',1771 0x0F => 'High Quality Audio Profile @ Level 2',1772 0x10 => 'High Quality Audio Profile @ Level 3',1773 0x11 => 'High Quality Audio Profile @ Level 4',1774 0x12 => 'High Quality Audio Profile @ Level 5',1775 0x13 => 'High Quality Audio Profile @ Level 6',1776 0x14 => 'High Quality Audio Profile @ Level 7',1777 0x15 => 'High Quality Audio Profile @ Level 8',1778 0x16 => 'Low Delay Audio Profile @ Level 1',1779 0x17 => 'Low Delay Audio Profile @ Level 2',1780 0x18 => 'Low Delay Audio Profile @ Level 3',1781 0x19 => 'Low Delay Audio Profile @ Level 4',1782 0x1A => 'Low Delay Audio Profile @ Level 5',1783 0x1B => 'Low Delay Audio Profile @ Level 6',1784 0x1C => 'Low Delay Audio Profile @ Level 7',1785 0x1D => 'Low Delay Audio Profile @ Level 8',1786 0x1E => 'Natural Audio Profile @ Level 1',1787 0x1F => 'Natural Audio Profile @ Level 2',1788 0x20 => 'Natural Audio Profile @ Level 3',1789 0x21 => 'Natural Audio Profile @ Level 4',1790 0x22 => 'Mobile Audio Internetworking Profile @ Level 1',1791 0x23 => 'Mobile Audio Internetworking Profile @ Level 2',1792 0x24 => 'Mobile Audio Internetworking Profile @ Level 3',1793 0x25 => 'Mobile Audio Internetworking Profile @ Level 4',1794 0x26 => 'Mobile Audio Internetworking Profile @ Level 5',1795 0x27 => 'Mobile Audio Internetworking Profile @ Level 6',1796 0x28 => 'AAC Profile @ Level 1',1797 0x29 => 'AAC Profile @ Level 2',1798 0x2A => 'AAC Profile @ Level 4',1799 0x2B => 'AAC Profile @ Level 5',1800 0x2C => 'High Efficiency AAC Profile @ Level 2',1801 0x2D => 'High Efficiency AAC Profile @ Level 3',1802 0x2E => 'High Efficiency AAC Profile @ Level 4',1803 0x2F => 'High Efficiency AAC Profile @ Level 5',1804 0xFE => 'Not part of MPEG-4 audio profiles',1805 0xFF => 'No audio capability required',2055 0x00 => 'ISO Reserved (0x00)', 2056 0x01 => 'Main Audio Profile @ Level 1', 2057 0x02 => 'Main Audio Profile @ Level 2', 2058 0x03 => 'Main Audio Profile @ Level 3', 2059 0x04 => 'Main Audio Profile @ Level 4', 2060 0x05 => 'Scalable Audio Profile @ Level 1', 2061 0x06 => 'Scalable Audio Profile @ Level 2', 2062 0x07 => 'Scalable Audio Profile @ Level 3', 2063 0x08 => 'Scalable Audio Profile @ Level 4', 2064 0x09 => 'Speech Audio Profile @ Level 1', 2065 0x0A => 'Speech Audio Profile @ Level 2', 2066 0x0B => 'Synthetic Audio Profile @ Level 1', 2067 0x0C => 'Synthetic Audio Profile @ Level 2', 2068 0x0D => 'Synthetic Audio Profile @ Level 3', 2069 0x0E => 'High Quality Audio Profile @ Level 1', 2070 0x0F => 'High Quality Audio Profile @ Level 2', 2071 0x10 => 'High Quality Audio Profile @ Level 3', 2072 0x11 => 'High Quality Audio Profile @ Level 4', 2073 0x12 => 'High Quality Audio Profile @ Level 5', 2074 0x13 => 'High Quality Audio Profile @ Level 6', 2075 0x14 => 'High Quality Audio Profile @ Level 7', 2076 0x15 => 'High Quality Audio Profile @ Level 8', 2077 0x16 => 'Low Delay Audio Profile @ Level 1', 2078 0x17 => 'Low Delay Audio Profile @ Level 2', 2079 0x18 => 'Low Delay Audio Profile @ Level 3', 2080 0x19 => 'Low Delay Audio Profile @ Level 4', 2081 0x1A => 'Low Delay Audio Profile @ Level 5', 2082 0x1B => 'Low Delay Audio Profile @ Level 6', 2083 0x1C => 'Low Delay Audio Profile @ Level 7', 2084 0x1D => 'Low Delay Audio Profile @ Level 8', 2085 0x1E => 'Natural Audio Profile @ Level 1', 2086 0x1F => 'Natural Audio Profile @ Level 2', 2087 0x20 => 'Natural Audio Profile @ Level 3', 2088 0x21 => 'Natural Audio Profile @ Level 4', 2089 0x22 => 'Mobile Audio Internetworking Profile @ Level 1', 2090 0x23 => 'Mobile Audio Internetworking Profile @ Level 2', 2091 0x24 => 'Mobile Audio Internetworking Profile @ Level 3', 2092 0x25 => 'Mobile Audio Internetworking Profile @ Level 4', 2093 0x26 => 'Mobile Audio Internetworking Profile @ Level 5', 2094 0x27 => 'Mobile Audio Internetworking Profile @ Level 6', 2095 0x28 => 'AAC Profile @ Level 1', 2096 0x29 => 'AAC Profile @ Level 2', 2097 0x2A => 'AAC Profile @ Level 4', 2098 0x2B => 'AAC Profile @ Level 5', 2099 0x2C => 'High Efficiency AAC Profile @ Level 2', 2100 0x2D => 'High Efficiency AAC Profile @ Level 3', 2101 0x2E => 'High Efficiency AAC Profile @ Level 4', 2102 0x2F => 'High Efficiency AAC Profile @ Level 5', 2103 0xFE => 'Not part of MPEG-4 audio profiles', 2104 0xFF => 'No audio capability required', 1806 2105 ); 1807 2106 } … … 2112 2411 static $handyatomtranslatorarray = array(); 2113 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 2423 $handyatomtranslatorarray["\xA9".'cpy'] = 'copyright'; 2115 $handyatomtranslatorarray["\xA9".'day'] = 'creation_date'; // iTunes 4.02424 $handyatomtranslatorarray["\xA9".'day'] = 'creation_date'; // iTunes 4.0 2116 2425 $handyatomtranslatorarray["\xA9".'dir'] = 'director'; 2117 2426 $handyatomtranslatorarray["\xA9".'ed1'] = 'edit1'; … … 2124 2433 $handyatomtranslatorarray["\xA9".'ed8'] = 'edit8'; 2125 2434 $handyatomtranslatorarray["\xA9".'ed9'] = 'edit9'; 2435 $handyatomtranslatorarray["\xA9".'enc'] = 'encoded_by'; 2126 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 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 2446 $handyatomtranslatorarray["\xA9".'prd'] = 'producer'; 2447 $handyatomtranslatorarray["\xA9".'PRD'] = 'product'; 2129 2448 $handyatomtranslatorarray["\xA9".'prf'] = 'performers'; 2130 2449 $handyatomtranslatorarray["\xA9".'req'] = 'system_requirements'; 2131 2450 $handyatomtranslatorarray["\xA9".'src'] = 'source_credit'; 2132 $handyatomtranslatorarray["\xA9".'wrt'] = 'writer'; 2133 2134 // http://www.geocities.com/xhelmboyx/quicktime/formats/qtm-layout.txt 2135 $handyatomtranslatorarray["\xA9".'nam'] = 'title'; // iTunes 4.0 2136 $handyatomtranslatorarray["\xA9".'cmt'] = 'comment'; // iTunes 4.0 2451 $handyatomtranslatorarray["\xA9".'swr'] = 'software'; 2452 $handyatomtranslatorarray["\xA9".'too'] = 'encoding_tool'; // iTunes 4.0 2453 $handyatomtranslatorarray["\xA9".'trk'] = 'track'; 2454 $handyatomtranslatorarray["\xA9".'url'] = 'url'; 2137 2455 $handyatomtranslatorarray["\xA9".'wrn'] = 'warning'; 2138 $handyatomtranslatorarray["\xA9".'hst'] = 'host_computer'; 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 2456 $handyatomtranslatorarray["\xA9".'wrt'] = 'composer'; 2155 2457 $handyatomtranslatorarray['aART'] = 'album_artist'; 2156 $handyatomtranslatorarray['trkn'] = 'track_number'; // iTunes 4.0 2157 $handyatomtranslatorarray['disk'] = 'disc_number'; // iTunes 4.0 2158 $handyatomtranslatorarray['gnre'] = 'genre'; // iTunes 4.0 2159 $handyatomtranslatorarray["\xA9".'too'] = 'encoder'; // iTunes 4.0 2160 $handyatomtranslatorarray['tmpo'] = 'bpm'; // iTunes 4.0 2161 $handyatomtranslatorarray['cprt'] = 'copyright'; // iTunes 4.0? 2162 $handyatomtranslatorarray['cpil'] = 'compilation'; // iTunes 4.0 2163 $handyatomtranslatorarray['covr'] = 'picture'; // iTunes 4.0 2164 $handyatomtranslatorarray['rtng'] = 'rating'; // iTunes 4.0 2165 $handyatomtranslatorarray["\xA9".'grp'] = 'grouping'; // iTunes 4.2 2166 $handyatomtranslatorarray['stik'] = 'stik'; // iTunes 4.9 2167 $handyatomtranslatorarray['pcst'] = 'podcast'; // iTunes 4.9 2168 $handyatomtranslatorarray['catg'] = 'category'; // iTunes 4.9 2169 $handyatomtranslatorarray['keyw'] = 'keyword'; // iTunes 4.9 2170 $handyatomtranslatorarray['purl'] = 'podcast_url'; // iTunes 4.9 2171 $handyatomtranslatorarray['egid'] = 'episode_guid'; // iTunes 4.9 2172 $handyatomtranslatorarray['desc'] = 'description'; // iTunes 5.0 2173 $handyatomtranslatorarray["\xA9".'lyr'] = 'lyrics'; // iTunes 5.0 2174 $handyatomtranslatorarray['tvnn'] = 'tv_network_name'; // iTunes 6.0 2175 $handyatomtranslatorarray['tvsh'] = 'tv_show_name'; // iTunes 6.0 2176 $handyatomtranslatorarray['tvsn'] = 'tv_season'; // iTunes 6.0 2177 $handyatomtranslatorarray['tves'] = 'tv_episode'; // iTunes 6.0 2178 $handyatomtranslatorarray['purd'] = 'purchase_date'; // iTunes 6.0.2 2179 $handyatomtranslatorarray['pgap'] = 'gapless_playback'; // iTunes 7.0 2180 2181 // http://www.geocities.com/xhelmboyx/quicktime/formats/mp4-layout.txt 2182 2183 2458 $handyatomtranslatorarray['apID'] = 'purchase_account'; 2459 $handyatomtranslatorarray['catg'] = 'category'; // iTunes 4.9 2460 $handyatomtranslatorarray['covr'] = 'picture'; // iTunes 4.0 2461 $handyatomtranslatorarray['cpil'] = 'compilation'; // iTunes 4.0 2462 $handyatomtranslatorarray['cprt'] = 'copyright'; // iTunes 4.0? 2463 $handyatomtranslatorarray['desc'] = 'description'; // iTunes 5.0 2464 $handyatomtranslatorarray['disk'] = 'disc_number'; // iTunes 4.0 2465 $handyatomtranslatorarray['egid'] = 'episode_guid'; // iTunes 4.9 2466 $handyatomtranslatorarray['gnre'] = 'genre'; // iTunes 4.0 2467 $handyatomtranslatorarray['hdvd'] = 'hd_video'; // iTunes 4.0 2468 $handyatomtranslatorarray['ldes'] = 'description_long'; // 2469 $handyatomtranslatorarray['keyw'] = 'keyword'; // iTunes 4.9 2470 $handyatomtranslatorarray['pcst'] = 'podcast'; // iTunes 4.9 2471 $handyatomtranslatorarray['pgap'] = 'gapless_playback'; // iTunes 7.0 2472 $handyatomtranslatorarray['purd'] = 'purchase_date'; // iTunes 6.0.2 2473 $handyatomtranslatorarray['purl'] = 'podcast_url'; // iTunes 4.9 2474 $handyatomtranslatorarray['rtng'] = 'rating'; // iTunes 4.0 2475 $handyatomtranslatorarray['soaa'] = 'sort_album_artist'; // 2476 $handyatomtranslatorarray['soal'] = 'sort_album'; // 2477 $handyatomtranslatorarray['soar'] = 'sort_artist'; // 2478 $handyatomtranslatorarray['soco'] = 'sort_composer'; // 2479 $handyatomtranslatorarray['sonm'] = 'sort_title'; // 2480 $handyatomtranslatorarray['sosn'] = 'sort_show'; // 2481 $handyatomtranslatorarray['stik'] = 'stik'; // iTunes 4.9 2482 $handyatomtranslatorarray['tmpo'] = 'bpm'; // iTunes 4.0 2483 $handyatomtranslatorarray['trkn'] = 'track_number'; // iTunes 4.0 2484 $handyatomtranslatorarray['tven'] = 'tv_episode_id'; // 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 2490 // boxnames: … … 2226 2531 } 2227 2532 } 2228 $info['quicktime']['comments'][$comment_key][] = $data; 2533 $gooddata = array($data); 2534 if ($comment_key == 'genre') { 2535 // some other taggers separate multiple genres with semicolon, e.g. "Heavy Metal;Thrash Metal;Metal" 2536 $gooddata = explode(';', $data); 2537 } 2538 foreach ($gooddata as $data) { 2539 $info['quicktime']['comments'][$comment_key][] = $data; 2540 } 2229 2541 } 2230 2542 return true; 2231 2543 } 2544 2545 public function LociString($lstring, &$count) { 2546 // Loci strings are UTF-8 or UTF-16 and null (x00/x0000) terminated. UTF-16 has a BOM 2547 // Also need to return the number of bytes the string occupied so additional fields can be extracted 2548 $len = strlen($lstring); 2549 if ($len == 0) { 2550 $count = 0; 2551 return ''; 2552 } 2553 if ($lstring[0] == "\x00") { 2554 $count = 1; 2555 return ''; 2556 } 2557 //check for BOM 2558 if ($len > 2 && (($lstring[0] == "\xFE" && $lstring[1] == "\xFF") || ($lstring[0] == "\xFF" && $lstring[1] == "\xFE"))) { 2559 //UTF-16 2560 if (preg_match('/(.*)\x00/', $lstring, $lmatches)){ 2561 $count = strlen($lmatches[1]) * 2 + 2; //account for 2 byte characters and trailing \x0000 2562 return getid3_lib::iconv_fallback_utf16_utf8($lmatches[1]); 2563 } else { 2564 return ''; 2565 } 2566 } else { 2567 //UTF-8 2568 if (preg_match('/(.*)\x00/', $lstring, $lmatches)){ 2569 $count = strlen($lmatches[1]) + 1; //account for trailing \x00 2570 return $lmatches[1]; 2571 }else { 2572 return ''; 2573 } 2574 2575 } 2576 } 2232 2577 2233 2578 public function NoNullString($nullterminatedstring) { … … 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 }
Note: See TracChangeset
for help on using the changeset viewer.