| [ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 <?php 2 3 ///////////////////////////////////////////////////////////////// 4 /// getID3() by James Heinrich <info@getid3.org> // 5 // available at https://github.com/JamesHeinrich/getID3 // 6 // or https://www.getid3.org // 7 // or http://getid3.sourceforge.net // 8 // see readme.txt for more details // 9 ///////////////////////////////////////////////////////////////// 10 // // 11 // module.audio-video.quicktime.php // 12 // module for analyzing Quicktime and MP3-in-MP4 files // 13 // dependencies: module.audio.mp3.php // 14 // dependencies: module.tag.id3v2.php // 15 // /// 16 ///////////////////////////////////////////////////////////////// 17 18 if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers 19 exit; 20 } 21 getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.mp3.php', __FILE__, true); 22 getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v2.php', __FILE__, true); // needed for ISO 639-2 language code lookup 23 24 class getid3_quicktime extends getid3_handler 25 { 26 27 /** audio-video.quicktime 28 * return all parsed data from all atoms if true, otherwise just returned parsed metadata 29 * 30 * @var bool 31 */ 32 public $ReturnAtomData = false; 33 34 /** audio-video.quicktime 35 * return all parsed data from all atoms if true, otherwise just returned parsed metadata 36 * 37 * @var bool 38 */ 39 public $ParseAllPossibleAtoms = false; 40 41 /** 42 * real ugly, but so is the QuickTime structure that stores keys and values in different multi-nested locations that are hard to relate to each other 43 * https://github.com/JamesHeinrich/getID3/issues/214 44 * 45 * @var int 46 */ 47 private $metaDATAkey = 1; 48 49 /** 50 * @return bool 51 */ 52 public function Analyze() { 53 $info = &$this->getid3->info; 54 55 $this->metaDATAkey = 1; 56 $info['fileformat'] = 'quicktime'; 57 $info['quicktime']['hinting'] = false; 58 $info['quicktime']['controller'] = 'standard'; // may be overridden if 'ctyp' atom is present 59 60 $this->fseek($info['avdataoffset']); 61 62 $offset = 0; 63 $atomcounter = 0; 64 $atom_data_read_buffer_size = $info['php_memory_limit'] ? round($info['php_memory_limit'] / 4) : $this->getid3->option_fread_buffer_size * 1024; // set read buffer to 25% of PHP memory limit (if one is specified), otherwise use option_fread_buffer_size [default: 32MB] 65 while ($offset < $info['avdataend']) { 66 if (!getid3_lib::intValueSupported($offset)) { 67 $this->error('Unable to parse atom at offset '.$offset.' because beyond '.round(PHP_INT_MAX / 1073741824).'GB limit of PHP filesystem functions'); 68 break; 69 } 70 $this->fseek($offset); 71 $AtomHeader = $this->fread(8); 72 73 // https://github.com/JamesHeinrich/getID3/issues/382 74 // Atom sizes are stored as 32-bit number in most cases, but sometimes (notably for "mdat") 75 // a 64-bit value is required, in which case the normal 32-bit size field is set to 0x00000001 76 // and the 64-bit "real" size value is the next 8 bytes. 77 $atom_size_extended_bytes = 0; 78 $atomsize = getid3_lib::BigEndian2Int(substr($AtomHeader, 0, 4)); 79 $atomname = substr($AtomHeader, 4, 4); 80 if ($atomsize == 1) { 81 $atom_size_extended_bytes = 8; 82 $atomsize = getid3_lib::BigEndian2Int($this->fread($atom_size_extended_bytes)); 83 } 84 85 if (($offset + $atomsize) > $info['avdataend']) { 86 $info['quicktime'][$atomname]['name'] = $atomname; 87 $info['quicktime'][$atomname]['size'] = $atomsize; 88 $info['quicktime'][$atomname]['offset'] = $offset; 89 $this->error('Atom at offset '.$offset.' claims to go beyond end-of-file (length: '.$atomsize.' bytes)'); 90 return false; 91 } 92 if ($atomsize == 0) { 93 // Furthermore, for historical reasons the list of atoms is optionally 94 // terminated by a 32-bit integer set to 0. If you are writing a program 95 // to read user data atoms, you should allow for the terminating 0. 96 $info['quicktime'][$atomname]['name'] = $atomname; 97 $info['quicktime'][$atomname]['size'] = $atomsize; 98 $info['quicktime'][$atomname]['offset'] = $offset; 99 break; 100 } 101 $atomHierarchy = array(); 102 $parsedAtomData = $this->QuicktimeParseAtom($atomname, $atomsize, $this->fread(min($atomsize - $atom_size_extended_bytes, $atom_data_read_buffer_size)), $offset, $atomHierarchy, $this->ParseAllPossibleAtoms); 103 $parsedAtomData['name'] = $atomname; 104 $parsedAtomData['size'] = $atomsize; 105 $parsedAtomData['offset'] = $offset; 106 if ($atom_size_extended_bytes) { 107 $parsedAtomData['xsize_bytes'] = $atom_size_extended_bytes; 108 } 109 if (in_array($atomname, array('uuid'))) { 110 @$info['quicktime'][$atomname][] = $parsedAtomData; 111 } else { 112 $info['quicktime'][$atomname] = $parsedAtomData; 113 } 114 115 $offset += $atomsize; 116 $atomcounter++; 117 } 118 119 if (!empty($info['avdataend_tmp'])) { 120 // this value is assigned to a temp value and then erased because 121 // otherwise any atoms beyond the 'mdat' atom would not get parsed 122 $info['avdataend'] = $info['avdataend_tmp']; 123 unset($info['avdataend_tmp']); 124 } 125 126 if (isset($info['quicktime']['comments']['chapters']) && is_array($info['quicktime']['comments']['chapters']) && (count($info['quicktime']['comments']['chapters']) > 0)) { 127 $durations = $this->quicktime_time_to_sample_table($info); 128 for ($i = 0; $i < count($info['quicktime']['comments']['chapters']); $i++) { 129 $bookmark = array(); 130 $bookmark['title'] = $info['quicktime']['comments']['chapters'][$i]; 131 if (isset($durations[$i])) { 132 $bookmark['duration_sample'] = $durations[$i]['sample_duration']; 133 if ($i > 0) { 134 $bookmark['start_sample'] = $info['quicktime']['bookmarks'][($i - 1)]['start_sample'] + $info['quicktime']['bookmarks'][($i - 1)]['duration_sample']; 135 } else { 136 $bookmark['start_sample'] = 0; 137 } 138 if ($time_scale = $this->quicktime_bookmark_time_scale($info)) { 139 $bookmark['duration_seconds'] = $bookmark['duration_sample'] / $time_scale; 140 $bookmark['start_seconds'] = $bookmark['start_sample'] / $time_scale; 141 } 142 } 143 $info['quicktime']['bookmarks'][] = $bookmark; 144 } 145 } 146 147 if (isset($info['quicktime']['temp_meta_key_names'])) { 148 unset($info['quicktime']['temp_meta_key_names']); 149 } 150 151 if (!empty($info['quicktime']['comments']['location.ISO6709'])) { 152 // https://en.wikipedia.org/wiki/ISO_6709 153 foreach ($info['quicktime']['comments']['location.ISO6709'] as $ISO6709string) { 154 $ISO6709parsed = array('latitude'=>false, 'longitude'=>false, 'altitude'=>false); 155 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)) { 156 @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; 157 158 if (strlen($lat_deg) == 2) { // [+-]DD.D 159 $ISO6709parsed['latitude'] = (($lat_sign == '-') ? -1 : 1) * (float) (ltrim($lat_deg, '0').$lat_deg_dec); 160 } elseif (strlen($lat_deg) == 4) { // [+-]DDMM.M 161 $ISO6709parsed['latitude'] = (($lat_sign == '-') ? -1 : 1) * (int) ltrim(substr($lat_deg, 0, 2), '0') + ((float) (ltrim(substr($lat_deg, 2, 2), '0').$lat_deg_dec) / 60); 162 } elseif (strlen($lat_deg) == 6) { // [+-]DDMMSS.S 163 $ISO6709parsed['latitude'] = (($lat_sign == '-') ? -1 : 1) * (int) ltrim(substr($lat_deg, 0, 2), '0') + ((int) ltrim(substr($lat_deg, 2, 2), '0') / 60) + ((float) (ltrim(substr($lat_deg, 4, 2), '0').$lat_deg_dec) / 3600); 164 } 165 166 if (strlen($lon_deg) == 3) { // [+-]DDD.D 167 $ISO6709parsed['longitude'] = (($lon_sign == '-') ? -1 : 1) * (float) (ltrim($lon_deg, '0').$lon_deg_dec); 168 } elseif (strlen($lon_deg) == 5) { // [+-]DDDMM.M 169 $ISO6709parsed['longitude'] = (($lon_sign == '-') ? -1 : 1) * (int) ltrim(substr($lon_deg, 0, 2), '0') + ((float) (ltrim(substr($lon_deg, 2, 2), '0').$lon_deg_dec) / 60); 170 } elseif (strlen($lon_deg) == 7) { // [+-]DDDMMSS.S 171 $ISO6709parsed['longitude'] = (($lon_sign == '-') ? -1 : 1) * (int) ltrim(substr($lon_deg, 0, 2), '0') + ((int) ltrim(substr($lon_deg, 2, 2), '0') / 60) + ((float) (ltrim(substr($lon_deg, 4, 2), '0').$lon_deg_dec) / 3600); 172 } 173 174 if (strlen($alt_deg) == 3) { // [+-]DDD.D 175 $ISO6709parsed['altitude'] = (($alt_sign == '-') ? -1 : 1) * (float) (ltrim($alt_deg, '0').$alt_deg_dec); 176 } elseif (strlen($alt_deg) == 5) { // [+-]DDDMM.M 177 $ISO6709parsed['altitude'] = (($alt_sign == '-') ? -1 : 1) * (int) ltrim(substr($alt_deg, 0, 2), '0') + ((float) (ltrim(substr($alt_deg, 2, 2), '0').$alt_deg_dec) / 60); 178 } elseif (strlen($alt_deg) == 7) { // [+-]DDDMMSS.S 179 $ISO6709parsed['altitude'] = (($alt_sign == '-') ? -1 : 1) * (int) ltrim(substr($alt_deg, 0, 2), '0') + ((int) ltrim(substr($alt_deg, 2, 2), '0') / 60) + ((float) (ltrim(substr($alt_deg, 4, 2), '0').$alt_deg_dec) / 3600); 180 } 181 182 foreach (array('latitude', 'longitude', 'altitude') as $key) { 183 if ($ISO6709parsed[$key] !== false) { 184 $value = (($lat_sign == '-') ? -1 : 1) * floatval($ISO6709parsed[$key]); 185 if (!isset($info['quicktime']['comments']['gps_'.$key]) || !in_array($value, $info['quicktime']['comments']['gps_'.$key])) { 186 @$info['quicktime']['comments']['gps_'.$key][] = (($lat_sign == '-') ? -1 : 1) * floatval($ISO6709parsed[$key]); 187 } 188 } 189 } 190 } 191 if ($ISO6709parsed['latitude'] === false) { 192 $this->warning('location.ISO6709 string not parsed correctly: "'.$ISO6709string.'", please submit as a bug'); 193 } 194 break; 195 } 196 } 197 198 if (!isset($info['bitrate']) && !empty($info['playtime_seconds'])) { 199 $info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds']; 200 } 201 if (isset($info['bitrate']) && !isset($info['audio']['bitrate']) && !isset($info['quicktime']['video'])) { 202 $info['audio']['bitrate'] = $info['bitrate']; 203 } 204 if (!empty($info['bitrate']) && !empty($info['audio']['bitrate']) && empty($info['video']['bitrate']) && !empty($info['video']['frame_rate']) && !empty($info['video']['resolution_x']) && ($info['bitrate'] > $info['audio']['bitrate'])) { 205 $info['video']['bitrate'] = $info['bitrate'] - $info['audio']['bitrate']; 206 } 207 if (!empty($info['playtime_seconds']) && !isset($info['video']['frame_rate']) && !empty($info['quicktime']['stts_framecount'])) { 208 foreach ($info['quicktime']['stts_framecount'] as $key => $samples_count) { 209 $samples_per_second = $samples_count / $info['playtime_seconds']; 210 if ($samples_per_second > 240) { 211 // has to be audio samples 212 } else { 213 $info['video']['frame_rate'] = $samples_per_second; 214 break; 215 } 216 } 217 } 218 if ($info['audio']['dataformat'] == 'mp4') { 219 $info['fileformat'] = 'mp4'; 220 if (empty($info['video']['resolution_x'])) { 221 $info['mime_type'] = 'audio/mp4'; 222 unset($info['video']['dataformat']); 223 } else { 224 $info['mime_type'] = 'video/mp4'; 225 } 226 } 227 if (!empty($info['quicktime']['ftyp']['signature']) && in_array($info['quicktime']['ftyp']['signature'], array('heic','heix','hevc','hevx','heim','heis','hevm','hevs'))) { 228 if ($info['mime_type'] == 'video/quicktime') { // default value, as we 229 // https://en.wikipedia.org/wiki/High_Efficiency_Image_File_Format 230 $this->error('HEIF files not currently supported'); 231 switch ($info['quicktime']['ftyp']['signature']) { 232 // https://github.com/strukturag/libheif/issues/83 (comment by Dirk Farin 2018-09-14) 233 case 'heic': // the usual HEIF images 234 case 'heix': // 10bit images, or anything that uses h265 with range extension 235 case 'hevc': // brands for image sequences 236 case 'hevx': // brands for image sequences 237 case 'heim': // multiview 238 case 'heis': // scalable 239 case 'hevm': // multiview sequence 240 case 'hevs': // scalable sequence 241 $info['fileformat'] = 'heif'; 242 $info['mime_type'] = 'image/heif'; 243 break; 244 } 245 } 246 } 247 248 if (!$this->ReturnAtomData) { 249 unset($info['quicktime']['moov']); 250 } 251 252 if (empty($info['audio']['dataformat']) && !empty($info['quicktime']['audio'])) { 253 $info['audio']['dataformat'] = 'quicktime'; 254 } 255 if (empty($info['video']['dataformat']) && !empty($info['quicktime']['video'])) { 256 $info['video']['dataformat'] = 'quicktime'; 257 } 258 if (isset($info['video']) && ($info['mime_type'] == 'audio/mp4') && empty($info['video']['resolution_x']) && empty($info['video']['resolution_y'])) { 259 unset($info['video']); 260 } 261 262 return true; 263 } 264 265 /** 266 * @param string $atomname 267 * @param int $atomsize 268 * @param string $atom_data 269 * @param int $baseoffset 270 * @param array $atomHierarchy 271 * @param bool $ParseAllPossibleAtoms 272 * 273 * @return array|false 274 */ 275 public function QuicktimeParseAtom($atomname, $atomsize, $atom_data, $baseoffset, &$atomHierarchy, $ParseAllPossibleAtoms) { 276 // http://developer.apple.com/techpubs/quicktime/qtdevdocs/APIREF/INDEX/atomalphaindex.htm 277 // https://code.google.com/p/mp4v2/wiki/iTunesMetadata 278 279 $info = &$this->getid3->info; 280 281 $atom_parent = end($atomHierarchy); // not array_pop($atomHierarchy); see https://www.getid3.org/phpBB3/viewtopic.php?t=1717 282 array_push($atomHierarchy, $atomname); 283 $atom_structure = array(); 284 $atom_structure['hierarchy'] = implode(' ', $atomHierarchy); 285 $atom_structure['name'] = $atomname; 286 $atom_structure['size'] = $atomsize; 287 $atom_structure['offset'] = $baseoffset; 288 if (substr($atomname, 0, 3) == "\x00\x00\x00") { 289 // https://github.com/JamesHeinrich/getID3/issues/139 290 $atomname = getid3_lib::BigEndian2Int($atomname); 291 $atom_structure['name'] = $atomname; 292 $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); 293 } else { 294 switch ($atomname) { 295 case 'moov': // MOVie container atom 296 case 'moof': // MOvie Fragment box 297 case 'trak': // TRAcK container atom 298 case 'traf': // TRAck Fragment box 299 case 'clip': // CLIPping container atom 300 case 'matt': // track MATTe container atom 301 case 'edts': // EDiTS container atom 302 case 'tref': // Track REFerence container atom 303 case 'mdia': // MeDIA container atom 304 case 'minf': // Media INFormation container atom 305 case 'dinf': // Data INFormation container atom 306 case 'nmhd': // Null Media HeaDer container atom 307 case 'udta': // User DaTA container atom 308 case 'cmov': // Compressed MOVie container atom 309 case 'rmra': // Reference Movie Record Atom 310 case 'rmda': // Reference Movie Descriptor Atom 311 case 'gmhd': // Generic Media info HeaDer atom (seen on QTVR) 312 $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); 313 break; 314 315 case 'ilst': // Item LiST container atom 316 if ($atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms)) { 317 // some "ilst" atoms contain data atoms that have a numeric name, and the data is far more accessible if the returned array is compacted 318 $allnumericnames = true; 319 foreach ($atom_structure['subatoms'] as $subatomarray) { 320 if (!is_integer($subatomarray['name']) || (count($subatomarray['subatoms']) != 1)) { 321 $allnumericnames = false; 322 break; 323 } 324 } 325 if ($allnumericnames) { 326 $newData = array(); 327 foreach ($atom_structure['subatoms'] as $subatomarray) { 328 foreach ($subatomarray['subatoms'] as $newData_subatomarray) { 329 unset($newData_subatomarray['hierarchy'], $newData_subatomarray['name']); 330 $newData[$subatomarray['name']] = $newData_subatomarray; 331 break; 332 } 333 } 334 $atom_structure['data'] = $newData; 335 unset($atom_structure['subatoms']); 336 } 337 } 338 break; 339 340 case 'stbl': // Sample TaBLe container atom 341 $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); 342 $isVideo = false; 343 $framerate = 0; 344 $framecount = 0; 345 foreach ($atom_structure['subatoms'] as $key => $value_array) { 346 if (isset($value_array['sample_description_table'])) { 347 foreach ($value_array['sample_description_table'] as $key2 => $value_array2) { 348 if (isset($value_array2['data_format'])) { 349 switch ($value_array2['data_format']) { 350 case 'avc1': 351 case 'mp4v': 352 // video data 353 $isVideo = true; 354 break; 355 case 'mp4a': 356 // audio data 357 break; 358 } 359 } 360 } 361 } elseif (isset($value_array['time_to_sample_table'])) { 362 foreach ($value_array['time_to_sample_table'] as $key2 => $value_array2) { 363 if (isset($value_array2['sample_count']) && isset($value_array2['sample_duration']) && ($value_array2['sample_duration'] > 0) && !empty($info['quicktime']['time_scale'])) { 364 $framerate = round($info['quicktime']['time_scale'] / $value_array2['sample_duration'], 3); 365 $framecount = $value_array2['sample_count']; 366 } 367 } 368 } 369 } 370 if ($isVideo && $framerate) { 371 $info['quicktime']['video']['frame_rate'] = $framerate; 372 $info['video']['frame_rate'] = $info['quicktime']['video']['frame_rate']; 373 } 374 if ($isVideo && $framecount) { 375 $info['quicktime']['video']['frame_count'] = $framecount; 376 } 377 break; 378 379 380 case "\xA9".'alb': // ALBum 381 case "\xA9".'ART': // 382 case "\xA9".'art': // ARTist 383 case "\xA9".'aut': // 384 case "\xA9".'cmt': // CoMmenT 385 case "\xA9".'com': // COMposer 386 case "\xA9".'cpy': // 387 case "\xA9".'day': // content created year 388 case "\xA9".'dir': // 389 case "\xA9".'ed1': // 390 case "\xA9".'ed2': // 391 case "\xA9".'ed3': // 392 case "\xA9".'ed4': // 393 case "\xA9".'ed5': // 394 case "\xA9".'ed6': // 395 case "\xA9".'ed7': // 396 case "\xA9".'ed8': // 397 case "\xA9".'ed9': // 398 case "\xA9".'enc': // 399 case "\xA9".'fmt': // 400 case "\xA9".'gen': // GENre 401 case "\xA9".'grp': // GRouPing 402 case "\xA9".'hst': // 403 case "\xA9".'inf': // 404 case "\xA9".'lyr': // LYRics 405 case "\xA9".'mak': // 406 case "\xA9".'mod': // 407 case "\xA9".'nam': // full NAMe 408 case "\xA9".'ope': // 409 case "\xA9".'PRD': // 410 case "\xA9".'prf': // 411 case "\xA9".'req': // 412 case "\xA9".'src': // 413 case "\xA9".'swr': // 414 case "\xA9".'too': // encoder 415 case "\xA9".'trk': // TRacK 416 case "\xA9".'url': // 417 case "\xA9".'wrn': // 418 case "\xA9".'wrt': // WRiTer 419 case '----': // itunes specific 420 case 'aART': // Album ARTist 421 case 'akID': // iTunes store account type 422 case 'apID': // Purchase Account 423 case 'atID': // 424 case 'catg': // CaTeGory 425 case 'cmID': // 426 case 'cnID': // 427 case 'covr': // COVeR artwork 428 case 'cpil': // ComPILation 429 case 'cprt': // CoPyRighT 430 case 'desc': // DESCription 431 case 'disk': // DISK number 432 case 'egid': // Episode Global ID 433 case 'geID': // 434 case 'gnre': // GeNRE 435 case 'hdvd': // HD ViDeo 436 case 'keyw': // KEYWord 437 case 'ldes': // Long DEScription 438 case 'pcst': // PodCaST 439 case 'pgap': // GAPless Playback 440 case 'plID': // 441 case 'purd': // PURchase Date 442 case 'purl': // Podcast URL 443 case 'rati': // 444 case 'rndu': // 445 case 'rpdu': // 446 case 'rtng': // RaTiNG 447 case 'sfID': // iTunes store country 448 case 'soaa': // SOrt Album Artist 449 case 'soal': // SOrt ALbum 450 case 'soar': // SOrt ARtist 451 case 'soco': // SOrt COmposer 452 case 'sonm': // SOrt NaMe 453 case 'sosn': // SOrt Show Name 454 case 'stik': // 455 case 'tmpo': // TeMPO (BPM) 456 case 'trkn': // TRacK Number 457 case 'tven': // tvEpisodeID 458 case 'tves': // TV EpiSode 459 case 'tvnn': // TV Network Name 460 case 'tvsh': // TV SHow Name 461 case 'tvsn': // TV SeasoN 462 if ($atom_parent == 'udta') { 463 // User data atom handler 464 $atom_structure['data_length'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2)); 465 $atom_structure['language_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 2)); 466 $atom_structure['data'] = substr($atom_data, 4); 467 468 $atom_structure['language'] = $this->QuicktimeLanguageLookup($atom_structure['language_id']); 469 if (empty($info['comments']['language']) || (!in_array($atom_structure['language'], $info['comments']['language']))) { 470 $info['comments']['language'][] = $atom_structure['language']; 471 } 472 } else { 473 // Apple item list box atom handler 474 $atomoffset = 0; 475 if (substr($atom_data, 2, 2) == "\x10\xB5") { 476 // not sure what it means, but observed on iPhone4 data. 477 // Each $atom_data has 2 bytes of datasize, plus 0x10B5, then data 478 while ($atomoffset < strlen($atom_data)) { 479 $boxsmallsize = getid3_lib::BigEndian2Int(substr($atom_data, $atomoffset, 2)); 480 $boxsmalltype = substr($atom_data, $atomoffset + 2, 2); 481 $boxsmalldata = substr($atom_data, $atomoffset + 4, $boxsmallsize); 482 if ($boxsmallsize <= 1) { 483 $this->warning('Invalid QuickTime atom smallbox size "'.$boxsmallsize.'" in atom "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $atomname).'" at offset: '.($atom_structure['offset'] + $atomoffset)); 484 $atom_structure['data'] = null; 485 $atomoffset = strlen($atom_data); 486 break; 487 } 488 switch ($boxsmalltype) { 489 case "\x10\xB5": 490 $atom_structure['data'] = $boxsmalldata; 491 break; 492 default: 493 $this->warning('Unknown QuickTime smallbox type: "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $boxsmalltype).'" ('.trim(getid3_lib::PrintHexBytes($boxsmalltype)).') at offset '.$baseoffset); 494 $atom_structure['data'] = $atom_data; 495 break; 496 } 497 $atomoffset += (4 + $boxsmallsize); 498 } 499 } else { 500 while ($atomoffset < strlen($atom_data)) { 501 $boxsize = getid3_lib::BigEndian2Int(substr($atom_data, $atomoffset, 4)); 502 $boxtype = substr($atom_data, $atomoffset + 4, 4); 503 $boxdata = substr($atom_data, $atomoffset + 8, $boxsize - 8); 504 if ($boxsize <= 1) { 505 $this->warning('Invalid QuickTime atom box size "'.$boxsize.'" in atom "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $atomname).'" at offset: '.($atom_structure['offset'] + $atomoffset)); 506 $atom_structure['data'] = null; 507 $atomoffset = strlen($atom_data); 508 break; 509 } 510 $atomoffset += $boxsize; 511 512 switch ($boxtype) { 513 case 'mean': 514 case 'name': 515 $atom_structure[$boxtype] = substr($boxdata, 4); 516 break; 517 518 case 'data': 519 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($boxdata, 0, 1)); 520 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($boxdata, 1, 3)); 521 switch ($atom_structure['flags_raw']) { 522 case 0: // data flag 523 case 21: // tmpo/cpil flag 524 switch ($atomname) { 525 case 'cpil': 526 case 'hdvd': 527 case 'pcst': 528 case 'pgap': 529 // 8-bit integer (boolean) 530 $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1)); 531 break; 532 533 case 'tmpo': 534 // 16-bit integer 535 $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 2)); 536 break; 537 538 case 'disk': 539 case 'trkn': 540 // binary 541 $num = getid3_lib::BigEndian2Int(substr($boxdata, 10, 2)); 542 $num_total = getid3_lib::BigEndian2Int(substr($boxdata, 12, 2)); 543 $atom_structure['data'] = empty($num) ? '' : $num; 544 $atom_structure['data'] .= empty($num_total) ? '' : '/'.$num_total; 545 break; 546 547 case 'gnre': 548 // enum 549 $GenreID = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4)); 550 $atom_structure['data'] = getid3_id3v1::LookupGenreName($GenreID - 1); 551 break; 552 553 case 'rtng': 554 // 8-bit integer 555 $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1)); 556 $atom_structure['data'] = $this->QuicktimeContentRatingLookup($atom_structure[$atomname]); 557 break; 558 559 case 'stik': 560 // 8-bit integer (enum) 561 $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1)); 562 $atom_structure['data'] = $this->QuicktimeSTIKLookup($atom_structure[$atomname]); 563 break; 564 565 case 'sfID': 566 // 32-bit integer 567 $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4)); 568 $atom_structure['data'] = $this->QuicktimeStoreFrontCodeLookup($atom_structure[$atomname]); 569 break; 570 571 case 'egid': 572 case 'purl': 573 $atom_structure['data'] = substr($boxdata, 8); 574 break; 575 576 case 'plID': 577 // 64-bit integer 578 $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 8)); 579 break; 580 581 case 'covr': 582 $atom_structure['data'] = substr($boxdata, 8); 583 // not a foolproof check, but better than nothing 584 if (preg_match('#^\\xFF\\xD8\\xFF#', $atom_structure['data'])) { 585 $atom_structure['image_mime'] = 'image/jpeg'; 586 } elseif (preg_match('#^\\x89\\x50\\x4E\\x47\\x0D\\x0A\\x1A\\x0A#', $atom_structure['data'])) { 587 $atom_structure['image_mime'] = 'image/png'; 588 } elseif (preg_match('#^GIF#', $atom_structure['data'])) { 589 $atom_structure['image_mime'] = 'image/gif'; 590 } 591 $info['quicktime']['comments']['picture'][] = array('image_mime'=>$atom_structure['image_mime'], 'data'=>$atom_structure['data'], 'description'=>'cover'); 592 break; 593 594 case 'atID': 595 case 'cnID': 596 case 'geID': 597 case 'tves': 598 case 'tvsn': 599 default: 600 // 32-bit integer 601 $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4)); 602 } 603 break; 604 605 case 1: // text flag 606 case 13: // image flag 607 default: 608 $atom_structure['data'] = substr($boxdata, 8); 609 if ($atomname == 'covr') { 610 if (!empty($atom_structure['data'])) { 611 $atom_structure['image_mime'] = 'image/unknown'; // provide default MIME type to ensure array keys exist 612 if (function_exists('getimagesizefromstring') && ($getimagesize = getimagesizefromstring($atom_structure['data'])) && !empty($getimagesize['mime'])) { 613 $atom_structure['image_mime'] = $getimagesize['mime']; 614 } else { 615 // if getimagesizefromstring is not available, or fails for some reason, fall back to simple detection of common image formats 616 $ImageFormatSignatures = array( 617 'image/jpeg' => "\xFF\xD8\xFF", 618 'image/png' => "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A", 619 'image/gif' => 'GIF', 620 ); 621 foreach ($ImageFormatSignatures as $mime => $image_format_signature) { 622 if (substr($atom_structure['data'], 0, strlen($image_format_signature)) == $image_format_signature) { 623 $atom_structure['image_mime'] = $mime; 624 break; 625 } 626 } 627 } 628 $info['quicktime']['comments']['picture'][] = array('image_mime'=>$atom_structure['image_mime'], 'data'=>$atom_structure['data'], 'description'=>'cover'); 629 } else { 630 $this->warning('Unknown empty "covr" image at offset '.$baseoffset); 631 } 632 } 633 break; 634 635 } 636 break; 637 638 default: 639 $this->warning('Unknown QuickTime box type: "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $boxtype).'" ('.trim(getid3_lib::PrintHexBytes($boxtype)).') at offset '.$baseoffset); 640 $atom_structure['data'] = $atom_data; 641 642 } 643 } 644 } 645 } 646 $this->CopyToAppropriateCommentsSection($atomname, $atom_structure['data'], $atom_structure['name']); 647 break; 648 649 650 case 'play': // auto-PLAY atom 651 $atom_structure['autoplay'] = (bool) getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 652 653 $info['quicktime']['autoplay'] = $atom_structure['autoplay']; 654 break; 655 656 657 case 'WLOC': // Window LOCation atom 658 $atom_structure['location_x'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2)); 659 $atom_structure['location_y'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 2)); 660 break; 661 662 663 case 'LOOP': // LOOPing atom 664 case 'SelO': // play SELection Only atom 665 case 'AllF': // play ALL Frames atom 666 $atom_structure['data'] = getid3_lib::BigEndian2Int($atom_data); 667 break; 668 669 670 case 'name': // 671 case 'MCPS': // Media Cleaner PRo 672 case '@PRM': // adobe PReMiere version 673 case '@PRQ': // adobe PRemiere Quicktime version 674 $atom_structure['data'] = $atom_data; 675 break; 676 677 678 case 'cmvd': // Compressed MooV Data atom 679 // Code by ubergeekØubergeek*tv based on information from 680 // http://developer.apple.com/quicktime/icefloe/dispatch012.html 681 $atom_structure['unCompressedSize'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); 682 683 $CompressedFileData = substr($atom_data, 4); 684 if ($UncompressedHeader = @gzuncompress($CompressedFileData)) { 685 $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($UncompressedHeader, 0, $atomHierarchy, $ParseAllPossibleAtoms); 686 } else { 687 $this->warning('Error decompressing compressed MOV atom at offset '.$atom_structure['offset']); 688 } 689 break; 690 691 692 case 'dcom': // Data COMpression atom 693 $atom_structure['compression_id'] = $atom_data; 694 $atom_structure['compression_text'] = $this->QuicktimeDCOMLookup($atom_data); 695 break; 696 697 698 case 'rdrf': // Reference movie Data ReFerence atom 699 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 700 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); 701 $atom_structure['flags']['internal_data'] = (bool) ($atom_structure['flags_raw'] & 0x000001); 702 703 $atom_structure['reference_type_name'] = substr($atom_data, 4, 4); 704 $atom_structure['reference_length'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); 705 switch ($atom_structure['reference_type_name']) { 706 case 'url ': 707 $atom_structure['url'] = $this->NoNullString(substr($atom_data, 12)); 708 break; 709 710 case 'alis': 711 $atom_structure['file_alias'] = substr($atom_data, 12); 712 break; 713 714 case 'rsrc': 715 $atom_structure['resource_alias'] = substr($atom_data, 12); 716 break; 717 718 default: 719 $atom_structure['data'] = substr($atom_data, 12); 720 break; 721 } 722 break; 723 724 725 case 'rmqu': // Reference Movie QUality atom 726 $atom_structure['movie_quality'] = getid3_lib::BigEndian2Int($atom_data); 727 break; 728 729 730 case 'rmcs': // Reference Movie Cpu Speed atom 731 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 732 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 733 $atom_structure['cpu_speed_rating'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); 734 break; 735 736 737 case 'rmvc': // Reference Movie Version Check atom 738 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 739 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 740 $atom_structure['gestalt_selector'] = substr($atom_data, 4, 4); 741 $atom_structure['gestalt_value_mask'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); 742 $atom_structure['gestalt_value'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); 743 $atom_structure['gestalt_check_type'] = getid3_lib::BigEndian2Int(substr($atom_data, 14, 2)); 744 break; 745 746 747 case 'rmcd': // Reference Movie Component check atom 748 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 749 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 750 $atom_structure['component_type'] = substr($atom_data, 4, 4); 751 $atom_structure['component_subtype'] = substr($atom_data, 8, 4); 752 $atom_structure['component_manufacturer'] = substr($atom_data, 12, 4); 753 $atom_structure['component_flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); 754 $atom_structure['component_flags_mask'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 4)); 755 $atom_structure['component_min_version'] = getid3_lib::BigEndian2Int(substr($atom_data, 24, 4)); 756 break; 757 758 759 case 'rmdr': // Reference Movie Data Rate atom 760 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 761 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 762 $atom_structure['data_rate'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 763 764 $atom_structure['data_rate_bps'] = $atom_structure['data_rate'] * 10; 765 break; 766 767 768 case 'rmla': // Reference Movie Language Atom 769 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 770 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 771 $atom_structure['language_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); 772 773 $atom_structure['language'] = $this->QuicktimeLanguageLookup($atom_structure['language_id']); 774 if (empty($info['comments']['language']) || (!in_array($atom_structure['language'], $info['comments']['language']))) { 775 $info['comments']['language'][] = $atom_structure['language']; 776 } 777 break; 778 779 780 case 'ptv ': // Print To Video - defines a movie's full screen mode 781 // http://developer.apple.com/documentation/QuickTime/APIREF/SOURCESIV/at_ptv-_pg.htm 782 $atom_structure['display_size_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2)); 783 $atom_structure['reserved_1'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 2)); // hardcoded: 0x0000 784 $atom_structure['reserved_2'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); // hardcoded: 0x0000 785 $atom_structure['slide_show_flag'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 1)); 786 $atom_structure['play_on_open_flag'] = getid3_lib::BigEndian2Int(substr($atom_data, 7, 1)); 787 788 $atom_structure['flags']['play_on_open'] = (bool) $atom_structure['play_on_open_flag']; 789 $atom_structure['flags']['slide_show'] = (bool) $atom_structure['slide_show_flag']; 790 791 $ptv_lookup = array( 792 0 => 'normal', 793 1 => 'double', 794 2 => 'half', 795 3 => 'full', 796 4 => 'current' 797 ); 798 if (isset($ptv_lookup[$atom_structure['display_size_raw']])) { 799 $atom_structure['display_size'] = $ptv_lookup[$atom_structure['display_size_raw']]; 800 } else { 801 $this->warning('unknown "ptv " display constant ('.$atom_structure['display_size_raw'].')'); 802 } 803 break; 804 805 806 case 'stsd': // Sample Table Sample Description atom 807 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); // hardcoded: 0x00 808 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x000000 809 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 810 811 // see: https://github.com/JamesHeinrich/getID3/issues/111 812 // Some corrupt files have been known to have high bits set in the number_entries field 813 // This field shouldn't really need to be 32-bits, values stores are likely in the range 1-100000 814 // Workaround: mask off the upper byte and throw a warning if it's nonzero 815 if ($atom_structure['number_entries'] > 0x000FFFFF) { 816 if ($atom_structure['number_entries'] > 0x00FFFFFF) { 817 $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)); 818 $atom_structure['number_entries'] = ($atom_structure['number_entries'] & 0x00FFFFFF); 819 } else { 820 $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'); 821 } 822 } 823 824 $stsdEntriesDataOffset = 8; 825 for ($i = 0; $i < (int) $atom_structure['number_entries']; $i++) { 826 $atom_structure['sample_description_table'][$i]['size'] = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 4)); 827 $stsdEntriesDataOffset += 4; 828 $atom_structure['sample_description_table'][$i]['data_format'] = substr($atom_data, $stsdEntriesDataOffset, 4); 829 $stsdEntriesDataOffset += 4; 830 $atom_structure['sample_description_table'][$i]['reserved'] = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 6)); 831 $stsdEntriesDataOffset += 6; 832 $atom_structure['sample_description_table'][$i]['reference_index'] = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 2)); 833 $stsdEntriesDataOffset += 2; 834 $atom_structure['sample_description_table'][$i]['data'] = substr($atom_data, $stsdEntriesDataOffset, ($atom_structure['sample_description_table'][$i]['size'] - 4 - 4 - 6 - 2)); 835 $stsdEntriesDataOffset += ($atom_structure['sample_description_table'][$i]['size'] - 4 - 4 - 6 - 2); 836 if (substr($atom_structure['sample_description_table'][$i]['data'], 1, 54) == 'application/octet-stream;type=com.parrot.videometadata') { 837 // special handling for apparently-malformed (TextMetaDataSampleEntry?) data for some version of Parrot drones 838 $atom_structure['sample_description_table'][$i]['parrot_frame_metadata']['mime_type'] = substr($atom_structure['sample_description_table'][$i]['data'], 1, 55); 839 $atom_structure['sample_description_table'][$i]['parrot_frame_metadata']['metadata_version'] = (int) substr($atom_structure['sample_description_table'][$i]['data'], 55, 1); 840 unset($atom_structure['sample_description_table'][$i]['data']); 841 $this->warning('incomplete/incorrect handling of "stsd" with Parrot metadata in this version of getID3() ['.$this->getid3->version().']'); 842 continue; 843 } 844 845 $atom_structure['sample_description_table'][$i]['encoder_version'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 0, 2)); 846 $atom_structure['sample_description_table'][$i]['encoder_revision'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 2, 2)); 847 $atom_structure['sample_description_table'][$i]['encoder_vendor'] = substr($atom_structure['sample_description_table'][$i]['data'], 4, 4); 848 849 switch ($atom_structure['sample_description_table'][$i]['encoder_vendor']) { 850 851 case "\x00\x00\x00\x00": 852 // audio tracks 853 $atom_structure['sample_description_table'][$i]['audio_channels'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 8, 2)); 854 $atom_structure['sample_description_table'][$i]['audio_bit_depth'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 10, 2)); 855 $atom_structure['sample_description_table'][$i]['audio_compression_id'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 12, 2)); 856 $atom_structure['sample_description_table'][$i]['audio_packet_size'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 14, 2)); 857 $atom_structure['sample_description_table'][$i]['audio_sample_rate'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 16, 4)); 858 859 // video tracks 860 // http://developer.apple.com/library/mac/#documentation/QuickTime/QTFF/QTFFChap3/qtff3.html 861 // https://developer.apple.com/documentation/quicktime-file-format 862 $STSDvOffset = 8; 863 $atom_structure['sample_description_table'][$i]['temporal_quality'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], $STSDvOffset, 4)); $STSDvOffset += 4; 864 $atom_structure['sample_description_table'][$i]['spatial_quality'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], $STSDvOffset, 4)); $STSDvOffset += 4; 865 $atom_structure['sample_description_table'][$i]['width'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], $STSDvOffset, 2)); $STSDvOffset += 2; 866 $atom_structure['sample_description_table'][$i]['height'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], $STSDvOffset, 2)); $STSDvOffset += 2; 867 $atom_structure['sample_description_table'][$i]['resolution_x'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], $STSDvOffset, 4)); $STSDvOffset += 4; 868 $atom_structure['sample_description_table'][$i]['resolution_y'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], $STSDvOffset, 4)); $STSDvOffset += 4; 869 $atom_structure['sample_description_table'][$i]['data_size'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], $STSDvOffset, 4)); $STSDvOffset += 4; 870 $atom_structure['sample_description_table'][$i]['frame_count'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], $STSDvOffset, 2)); $STSDvOffset += 2; 871 $atom_structure['sample_description_table'][$i]['compressor_name'] = substr($atom_structure['sample_description_table'][$i]['data'], $STSDvOffset, 32) ; $STSDvOffset += 32; 872 $atom_structure['sample_description_table'][$i]['compressor_name'] = $this->MaybePascal2String(rtrim($atom_structure['sample_description_table'][$i]['compressor_name'], "\x00")); // https://github.com/JamesHeinrich/getID3/issues/452 873 $atom_structure['sample_description_table'][$i]['pixel_depth'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], $STSDvOffset, 2)); $STSDvOffset += 2; 874 $atom_structure['sample_description_table'][$i]['color_table_id'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], $STSDvOffset, 2)); $STSDvOffset += 2; 875 876 switch ($atom_structure['sample_description_table'][$i]['data_format']) { 877 case '2vuY': 878 case 'avc1': 879 case 'cvid': 880 case 'dvc ': 881 case 'dvcp': 882 case 'gif ': 883 case 'h263': 884 case 'hvc1': 885 case 'jpeg': 886 case 'kpcd': 887 case 'mjpa': 888 case 'mjpb': 889 case 'mp4v': 890 case 'png ': 891 case 'raw ': 892 case 'rle ': 893 case 'rpza': 894 case 'smc ': 895 case 'SVQ1': 896 case 'SVQ3': 897 case 'tiff': 898 case 'v210': 899 case 'v216': 900 case 'v308': 901 case 'v408': 902 case 'v410': 903 case 'yuv2': 904 $info['fileformat'] = 'mp4'; 905 $info['video']['fourcc'] = $atom_structure['sample_description_table'][$i]['data_format']; 906 if ($this->QuicktimeVideoCodecLookup($info['video']['fourcc'])) { 907 $info['video']['fourcc_lookup'] = $this->QuicktimeVideoCodecLookup($info['video']['fourcc']); 908 } 909 910 // https://www.getid3.org/phpBB3/viewtopic.php?t=1550 911 //if ((!empty($atom_structure['sample_description_table'][$i]['width']) && !empty($atom_structure['sample_description_table'][$i]['width'])) && (empty($info['video']['resolution_x']) || empty($info['video']['resolution_y']) || (number_format($info['video']['resolution_x'], 6) != number_format(round($info['video']['resolution_x']), 6)) || (number_format($info['video']['resolution_y'], 6) != number_format(round($info['video']['resolution_y']), 6)))) { // ugly check for floating point numbers 912 if (!empty($atom_structure['sample_description_table'][$i]['width']) && !empty($atom_structure['sample_description_table'][$i]['height'])) { 913 // assume that values stored here are more important than values stored in [tkhd] atom 914 $info['video']['resolution_x'] = $atom_structure['sample_description_table'][$i]['width']; 915 $info['video']['resolution_y'] = $atom_structure['sample_description_table'][$i]['height']; 916 $info['quicktime']['video']['resolution_x'] = $info['video']['resolution_x']; 917 $info['quicktime']['video']['resolution_y'] = $info['video']['resolution_y']; 918 } 919 break; 920 921 case 'qtvr': 922 $info['video']['dataformat'] = 'quicktimevr'; 923 break; 924 925 case 'mp4a': 926 $atom_structure['sample_description_table'][$i]['subatoms'] = $this->QuicktimeParseContainerAtom(substr($atom_structure['sample_description_table'][$i]['data'], 20), $baseoffset + $stsdEntriesDataOffset - 20 - 16, $atomHierarchy, $ParseAllPossibleAtoms); 927 928 $info['quicktime']['audio']['codec'] = $this->QuicktimeAudioCodecLookup($atom_structure['sample_description_table'][$i]['data_format']); 929 $info['quicktime']['audio']['sample_rate'] = $atom_structure['sample_description_table'][$i]['audio_sample_rate']; 930 $info['quicktime']['audio']['channels'] = $atom_structure['sample_description_table'][$i]['audio_channels']; 931 $info['quicktime']['audio']['bit_depth'] = $atom_structure['sample_description_table'][$i]['audio_bit_depth']; 932 $info['audio']['codec'] = $info['quicktime']['audio']['codec']; 933 $info['audio']['sample_rate'] = $info['quicktime']['audio']['sample_rate']; 934 $info['audio']['channels'] = $info['quicktime']['audio']['channels']; 935 $info['audio']['bits_per_sample'] = $info['quicktime']['audio']['bit_depth']; 936 switch ($atom_structure['sample_description_table'][$i]['data_format']) { 937 case 'raw ': // PCM 938 case 'alac': // Apple Lossless Audio Codec 939 case 'sowt': // signed/two's complement (Little Endian) 940 case 'twos': // signed/two's complement (Big Endian) 941 case 'in24': // 24-bit Integer 942 case 'in32': // 32-bit Integer 943 case 'fl32': // 32-bit Floating Point 944 case 'fl64': // 64-bit Floating Point 945 $info['audio']['lossless'] = $info['quicktime']['audio']['lossless'] = true; 946 $info['audio']['bitrate'] = $info['quicktime']['audio']['bitrate'] = $info['audio']['channels'] * $info['audio']['bits_per_sample'] * $info['audio']['sample_rate']; 947 break; 948 default: 949 $info['audio']['lossless'] = false; 950 break; 951 } 952 break; 953 954 default: 955 break; 956 } 957 break; 958 959 default: 960 switch ($atom_structure['sample_description_table'][$i]['data_format']) { 961 case 'mp4s': 962 $info['fileformat'] = 'mp4'; 963 break; 964 965 default: 966 // video atom 967 $atom_structure['sample_description_table'][$i]['video_temporal_quality'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 8, 4)); 968 $atom_structure['sample_description_table'][$i]['video_spatial_quality'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 12, 4)); 969 $atom_structure['sample_description_table'][$i]['video_frame_width'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 16, 2)); 970 $atom_structure['sample_description_table'][$i]['video_frame_height'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 18, 2)); 971 $atom_structure['sample_description_table'][$i]['video_resolution_x'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 20, 4)); 972 $atom_structure['sample_description_table'][$i]['video_resolution_y'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 24, 4)); 973 $atom_structure['sample_description_table'][$i]['video_data_size'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 28, 4)); 974 $atom_structure['sample_description_table'][$i]['video_frame_count'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 32, 2)); 975 $atom_structure['sample_description_table'][$i]['video_encoder_name_len'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 34, 1)); 976 $atom_structure['sample_description_table'][$i]['video_encoder_name'] = substr($atom_structure['sample_description_table'][$i]['data'], 35, $atom_structure['sample_description_table'][$i]['video_encoder_name_len']); 977 $atom_structure['sample_description_table'][$i]['video_pixel_color_depth'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 66, 2)); 978 $atom_structure['sample_description_table'][$i]['video_color_table_id'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 68, 2)); 979 980 $atom_structure['sample_description_table'][$i]['video_pixel_color_type'] = (((int) $atom_structure['sample_description_table'][$i]['video_pixel_color_depth'] > 32) ? 'grayscale' : 'color'); 981 $atom_structure['sample_description_table'][$i]['video_pixel_color_name'] = $this->QuicktimeColorNameLookup($atom_structure['sample_description_table'][$i]['video_pixel_color_depth']); 982 983 if ($atom_structure['sample_description_table'][$i]['video_pixel_color_name'] != 'invalid') { 984 $info['quicktime']['video']['codec_fourcc'] = $atom_structure['sample_description_table'][$i]['data_format']; 985 $info['quicktime']['video']['codec_fourcc_lookup'] = $this->QuicktimeVideoCodecLookup($atom_structure['sample_description_table'][$i]['data_format']); 986 $info['quicktime']['video']['codec'] = (((int) $atom_structure['sample_description_table'][$i]['video_encoder_name_len'] > 0) ? $atom_structure['sample_description_table'][$i]['video_encoder_name'] : $atom_structure['sample_description_table'][$i]['data_format']); 987 $info['quicktime']['video']['color_depth'] = $atom_structure['sample_description_table'][$i]['video_pixel_color_depth']; 988 $info['quicktime']['video']['color_depth_name'] = $atom_structure['sample_description_table'][$i]['video_pixel_color_name']; 989 990 $info['video']['codec'] = $info['quicktime']['video']['codec']; 991 $info['video']['bits_per_sample'] = $info['quicktime']['video']['color_depth']; 992 } 993 $info['video']['lossless'] = false; 994 $info['video']['pixel_aspect_ratio'] = (float) 1; 995 break; 996 } 997 break; 998 } 999 switch (strtolower($atom_structure['sample_description_table'][$i]['data_format'])) { 1000 case 'mp4a': 1001 $info['audio']['dataformat'] = 'mp4'; 1002 $info['quicktime']['audio']['codec'] = 'mp4'; 1003 break; 1004 1005 case '3ivx': 1006 case '3iv1': 1007 case '3iv2': 1008 $info['video']['dataformat'] = '3ivx'; 1009 break; 1010 1011 case 'xvid': 1012 $info['video']['dataformat'] = 'xvid'; 1013 break; 1014 1015 case 'mp4v': 1016 $info['video']['dataformat'] = 'mpeg4'; 1017 break; 1018 1019 case 'divx': 1020 case 'div1': 1021 case 'div2': 1022 case 'div3': 1023 case 'div4': 1024 case 'div5': 1025 case 'div6': 1026 $info['video']['dataformat'] = 'divx'; 1027 break; 1028 1029 default: 1030 // do nothing 1031 break; 1032 } 1033 unset($atom_structure['sample_description_table'][$i]['data']); 1034 } 1035 break; 1036 1037 1038 case 'stts': // Sample Table Time-to-Sample atom 1039 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1040 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 1041 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 1042 $sttsEntriesDataOffset = 8; 1043 //$FrameRateCalculatorArray = array(); 1044 $frames_count = 0; 1045 1046 $max_stts_entries_to_scan = ($info['php_memory_limit'] ? min(floor($this->getid3->memory_limit / 10000), $atom_structure['number_entries']) : $atom_structure['number_entries']); 1047 if ($max_stts_entries_to_scan < $atom_structure['number_entries']) { 1048 $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($this->getid3->memory_limit / 1048576).'MB).'); 1049 } 1050 for ($i = 0; $i < $max_stts_entries_to_scan; $i++) { 1051 $atom_structure['time_to_sample_table'][$i]['sample_count'] = getid3_lib::BigEndian2Int(substr($atom_data, $sttsEntriesDataOffset, 4)); 1052 $sttsEntriesDataOffset += 4; 1053 $atom_structure['time_to_sample_table'][$i]['sample_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, $sttsEntriesDataOffset, 4)); 1054 $sttsEntriesDataOffset += 4; 1055 1056 $frames_count += $atom_structure['time_to_sample_table'][$i]['sample_count']; 1057 1058 // THIS SECTION REPLACED WITH CODE IN "stbl" ATOM 1059 //if (!empty($info['quicktime']['time_scale']) && ($atom_structure['time_to_sample_table'][$i]['sample_duration'] > 0)) { 1060 // $stts_new_framerate = $info['quicktime']['time_scale'] / $atom_structure['time_to_sample_table'][$i]['sample_duration']; 1061 // if ($stts_new_framerate <= 60) { 1062 // // some atoms have durations of "1" giving a very large framerate, which probably is not right 1063 // $info['video']['frame_rate'] = max($info['video']['frame_rate'], $stts_new_framerate); 1064 // } 1065 //} 1066 // 1067 //$FrameRateCalculatorArray[($info['quicktime']['time_scale'] / $atom_structure['time_to_sample_table'][$i]['sample_duration'])] += $atom_structure['time_to_sample_table'][$i]['sample_count']; 1068 } 1069 $info['quicktime']['stts_framecount'][] = $frames_count; 1070 //$sttsFramesTotal = 0; 1071 //$sttsSecondsTotal = 0; 1072 //foreach ($FrameRateCalculatorArray as $frames_per_second => $frame_count) { 1073 // if (($frames_per_second > 60) || ($frames_per_second < 1)) { 1074 // // not video FPS information, probably audio information 1075 // $sttsFramesTotal = 0; 1076 // $sttsSecondsTotal = 0; 1077 // break; 1078 // } 1079 // $sttsFramesTotal += $frame_count; 1080 // $sttsSecondsTotal += $frame_count / $frames_per_second; 1081 //} 1082 //if (($sttsFramesTotal > 0) && ($sttsSecondsTotal > 0)) { 1083 // if (($sttsFramesTotal / $sttsSecondsTotal) > $info['video']['frame_rate']) { 1084 // $info['video']['frame_rate'] = $sttsFramesTotal / $sttsSecondsTotal; 1085 // } 1086 //} 1087 break; 1088 1089 1090 case 'stss': // Sample Table Sync Sample (key frames) atom 1091 if ($ParseAllPossibleAtoms) { 1092 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1093 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 1094 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 1095 $stssEntriesDataOffset = 8; 1096 for ($i = 0; $i < $atom_structure['number_entries']; $i++) { 1097 $atom_structure['time_to_sample_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stssEntriesDataOffset, 4)); 1098 $stssEntriesDataOffset += 4; 1099 } 1100 } 1101 break; 1102 1103 1104 case 'stsc': // Sample Table Sample-to-Chunk atom 1105 if ($ParseAllPossibleAtoms) { 1106 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1107 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 1108 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 1109 $stscEntriesDataOffset = 8; 1110 for ($i = 0; $i < $atom_structure['number_entries']; $i++) { 1111 $atom_structure['sample_to_chunk_table'][$i]['first_chunk'] = getid3_lib::BigEndian2Int(substr($atom_data, $stscEntriesDataOffset, 4)); 1112 $stscEntriesDataOffset += 4; 1113 $atom_structure['sample_to_chunk_table'][$i]['samples_per_chunk'] = getid3_lib::BigEndian2Int(substr($atom_data, $stscEntriesDataOffset, 4)); 1114 $stscEntriesDataOffset += 4; 1115 $atom_structure['sample_to_chunk_table'][$i]['sample_description'] = getid3_lib::BigEndian2Int(substr($atom_data, $stscEntriesDataOffset, 4)); 1116 $stscEntriesDataOffset += 4; 1117 } 1118 } 1119 break; 1120 1121 1122 case 'stsz': // Sample Table SiZe atom 1123 if ($ParseAllPossibleAtoms) { 1124 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1125 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 1126 $atom_structure['sample_size'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 1127 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); 1128 $stszEntriesDataOffset = 12; 1129 if ($atom_structure['sample_size'] == 0) { 1130 for ($i = 0; $i < $atom_structure['number_entries']; $i++) { 1131 $atom_structure['sample_size_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stszEntriesDataOffset, 4)); 1132 $stszEntriesDataOffset += 4; 1133 } 1134 } 1135 } 1136 break; 1137 1138 1139 case 'stco': // Sample Table Chunk Offset atom 1140 // if (true) { 1141 if ($ParseAllPossibleAtoms) { 1142 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1143 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 1144 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 1145 $stcoEntriesDataOffset = 8; 1146 for ($i = 0; $i < $atom_structure['number_entries']; $i++) { 1147 $atom_structure['chunk_offset_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stcoEntriesDataOffset, 4)); 1148 $stcoEntriesDataOffset += 4; 1149 } 1150 } 1151 break; 1152 1153 1154 case 'co64': // Chunk Offset 64-bit (version of "stco" that supports > 2GB files) 1155 if ($ParseAllPossibleAtoms) { 1156 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1157 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 1158 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 1159 $stcoEntriesDataOffset = 8; 1160 for ($i = 0; $i < $atom_structure['number_entries']; $i++) { 1161 $atom_structure['chunk_offset_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stcoEntriesDataOffset, 8)); 1162 $stcoEntriesDataOffset += 8; 1163 } 1164 } 1165 break; 1166 1167 1168 case 'dref': // Data REFerence atom 1169 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1170 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 1171 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 1172 $drefDataOffset = 8; 1173 for ($i = 0; $i < $atom_structure['number_entries']; $i++) { 1174 $atom_structure['data_references'][$i]['size'] = getid3_lib::BigEndian2Int(substr($atom_data, $drefDataOffset, 4)); 1175 $drefDataOffset += 4; 1176 $atom_structure['data_references'][$i]['type'] = substr($atom_data, $drefDataOffset, 4); 1177 $drefDataOffset += 4; 1178 $atom_structure['data_references'][$i]['version'] = getid3_lib::BigEndian2Int(substr($atom_data, $drefDataOffset, 1)); 1179 $drefDataOffset += 1; 1180 $atom_structure['data_references'][$i]['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, $drefDataOffset, 3)); // hardcoded: 0x0000 1181 $drefDataOffset += 3; 1182 $atom_structure['data_references'][$i]['data'] = substr($atom_data, $drefDataOffset, ($atom_structure['data_references'][$i]['size'] - 4 - 4 - 1 - 3)); 1183 $drefDataOffset += ($atom_structure['data_references'][$i]['size'] - 4 - 4 - 1 - 3); 1184 1185 $atom_structure['data_references'][$i]['flags']['self_reference'] = (bool) ($atom_structure['data_references'][$i]['flags_raw'] & 0x001); 1186 } 1187 break; 1188 1189 1190 case 'gmin': // base Media INformation atom 1191 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1192 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 1193 $atom_structure['graphics_mode'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); 1194 $atom_structure['opcolor_red'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2)); 1195 $atom_structure['opcolor_green'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 2)); 1196 $atom_structure['opcolor_blue'] = getid3_lib::BigEndian2Int(substr($atom_data, 10, 2)); 1197 $atom_structure['balance'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 2)); 1198 $atom_structure['reserved'] = getid3_lib::BigEndian2Int(substr($atom_data, 14, 2)); 1199 break; 1200 1201 1202 case 'smhd': // Sound Media information HeaDer atom 1203 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1204 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 1205 $atom_structure['balance'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); 1206 $atom_structure['reserved'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2)); 1207 break; 1208 1209 1210 case 'vmhd': // Video Media information HeaDer atom 1211 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1212 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); 1213 $atom_structure['graphics_mode'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); 1214 $atom_structure['opcolor_red'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2)); 1215 $atom_structure['opcolor_green'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 2)); 1216 $atom_structure['opcolor_blue'] = getid3_lib::BigEndian2Int(substr($atom_data, 10, 2)); 1217 1218 $atom_structure['flags']['no_lean_ahead'] = (bool) ($atom_structure['flags_raw'] & 0x001); 1219 break; 1220 1221 1222 case 'hdlr': // HanDLeR reference atom 1223 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1224 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 1225 $atom_structure['component_type'] = substr($atom_data, 4, 4); 1226 $atom_structure['component_subtype'] = substr($atom_data, 8, 4); 1227 $atom_structure['component_manufacturer'] = substr($atom_data, 12, 4); 1228 $atom_structure['component_flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); 1229 $atom_structure['component_flags_mask'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 4)); 1230 $atom_structure['component_name'] = $this->MaybePascal2String(substr($atom_data, 24)); 1231 1232 if (($atom_structure['component_subtype'] == 'STpn') && ($atom_structure['component_manufacturer'] == 'zzzz')) { 1233 $info['video']['dataformat'] = 'quicktimevr'; 1234 } 1235 break; 1236 1237 1238 case 'mdhd': // MeDia HeaDer atom 1239 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1240 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 1241 $atom_structure['creation_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 1242 $atom_structure['modify_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); 1243 $atom_structure['time_scale'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); 1244 $atom_structure['duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); 1245 $atom_structure['language_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 2)); 1246 $atom_structure['quality'] = getid3_lib::BigEndian2Int(substr($atom_data, 22, 2)); 1247 1248 if ($atom_structure['time_scale'] == 0) { 1249 $this->error('Corrupt Quicktime file: mdhd.time_scale == zero'); 1250 return false; 1251 } 1252 $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']); 1253 1254 $atom_structure['creation_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['creation_time']); 1255 $atom_structure['modify_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['modify_time']); 1256 $atom_structure['playtime_seconds'] = $atom_structure['duration'] / $atom_structure['time_scale']; 1257 $atom_structure['language'] = $this->QuicktimeLanguageLookup($atom_structure['language_id']); 1258 if (empty($info['comments']['language']) || (!in_array($atom_structure['language'], $info['comments']['language']))) { 1259 $info['comments']['language'][] = $atom_structure['language']; 1260 } 1261 $info['quicktime']['timestamps_unix']['create'][$atom_structure['hierarchy']] = $atom_structure['creation_time_unix']; 1262 $info['quicktime']['timestamps_unix']['modify'][$atom_structure['hierarchy']] = $atom_structure['modify_time_unix']; 1263 break; 1264 1265 1266 case 'pnot': // Preview atom 1267 $atom_structure['modification_date'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); // "standard Macintosh format" 1268 $atom_structure['version_number'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); // hardcoded: 0x00 1269 $atom_structure['atom_type'] = substr($atom_data, 6, 4); // usually: 'PICT' 1270 $atom_structure['atom_index'] = getid3_lib::BigEndian2Int(substr($atom_data, 10, 2)); // usually: 0x01 1271 1272 $atom_structure['modification_date_unix'] = getid3_lib::DateMac2Unix($atom_structure['modification_date']); 1273 $info['quicktime']['timestamps_unix']['modify'][$atom_structure['hierarchy']] = $atom_structure['modification_date_unix']; 1274 break; 1275 1276 1277 case 'crgn': // Clipping ReGioN atom 1278 $atom_structure['region_size'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2)); // The Region size, Region boundary box, 1279 $atom_structure['boundary_box'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 8)); // and Clipping region data fields 1280 $atom_structure['clipping_data'] = substr($atom_data, 10); // constitute a QuickDraw region. 1281 break; 1282 1283 1284 case 'load': // track LOAD settings atom 1285 $atom_structure['preload_start_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); 1286 $atom_structure['preload_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 1287 $atom_structure['preload_flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); 1288 $atom_structure['default_hints_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); 1289 1290 $atom_structure['default_hints']['double_buffer'] = (bool) ($atom_structure['default_hints_raw'] & 0x0020); 1291 $atom_structure['default_hints']['high_quality'] = (bool) ($atom_structure['default_hints_raw'] & 0x0100); 1292 break; 1293 1294 1295 case 'tmcd': // TiMe CoDe atom 1296 case 'chap': // CHAPter list atom 1297 case 'sync': // SYNChronization atom 1298 case 'scpt': // tranSCriPT atom 1299 case 'ssrc': // non-primary SouRCe atom 1300 for ($i = 0; $i < strlen($atom_data); $i += 4) { 1301 @$atom_structure['track_id'][] = getid3_lib::BigEndian2Int(substr($atom_data, $i, 4)); 1302 } 1303 break; 1304 1305 1306 case 'elst': // Edit LiST atom 1307 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1308 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 1309 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 1310 for ($i = 0; $i < $atom_structure['number_entries']; $i++ ) { 1311 $atom_structure['edit_list'][$i]['track_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($i * 12) + 0, 4)); 1312 $atom_structure['edit_list'][$i]['media_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($i * 12) + 4, 4)); 1313 $atom_structure['edit_list'][$i]['media_rate'] = getid3_lib::FixedPoint16_16(substr($atom_data, 8 + ($i * 12) + 8, 4)); 1314 } 1315 break; 1316 1317 1318 case 'kmat': // compressed MATte atom 1319 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1320 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 1321 $atom_structure['matte_data_raw'] = substr($atom_data, 4); 1322 break; 1323 1324 1325 case 'ctab': // Color TABle atom 1326 $atom_structure['color_table_seed'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); // hardcoded: 0x00000000 1327 $atom_structure['color_table_flags'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); // hardcoded: 0x8000 1328 $atom_structure['color_table_size'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2)) + 1; 1329 for ($colortableentry = 0; $colortableentry < $atom_structure['color_table_size']; $colortableentry++) { 1330 $atom_structure['color_table'][$colortableentry]['alpha'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 0, 2)); 1331 $atom_structure['color_table'][$colortableentry]['red'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 2, 2)); 1332 $atom_structure['color_table'][$colortableentry]['green'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 4, 2)); 1333 $atom_structure['color_table'][$colortableentry]['blue'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 6, 2)); 1334 } 1335 break; 1336 1337 1338 case 'mvhd': // MoVie HeaDer atom 1339 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1340 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); 1341 $atom_structure['creation_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 1342 $atom_structure['modify_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); 1343 $atom_structure['time_scale'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); 1344 $atom_structure['duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); 1345 $atom_structure['preferred_rate'] = getid3_lib::FixedPoint16_16(substr($atom_data, 20, 4)); 1346 $atom_structure['preferred_volume'] = getid3_lib::FixedPoint8_8(substr($atom_data, 24, 2)); 1347 $atom_structure['reserved'] = substr($atom_data, 26, 10); 1348 $atom_structure['matrix_a'] = getid3_lib::FixedPoint16_16(substr($atom_data, 36, 4)); 1349 $atom_structure['matrix_b'] = getid3_lib::FixedPoint16_16(substr($atom_data, 40, 4)); 1350 $atom_structure['matrix_u'] = getid3_lib::FixedPoint2_30(substr($atom_data, 44, 4)); 1351 $atom_structure['matrix_c'] = getid3_lib::FixedPoint16_16(substr($atom_data, 48, 4)); 1352 $atom_structure['matrix_d'] = getid3_lib::FixedPoint16_16(substr($atom_data, 52, 4)); 1353 $atom_structure['matrix_v'] = getid3_lib::FixedPoint2_30(substr($atom_data, 56, 4)); 1354 $atom_structure['matrix_x'] = getid3_lib::FixedPoint16_16(substr($atom_data, 60, 4)); 1355 $atom_structure['matrix_y'] = getid3_lib::FixedPoint16_16(substr($atom_data, 64, 4)); 1356 $atom_structure['matrix_w'] = getid3_lib::FixedPoint2_30(substr($atom_data, 68, 4)); 1357 $atom_structure['preview_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 72, 4)); 1358 $atom_structure['preview_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 76, 4)); 1359 $atom_structure['poster_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 80, 4)); 1360 $atom_structure['selection_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 84, 4)); 1361 $atom_structure['selection_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 88, 4)); 1362 $atom_structure['current_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 92, 4)); 1363 $atom_structure['next_track_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 96, 4)); 1364 1365 if ($atom_structure['time_scale'] == 0) { 1366 $this->error('Corrupt Quicktime file: mvhd.time_scale == zero'); 1367 return false; 1368 } 1369 $atom_structure['creation_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['creation_time']); 1370 $atom_structure['modify_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['modify_time']); 1371 $info['quicktime']['timestamps_unix']['create'][$atom_structure['hierarchy']] = $atom_structure['creation_time_unix']; 1372 $info['quicktime']['timestamps_unix']['modify'][$atom_structure['hierarchy']] = $atom_structure['modify_time_unix']; 1373 $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']); 1374 $info['quicktime']['display_scale'] = $atom_structure['matrix_a']; 1375 $info['playtime_seconds'] = $atom_structure['duration'] / $atom_structure['time_scale']; 1376 break; 1377 1378 1379 case 'tkhd': // TracK HeaDer atom 1380 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1381 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); 1382 $atom_structure['creation_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 1383 $atom_structure['modify_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); 1384 $atom_structure['trackid'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); 1385 $atom_structure['reserved1'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); 1386 $atom_structure['duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 4)); 1387 $atom_structure['reserved2'] = getid3_lib::BigEndian2Int(substr($atom_data, 24, 8)); 1388 $atom_structure['layer'] = getid3_lib::BigEndian2Int(substr($atom_data, 32, 2)); 1389 $atom_structure['alternate_group'] = getid3_lib::BigEndian2Int(substr($atom_data, 34, 2)); 1390 $atom_structure['volume'] = getid3_lib::FixedPoint8_8(substr($atom_data, 36, 2)); 1391 $atom_structure['reserved3'] = getid3_lib::BigEndian2Int(substr($atom_data, 38, 2)); 1392 // http://developer.apple.com/library/mac/#documentation/QuickTime/RM/MovieBasics/MTEditing/K-Chapter/11MatrixFunctions.html 1393 // http://developer.apple.com/library/mac/#documentation/QuickTime/qtff/QTFFChap4/qtff4.html#//apple_ref/doc/uid/TP40000939-CH206-18737 1394 $atom_structure['matrix_a'] = getid3_lib::FixedPoint16_16(substr($atom_data, 40, 4)); 1395 $atom_structure['matrix_b'] = getid3_lib::FixedPoint16_16(substr($atom_data, 44, 4)); 1396 $atom_structure['matrix_u'] = getid3_lib::FixedPoint2_30(substr($atom_data, 48, 4)); 1397 $atom_structure['matrix_c'] = getid3_lib::FixedPoint16_16(substr($atom_data, 52, 4)); 1398 $atom_structure['matrix_d'] = getid3_lib::FixedPoint16_16(substr($atom_data, 56, 4)); 1399 $atom_structure['matrix_v'] = getid3_lib::FixedPoint2_30(substr($atom_data, 60, 4)); 1400 $atom_structure['matrix_x'] = getid3_lib::FixedPoint16_16(substr($atom_data, 64, 4)); 1401 $atom_structure['matrix_y'] = getid3_lib::FixedPoint16_16(substr($atom_data, 68, 4)); 1402 $atom_structure['matrix_w'] = getid3_lib::FixedPoint2_30(substr($atom_data, 72, 4)); 1403 $atom_structure['width'] = getid3_lib::FixedPoint16_16(substr($atom_data, 76, 4)); 1404 $atom_structure['height'] = getid3_lib::FixedPoint16_16(substr($atom_data, 80, 4)); 1405 $atom_structure['flags']['enabled'] = (bool) ($atom_structure['flags_raw'] & 0x0001); 1406 $atom_structure['flags']['in_movie'] = (bool) ($atom_structure['flags_raw'] & 0x0002); 1407 $atom_structure['flags']['in_preview'] = (bool) ($atom_structure['flags_raw'] & 0x0004); 1408 $atom_structure['flags']['in_poster'] = (bool) ($atom_structure['flags_raw'] & 0x0008); 1409 $atom_structure['creation_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['creation_time']); 1410 $atom_structure['modify_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['modify_time']); 1411 $info['quicktime']['timestamps_unix']['create'][$atom_structure['hierarchy']] = $atom_structure['creation_time_unix']; 1412 $info['quicktime']['timestamps_unix']['modify'][$atom_structure['hierarchy']] = $atom_structure['modify_time_unix']; 1413 1414 // https://www.getid3.org/phpBB3/viewtopic.php?t=1908 1415 // attempt to compute rotation from matrix values 1416 // 2017-Dec-28: uncertain if 90/270 are correctly oriented; values returned by FixedPoint16_16 should perhaps be -1 instead of 65535(?) 1417 $matrixRotation = 0; 1418 switch ($atom_structure['matrix_a'].':'.$atom_structure['matrix_b'].':'.$atom_structure['matrix_c'].':'.$atom_structure['matrix_d']) { 1419 case '1:0:0:1': $matrixRotation = 0; break; 1420 case '0:1:65535:0': $matrixRotation = 90; break; 1421 case '65535:0:0:65535': $matrixRotation = 180; break; 1422 case '0:65535:1:0': $matrixRotation = 270; break; 1423 default: break; 1424 } 1425 1426 // https://www.getid3.org/phpBB3/viewtopic.php?t=2468 1427 // The rotation matrix can appear in the Quicktime file multiple times, at least once for each track, 1428 // and it's possible that only the video track (or, in theory, one of the video tracks) is flagged as 1429 // rotated while the other tracks (e.g. audio) is tagged as rotation=0 (behavior noted on iPhone 8 Plus) 1430 // The correct solution would be to check if the TrackID associated with the rotation matrix is indeed 1431 // a video track (or the main video track) and only set the rotation then, but since information about 1432 // what track is what is not trivially there to be examined, the lazy solution is to set the rotation 1433 // if it is found to be nonzero, on the assumption that tracks that don't need it will have rotation set 1434 // to zero (and be effectively ignored) and the video track will have rotation set correctly, which will 1435 // either be zero and automatically correct, or nonzero and be set correctly. 1436 if (!isset($info['video']['rotate']) || (($info['video']['rotate'] == 0) && ($matrixRotation > 0))) { 1437 $info['quicktime']['video']['rotate'] = $info['video']['rotate'] = $matrixRotation; 1438 } 1439 1440 if ($atom_structure['flags']['enabled'] == 1) { 1441 if (!isset($info['video']['resolution_x']) || !isset($info['video']['resolution_y'])) { 1442 $info['video']['resolution_x'] = $atom_structure['width']; 1443 $info['video']['resolution_y'] = $atom_structure['height']; 1444 } 1445 $info['video']['resolution_x'] = max($info['video']['resolution_x'], $atom_structure['width']); 1446 $info['video']['resolution_y'] = max($info['video']['resolution_y'], $atom_structure['height']); 1447 $info['quicktime']['video']['resolution_x'] = $info['video']['resolution_x']; 1448 $info['quicktime']['video']['resolution_y'] = $info['video']['resolution_y']; 1449 } else { 1450 // see: https://www.getid3.org/phpBB3/viewtopic.php?t=1295 1451 //if (isset($info['video']['resolution_x'])) { unset($info['video']['resolution_x']); } 1452 //if (isset($info['video']['resolution_y'])) { unset($info['video']['resolution_y']); } 1453 //if (isset($info['quicktime']['video'])) { unset($info['quicktime']['video']); } 1454 } 1455 break; 1456 1457 1458 case 'iods': // Initial Object DeScriptor atom 1459 // http://www.koders.com/c/fid1FAB3E762903DC482D8A246D4A4BF9F28E049594.aspx?s=windows.h 1460 // http://libquicktime.sourcearchive.com/documentation/1.0.2plus-pdebian/iods_8c-source.html 1461 $offset = 0; 1462 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); 1463 $offset += 1; 1464 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 3)); 1465 $offset += 3; 1466 $atom_structure['mp4_iod_tag'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); 1467 $offset += 1; 1468 $atom_structure['length'] = $this->quicktime_read_mp4_descr_length($atom_data, $offset); 1469 //$offset already adjusted by quicktime_read_mp4_descr_length() 1470 $atom_structure['object_descriptor_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 2)); 1471 $offset += 2; 1472 $atom_structure['od_profile_level'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); 1473 $offset += 1; 1474 $atom_structure['scene_profile_level'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); 1475 $offset += 1; 1476 $atom_structure['audio_profile_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); 1477 $offset += 1; 1478 $atom_structure['video_profile_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); 1479 $offset += 1; 1480 $atom_structure['graphics_profile_level'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); 1481 $offset += 1; 1482 1483 $atom_structure['num_iods_tracks'] = ($atom_structure['length'] - 7) / 6; // 6 bytes would only be right if all tracks use 1-byte length fields 1484 for ($i = 0; $i < $atom_structure['num_iods_tracks']; $i++) { 1485 $atom_structure['track'][$i]['ES_ID_IncTag'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); 1486 $offset += 1; 1487 $atom_structure['track'][$i]['length'] = $this->quicktime_read_mp4_descr_length($atom_data, $offset); 1488 //$offset already adjusted by quicktime_read_mp4_descr_length() 1489 $atom_structure['track'][$i]['track_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 4)); 1490 $offset += 4; 1491 } 1492 1493 $atom_structure['audio_profile_name'] = $this->QuicktimeIODSaudioProfileName($atom_structure['audio_profile_id']); 1494 $atom_structure['video_profile_name'] = $this->QuicktimeIODSvideoProfileName($atom_structure['video_profile_id']); 1495 break; 1496 1497 case 'ftyp': // FileTYPe (?) atom (for MP4 it seems) 1498 $atom_structure['signature'] = substr($atom_data, 0, 4); 1499 $atom_structure['unknown_1'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 1500 $atom_structure['fourcc'] = substr($atom_data, 8, 4); 1501 break; 1502 1503 case 'mdat': // Media DATa atom 1504 // 'mdat' contains the actual data for the audio/video, possibly also subtitles 1505 1506 /* due to lack of known documentation, this is a kludge implementation. If you know of documentation on how mdat is properly structed, please send it to info@getid3.org */ 1507 1508 // first, skip any 'wide' padding, and second 'mdat' header (with specified size of zero?) 1509 $mdat_offset = 0; 1510 while (true) { 1511 if (substr($atom_data, $mdat_offset, 8) == "\x00\x00\x00\x08".'wide') { 1512 $mdat_offset += 8; 1513 } elseif (substr($atom_data, $mdat_offset, 8) == "\x00\x00\x00\x00".'mdat') { 1514 $mdat_offset += 8; 1515 } else { 1516 break; 1517 } 1518 } 1519 if (substr($atom_data, $mdat_offset, 4) == 'GPRO') { 1520 $GOPRO_chunk_length = getid3_lib::LittleEndian2Int(substr($atom_data, $mdat_offset + 4, 4)); 1521 $GOPRO_offset = 8; 1522 $atom_structure['GPRO']['raw'] = substr($atom_data, $mdat_offset + 8, $GOPRO_chunk_length - 8); 1523 $atom_structure['GPRO']['firmware'] = substr($atom_structure['GPRO']['raw'], 0, 15); 1524 $atom_structure['GPRO']['unknown1'] = substr($atom_structure['GPRO']['raw'], 15, 16); 1525 $atom_structure['GPRO']['unknown2'] = substr($atom_structure['GPRO']['raw'], 31, 32); 1526 $atom_structure['GPRO']['unknown3'] = substr($atom_structure['GPRO']['raw'], 63, 16); 1527 $atom_structure['GPRO']['camera'] = substr($atom_structure['GPRO']['raw'], 79, 32); 1528 $info['quicktime']['camera']['model'] = rtrim($atom_structure['GPRO']['camera'], "\x00"); 1529 } 1530 1531 // check to see if it looks like chapter titles, in the form of unterminated strings with a leading 16-bit size field 1532 while (($mdat_offset < (strlen($atom_data) - 8)) 1533 && ($chapter_string_length = getid3_lib::BigEndian2Int(substr($atom_data, $mdat_offset, 2))) 1534 && ($chapter_string_length < 1000) 1535 && ($chapter_string_length <= (strlen($atom_data) - $mdat_offset - 2)) 1536 && preg_match('#^([\x00-\xFF]{2})([\x20-\xFF]+)$#', substr($atom_data, $mdat_offset, $chapter_string_length + 2), $chapter_matches)) { 1537 list($dummy, $chapter_string_length_hex, $chapter_string) = $chapter_matches; 1538 $mdat_offset += (2 + $chapter_string_length); 1539 @$info['quicktime']['comments']['chapters'][] = $chapter_string; 1540 1541 // "encd" atom specifies encoding. In theory could be anything, almost always UTF-8, but may be UTF-16 with BOM (not currently handled) 1542 if (substr($atom_data, $mdat_offset, 12) == "\x00\x00\x00\x0C\x65\x6E\x63\x64\x00\x00\x01\x00") { // UTF-8 1543 $mdat_offset += 12; 1544 } 1545 } 1546 1547 if (($atomsize > 8) && (!isset($info['avdataend_tmp']) || ($info['quicktime'][$atomname]['size'] > ($info['avdataend_tmp'] - $info['avdataoffset'])))) { 1548 1549 $info['avdataoffset'] = $atom_structure['offset'] + 8; // $info['quicktime'][$atomname]['offset'] + 8; 1550 $OldAVDataEnd = $info['avdataend']; 1551 $info['avdataend'] = $atom_structure['offset'] + $atom_structure['size']; // $info['quicktime'][$atomname]['offset'] + $info['quicktime'][$atomname]['size']; 1552 1553 $getid3_temp = new getID3(); 1554 $getid3_temp->openfile($this->getid3->filename, $this->getid3->info['filesize'], $this->getid3->fp); 1555 $getid3_temp->info['avdataoffset'] = $info['avdataoffset']; 1556 $getid3_temp->info['avdataend'] = $info['avdataend']; 1557 $getid3_mp3 = new getid3_mp3($getid3_temp); 1558 if ($getid3_mp3->MPEGaudioHeaderValid($getid3_mp3->MPEGaudioHeaderDecode($this->fread(4)))) { 1559 $getid3_mp3->getOnlyMPEGaudioInfo($getid3_temp->info['avdataoffset'], false); 1560 if (!empty($getid3_temp->info['warning'])) { 1561 foreach ($getid3_temp->info['warning'] as $value) { 1562 $this->warning($value); 1563 } 1564 } 1565 if (!empty($getid3_temp->info['mpeg'])) { 1566 $info['mpeg'] = $getid3_temp->info['mpeg']; 1567 if (isset($info['mpeg']['audio'])) { 1568 $info['audio']['dataformat'] = 'mp3'; 1569 $info['audio']['codec'] = (!empty($info['mpeg']['audio']['encoder']) ? $info['mpeg']['audio']['encoder'] : (!empty($info['mpeg']['audio']['codec']) ? $info['mpeg']['audio']['codec'] : (!empty($info['mpeg']['audio']['LAME']) ? 'LAME' :'mp3'))); 1570 $info['audio']['sample_rate'] = $info['mpeg']['audio']['sample_rate']; 1571 $info['audio']['channels'] = $info['mpeg']['audio']['channels']; 1572 $info['audio']['bitrate'] = $info['mpeg']['audio']['bitrate']; 1573 $info['audio']['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']); 1574 $info['bitrate'] = $info['audio']['bitrate']; 1575 } 1576 } 1577 } 1578 unset($getid3_mp3, $getid3_temp); 1579 $info['avdataend'] = $OldAVDataEnd; 1580 unset($OldAVDataEnd); 1581 1582 } 1583 1584 unset($mdat_offset, $chapter_string_length, $chapter_matches); 1585 break; 1586 1587 case 'ID32': // ID3v2 1588 getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v2.php', __FILE__, true); 1589 1590 $getid3_temp = new getID3(); 1591 $getid3_temp->openfile($this->getid3->filename, $this->getid3->info['filesize'], $this->getid3->fp); 1592 $getid3_id3v2 = new getid3_id3v2($getid3_temp); 1593 $getid3_id3v2->StartingOffset = $atom_structure['offset'] + 14; // framelength(4)+framename(4)+flags(4)+??(2) 1594 if ($atom_structure['valid'] = $getid3_id3v2->Analyze()) { 1595 $atom_structure['id3v2'] = $getid3_temp->info['id3v2']; 1596 } else { 1597 $this->warning('ID32 frame at offset '.$atom_structure['offset'].' did not parse'); 1598 } 1599 unset($getid3_temp, $getid3_id3v2); 1600 break; 1601 1602 case 'free': // FREE space atom 1603 case 'skip': // SKIP atom 1604 case 'wide': // 64-bit expansion placeholder atom 1605 // 'free', 'skip' and 'wide' are just padding, contains no useful data at all 1606 1607 // When writing QuickTime files, it is sometimes necessary to update an atom's size. 1608 // It is impossible to update a 32-bit atom to a 64-bit atom since the 32-bit atom 1609 // is only 8 bytes in size, and the 64-bit atom requires 16 bytes. Therefore, QuickTime 1610 // puts an 8-byte placeholder atom before any atoms it may have to update the size of. 1611 // In this way, if the atom needs to be converted from a 32-bit to a 64-bit atom, the 1612 // placeholder atom can be overwritten to obtain the necessary 8 extra bytes. 1613 // The placeholder atom has a type of kWideAtomPlaceholderType ( 'wide' ). 1614 break; 1615 1616 1617 case 'nsav': // NoSAVe atom 1618 // http://developer.apple.com/technotes/tn/tn2038.html 1619 $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); 1620 break; 1621 1622 case 'ctyp': // Controller TYPe atom (seen on QTVR) 1623 // http://homepages.slingshot.co.nz/~helmboy/quicktime/formats/qtm-layout.txt 1624 // some controller names are: 1625 // 0x00 + 'std' for linear movie 1626 // 'none' for no controls 1627 $atom_structure['ctyp'] = substr($atom_data, 0, 4); 1628 $info['quicktime']['controller'] = $atom_structure['ctyp']; 1629 switch ($atom_structure['ctyp']) { 1630 case 'qtvr': 1631 $info['video']['dataformat'] = 'quicktimevr'; 1632 break; 1633 } 1634 break; 1635 1636 case 'pano': // PANOrama track (seen on QTVR) 1637 $atom_structure['pano'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); 1638 break; 1639 1640 case 'hint': // HINT track 1641 case 'hinf': // 1642 case 'hinv': // 1643 case 'hnti': // 1644 $info['quicktime']['hinting'] = true; 1645 break; 1646 1647 case 'imgt': // IMaGe Track reference (kQTVRImageTrackRefType) (seen on QTVR) 1648 for ($i = 0; $i < ($atom_structure['size'] - 8); $i += 4) { 1649 $atom_structure['imgt'][] = getid3_lib::BigEndian2Int(substr($atom_data, $i, 4)); 1650 } 1651 break; 1652 1653 1654 // Observed-but-not-handled atom types are just listed here to prevent warnings being generated 1655 case 'FXTC': // Something to do with Adobe After Effects (?) 1656 case 'PrmA': 1657 case 'code': 1658 case 'FIEL': // this is NOT "fiel" (Field Ordering) as describe here: http://developer.apple.com/documentation/QuickTime/QTFF/QTFFChap3/chapter_4_section_2.html 1659 case 'tapt': // TrackApertureModeDimensionsAID - http://developer.apple.com/documentation/QuickTime/Reference/QT7-1_Update_Reference/Constants/Constants.html 1660 // tapt seems to be used to compute the video size [https://www.getid3.org/phpBB3/viewtopic.php?t=838] 1661 // * http://lists.apple.com/archives/quicktime-api/2006/Aug/msg00014.html 1662 // * http://handbrake.fr/irclogs/handbrake-dev/handbrake-dev20080128_pg2.html 1663 case 'ctts':// STCompositionOffsetAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html 1664 case 'cslg':// STCompositionShiftLeastGreatestAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html 1665 case 'sdtp':// STSampleDependencyAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html 1666 case 'stps':// STPartialSyncSampleAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html 1667 //$atom_structure['data'] = $atom_data; 1668 break; 1669 1670 case "\xA9".'xyz': // GPS latitude+longitude+altitude 1671 $atom_structure['data'] = $atom_data; 1672 if (preg_match('#([\\+\\-][0-9\\.]+)([\\+\\-][0-9\\.]+)([\\+\\-][0-9\\.]+)?/$#i', $atom_data, $matches)) { 1673 @list($all, $latitude, $longitude, $altitude) = $matches; 1674 $info['quicktime']['comments']['gps_latitude'][] = floatval($latitude); 1675 $info['quicktime']['comments']['gps_longitude'][] = floatval($longitude); 1676 if (!empty($altitude)) { // @phpstan-ignore-line 1677 $info['quicktime']['comments']['gps_altitude'][] = floatval($altitude); 1678 } 1679 } else { 1680 $this->warning('QuickTime atom "©xyz" data does not match expected data pattern at offset '.$baseoffset.'. Please report as getID3() bug.'); 1681 } 1682 break; 1683 1684 case 'NCDT': 1685 // https://exiftool.org/TagNames/Nikon.html 1686 // Nikon-specific QuickTime tags found in the NCDT atom of MOV videos from some Nikon cameras such as the Coolpix S8000 and D5100 1687 $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 4, $atomHierarchy, $ParseAllPossibleAtoms); 1688 break; 1689 case 'NCTH': // Nikon Camera THumbnail image 1690 case 'NCVW': // Nikon Camera preVieW image 1691 case 'NCM1': // Nikon Camera preview iMage 1 1692 case 'NCM2': // Nikon Camera preview iMage 2 1693 // https://exiftool.org/TagNames/Nikon.html 1694 if (preg_match('/^\xFF\xD8\xFF/', $atom_data)) { 1695 $descriptions = array( 1696 'NCTH' => 'Nikon Camera Thumbnail Image', 1697 'NCVW' => 'Nikon Camera Preview Image', 1698 'NCM1' => 'Nikon Camera Preview Image 1', 1699 'NCM2' => 'Nikon Camera Preview Image 2', 1700 ); 1701 $atom_structure['data'] = $atom_data; 1702 $atom_structure['image_mime'] = 'image/jpeg'; 1703 $atom_structure['description'] = $descriptions[$atomname]; 1704 $info['quicktime']['comments']['picture'][] = array( 1705 'image_mime' => $atom_structure['image_mime'], 1706 'data' => $atom_data, 1707 'description' => $atom_structure['description'] 1708 ); 1709 } 1710 break; 1711 case 'NCTG': // Nikon - https://exiftool.org/TagNames/Nikon.html#NCTG 1712 getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.nikon-nctg.php', __FILE__, true); 1713 $nikonNCTG = new getid3_tag_nikon_nctg($this->getid3); 1714 1715 $atom_structure['data'] = $nikonNCTG->parse($atom_data); 1716 break; 1717 case 'NCHD': // Nikon:MakerNoteVersion - https://exiftool.org/TagNames/Nikon.html 1718 $makerNoteVersion = ''; 1719 for ($i = 0, $iMax = strlen($atom_data); $i < $iMax; ++$i) { 1720 if (ord($atom_data[$i]) <= 0x1F) { 1721 $makerNoteVersion .= ' '.ord($atom_data[$i]); 1722 } else { 1723 $makerNoteVersion .= $atom_data[$i]; 1724 } 1725 } 1726 $makerNoteVersion = rtrim($makerNoteVersion, "\x00"); 1727 $atom_structure['data'] = array( 1728 'MakerNoteVersion' => $makerNoteVersion 1729 ); 1730 break; 1731 case 'NCDB': // Nikon - https://exiftool.org/TagNames/Nikon.html 1732 case 'CNCV': // Canon:CompressorVersion - https://exiftool.org/TagNames/Canon.html 1733 $atom_structure['data'] = $atom_data; 1734 break; 1735 1736 case "\x00\x00\x00\x00": 1737 // some kind of metacontainer, may contain a big data dump such as: 1738 // 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 1739 // https://xhelmboyx.tripod.com/formats/qti-layout.txt 1740 1741 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1742 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); 1743 $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom(substr($atom_data, 4), $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); 1744 //$atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); 1745 break; 1746 1747 case 'meta': // METAdata atom 1748 // https://developer.apple.com/library/mac/documentation/QuickTime/QTFF/Metadata/Metadata.html 1749 1750 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1751 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); 1752 $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); 1753 break; 1754 1755 case 'data': // metaDATA atom 1756 // seems to be 2 bytes language code (ASCII), 2 bytes unknown (set to 0x10B5 in sample I have), remainder is useful data 1757 $atom_structure['language'] = substr($atom_data, 4 + 0, 2); 1758 $atom_structure['unknown'] = getid3_lib::BigEndian2Int(substr($atom_data, 4 + 2, 2)); 1759 $atom_structure['data'] = substr($atom_data, 4 + 4); 1760 $atom_structure['key_name'] = (isset($info['quicktime']['temp_meta_key_names'][$this->metaDATAkey]) ? $info['quicktime']['temp_meta_key_names'][$this->metaDATAkey] : ''); 1761 $this->metaDATAkey++; 1762 1763 switch ($atom_structure['key_name']) { 1764 case 'com.android.capture.fps': 1765 $atom_structure['data'] = getid3_lib::BigEndian2Float($atom_structure['data']); 1766 break; 1767 } 1768 1769 if ($atom_structure['key_name'] && $atom_structure['data']) { 1770 @$info['quicktime']['comments'][str_replace('com.android.', '', str_replace('com.apple.quicktime.', '', $atom_structure['key_name']))][] = $atom_structure['data']; 1771 } 1772 break; 1773 1774 case 'keys': // KEYS that may be present in the metadata atom. 1775 // https://developer.apple.com/library/mac/documentation/QuickTime/QTFF/Metadata/Metadata.html#//apple_ref/doc/uid/TP40000939-CH1-SW21 1776 // The metadata item keys atom holds a list of the metadata keys that may be present in the metadata atom. 1777 // 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". 1778 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1779 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); 1780 $atom_structure['entry_count'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 1781 $keys_atom_offset = 8; 1782 for ($i = 1; $i <= $atom_structure['entry_count']; $i++) { 1783 $atom_structure['keys'][$i]['key_size'] = getid3_lib::BigEndian2Int(substr($atom_data, $keys_atom_offset + 0, 4)); 1784 $atom_structure['keys'][$i]['key_namespace'] = substr($atom_data, $keys_atom_offset + 4, 4); 1785 $atom_structure['keys'][$i]['key_value'] = substr($atom_data, $keys_atom_offset + 8, $atom_structure['keys'][$i]['key_size'] - 8); 1786 $keys_atom_offset += $atom_structure['keys'][$i]['key_size']; // key_size includes the 4+4 bytes for key_size and key_namespace 1787 1788 $info['quicktime']['temp_meta_key_names'][$i] = $atom_structure['keys'][$i]['key_value']; 1789 } 1790 break; 1791 1792 case 'uuid': // user-defined atom often seen containing XML data, also used for potentially many other purposes, only a few specifically handled by getID3 (e.g. 360fly spatial data) 1793 //Get the UUID ID in first 16 bytes 1794 $uuid_bytes_read = unpack('H8time_low/H4time_mid/H4time_hi/H4clock_seq_hi/H12clock_seq_low', substr($atom_data, 0, 16)); 1795 $atom_structure['uuid_field_id'] = implode('-', $uuid_bytes_read); 1796 1797 switch ($atom_structure['uuid_field_id']) { // http://fileformats.archiveteam.org/wiki/Boxes/atoms_format#UUID_boxes 1798 1799 case '0537cdab-9d0c-4431-a72a-fa561f2a113e': // Exif - http://fileformats.archiveteam.org/wiki/Exif 1800 case '2c4c0100-8504-40b9-a03e-562148d6dfeb': // Photoshop Image Resources - http://fileformats.archiveteam.org/wiki/Photoshop_Image_Resources 1801 case '33c7a4d2-b81d-4723-a0ba-f1a3e097ad38': // IPTC-IIM - http://fileformats.archiveteam.org/wiki/IPTC-IIM 1802 case '8974dbce-7be7-4c51-84f9-7148f9882554': // PIFF Track Encryption Box - http://fileformats.archiveteam.org/wiki/Protected_Interoperable_File_Format 1803 case '96a9f1f1-dc98-402d-a7ae-d68e34451809': // GeoJP2 World File Box - http://fileformats.archiveteam.org/wiki/GeoJP2 1804 case 'a2394f52-5a9b-4f14-a244-6c427c648df4': // PIFF Sample Encryption Box - http://fileformats.archiveteam.org/wiki/Protected_Interoperable_File_Format 1805 case 'b14bf8bd-083d-4b43-a5ae-8cd7d5a6ce03': // GeoJP2 GeoTIFF Box - http://fileformats.archiveteam.org/wiki/GeoJP2 1806 case 'd08a4f18-10f3-4a82-b6c8-32d8aba183d3': // PIFF Protection System Specific Header Box - http://fileformats.archiveteam.org/wiki/Protected_Interoperable_File_Format 1807 $this->warning('Unhandled (but recognized) "uuid" atom identified by "'.$atom_structure['uuid_field_id'].'" at offset '.$atom_structure['offset'].' ('.strlen($atom_data).' bytes)'); 1808 break; 1809 1810 case 'be7acfcb-97a9-42e8-9c71-999491e3afac': // XMP data (in XML format) 1811 $atom_structure['xml'] = substr($atom_data, 16, strlen($atom_data) - 16 - 8); // 16 bytes for UUID, 8 bytes header(?) 1812 break; 1813 1814 case 'efe1589a-bb77-49ef-8095-27759eb1dc6f': // 360fly data 1815 /* 360fly code in this block by Paul Lewis 2019-Oct-31 */ 1816 /* Sensor Timestamps need to be calculated using the recordings base time at ['quicktime']['moov']['subatoms'][0]['creation_time_unix']. */ 1817 $atom_structure['title'] = '360Fly Sensor Data'; 1818 1819 //Get the UUID HEADER data 1820 $uuid_bytes_read = unpack('vheader_size/vheader_version/vtimescale/vhardware_version/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/', substr($atom_data, 16, 32)); 1821 $atom_structure['uuid_header'] = $uuid_bytes_read; 1822 1823 $start_byte = 48; 1824 $atom_SENSOR_data = substr($atom_data, $start_byte); 1825 $atom_structure['sensor_data']['data_type'] = array( 1826 'fusion_count' => 0, // ID 250 1827 'fusion_data' => array(), 1828 'accel_count' => 0, // ID 1 1829 'accel_data' => array(), 1830 'gyro_count' => 0, // ID 2 1831 'gyro_data' => array(), 1832 'magno_count' => 0, // ID 3 1833 'magno_data' => array(), 1834 'gps_count' => 0, // ID 5 1835 'gps_data' => array(), 1836 'rotation_count' => 0, // ID 6 1837 'rotation_data' => array(), 1838 'unknown_count' => 0, // ID ?? 1839 'unknown_data' => array(), 1840 'debug_list' => '', // Used to debug variables stored as comma delimited strings 1841 ); 1842 $debug_structure = array(); 1843 $debug_structure['debug_items'] = array(); 1844 // Can start loop here to decode all sensor data in 32 Byte chunks: 1845 foreach (str_split($atom_SENSOR_data, 32) as $sensor_key => $sensor_data) { 1846 // This gets me a data_type code to work out what data is in the next 31 bytes. 1847 $sensor_data_type = substr($sensor_data, 0, 1); 1848 $sensor_data_content = substr($sensor_data, 1); 1849 $uuid_bytes_read = unpack('C*', $sensor_data_type); 1850 $sensor_data_array = array(); 1851 switch ($uuid_bytes_read[1]) { 1852 case 250: 1853 $atom_structure['sensor_data']['data_type']['fusion_count']++; 1854 $uuid_bytes_read = unpack('cmode/Jtimestamp/Gyaw/Gpitch/Groll/x*', $sensor_data_content); 1855 $sensor_data_array['mode'] = $uuid_bytes_read['mode']; 1856 $sensor_data_array['timestamp'] = $uuid_bytes_read['timestamp']; 1857 $sensor_data_array['yaw'] = $uuid_bytes_read['yaw']; 1858 $sensor_data_array['pitch'] = $uuid_bytes_read['pitch']; 1859 $sensor_data_array['roll'] = $uuid_bytes_read['roll']; 1860 array_push($atom_structure['sensor_data']['data_type']['fusion_data'], $sensor_data_array); 1861 break; 1862 case 1: 1863 $atom_structure['sensor_data']['data_type']['accel_count']++; 1864 $uuid_bytes_read = unpack('cmode/Jtimestamp/Gyaw/Gpitch/Groll/x*', $sensor_data_content); 1865 $sensor_data_array['mode'] = $uuid_bytes_read['mode']; 1866 $sensor_data_array['timestamp'] = $uuid_bytes_read['timestamp']; 1867 $sensor_data_array['yaw'] = $uuid_bytes_read['yaw']; 1868 $sensor_data_array['pitch'] = $uuid_bytes_read['pitch']; 1869 $sensor_data_array['roll'] = $uuid_bytes_read['roll']; 1870 array_push($atom_structure['sensor_data']['data_type']['accel_data'], $sensor_data_array); 1871 break; 1872 case 2: 1873 $atom_structure['sensor_data']['data_type']['gyro_count']++; 1874 $uuid_bytes_read = unpack('cmode/Jtimestamp/Gyaw/Gpitch/Groll/x*', $sensor_data_content); 1875 $sensor_data_array['mode'] = $uuid_bytes_read['mode']; 1876 $sensor_data_array['timestamp'] = $uuid_bytes_read['timestamp']; 1877 $sensor_data_array['yaw'] = $uuid_bytes_read['yaw']; 1878 $sensor_data_array['pitch'] = $uuid_bytes_read['pitch']; 1879 $sensor_data_array['roll'] = $uuid_bytes_read['roll']; 1880 array_push($atom_structure['sensor_data']['data_type']['gyro_data'], $sensor_data_array); 1881 break; 1882 case 3: 1883 $atom_structure['sensor_data']['data_type']['magno_count']++; 1884 $uuid_bytes_read = unpack('cmode/Jtimestamp/Gmagx/Gmagy/Gmagz/x*', $sensor_data_content); 1885 $sensor_data_array['mode'] = $uuid_bytes_read['mode']; 1886 $sensor_data_array['timestamp'] = $uuid_bytes_read['timestamp']; 1887 $sensor_data_array['magx'] = $uuid_bytes_read['magx']; 1888 $sensor_data_array['magy'] = $uuid_bytes_read['magy']; 1889 $sensor_data_array['magz'] = $uuid_bytes_read['magz']; 1890 array_push($atom_structure['sensor_data']['data_type']['magno_data'], $sensor_data_array); 1891 break; 1892 case 5: 1893 $atom_structure['sensor_data']['data_type']['gps_count']++; 1894 $uuid_bytes_read = unpack('cmode/Jtimestamp/Glat/Glon/Galt/Gspeed/nbearing/nacc/x*', $sensor_data_content); 1895 $sensor_data_array['mode'] = $uuid_bytes_read['mode']; 1896 $sensor_data_array['timestamp'] = $uuid_bytes_read['timestamp']; 1897 $sensor_data_array['lat'] = $uuid_bytes_read['lat']; 1898 $sensor_data_array['lon'] = $uuid_bytes_read['lon']; 1899 $sensor_data_array['alt'] = $uuid_bytes_read['alt']; 1900 $sensor_data_array['speed'] = $uuid_bytes_read['speed']; 1901 $sensor_data_array['bearing'] = $uuid_bytes_read['bearing']; 1902 $sensor_data_array['acc'] = $uuid_bytes_read['acc']; 1903 array_push($atom_structure['sensor_data']['data_type']['gps_data'], $sensor_data_array); 1904 //array_push($debug_structure['debug_items'], $uuid_bytes_read['timestamp']); 1905 break; 1906 case 6: 1907 $atom_structure['sensor_data']['data_type']['rotation_count']++; 1908 $uuid_bytes_read = unpack('cmode/Jtimestamp/Grotx/Groty/Grotz/x*', $sensor_data_content); 1909 $sensor_data_array['mode'] = $uuid_bytes_read['mode']; 1910 $sensor_data_array['timestamp'] = $uuid_bytes_read['timestamp']; 1911 $sensor_data_array['rotx'] = $uuid_bytes_read['rotx']; 1912 $sensor_data_array['roty'] = $uuid_bytes_read['roty']; 1913 $sensor_data_array['rotz'] = $uuid_bytes_read['rotz']; 1914 array_push($atom_structure['sensor_data']['data_type']['rotation_data'], $sensor_data_array); 1915 break; 1916 default: 1917 $atom_structure['sensor_data']['data_type']['unknown_count']++; 1918 break; 1919 } 1920 } 1921 //if (isset($debug_structure['debug_items']) && count($debug_structure['debug_items']) > 0) { 1922 // $atom_structure['sensor_data']['data_type']['debug_list'] = implode(',', $debug_structure['debug_items']); 1923 //} else { 1924 $atom_structure['sensor_data']['data_type']['debug_list'] = 'No debug items in list!'; 1925 //} 1926 break; 1927 1928 default: 1929 $this->warning('Unhandled "uuid" atom identified by "'.$atom_structure['uuid_field_id'].'" at offset '.$atom_structure['offset'].' ('.strlen($atom_data).' bytes)'); 1930 } 1931 break; 1932 1933 case 'gps ': 1934 // https://dashcamtalk.com/forum/threads/script-to-extract-gps-data-from-novatek-mp4.20808/page-2#post-291730 1935 // The 'gps ' contains simple look up table made up of 8byte rows, that point to the 'free' atoms that contains the actual GPS data. 1936 // The first row is version/metadata/notsure, I skip that. 1937 // The following rows consist of 4byte address (absolute) and 4byte size (0x1000), these point to the GPS data in the file. 1938 1939 $GPS_rowsize = 8; // 4 bytes for offset, 4 bytes for size 1940 if (strlen($atom_data) > 0) { 1941 if ((strlen($atom_data) % $GPS_rowsize) == 0) { 1942 $atom_structure['gps_toc'] = array(); 1943 foreach (str_split($atom_data, $GPS_rowsize) as $counter => $datapair) { 1944 $atom_structure['gps_toc'][] = unpack('Noffset/Nsize', substr($atom_data, $counter * $GPS_rowsize, $GPS_rowsize)); 1945 } 1946 1947 $atom_structure['gps_entries'] = array(); 1948 $previous_offset = $this->ftell(); 1949 foreach ($atom_structure['gps_toc'] as $key => $gps_pointer) { 1950 if ($key == 0) { 1951 // "The first row is version/metadata/notsure, I skip that." 1952 continue; 1953 } 1954 $this->fseek($gps_pointer['offset']); 1955 $GPS_free_data = $this->fread($gps_pointer['size']); 1956 1957 /* 1958 // 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 1959 1960 // https://dashcamtalk.com/forum/threads/script-to-extract-gps-data-from-novatek-mp4.20808/page-2#post-291730 1961 // The structure of the GPS data atom (the 'free' atoms mentioned above) is following: 1962 // hour,minute,second,year,month,day,active,latitude_b,longitude_b,unknown2,latitude,longitude,speed = struct.unpack_from('<IIIIIIssssfff',data, 48) 1963 // For those unfamiliar with python struct: 1964 // I = int 1965 // s = is string (size 1, in this case) 1966 // f = float 1967 1968 //$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)); 1969 */ 1970 1971 // $GPRMC,081836,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E*62 1972 // $GPRMC,183731,A,3907.482,N,12102.436,W,000.0,360.0,080301,015.5,E*67 1973 // $GPRMC,002454,A,3553.5295,N,13938.6570,E,0.0,43.1,180700,7.1,W,A*3F 1974 // $GPRMC,094347.000,A,5342.0061,N,00737.9908,W,0.01,156.75,140217,,,A*7D 1975 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)) { 1976 $GPS_this_GPRMC = array(); 1977 $GPS_this_GPRMC_raw = array(); 1978 list( 1979 $GPS_this_GPRMC_raw['gprmc'], 1980 $GPS_this_GPRMC_raw['timestamp'], 1981 $GPS_this_GPRMC_raw['status'], 1982 $GPS_this_GPRMC_raw['latitude'], 1983 $GPS_this_GPRMC_raw['latitude_direction'], 1984 $GPS_this_GPRMC_raw['longitude'], 1985 $GPS_this_GPRMC_raw['longitude_direction'], 1986 $GPS_this_GPRMC_raw['knots'], 1987 $GPS_this_GPRMC_raw['angle'], 1988 $GPS_this_GPRMC_raw['datestamp'], 1989 $GPS_this_GPRMC_raw['variation'], 1990 $GPS_this_GPRMC_raw['variation_direction'], 1991 $dummy, 1992 $GPS_this_GPRMC_raw['checksum'], 1993 ) = $matches; 1994 $GPS_this_GPRMC['raw'] = $GPS_this_GPRMC_raw; 1995 1996 $hour = substr($GPS_this_GPRMC['raw']['timestamp'], 0, 2); 1997 $minute = substr($GPS_this_GPRMC['raw']['timestamp'], 2, 2); 1998 $second = substr($GPS_this_GPRMC['raw']['timestamp'], 4, 2); 1999 $ms = substr($GPS_this_GPRMC['raw']['timestamp'], 6); // may contain decimal seconds 2000 $day = substr($GPS_this_GPRMC['raw']['datestamp'], 0, 2); 2001 $month = substr($GPS_this_GPRMC['raw']['datestamp'], 2, 2); 2002 $year = (int) substr($GPS_this_GPRMC['raw']['datestamp'], 4, 2); 2003 $year += (($year > 90) ? 1900 : 2000); // complete lack of foresight: datestamps are stored with 2-digit years, take best guess 2004 $GPS_this_GPRMC['timestamp'] = $year.'-'.$month.'-'.$day.' '.$hour.':'.$minute.':'.$second.$ms; 2005 2006 $GPS_this_GPRMC['active'] = ($GPS_this_GPRMC['raw']['status'] == 'A'); // A=Active,V=Void 2007 2008 foreach (array('latitude','longitude') as $latlon) { 2009 preg_match('#^([0-9]{1,3})([0-9]{2}\\.[0-9]+)$#', $GPS_this_GPRMC['raw'][$latlon], $matches); 2010 list($dummy, $deg, $min) = $matches; 2011 $GPS_this_GPRMC[$latlon] = (int) $deg + ((float) $min / 60); 2012 } 2013 $GPS_this_GPRMC['latitude'] *= (($GPS_this_GPRMC['raw']['latitude_direction'] == 'S') ? -1 : 1); 2014 $GPS_this_GPRMC['longitude'] *= (($GPS_this_GPRMC['raw']['longitude_direction'] == 'W') ? -1 : 1); 2015 2016 $GPS_this_GPRMC['heading'] = $GPS_this_GPRMC['raw']['angle']; 2017 $GPS_this_GPRMC['speed_knot'] = $GPS_this_GPRMC['raw']['knots']; 2018 $GPS_this_GPRMC['speed_kmh'] = (float) $GPS_this_GPRMC['raw']['knots'] * 1.852; 2019 if ($GPS_this_GPRMC['raw']['variation']) { 2020 $GPS_this_GPRMC['variation'] = (float) $GPS_this_GPRMC['raw']['variation']; 2021 $GPS_this_GPRMC['variation'] *= (($GPS_this_GPRMC['raw']['variation_direction'] == 'W') ? -1 : 1); 2022 } 2023 2024 $atom_structure['gps_entries'][$key] = $GPS_this_GPRMC; 2025 2026 @$info['quicktime']['gps_track'][$GPS_this_GPRMC['timestamp']] = array( 2027 'latitude' => (float) $GPS_this_GPRMC['latitude'], 2028 'longitude' => (float) $GPS_this_GPRMC['longitude'], 2029 'speed_kmh' => (float) $GPS_this_GPRMC['speed_kmh'], 2030 'heading' => (float) $GPS_this_GPRMC['heading'], 2031 ); 2032 2033 } else { 2034 $this->warning('Unhandled GPS format in "free" atom at offset '.$gps_pointer['offset']); 2035 } 2036 } 2037 $this->fseek($previous_offset); 2038 2039 } else { 2040 $this->warning('QuickTime atom "'.$atomname.'" is not mod-8 bytes long ('.$atomsize.' bytes) at offset '.$baseoffset); 2041 } 2042 } else { 2043 $this->warning('QuickTime atom "'.$atomname.'" is zero bytes long at offset '.$baseoffset); 2044 } 2045 break; 2046 2047 case 'loci':// 3GP location (El Loco) 2048 $loffset = 0; 2049 $info['quicktime']['comments']['gps_flags'] = array( getid3_lib::BigEndian2Int(substr($atom_data, 0, 4))); 2050 $info['quicktime']['comments']['gps_lang'] = array( getid3_lib::BigEndian2Int(substr($atom_data, 4, 2))); 2051 $info['quicktime']['comments']['gps_location'] = array( $this->LociString(substr($atom_data, 6), $loffset)); 2052 $loci_data = substr($atom_data, 6 + $loffset); 2053 $info['quicktime']['comments']['gps_role'] = array( getid3_lib::BigEndian2Int(substr($loci_data, 0, 1))); 2054 $info['quicktime']['comments']['gps_longitude'] = array(getid3_lib::FixedPoint16_16(substr($loci_data, 1, 4))); 2055 $info['quicktime']['comments']['gps_latitude'] = array(getid3_lib::FixedPoint16_16(substr($loci_data, 5, 4))); 2056 $info['quicktime']['comments']['gps_altitude'] = array(getid3_lib::FixedPoint16_16(substr($loci_data, 9, 4))); 2057 $info['quicktime']['comments']['gps_body'] = array( $this->LociString(substr($loci_data, 13 ), $loffset)); 2058 $info['quicktime']['comments']['gps_notes'] = array( $this->LociString(substr($loci_data, 13 + $loffset), $loffset)); 2059 break; 2060 2061 case 'chpl': // CHaPter List 2062 // https://www.adobe.com/content/dam/Adobe/en/devnet/flv/pdfs/video_file_format_spec_v10.pdf 2063 $chpl_version = getid3_lib::BigEndian2Int(substr($atom_data, 4, 1)); // Expected to be 0 2064 $chpl_flags = getid3_lib::BigEndian2Int(substr($atom_data, 5, 3)); // Reserved, set to 0 2065 $chpl_count = getid3_lib::BigEndian2Int(substr($atom_data, 8, 1)); 2066 $chpl_offset = 9; 2067 for ($i = 0; $i < $chpl_count; $i++) { 2068 if (($chpl_offset + 9) >= strlen($atom_data)) { 2069 $this->warning('QuickTime chapter '.$i.' extends beyond end of "chpl" atom'); 2070 break; 2071 } 2072 $info['quicktime']['chapters'][$i]['timestamp'] = getid3_lib::BigEndian2Int(substr($atom_data, $chpl_offset, 8)) / 10000000; // timestamps are stored as 100-nanosecond units 2073 $chpl_offset += 8; 2074 $chpl_title_size = getid3_lib::BigEndian2Int(substr($atom_data, $chpl_offset, 1)); 2075 $chpl_offset += 1; 2076 $info['quicktime']['chapters'][$i]['title'] = substr($atom_data, $chpl_offset, $chpl_title_size); 2077 $chpl_offset += $chpl_title_size; 2078 } 2079 break; 2080 2081 case 'FIRM': // FIRMware version(?), seen on GoPro Hero4 2082 $info['quicktime']['camera']['firmware'] = $atom_data; 2083 break; 2084 2085 case 'CAME': // FIRMware version(?), seen on GoPro Hero4 2086 $info['quicktime']['camera']['serial_hash'] = unpack('H*', $atom_data); 2087 break; 2088 2089 case 'dscp': 2090 case 'rcif': 2091 // https://www.getid3.org/phpBB3/viewtopic.php?t=1908 2092 if (substr($atom_data, 0, 7) == "\x00\x00\x00\x00\x55\xC4".'{') { 2093 if ($json_decoded = @json_decode(rtrim(substr($atom_data, 6), "\x00"), true)) { 2094 $info['quicktime']['camera'][$atomname] = $json_decoded; 2095 if (($atomname == 'rcif') && isset($info['quicktime']['camera']['rcif']['wxcamera']['rotate'])) { 2096 $info['video']['rotate'] = $info['quicktime']['video']['rotate'] = $info['quicktime']['camera']['rcif']['wxcamera']['rotate']; 2097 } 2098 } else { 2099 $this->warning('Failed to JSON decode atom "'.$atomname.'"'); 2100 $atom_structure['data'] = $atom_data; 2101 } 2102 unset($json_decoded); 2103 } else { 2104 $this->warning('Expecting 55 C4 7B at start of atom "'.$atomname.'", found '.getid3_lib::PrintHexBytes(substr($atom_data, 4, 3)).' instead'); 2105 $atom_structure['data'] = $atom_data; 2106 } 2107 break; 2108 2109 case 'frea': 2110 // https://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Kodak.html#frea 2111 // may contain "scra" (PreviewImage) and/or "thma" (ThumbnailImage) 2112 $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 4, $atomHierarchy, $ParseAllPossibleAtoms); 2113 break; 2114 case 'tima': // subatom to "frea" 2115 // no idea what this does, the one sample file I've seen has a value of 0x00000027 2116 $atom_structure['data'] = $atom_data; 2117 break; 2118 case 'ver ': // subatom to "frea" 2119 // some kind of version number, the one sample file I've seen has a value of "3.00.073" 2120 $atom_structure['data'] = $atom_data; 2121 break; 2122 case 'thma': // subatom to "frea" -- "ThumbnailImage" 2123 // https://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Kodak.html#frea 2124 if (strlen($atom_data) > 0) { 2125 $info['quicktime']['comments']['picture'][] = array('data'=>$atom_data, 'image_mime'=>'image/jpeg', 'description'=>'ThumbnailImage'); 2126 } 2127 break; 2128 case 'scra': // subatom to "frea" -- "PreviewImage" 2129 // https://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Kodak.html#frea 2130 // but the only sample file I've seen has no useful data here 2131 if (strlen($atom_data) > 0) { 2132 $info['quicktime']['comments']['picture'][] = array('data'=>$atom_data, 'image_mime'=>'image/jpeg', 'description'=>'PreviewImage'); 2133 } 2134 break; 2135 2136 case 'cdsc': // timed metadata reference 2137 // A QuickTime movie can contain none, one, or several timed metadata tracks. Timed metadata tracks can refer to multiple tracks. 2138 // Metadata tracks are linked to the tracks they describe using a track-reference of type 'cdsc'. The metadata track holds the 'cdsc' track reference. 2139 $atom_structure['track_number'] = getid3_lib::BigEndian2Int($atom_data); 2140 break; 2141 2142 2143 case 'esds': // Elementary Stream DeScriptor 2144 // https://github.com/JamesHeinrich/getID3/issues/414 2145 // https://chromium.googlesource.com/chromium/src/media/+/refs/heads/main/formats/mp4/es_descriptor.cc 2146 // https://chromium.googlesource.com/chromium/src/media/+/refs/heads/main/formats/mp4/es_descriptor.h 2147 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); // hardcoded: 0x00 2148 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x000000 2149 $esds_offset = 4; 2150 2151 $atom_structure['ES_DescrTag'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 1)); 2152 $esds_offset += 1; 2153 if ($atom_structure['ES_DescrTag'] != 0x03) { 2154 $this->warning('expecting esds.ES_DescrTag = 0x03, found 0x'.sprintf('%02X', $atom_structure['ES_DescrTag']).', at offset '.$atom_structure['offset']); 2155 break; 2156 } 2157 $atom_structure['ES_DescrSize'] = $this->quicktime_read_mp4_descr_length($atom_data, $esds_offset); 2158 2159 $atom_structure['ES_ID'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 2)); 2160 $esds_offset += 2; 2161 $atom_structure['ES_flagsraw'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 1)); 2162 $esds_offset += 1; 2163 $atom_structure['ES_flags']['stream_dependency'] = (bool) ($atom_structure['ES_flagsraw'] & 0x80); 2164 $atom_structure['ES_flags']['url_flag'] = (bool) ($atom_structure['ES_flagsraw'] & 0x40); 2165 $atom_structure['ES_flags']['ocr_stream'] = (bool) ($atom_structure['ES_flagsraw'] & 0x20); 2166 $atom_structure['ES_stream_priority'] = ($atom_structure['ES_flagsraw'] & 0x1F); 2167 if ($atom_structure['ES_flags']['url_flag']) { 2168 $this->warning('Unsupported esds.url_flag enabled at offset '.$atom_structure['offset']); 2169 break; 2170 } 2171 if ($atom_structure['ES_flags']['stream_dependency']) { 2172 $atom_structure['ES_dependsOn_ES_ID'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 2)); 2173 $esds_offset += 2; 2174 } 2175 if ($atom_structure['ES_flags']['ocr_stream']) { 2176 $atom_structure['ES_OCR_ES_Id'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 2)); 2177 $esds_offset += 2; 2178 } 2179 2180 $atom_structure['ES_DecoderConfigDescrTag'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 1)); 2181 $esds_offset += 1; 2182 if ($atom_structure['ES_DecoderConfigDescrTag'] != 0x04) { 2183 $this->warning('expecting esds.ES_DecoderConfigDescrTag = 0x04, found 0x'.sprintf('%02X', $atom_structure['ES_DecoderConfigDescrTag']).', at offset '.$atom_structure['offset']); 2184 break; 2185 } 2186 $atom_structure['ES_DecoderConfigDescrTagSize'] = $this->quicktime_read_mp4_descr_length($atom_data, $esds_offset); 2187 2188 $atom_structure['ES_objectTypeIndication'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 1)); 2189 $esds_offset += 1; 2190 // https://stackoverflow.com/questions/3987850 2191 // 0x40 = "Audio ISO/IEC 14496-3" = MPEG-4 Audio 2192 // 0x67 = "Audio ISO/IEC 13818-7 LowComplexity Profile" = MPEG-2 AAC LC 2193 // 0x69 = "Audio ISO/IEC 13818-3" = MPEG-2 Backward Compatible Audio (MPEG-2 Layers 1, 2, and 3) 2194 // 0x6B = "Audio ISO/IEC 11172-3" = MPEG-1 Audio (MPEG-1 Layers 1, 2, and 3) 2195 2196 $streamTypePlusFlags = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 1)); 2197 $esds_offset += 1; 2198 $atom_structure['ES_streamType'] = ($streamTypePlusFlags & 0xFC) >> 2; 2199 $atom_structure['ES_upStream'] = (bool) ($streamTypePlusFlags & 0x02) >> 1; 2200 $atom_structure['ES_bufferSizeDB'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 3)); 2201 $esds_offset += 3; 2202 $atom_structure['ES_maxBitrate'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 4)); 2203 $esds_offset += 4; 2204 $atom_structure['ES_avgBitrate'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 4)); 2205 $esds_offset += 4; 2206 if ($atom_structure['ES_avgBitrate']) { 2207 $info['quicktime']['audio']['bitrate'] = $atom_structure['ES_avgBitrate']; 2208 $info['audio']['bitrate'] = $atom_structure['ES_avgBitrate']; 2209 } 2210 2211 $atom_structure['ES_DecSpecificInfoTag'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 1)); 2212 $esds_offset += 1; 2213 if ($atom_structure['ES_DecSpecificInfoTag'] != 0x05) { 2214 $this->warning('expecting esds.ES_DecSpecificInfoTag = 0x05, found 0x'.sprintf('%02X', $atom_structure['ES_DecSpecificInfoTag']).', at offset '.$atom_structure['offset']); 2215 break; 2216 } 2217 $atom_structure['ES_DecSpecificInfoTagSize'] = $this->quicktime_read_mp4_descr_length($atom_data, $esds_offset); 2218 2219 $atom_structure['ES_DecSpecificInfo'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, $atom_structure['ES_DecSpecificInfoTagSize'])); 2220 $esds_offset += $atom_structure['ES_DecSpecificInfoTagSize']; 2221 2222 $atom_structure['ES_SLConfigDescrTag'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 1)); 2223 $esds_offset += 1; 2224 if ($atom_structure['ES_SLConfigDescrTag'] != 0x06) { 2225 $this->warning('expecting esds.ES_SLConfigDescrTag = 0x05, found 0x'.sprintf('%02X', $atom_structure['ES_SLConfigDescrTag']).', at offset '.$atom_structure['offset']); 2226 break; 2227 } 2228 $atom_structure['ES_SLConfigDescrTagSize'] = $this->quicktime_read_mp4_descr_length($atom_data, $esds_offset); 2229 2230 $atom_structure['ES_SLConfigDescr'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, $atom_structure['ES_SLConfigDescrTagSize'])); 2231 $esds_offset += $atom_structure['ES_SLConfigDescrTagSize']; 2232 break; 2233 2234 // AVIF-related - https://docs.rs/avif-parse/0.13.2/src/avif_parse/boxes.rs.html 2235 case 'pitm': // Primary ITeM 2236 case 'iloc': // Item LOCation 2237 case 'iinf': // Item INFo 2238 case 'iref': // Image REFerence 2239 case 'iprp': // Image PRoPerties 2240 $this->error('AVIF files not currently supported'); 2241 $atom_structure['data'] = $atom_data; 2242 break; 2243 2244 case 'tfdt': // Track Fragment base media Decode Time box 2245 case 'tfhd': // Track Fragment HeaDer box 2246 case 'mfhd': // Movie Fragment HeaDer box 2247 case 'trun': // Track fragment RUN box 2248 $this->error('fragmented mp4 files not currently supported'); 2249 $atom_structure['data'] = $atom_data; 2250 break; 2251 2252 case 'mvex': // MoVie EXtends box 2253 case 'pssh': // Protection System Specific Header box 2254 case 'sidx': // Segment InDeX box 2255 default: 2256 $this->warning('Unknown QuickTime atom type: "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $atomname).'" ('.trim(getid3_lib::PrintHexBytes($atomname)).'), '.$atomsize.' bytes at offset '.$baseoffset); 2257 $atom_structure['data'] = $atom_data; 2258 break; 2259 } 2260 } 2261 array_pop($atomHierarchy); 2262 return $atom_structure; 2263 } 2264 2265 /** 2266 * @param string $atom_data 2267 * @param int $baseoffset 2268 * @param array $atomHierarchy 2269 * @param bool $ParseAllPossibleAtoms 2270 * 2271 * @return array|false 2272 */ 2273 public function QuicktimeParseContainerAtom($atom_data, $baseoffset, &$atomHierarchy, $ParseAllPossibleAtoms) { 2274 $atom_structure = array(); 2275 $subatomoffset = 0; 2276 $subatomcounter = 0; 2277 if ((strlen($atom_data) == 4) && (getid3_lib::BigEndian2Int($atom_data) == 0x00000000)) { 2278 return false; 2279 } 2280 while ($subatomoffset < strlen($atom_data)) { 2281 $subatomsize = getid3_lib::BigEndian2Int(substr($atom_data, $subatomoffset + 0, 4)); 2282 $subatomname = substr($atom_data, $subatomoffset + 4, 4); 2283 $subatomdata = substr($atom_data, $subatomoffset + 8, $subatomsize - 8); 2284 if ($subatomsize == 0) { 2285 // Furthermore, for historical reasons the list of atoms is optionally 2286 // terminated by a 32-bit integer set to 0. If you are writing a program 2287 // to read user data atoms, you should allow for the terminating 0. 2288 if (strlen($atom_data) > 12) { 2289 $subatomoffset += 4; 2290 continue; 2291 } 2292 break; 2293 } 2294 if (strlen($subatomdata) < ($subatomsize - 8)) { 2295 // we don't have enough data to decode the subatom. 2296 // this may be because we are refusing to parse large subatoms, or it may be because this atom had its size set too large 2297 // so we passed in the start of a following atom incorrectly? 2298 break; 2299 } 2300 $atom_structure[$subatomcounter++] = $this->QuicktimeParseAtom($subatomname, $subatomsize, $subatomdata, $baseoffset + $subatomoffset, $atomHierarchy, $ParseAllPossibleAtoms); 2301 $subatomoffset += $subatomsize; 2302 } 2303 2304 if (empty($atom_structure)) { 2305 return false; 2306 } 2307 2308 return $atom_structure; 2309 } 2310 2311 /** 2312 * @param string $data 2313 * @param int $offset 2314 * 2315 * @return int 2316 */ 2317 public function quicktime_read_mp4_descr_length($data, &$offset) { 2318 // http://libquicktime.sourcearchive.com/documentation/2:1.0.2plus-pdebian-2build1/esds_8c-source.html 2319 $num_bytes = 0; 2320 $length = 0; 2321 do { 2322 $b = ord(substr($data, $offset++, 1)); 2323 $length = ($length << 7) | ($b & 0x7F); 2324 } while (($b & 0x80) && ($num_bytes++ < 4)); 2325 return $length; 2326 } 2327 2328 /** 2329 * @param int $languageid 2330 * 2331 * @return string 2332 */ 2333 public function QuicktimeLanguageLookup($languageid) { 2334 // http://developer.apple.com/library/mac/#documentation/QuickTime/QTFF/QTFFChap4/qtff4.html#//apple_ref/doc/uid/TP40000939-CH206-34353 2335 static $QuicktimeLanguageLookup = array(); 2336 if (empty($QuicktimeLanguageLookup)) { 2337 $QuicktimeLanguageLookup[0] = 'English'; 2338 $QuicktimeLanguageLookup[1] = 'French'; 2339 $QuicktimeLanguageLookup[2] = 'German'; 2340 $QuicktimeLanguageLookup[3] = 'Italian'; 2341 $QuicktimeLanguageLookup[4] = 'Dutch'; 2342 $QuicktimeLanguageLookup[5] = 'Swedish'; 2343 $QuicktimeLanguageLookup[6] = 'Spanish'; 2344 $QuicktimeLanguageLookup[7] = 'Danish'; 2345 $QuicktimeLanguageLookup[8] = 'Portuguese'; 2346 $QuicktimeLanguageLookup[9] = 'Norwegian'; 2347 $QuicktimeLanguageLookup[10] = 'Hebrew'; 2348 $QuicktimeLanguageLookup[11] = 'Japanese'; 2349 $QuicktimeLanguageLookup[12] = 'Arabic'; 2350 $QuicktimeLanguageLookup[13] = 'Finnish'; 2351 $QuicktimeLanguageLookup[14] = 'Greek'; 2352 $QuicktimeLanguageLookup[15] = 'Icelandic'; 2353 $QuicktimeLanguageLookup[16] = 'Maltese'; 2354 $QuicktimeLanguageLookup[17] = 'Turkish'; 2355 $QuicktimeLanguageLookup[18] = 'Croatian'; 2356 $QuicktimeLanguageLookup[19] = 'Chinese (Traditional)'; 2357 $QuicktimeLanguageLookup[20] = 'Urdu'; 2358 $QuicktimeLanguageLookup[21] = 'Hindi'; 2359 $QuicktimeLanguageLookup[22] = 'Thai'; 2360 $QuicktimeLanguageLookup[23] = 'Korean'; 2361 $QuicktimeLanguageLookup[24] = 'Lithuanian'; 2362 $QuicktimeLanguageLookup[25] = 'Polish'; 2363 $QuicktimeLanguageLookup[26] = 'Hungarian'; 2364 $QuicktimeLanguageLookup[27] = 'Estonian'; 2365 $QuicktimeLanguageLookup[28] = 'Lettish'; 2366 $QuicktimeLanguageLookup[28] = 'Latvian'; 2367 $QuicktimeLanguageLookup[29] = 'Saamisk'; 2368 $QuicktimeLanguageLookup[29] = 'Lappish'; 2369 $QuicktimeLanguageLookup[30] = 'Faeroese'; 2370 $QuicktimeLanguageLookup[31] = 'Farsi'; 2371 $QuicktimeLanguageLookup[31] = 'Persian'; 2372 $QuicktimeLanguageLookup[32] = 'Russian'; 2373 $QuicktimeLanguageLookup[33] = 'Chinese (Simplified)'; 2374 $QuicktimeLanguageLookup[34] = 'Flemish'; 2375 $QuicktimeLanguageLookup[35] = 'Irish'; 2376 $QuicktimeLanguageLookup[36] = 'Albanian'; 2377 $QuicktimeLanguageLookup[37] = 'Romanian'; 2378 $QuicktimeLanguageLookup[38] = 'Czech'; 2379 $QuicktimeLanguageLookup[39] = 'Slovak'; 2380 $QuicktimeLanguageLookup[40] = 'Slovenian'; 2381 $QuicktimeLanguageLookup[41] = 'Yiddish'; 2382 $QuicktimeLanguageLookup[42] = 'Serbian'; 2383 $QuicktimeLanguageLookup[43] = 'Macedonian'; 2384 $QuicktimeLanguageLookup[44] = 'Bulgarian'; 2385 $QuicktimeLanguageLookup[45] = 'Ukrainian'; 2386 $QuicktimeLanguageLookup[46] = 'Byelorussian'; 2387 $QuicktimeLanguageLookup[47] = 'Uzbek'; 2388 $QuicktimeLanguageLookup[48] = 'Kazakh'; 2389 $QuicktimeLanguageLookup[49] = 'Azerbaijani'; 2390 $QuicktimeLanguageLookup[50] = 'AzerbaijanAr'; 2391 $QuicktimeLanguageLookup[51] = 'Armenian'; 2392 $QuicktimeLanguageLookup[52] = 'Georgian'; 2393 $QuicktimeLanguageLookup[53] = 'Moldavian'; 2394 $QuicktimeLanguageLookup[54] = 'Kirghiz'; 2395 $QuicktimeLanguageLookup[55] = 'Tajiki'; 2396 $QuicktimeLanguageLookup[56] = 'Turkmen'; 2397 $QuicktimeLanguageLookup[57] = 'Mongolian'; 2398 $QuicktimeLanguageLookup[58] = 'MongolianCyr'; 2399 $QuicktimeLanguageLookup[59] = 'Pashto'; 2400 $QuicktimeLanguageLookup[60] = 'Kurdish'; 2401 $QuicktimeLanguageLookup[61] = 'Kashmiri'; 2402 $QuicktimeLanguageLookup[62] = 'Sindhi'; 2403 $QuicktimeLanguageLookup[63] = 'Tibetan'; 2404 $QuicktimeLanguageLookup[64] = 'Nepali'; 2405 $QuicktimeLanguageLookup[65] = 'Sanskrit'; 2406 $QuicktimeLanguageLookup[66] = 'Marathi'; 2407 $QuicktimeLanguageLookup[67] = 'Bengali'; 2408 $QuicktimeLanguageLookup[68] = 'Assamese'; 2409 $QuicktimeLanguageLookup[69] = 'Gujarati'; 2410 $QuicktimeLanguageLookup[70] = 'Punjabi'; 2411 $QuicktimeLanguageLookup[71] = 'Oriya'; 2412 $QuicktimeLanguageLookup[72] = 'Malayalam'; 2413 $QuicktimeLanguageLookup[73] = 'Kannada'; 2414 $QuicktimeLanguageLookup[74] = 'Tamil'; 2415 $QuicktimeLanguageLookup[75] = 'Telugu'; 2416 $QuicktimeLanguageLookup[76] = 'Sinhalese'; 2417 $QuicktimeLanguageLookup[77] = 'Burmese'; 2418 $QuicktimeLanguageLookup[78] = 'Khmer'; 2419 $QuicktimeLanguageLookup[79] = 'Lao'; 2420 $QuicktimeLanguageLookup[80] = 'Vietnamese'; 2421 $QuicktimeLanguageLookup[81] = 'Indonesian'; 2422 $QuicktimeLanguageLookup[82] = 'Tagalog'; 2423 $QuicktimeLanguageLookup[83] = 'MalayRoman'; 2424 $QuicktimeLanguageLookup[84] = 'MalayArabic'; 2425 $QuicktimeLanguageLookup[85] = 'Amharic'; 2426 $QuicktimeLanguageLookup[86] = 'Tigrinya'; 2427 $QuicktimeLanguageLookup[87] = 'Galla'; 2428 $QuicktimeLanguageLookup[87] = 'Oromo'; 2429 $QuicktimeLanguageLookup[88] = 'Somali'; 2430 $QuicktimeLanguageLookup[89] = 'Swahili'; 2431 $QuicktimeLanguageLookup[90] = 'Ruanda'; 2432 $QuicktimeLanguageLookup[91] = 'Rundi'; 2433 $QuicktimeLanguageLookup[92] = 'Chewa'; 2434 $QuicktimeLanguageLookup[93] = 'Malagasy'; 2435 $QuicktimeLanguageLookup[94] = 'Esperanto'; 2436 $QuicktimeLanguageLookup[128] = 'Welsh'; 2437 $QuicktimeLanguageLookup[129] = 'Basque'; 2438 $QuicktimeLanguageLookup[130] = 'Catalan'; 2439 $QuicktimeLanguageLookup[131] = 'Latin'; 2440 $QuicktimeLanguageLookup[132] = 'Quechua'; 2441 $QuicktimeLanguageLookup[133] = 'Guarani'; 2442 $QuicktimeLanguageLookup[134] = 'Aymara'; 2443 $QuicktimeLanguageLookup[135] = 'Tatar'; 2444 $QuicktimeLanguageLookup[136] = 'Uighur'; 2445 $QuicktimeLanguageLookup[137] = 'Dzongkha'; 2446 $QuicktimeLanguageLookup[138] = 'JavaneseRom'; 2447 $QuicktimeLanguageLookup[32767] = 'Unspecified'; 2448 } 2449 if (($languageid > 138) && ($languageid < 32767)) { 2450 /* 2451 ISO Language Codes - http://www.loc.gov/standards/iso639-2/php/code_list.php 2452 Because the language codes specified by ISO 639-2/T are three characters long, they must be packed to fit into a 16-bit field. 2453 The packing algorithm must map each of the three characters, which are always lowercase, into a 5-bit integer and then concatenate 2454 these integers into the least significant 15 bits of a 16-bit integer, leaving the 16-bit integer's most significant bit set to zero. 2455 2456 One algorithm for performing this packing is to treat each ISO character as a 16-bit integer. Subtract 0x60 from the first character 2457 and multiply by 2^10 (0x400), subtract 0x60 from the second character and multiply by 2^5 (0x20), subtract 0x60 from the third character, 2458 and add the three 16-bit values. This will result in a single 16-bit value with the three codes correctly packed into the 15 least 2459 significant bits and the most significant bit set to zero. 2460 */ 2461 $iso_language_id = ''; 2462 $iso_language_id .= chr((($languageid & 0x7C00) >> 10) + 0x60); 2463 $iso_language_id .= chr((($languageid & 0x03E0) >> 5) + 0x60); 2464 $iso_language_id .= chr((($languageid & 0x001F) >> 0) + 0x60); 2465 $QuicktimeLanguageLookup[$languageid] = getid3_id3v2::LanguageLookup($iso_language_id); 2466 } 2467 return (isset($QuicktimeLanguageLookup[$languageid]) ? $QuicktimeLanguageLookup[$languageid] : 'invalid'); 2468 } 2469 2470 /** 2471 * @param string $codecid 2472 * 2473 * @return string 2474 */ 2475 public function QuicktimeVideoCodecLookup($codecid) { 2476 static $QuicktimeVideoCodecLookup = array(); 2477 if (empty($QuicktimeVideoCodecLookup)) { 2478 $QuicktimeVideoCodecLookup['.SGI'] = 'SGI'; 2479 $QuicktimeVideoCodecLookup['3IV1'] = '3ivx MPEG-4 v1'; 2480 $QuicktimeVideoCodecLookup['3IV2'] = '3ivx MPEG-4 v2'; 2481 $QuicktimeVideoCodecLookup['3IVX'] = '3ivx MPEG-4'; 2482 $QuicktimeVideoCodecLookup['8BPS'] = 'Planar RGB'; 2483 $QuicktimeVideoCodecLookup['avc1'] = 'H.264/MPEG-4 AVC'; 2484 $QuicktimeVideoCodecLookup['avr '] = 'AVR-JPEG'; 2485 $QuicktimeVideoCodecLookup['b16g'] = '16Gray'; 2486 $QuicktimeVideoCodecLookup['b32a'] = '32AlphaGray'; 2487 $QuicktimeVideoCodecLookup['b48r'] = '48RGB'; 2488 $QuicktimeVideoCodecLookup['b64a'] = '64ARGB'; 2489 $QuicktimeVideoCodecLookup['base'] = 'Base'; 2490 $QuicktimeVideoCodecLookup['clou'] = 'Cloud'; 2491 $QuicktimeVideoCodecLookup['cmyk'] = 'CMYK'; 2492 $QuicktimeVideoCodecLookup['cvid'] = 'Cinepak'; 2493 $QuicktimeVideoCodecLookup['dmb1'] = 'OpenDML JPEG'; 2494 $QuicktimeVideoCodecLookup['dvc '] = 'DVC-NTSC'; 2495 $QuicktimeVideoCodecLookup['dvcp'] = 'DVC-PAL'; 2496 $QuicktimeVideoCodecLookup['dvpn'] = 'DVCPro-NTSC'; 2497 $QuicktimeVideoCodecLookup['dvpp'] = 'DVCPro-PAL'; 2498 $QuicktimeVideoCodecLookup['fire'] = 'Fire'; 2499 $QuicktimeVideoCodecLookup['flic'] = 'FLC'; 2500 $QuicktimeVideoCodecLookup['gif '] = 'GIF'; 2501 $QuicktimeVideoCodecLookup['h261'] = 'H261'; 2502 $QuicktimeVideoCodecLookup['h263'] = 'H263'; 2503 $QuicktimeVideoCodecLookup['hvc1'] = 'H.265/HEVC'; 2504 $QuicktimeVideoCodecLookup['IV41'] = 'Indeo4'; 2505 $QuicktimeVideoCodecLookup['jpeg'] = 'JPEG'; 2506 $QuicktimeVideoCodecLookup['kpcd'] = 'PhotoCD'; 2507 $QuicktimeVideoCodecLookup['mjpa'] = 'Motion JPEG-A'; 2508 $QuicktimeVideoCodecLookup['mjpb'] = 'Motion JPEG-B'; 2509 $QuicktimeVideoCodecLookup['msvc'] = 'Microsoft Video1'; 2510 $QuicktimeVideoCodecLookup['myuv'] = 'MPEG YUV420'; 2511 $QuicktimeVideoCodecLookup['path'] = 'Vector'; 2512 $QuicktimeVideoCodecLookup['png '] = 'PNG'; 2513 $QuicktimeVideoCodecLookup['PNTG'] = 'MacPaint'; 2514 $QuicktimeVideoCodecLookup['qdgx'] = 'QuickDrawGX'; 2515 $QuicktimeVideoCodecLookup['qdrw'] = 'QuickDraw'; 2516 $QuicktimeVideoCodecLookup['raw '] = 'RAW'; 2517 $QuicktimeVideoCodecLookup['ripl'] = 'WaterRipple'; 2518 $QuicktimeVideoCodecLookup['rpza'] = 'Video'; 2519 $QuicktimeVideoCodecLookup['smc '] = 'Graphics'; 2520 $QuicktimeVideoCodecLookup['SVQ1'] = 'Sorenson Video 1'; 2521 $QuicktimeVideoCodecLookup['SVQ1'] = 'Sorenson Video 3'; 2522 $QuicktimeVideoCodecLookup['syv9'] = 'Sorenson YUV9'; 2523 $QuicktimeVideoCodecLookup['tga '] = 'Targa'; 2524 $QuicktimeVideoCodecLookup['tiff'] = 'TIFF'; 2525 $QuicktimeVideoCodecLookup['WRAW'] = 'Windows RAW'; 2526 $QuicktimeVideoCodecLookup['WRLE'] = 'BMP'; 2527 $QuicktimeVideoCodecLookup['y420'] = 'YUV420'; 2528 $QuicktimeVideoCodecLookup['yuv2'] = 'ComponentVideo'; 2529 $QuicktimeVideoCodecLookup['yuvs'] = 'ComponentVideoUnsigned'; 2530 $QuicktimeVideoCodecLookup['yuvu'] = 'ComponentVideoSigned'; 2531 } 2532 return (isset($QuicktimeVideoCodecLookup[$codecid]) ? $QuicktimeVideoCodecLookup[$codecid] : ''); 2533 } 2534 2535 /** 2536 * @param string $codecid 2537 * 2538 * @return mixed|string 2539 */ 2540 public function QuicktimeAudioCodecLookup($codecid) { 2541 static $QuicktimeAudioCodecLookup = array(); 2542 if (empty($QuicktimeAudioCodecLookup)) { 2543 $QuicktimeAudioCodecLookup['.mp3'] = 'Fraunhofer MPEG Layer-III alias'; 2544 $QuicktimeAudioCodecLookup['aac '] = 'ISO/IEC 14496-3 AAC'; 2545 $QuicktimeAudioCodecLookup['agsm'] = 'Apple GSM 10:1'; 2546 $QuicktimeAudioCodecLookup['alac'] = 'Apple Lossless Audio Codec'; 2547 $QuicktimeAudioCodecLookup['alaw'] = 'A-law 2:1'; 2548 $QuicktimeAudioCodecLookup['conv'] = 'Sample Format'; 2549 $QuicktimeAudioCodecLookup['dvca'] = 'DV'; 2550 $QuicktimeAudioCodecLookup['dvi '] = 'DV 4:1'; 2551 $QuicktimeAudioCodecLookup['eqal'] = 'Frequency Equalizer'; 2552 $QuicktimeAudioCodecLookup['fl32'] = '32-bit Floating Point'; 2553 $QuicktimeAudioCodecLookup['fl64'] = '64-bit Floating Point'; 2554 $QuicktimeAudioCodecLookup['ima4'] = 'Interactive Multimedia Association 4:1'; 2555 $QuicktimeAudioCodecLookup['in24'] = '24-bit Integer'; 2556 $QuicktimeAudioCodecLookup['in32'] = '32-bit Integer'; 2557 $QuicktimeAudioCodecLookup['lpc '] = 'LPC 23:1'; 2558 $QuicktimeAudioCodecLookup['MAC3'] = 'Macintosh Audio Compression/Expansion (MACE) 3:1'; 2559 $QuicktimeAudioCodecLookup['MAC6'] = 'Macintosh Audio Compression/Expansion (MACE) 6:1'; 2560 $QuicktimeAudioCodecLookup['mixb'] = '8-bit Mixer'; 2561 $QuicktimeAudioCodecLookup['mixw'] = '16-bit Mixer'; 2562 $QuicktimeAudioCodecLookup['mp4a'] = 'ISO/IEC 14496-3 AAC'; 2563 $QuicktimeAudioCodecLookup['MS'."\x00\x02"] = 'Microsoft ADPCM'; 2564 $QuicktimeAudioCodecLookup['MS'."\x00\x11"] = 'DV IMA'; 2565 $QuicktimeAudioCodecLookup['MS'."\x00\x55"] = 'Fraunhofer MPEG Layer III'; 2566 $QuicktimeAudioCodecLookup['NONE'] = 'No Encoding'; 2567 $QuicktimeAudioCodecLookup['Qclp'] = 'Qualcomm PureVoice'; 2568 $QuicktimeAudioCodecLookup['QDM2'] = 'QDesign Music 2'; 2569 $QuicktimeAudioCodecLookup['QDMC'] = 'QDesign Music 1'; 2570 $QuicktimeAudioCodecLookup['ratb'] = '8-bit Rate'; 2571 $QuicktimeAudioCodecLookup['ratw'] = '16-bit Rate'; 2572 $QuicktimeAudioCodecLookup['raw '] = 'raw PCM'; 2573 $QuicktimeAudioCodecLookup['sour'] = 'Sound Source'; 2574 $QuicktimeAudioCodecLookup['sowt'] = 'signed/two\'s complement (Little Endian)'; 2575 $QuicktimeAudioCodecLookup['str1'] = 'Iomega MPEG layer II'; 2576 $QuicktimeAudioCodecLookup['str2'] = 'Iomega MPEG *layer II'; 2577 $QuicktimeAudioCodecLookup['str3'] = 'Iomega MPEG **layer II'; 2578 $QuicktimeAudioCodecLookup['str4'] = 'Iomega MPEG ***layer II'; 2579 $QuicktimeAudioCodecLookup['twos'] = 'signed/two\'s complement (Big Endian)'; 2580 $QuicktimeAudioCodecLookup['ulaw'] = 'mu-law 2:1'; 2581 } 2582 return (isset($QuicktimeAudioCodecLookup[$codecid]) ? $QuicktimeAudioCodecLookup[$codecid] : ''); 2583 } 2584 2585 /** 2586 * @param string $compressionid 2587 * 2588 * @return string 2589 */ 2590 public function QuicktimeDCOMLookup($compressionid) { 2591 static $QuicktimeDCOMLookup = array(); 2592 if (empty($QuicktimeDCOMLookup)) { 2593 $QuicktimeDCOMLookup['zlib'] = 'ZLib Deflate'; 2594 $QuicktimeDCOMLookup['adec'] = 'Apple Compression'; 2595 } 2596 return (isset($QuicktimeDCOMLookup[$compressionid]) ? $QuicktimeDCOMLookup[$compressionid] : ''); 2597 } 2598 2599 /** 2600 * @param int $colordepthid 2601 * 2602 * @return string 2603 */ 2604 public function QuicktimeColorNameLookup($colordepthid) { 2605 static $QuicktimeColorNameLookup = array(); 2606 if (empty($QuicktimeColorNameLookup)) { 2607 $QuicktimeColorNameLookup[1] = '2-color (monochrome)'; 2608 $QuicktimeColorNameLookup[2] = '4-color'; 2609 $QuicktimeColorNameLookup[4] = '16-color'; 2610 $QuicktimeColorNameLookup[8] = '256-color'; 2611 $QuicktimeColorNameLookup[16] = 'thousands (16-bit color)'; 2612 $QuicktimeColorNameLookup[24] = 'millions (24-bit color)'; 2613 $QuicktimeColorNameLookup[32] = 'millions+ (32-bit color)'; 2614 $QuicktimeColorNameLookup[33] = 'black & white'; 2615 $QuicktimeColorNameLookup[34] = '4-gray'; 2616 $QuicktimeColorNameLookup[36] = '16-gray'; 2617 $QuicktimeColorNameLookup[40] = '256-gray'; 2618 } 2619 return (isset($QuicktimeColorNameLookup[$colordepthid]) ? $QuicktimeColorNameLookup[$colordepthid] : 'invalid'); 2620 } 2621 2622 /** 2623 * @param int $stik 2624 * 2625 * @return string 2626 */ 2627 public function QuicktimeSTIKLookup($stik) { 2628 static $QuicktimeSTIKLookup = array(); 2629 if (empty($QuicktimeSTIKLookup)) { 2630 $QuicktimeSTIKLookup[0] = 'Movie'; 2631 $QuicktimeSTIKLookup[1] = 'Normal'; 2632 $QuicktimeSTIKLookup[2] = 'Audiobook'; 2633 $QuicktimeSTIKLookup[5] = 'Whacked Bookmark'; 2634 $QuicktimeSTIKLookup[6] = 'Music Video'; 2635 $QuicktimeSTIKLookup[9] = 'Short Film'; 2636 $QuicktimeSTIKLookup[10] = 'TV Show'; 2637 $QuicktimeSTIKLookup[11] = 'Booklet'; 2638 $QuicktimeSTIKLookup[14] = 'Ringtone'; 2639 $QuicktimeSTIKLookup[21] = 'Podcast'; 2640 } 2641 return (isset($QuicktimeSTIKLookup[$stik]) ? $QuicktimeSTIKLookup[$stik] : 'invalid'); 2642 } 2643 2644 /** 2645 * @param int $audio_profile_id 2646 * 2647 * @return string 2648 */ 2649 public function QuicktimeIODSaudioProfileName($audio_profile_id) { 2650 static $QuicktimeIODSaudioProfileNameLookup = array(); 2651 if (empty($QuicktimeIODSaudioProfileNameLookup)) { 2652 $QuicktimeIODSaudioProfileNameLookup = array( 2653 0x00 => 'ISO Reserved (0x00)', 2654 0x01 => 'Main Audio Profile @ Level 1', 2655 0x02 => 'Main Audio Profile @ Level 2', 2656 0x03 => 'Main Audio Profile @ Level 3', 2657 0x04 => 'Main Audio Profile @ Level 4', 2658 0x05 => 'Scalable Audio Profile @ Level 1', 2659 0x06 => 'Scalable Audio Profile @ Level 2', 2660 0x07 => 'Scalable Audio Profile @ Level 3', 2661 0x08 => 'Scalable Audio Profile @ Level 4', 2662 0x09 => 'Speech Audio Profile @ Level 1', 2663 0x0A => 'Speech Audio Profile @ Level 2', 2664 0x0B => 'Synthetic Audio Profile @ Level 1', 2665 0x0C => 'Synthetic Audio Profile @ Level 2', 2666 0x0D => 'Synthetic Audio Profile @ Level 3', 2667 0x0E => 'High Quality Audio Profile @ Level 1', 2668 0x0F => 'High Quality Audio Profile @ Level 2', 2669 0x10 => 'High Quality Audio Profile @ Level 3', 2670 0x11 => 'High Quality Audio Profile @ Level 4', 2671 0x12 => 'High Quality Audio Profile @ Level 5', 2672 0x13 => 'High Quality Audio Profile @ Level 6', 2673 0x14 => 'High Quality Audio Profile @ Level 7', 2674 0x15 => 'High Quality Audio Profile @ Level 8', 2675 0x16 => 'Low Delay Audio Profile @ Level 1', 2676 0x17 => 'Low Delay Audio Profile @ Level 2', 2677 0x18 => 'Low Delay Audio Profile @ Level 3', 2678 0x19 => 'Low Delay Audio Profile @ Level 4', 2679 0x1A => 'Low Delay Audio Profile @ Level 5', 2680 0x1B => 'Low Delay Audio Profile @ Level 6', 2681 0x1C => 'Low Delay Audio Profile @ Level 7', 2682 0x1D => 'Low Delay Audio Profile @ Level 8', 2683 0x1E => 'Natural Audio Profile @ Level 1', 2684 0x1F => 'Natural Audio Profile @ Level 2', 2685 0x20 => 'Natural Audio Profile @ Level 3', 2686 0x21 => 'Natural Audio Profile @ Level 4', 2687 0x22 => 'Mobile Audio Internetworking Profile @ Level 1', 2688 0x23 => 'Mobile Audio Internetworking Profile @ Level 2', 2689 0x24 => 'Mobile Audio Internetworking Profile @ Level 3', 2690 0x25 => 'Mobile Audio Internetworking Profile @ Level 4', 2691 0x26 => 'Mobile Audio Internetworking Profile @ Level 5', 2692 0x27 => 'Mobile Audio Internetworking Profile @ Level 6', 2693 0x28 => 'AAC Profile @ Level 1', 2694 0x29 => 'AAC Profile @ Level 2', 2695 0x2A => 'AAC Profile @ Level 4', 2696 0x2B => 'AAC Profile @ Level 5', 2697 0x2C => 'High Efficiency AAC Profile @ Level 2', 2698 0x2D => 'High Efficiency AAC Profile @ Level 3', 2699 0x2E => 'High Efficiency AAC Profile @ Level 4', 2700 0x2F => 'High Efficiency AAC Profile @ Level 5', 2701 0xFE => 'Not part of MPEG-4 audio profiles', 2702 0xFF => 'No audio capability required', 2703 ); 2704 } 2705 return (isset($QuicktimeIODSaudioProfileNameLookup[$audio_profile_id]) ? $QuicktimeIODSaudioProfileNameLookup[$audio_profile_id] : 'ISO Reserved / User Private'); 2706 } 2707 2708 /** 2709 * @param int $video_profile_id 2710 * 2711 * @return string 2712 */ 2713 public function QuicktimeIODSvideoProfileName($video_profile_id) { 2714 static $QuicktimeIODSvideoProfileNameLookup = array(); 2715 if (empty($QuicktimeIODSvideoProfileNameLookup)) { 2716 $QuicktimeIODSvideoProfileNameLookup = array( 2717 0x00 => 'Reserved (0x00) Profile', 2718 0x01 => 'Simple Profile @ Level 1', 2719 0x02 => 'Simple Profile @ Level 2', 2720 0x03 => 'Simple Profile @ Level 3', 2721 0x08 => 'Simple Profile @ Level 0', 2722 0x10 => 'Simple Scalable Profile @ Level 0', 2723 0x11 => 'Simple Scalable Profile @ Level 1', 2724 0x12 => 'Simple Scalable Profile @ Level 2', 2725 0x15 => 'AVC/H264 Profile', 2726 0x21 => 'Core Profile @ Level 1', 2727 0x22 => 'Core Profile @ Level 2', 2728 0x32 => 'Main Profile @ Level 2', 2729 0x33 => 'Main Profile @ Level 3', 2730 0x34 => 'Main Profile @ Level 4', 2731 0x42 => 'N-bit Profile @ Level 2', 2732 0x51 => 'Scalable Texture Profile @ Level 1', 2733 0x61 => 'Simple Face Animation Profile @ Level 1', 2734 0x62 => 'Simple Face Animation Profile @ Level 2', 2735 0x63 => 'Simple FBA Profile @ Level 1', 2736 0x64 => 'Simple FBA Profile @ Level 2', 2737 0x71 => 'Basic Animated Texture Profile @ Level 1', 2738 0x72 => 'Basic Animated Texture Profile @ Level 2', 2739 0x81 => 'Hybrid Profile @ Level 1', 2740 0x82 => 'Hybrid Profile @ Level 2', 2741 0x91 => 'Advanced Real Time Simple Profile @ Level 1', 2742 0x92 => 'Advanced Real Time Simple Profile @ Level 2', 2743 0x93 => 'Advanced Real Time Simple Profile @ Level 3', 2744 0x94 => 'Advanced Real Time Simple Profile @ Level 4', 2745 0xA1 => 'Core Scalable Profile @ Level1', 2746 0xA2 => 'Core Scalable Profile @ Level2', 2747 0xA3 => 'Core Scalable Profile @ Level3', 2748 0xB1 => 'Advanced Coding Efficiency Profile @ Level 1', 2749 0xB2 => 'Advanced Coding Efficiency Profile @ Level 2', 2750 0xB3 => 'Advanced Coding Efficiency Profile @ Level 3', 2751 0xB4 => 'Advanced Coding Efficiency Profile @ Level 4', 2752 0xC1 => 'Advanced Core Profile @ Level 1', 2753 0xC2 => 'Advanced Core Profile @ Level 2', 2754 0xD1 => 'Advanced Scalable Texture @ Level1', 2755 0xD2 => 'Advanced Scalable Texture @ Level2', 2756 0xE1 => 'Simple Studio Profile @ Level 1', 2757 0xE2 => 'Simple Studio Profile @ Level 2', 2758 0xE3 => 'Simple Studio Profile @ Level 3', 2759 0xE4 => 'Simple Studio Profile @ Level 4', 2760 0xE5 => 'Core Studio Profile @ Level 1', 2761 0xE6 => 'Core Studio Profile @ Level 2', 2762 0xE7 => 'Core Studio Profile @ Level 3', 2763 0xE8 => 'Core Studio Profile @ Level 4', 2764 0xF0 => 'Advanced Simple Profile @ Level 0', 2765 0xF1 => 'Advanced Simple Profile @ Level 1', 2766 0xF2 => 'Advanced Simple Profile @ Level 2', 2767 0xF3 => 'Advanced Simple Profile @ Level 3', 2768 0xF4 => 'Advanced Simple Profile @ Level 4', 2769 0xF5 => 'Advanced Simple Profile @ Level 5', 2770 0xF7 => 'Advanced Simple Profile @ Level 3b', 2771 0xF8 => 'Fine Granularity Scalable Profile @ Level 0', 2772 0xF9 => 'Fine Granularity Scalable Profile @ Level 1', 2773 0xFA => 'Fine Granularity Scalable Profile @ Level 2', 2774 0xFB => 'Fine Granularity Scalable Profile @ Level 3', 2775 0xFC => 'Fine Granularity Scalable Profile @ Level 4', 2776 0xFD => 'Fine Granularity Scalable Profile @ Level 5', 2777 0xFE => 'Not part of MPEG-4 Visual profiles', 2778 0xFF => 'No visual capability required', 2779 ); 2780 } 2781 return (isset($QuicktimeIODSvideoProfileNameLookup[$video_profile_id]) ? $QuicktimeIODSvideoProfileNameLookup[$video_profile_id] : 'ISO Reserved Profile'); 2782 } 2783 2784 /** 2785 * @param int $rtng 2786 * 2787 * @return string 2788 */ 2789 public function QuicktimeContentRatingLookup($rtng) { 2790 static $QuicktimeContentRatingLookup = array(); 2791 if (empty($QuicktimeContentRatingLookup)) { 2792 $QuicktimeContentRatingLookup[0] = 'None'; 2793 $QuicktimeContentRatingLookup[1] = 'Explicit'; 2794 $QuicktimeContentRatingLookup[2] = 'Clean'; 2795 $QuicktimeContentRatingLookup[4] = 'Explicit (old)'; 2796 } 2797 return (isset($QuicktimeContentRatingLookup[$rtng]) ? $QuicktimeContentRatingLookup[$rtng] : 'invalid'); 2798 } 2799 2800 /** 2801 * @param int $akid 2802 * 2803 * @return string 2804 */ 2805 public function QuicktimeStoreAccountTypeLookup($akid) { 2806 static $QuicktimeStoreAccountTypeLookup = array(); 2807 if (empty($QuicktimeStoreAccountTypeLookup)) { 2808 $QuicktimeStoreAccountTypeLookup[0] = 'iTunes'; 2809 $QuicktimeStoreAccountTypeLookup[1] = 'AOL'; 2810 } 2811 return (isset($QuicktimeStoreAccountTypeLookup[$akid]) ? $QuicktimeStoreAccountTypeLookup[$akid] : 'invalid'); 2812 } 2813 2814 /** 2815 * @param int $sfid 2816 * 2817 * @return string 2818 */ 2819 public function QuicktimeStoreFrontCodeLookup($sfid) { 2820 static $QuicktimeStoreFrontCodeLookup = array(); 2821 if (empty($QuicktimeStoreFrontCodeLookup)) { 2822 $QuicktimeStoreFrontCodeLookup[143460] = 'Australia'; 2823 $QuicktimeStoreFrontCodeLookup[143445] = 'Austria'; 2824 $QuicktimeStoreFrontCodeLookup[143446] = 'Belgium'; 2825 $QuicktimeStoreFrontCodeLookup[143455] = 'Canada'; 2826 $QuicktimeStoreFrontCodeLookup[143458] = 'Denmark'; 2827 $QuicktimeStoreFrontCodeLookup[143447] = 'Finland'; 2828 $QuicktimeStoreFrontCodeLookup[143442] = 'France'; 2829 $QuicktimeStoreFrontCodeLookup[143443] = 'Germany'; 2830 $QuicktimeStoreFrontCodeLookup[143448] = 'Greece'; 2831 $QuicktimeStoreFrontCodeLookup[143449] = 'Ireland'; 2832 $QuicktimeStoreFrontCodeLookup[143450] = 'Italy'; 2833 $QuicktimeStoreFrontCodeLookup[143462] = 'Japan'; 2834 $QuicktimeStoreFrontCodeLookup[143451] = 'Luxembourg'; 2835 $QuicktimeStoreFrontCodeLookup[143452] = 'Netherlands'; 2836 $QuicktimeStoreFrontCodeLookup[143461] = 'New Zealand'; 2837 $QuicktimeStoreFrontCodeLookup[143457] = 'Norway'; 2838 $QuicktimeStoreFrontCodeLookup[143453] = 'Portugal'; 2839 $QuicktimeStoreFrontCodeLookup[143454] = 'Spain'; 2840 $QuicktimeStoreFrontCodeLookup[143456] = 'Sweden'; 2841 $QuicktimeStoreFrontCodeLookup[143459] = 'Switzerland'; 2842 $QuicktimeStoreFrontCodeLookup[143444] = 'United Kingdom'; 2843 $QuicktimeStoreFrontCodeLookup[143441] = 'United States'; 2844 } 2845 return (isset($QuicktimeStoreFrontCodeLookup[$sfid]) ? $QuicktimeStoreFrontCodeLookup[$sfid] : 'invalid'); 2846 } 2847 2848 /** 2849 * @param string $keyname 2850 * @param string|array $data 2851 * @param string $boxname 2852 * 2853 * @return bool 2854 */ 2855 public function CopyToAppropriateCommentsSection($keyname, $data, $boxname='') { 2856 static $handyatomtranslatorarray = array(); 2857 if (empty($handyatomtranslatorarray)) { 2858 // http://www.geocities.com/xhelmboyx/quicktime/formats/qtm-layout.txt 2859 // http://www.geocities.com/xhelmboyx/quicktime/formats/mp4-layout.txt 2860 // http://atomicparsley.sourceforge.net/mpeg-4files.html 2861 // https://code.google.com/p/mp4v2/wiki/iTunesMetadata 2862 $handyatomtranslatorarray["\xA9".'alb'] = 'album'; // iTunes 4.0 2863 $handyatomtranslatorarray["\xA9".'ART'] = 'artist'; 2864 $handyatomtranslatorarray["\xA9".'art'] = 'artist'; // iTunes 4.0 2865 $handyatomtranslatorarray["\xA9".'aut'] = 'author'; 2866 $handyatomtranslatorarray["\xA9".'cmt'] = 'comment'; // iTunes 4.0 2867 $handyatomtranslatorarray["\xA9".'com'] = 'comment'; 2868 $handyatomtranslatorarray["\xA9".'cpy'] = 'copyright'; 2869 $handyatomtranslatorarray["\xA9".'day'] = 'creation_date'; // iTunes 4.0 2870 $handyatomtranslatorarray["\xA9".'dir'] = 'director'; 2871 $handyatomtranslatorarray["\xA9".'ed1'] = 'edit1'; 2872 $handyatomtranslatorarray["\xA9".'ed2'] = 'edit2'; 2873 $handyatomtranslatorarray["\xA9".'ed3'] = 'edit3'; 2874 $handyatomtranslatorarray["\xA9".'ed4'] = 'edit4'; 2875 $handyatomtranslatorarray["\xA9".'ed5'] = 'edit5'; 2876 $handyatomtranslatorarray["\xA9".'ed6'] = 'edit6'; 2877 $handyatomtranslatorarray["\xA9".'ed7'] = 'edit7'; 2878 $handyatomtranslatorarray["\xA9".'ed8'] = 'edit8'; 2879 $handyatomtranslatorarray["\xA9".'ed9'] = 'edit9'; 2880 $handyatomtranslatorarray["\xA9".'enc'] = 'encoded_by'; 2881 $handyatomtranslatorarray["\xA9".'fmt'] = 'format'; 2882 $handyatomtranslatorarray["\xA9".'gen'] = 'genre'; // iTunes 4.0 2883 $handyatomtranslatorarray["\xA9".'grp'] = 'grouping'; // iTunes 4.2 2884 $handyatomtranslatorarray["\xA9".'hst'] = 'host_computer'; 2885 $handyatomtranslatorarray["\xA9".'inf'] = 'information'; 2886 $handyatomtranslatorarray["\xA9".'lyr'] = 'lyrics'; // iTunes 5.0 2887 $handyatomtranslatorarray["\xA9".'mak'] = 'make'; 2888 $handyatomtranslatorarray["\xA9".'mod'] = 'model'; 2889 $handyatomtranslatorarray["\xA9".'nam'] = 'title'; // iTunes 4.0 2890 $handyatomtranslatorarray["\xA9".'ope'] = 'composer'; 2891 $handyatomtranslatorarray["\xA9".'prd'] = 'producer'; 2892 $handyatomtranslatorarray["\xA9".'PRD'] = 'product'; 2893 $handyatomtranslatorarray["\xA9".'prf'] = 'performers'; 2894 $handyatomtranslatorarray["\xA9".'req'] = 'system_requirements'; 2895 $handyatomtranslatorarray["\xA9".'src'] = 'source_credit'; 2896 $handyatomtranslatorarray["\xA9".'swr'] = 'software'; 2897 $handyatomtranslatorarray["\xA9".'too'] = 'encoding_tool'; // iTunes 4.0 2898 $handyatomtranslatorarray["\xA9".'trk'] = 'track_number'; 2899 $handyatomtranslatorarray["\xA9".'url'] = 'url'; 2900 $handyatomtranslatorarray["\xA9".'wrn'] = 'warning'; 2901 $handyatomtranslatorarray["\xA9".'wrt'] = 'composer'; 2902 $handyatomtranslatorarray['aART'] = 'album_artist'; 2903 $handyatomtranslatorarray['apID'] = 'purchase_account'; 2904 $handyatomtranslatorarray['catg'] = 'category'; // iTunes 4.9 2905 $handyatomtranslatorarray['covr'] = 'picture'; // iTunes 4.0 2906 $handyatomtranslatorarray['cpil'] = 'compilation'; // iTunes 4.0 2907 $handyatomtranslatorarray['cprt'] = 'copyright'; // iTunes 4.0? 2908 $handyatomtranslatorarray['desc'] = 'description'; // iTunes 5.0 2909 $handyatomtranslatorarray['disk'] = 'disc_number'; // iTunes 4.0 2910 $handyatomtranslatorarray['egid'] = 'episode_guid'; // iTunes 4.9 2911 $handyatomtranslatorarray['gnre'] = 'genre'; // iTunes 4.0 2912 $handyatomtranslatorarray['hdvd'] = 'hd_video'; // iTunes 4.0 2913 $handyatomtranslatorarray['ldes'] = 'description_long'; // 2914 $handyatomtranslatorarray['keyw'] = 'keyword'; // iTunes 4.9 2915 $handyatomtranslatorarray['pcst'] = 'podcast'; // iTunes 4.9 2916 $handyatomtranslatorarray['pgap'] = 'gapless_playback'; // iTunes 7.0 2917 $handyatomtranslatorarray['purd'] = 'purchase_date'; // iTunes 6.0.2 2918 $handyatomtranslatorarray['purl'] = 'podcast_url'; // iTunes 4.9 2919 $handyatomtranslatorarray['rtng'] = 'rating'; // iTunes 4.0 2920 $handyatomtranslatorarray['soaa'] = 'sort_album_artist'; // 2921 $handyatomtranslatorarray['soal'] = 'sort_album'; // 2922 $handyatomtranslatorarray['soar'] = 'sort_artist'; // 2923 $handyatomtranslatorarray['soco'] = 'sort_composer'; // 2924 $handyatomtranslatorarray['sonm'] = 'sort_title'; // 2925 $handyatomtranslatorarray['sosn'] = 'sort_show'; // 2926 $handyatomtranslatorarray['stik'] = 'stik'; // iTunes 4.9 2927 $handyatomtranslatorarray['tmpo'] = 'bpm'; // iTunes 4.0 2928 $handyatomtranslatorarray['trkn'] = 'track_number'; // iTunes 4.0 2929 $handyatomtranslatorarray['tven'] = 'tv_episode_id'; // 2930 $handyatomtranslatorarray['tves'] = 'tv_episode'; // iTunes 6.0 2931 $handyatomtranslatorarray['tvnn'] = 'tv_network_name'; // iTunes 6.0 2932 $handyatomtranslatorarray['tvsh'] = 'tv_show_name'; // iTunes 6.0 2933 $handyatomtranslatorarray['tvsn'] = 'tv_season'; // iTunes 6.0 2934 2935 // boxnames: 2936 /* 2937 $handyatomtranslatorarray['iTunSMPB'] = 'iTunSMPB'; 2938 $handyatomtranslatorarray['iTunNORM'] = 'iTunNORM'; 2939 $handyatomtranslatorarray['Encoding Params'] = 'Encoding Params'; 2940 $handyatomtranslatorarray['replaygain_track_gain'] = 'replaygain_track_gain'; 2941 $handyatomtranslatorarray['replaygain_track_peak'] = 'replaygain_track_peak'; 2942 $handyatomtranslatorarray['replaygain_track_minmax'] = 'replaygain_track_minmax'; 2943 $handyatomtranslatorarray['MusicIP PUID'] = 'MusicIP PUID'; 2944 $handyatomtranslatorarray['MusicBrainz Artist Id'] = 'MusicBrainz Artist Id'; 2945 $handyatomtranslatorarray['MusicBrainz Album Id'] = 'MusicBrainz Album Id'; 2946 $handyatomtranslatorarray['MusicBrainz Album Artist Id'] = 'MusicBrainz Album Artist Id'; 2947 $handyatomtranslatorarray['MusicBrainz Track Id'] = 'MusicBrainz Track Id'; 2948 $handyatomtranslatorarray['MusicBrainz Disc Id'] = 'MusicBrainz Disc Id'; 2949 2950 // http://age.hobba.nl/audio/tag_frame_reference.html 2951 $handyatomtranslatorarray['PLAY_COUNTER'] = 'play_counter'; // Foobar2000 - https://www.getid3.org/phpBB3/viewtopic.php?t=1355 2952 $handyatomtranslatorarray['MEDIATYPE'] = 'mediatype'; // Foobar2000 - https://www.getid3.org/phpBB3/viewtopic.php?t=1355 2953 */ 2954 } 2955 $info = &$this->getid3->info; 2956 $comment_key = ''; 2957 if ($boxname && ($boxname != $keyname)) { 2958 $comment_key = (isset($handyatomtranslatorarray[$boxname]) ? $handyatomtranslatorarray[$boxname] : $boxname); 2959 } elseif (isset($handyatomtranslatorarray[$keyname])) { 2960 $comment_key = $handyatomtranslatorarray[$keyname]; 2961 } 2962 if ($comment_key) { 2963 if ($comment_key == 'picture') { 2964 // already copied directly into [comments][picture] elsewhere, do not re-copy here 2965 return true; 2966 } 2967 $gooddata = array($data); 2968 if ($comment_key == 'genre') { 2969 // some other taggers separate multiple genres with semicolon, e.g. "Heavy Metal;Thrash Metal;Metal" 2970 $gooddata = explode(';', $data); 2971 } 2972 foreach ($gooddata as $data) { 2973 if (!empty($info['quicktime']['comments'][$comment_key]) && in_array($data, $info['quicktime']['comments'][$comment_key], true)) { 2974 // avoid duplicate copies of identical data 2975 continue; 2976 } 2977 $info['quicktime']['comments'][$comment_key][] = $data; 2978 } 2979 } 2980 return true; 2981 } 2982 2983 /** 2984 * @param string $lstring 2985 * @param int $count 2986 * 2987 * @return string 2988 */ 2989 public function LociString($lstring, &$count) { 2990 // Loci strings are UTF-8 or UTF-16 and null (x00/x0000) terminated. UTF-16 has a BOM 2991 // Also need to return the number of bytes the string occupied so additional fields can be extracted 2992 $len = strlen($lstring); 2993 if ($len == 0) { 2994 $count = 0; 2995 return ''; 2996 } 2997 if ($lstring[0] == "\x00") { 2998 $count = 1; 2999 return ''; 3000 } 3001 // check for BOM 3002 if (($len > 2) && ((($lstring[0] == "\xFE") && ($lstring[1] == "\xFF")) || (($lstring[0] == "\xFF") && ($lstring[1] == "\xFE")))) { 3003 // UTF-16 3004 if (preg_match('/(.*)\x00/', $lstring, $lmatches)) { 3005 $count = strlen($lmatches[1]) * 2 + 2; //account for 2 byte characters and trailing \x0000 3006 return getid3_lib::iconv_fallback_utf16_utf8($lmatches[1]); 3007 } else { 3008 return ''; 3009 } 3010 } 3011 // UTF-8 3012 if (preg_match('/(.*)\x00/', $lstring, $lmatches)) { 3013 $count = strlen($lmatches[1]) + 1; //account for trailing \x00 3014 return $lmatches[1]; 3015 } 3016 return ''; 3017 } 3018 3019 /** 3020 * @param string $nullterminatedstring 3021 * 3022 * @return string 3023 */ 3024 public function NoNullString($nullterminatedstring) { 3025 // remove the single null terminator on null terminated strings 3026 if (substr($nullterminatedstring, strlen($nullterminatedstring) - 1, 1) === "\x00") { 3027 return substr($nullterminatedstring, 0, strlen($nullterminatedstring) - 1); 3028 } 3029 return $nullterminatedstring; 3030 } 3031 3032 /** 3033 * @param string $pascalstring 3034 * 3035 * @return string 3036 */ 3037 public function Pascal2String($pascalstring) { 3038 // Pascal strings have 1 unsigned byte at the beginning saying how many chars (1-255) are in the string 3039 return substr($pascalstring, 1); 3040 } 3041 3042 /** 3043 * @param string $pascalstring 3044 * 3045 * @return string 3046 */ 3047 public function MaybePascal2String($pascalstring) { 3048 // Pascal strings have 1 unsigned byte at the beginning saying how many chars (1-255) are in the string 3049 // Check if string actually is in this format or written incorrectly, straight string, or null-terminated string 3050 if (ord(substr($pascalstring, 0, 1)) == (strlen($pascalstring) - 1)) { 3051 return substr($pascalstring, 1); 3052 } elseif (substr($pascalstring, -1, 1) == "\x00") { 3053 // appears to be null-terminated instead of Pascal-style 3054 return substr($pascalstring, 0, -1); 3055 } 3056 return $pascalstring; 3057 } 3058 3059 3060 /** 3061 * Helper functions for m4b audiobook chapters 3062 * code by Steffen Hartmann 2015-Nov-08. 3063 * 3064 * @param array $info 3065 * @param string $tag 3066 * @param string $history 3067 * @param array $result 3068 */ 3069 public function search_tag_by_key($info, $tag, $history, &$result) { 3070 foreach ($info as $key => $value) { 3071 $key_history = $history.'/'.$key; 3072 if ($key === $tag) { 3073 $result[] = array($key_history, $info); 3074 } else { 3075 if (is_array($value)) { 3076 $this->search_tag_by_key($value, $tag, $key_history, $result); 3077 } 3078 } 3079 } 3080 } 3081 3082 /** 3083 * @param array $info 3084 * @param string $k 3085 * @param string $v 3086 * @param string $history 3087 * @param array $result 3088 */ 3089 public function search_tag_by_pair($info, $k, $v, $history, &$result) { 3090 foreach ($info as $key => $value) { 3091 $key_history = $history.'/'.$key; 3092 if (($key === $k) && ($value === $v)) { 3093 $result[] = array($key_history, $info); 3094 } else { 3095 if (is_array($value)) { 3096 $this->search_tag_by_pair($value, $k, $v, $key_history, $result); 3097 } 3098 } 3099 } 3100 } 3101 3102 /** 3103 * @param array $info 3104 * 3105 * @return array 3106 */ 3107 public function quicktime_time_to_sample_table($info) { 3108 $res = array(); 3109 $this->search_tag_by_pair($info['quicktime']['moov'], 'name', 'stbl', 'quicktime/moov', $res); 3110 foreach ($res as $value) { 3111 $stbl_res = array(); 3112 $this->search_tag_by_pair($value[1], 'data_format', 'text', $value[0], $stbl_res); 3113 if (count($stbl_res) > 0) { 3114 $stts_res = array(); 3115 $this->search_tag_by_key($value[1], 'time_to_sample_table', $value[0], $stts_res); 3116 if (count($stts_res) > 0) { 3117 return $stts_res[0][1]['time_to_sample_table']; 3118 } 3119 } 3120 } 3121 return array(); 3122 } 3123 3124 3125 /** 3126 * @param array $info 3127 * 3128 * @return int 3129 */ 3130 public function quicktime_bookmark_time_scale($info) { 3131 $time_scale = ''; 3132 $ts_prefix_len = 0; 3133 $res = array(); 3134 $this->search_tag_by_pair($info['quicktime']['moov'], 'name', 'stbl', 'quicktime/moov', $res); 3135 foreach ($res as $value) { 3136 $stbl_res = array(); 3137 $this->search_tag_by_pair($value[1], 'data_format', 'text', $value[0], $stbl_res); 3138 if (count($stbl_res) > 0) { 3139 $ts_res = array(); 3140 $this->search_tag_by_key($info['quicktime']['moov'], 'time_scale', 'quicktime/moov', $ts_res); 3141 foreach ($ts_res as $sub_value) { 3142 $prefix = substr($sub_value[0], 0, -12); 3143 if ((substr($stbl_res[0][0], 0, strlen($prefix)) === $prefix) && ($ts_prefix_len < strlen($prefix))) { 3144 $time_scale = $sub_value[1]['time_scale']; 3145 $ts_prefix_len = strlen($prefix); 3146 } 3147 } 3148 } 3149 } 3150 return $time_scale; 3151 } 3152 /* 3153 // END helper functions for m4b audiobook chapters 3154 */ 3155 3156 3157 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated : Wed Apr 15 08:20:10 2026 | Cross-referenced by PHPXref |