- 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 1406 1407 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 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 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.