- Timestamp:
- 09/14/2019 07:06:09 PM (6 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-includes/ID3/module.audio-video.quicktime.php
r41196 r46112 1 1 <?php 2 2 3 ///////////////////////////////////////////////////////////////// 3 4 /// getID3() by James Heinrich <info@getid3.org> // 4 // available at http://getid3.sourceforge.net // 5 // or http://www.getid3.org // 6 // also https://github.com/JamesHeinrich/getID3 // 7 ///////////////////////////////////////////////////////////////// 8 // See readme.txt for more details // 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 9 ///////////////////////////////////////////////////////////////// 10 10 // // … … 25 25 public $ParseAllPossibleAtoms = false; 26 26 27 /** 28 * @return bool 29 */ 27 30 public function Analyze() { 28 31 $info = &$this->getid3->info; … … 36 39 $offset = 0; 37 40 $atomcounter = 0; 38 $atom_data_read_buffer_size = max($this->getid3->option_fread_buffer_size * 1024, ($info['php_memory_limit'] ? round($info['php_memory_limit'] / 4) : 1024)); // set read buffer to 25% of PHP memory limit (if one is specified), otherwise use option_fread_buffer_size [default: 32MB]41 $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] 39 42 while ($offset < $info['avdataend']) { 40 43 if (!getid3_lib::intValueSupported($offset)) { … … 163 166 $info['audio']['bitrate'] = $info['bitrate']; 164 167 } 168 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'])) { 169 $info['video']['bitrate'] = $info['bitrate'] - $info['audio']['bitrate']; 170 } 165 171 if (!empty($info['playtime_seconds']) && !isset($info['video']['frame_rate']) && !empty($info['quicktime']['stts_framecount'])) { 166 172 foreach ($info['quicktime']['stts_framecount'] as $key => $samples_count) { … … 194 200 $info['video']['dataformat'] = 'quicktime'; 195 201 } 202 if (isset($info['video']) && ($info['mime_type'] == 'audio/mp4') && empty($info['video']['resolution_x']) && empty($info['video']['resolution_y'])) { 203 unset($info['video']); 204 } 196 205 197 206 return true; 198 207 } 199 208 209 /** 210 * @param string $atomname 211 * @param int $atomsize 212 * @param string $atom_data 213 * @param int $baseoffset 214 * @param array $atomHierarchy 215 * @param bool $ParseAllPossibleAtoms 216 * 217 * @return array|false 218 */ 200 219 public function QuicktimeParseAtom($atomname, $atomsize, $atom_data, $baseoffset, &$atomHierarchy, $ParseAllPossibleAtoms) { 201 220 // http://developer.apple.com/techpubs/quicktime/qtdevdocs/APIREF/INDEX/atomalphaindex.htm … … 204 223 $info = &$this->getid3->info; 205 224 206 $atom_parent = end($atomHierarchy); // not array_pop($atomHierarchy); see http ://www.getid3.org/phpBB3/viewtopic.php?t=1717225 $atom_parent = end($atomHierarchy); // not array_pop($atomHierarchy); see https://www.getid3.org/phpBB3/viewtopic.php?t=1717 207 226 array_push($atomHierarchy, $atomname); 208 227 $atom_structure['hierarchy'] = implode(' ', $atomHierarchy); … … 210 229 $atom_structure['size'] = $atomsize; 211 230 $atom_structure['offset'] = $baseoffset; 212 switch ($atomname) { 213 case 'moov': // MOVie container atom 214 case 'trak': // TRAcK container atom 215 case 'clip': // CLIPping container atom 216 case 'matt': // track MATTe container atom 217 case 'edts': // EDiTS container atom 218 case 'tref': // Track REFerence container atom 219 case 'mdia': // MeDIA container atom 220 case 'minf': // Media INFormation container atom 221 case 'dinf': // Data INFormation container atom 222 case 'udta': // User DaTA container atom 223 case 'cmov': // Compressed MOVie container atom 224 case 'rmra': // Reference Movie Record Atom 225 case 'rmda': // Reference Movie Descriptor Atom 226 case 'gmhd': // Generic Media info HeaDer atom (seen on QTVR) 227 $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); 228 break; 229 230 case 'ilst': // Item LiST container atom 231 if ($atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms)) { 232 // some "ilst" atoms contain data atoms that have a numeric name, and the data is far more accessible if the returned array is compacted 233 $allnumericnames = true; 234 foreach ($atom_structure['subatoms'] as $subatomarray) { 235 if (!is_integer($subatomarray['name']) || (count($subatomarray['subatoms']) != 1)) { 236 $allnumericnames = false; 237 break; 238 } 239 } 240 if ($allnumericnames) { 241 $newData = array(); 231 if (substr($atomname, 0, 3) == "\x00\x00\x00") { 232 // https://github.com/JamesHeinrich/getID3/issues/139 233 $atomname = getid3_lib::BigEndian2Int($atomname); 234 $atom_structure['name'] = $atomname; 235 $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); 236 } else { 237 switch ($atomname) { 238 case 'moov': // MOVie container atom 239 case 'trak': // TRAcK container atom 240 case 'clip': // CLIPping container atom 241 case 'matt': // track MATTe container atom 242 case 'edts': // EDiTS container atom 243 case 'tref': // Track REFerence container atom 244 case 'mdia': // MeDIA container atom 245 case 'minf': // Media INFormation container atom 246 case 'dinf': // Data INFormation container atom 247 case 'udta': // User DaTA container atom 248 case 'cmov': // Compressed MOVie container atom 249 case 'rmra': // Reference Movie Record Atom 250 case 'rmda': // Reference Movie Descriptor Atom 251 case 'gmhd': // Generic Media info HeaDer atom (seen on QTVR) 252 $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); 253 break; 254 255 case 'ilst': // Item LiST container atom 256 if ($atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms)) { 257 // some "ilst" atoms contain data atoms that have a numeric name, and the data is far more accessible if the returned array is compacted 258 $allnumericnames = true; 242 259 foreach ($atom_structure['subatoms'] as $subatomarray) { 243 foreach ($subatomarray['subatoms'] as $newData_subatomarray) { 244 unset($newData_subatomarray['hierarchy'], $newData_subatomarray['name']); 245 $newData[$subatomarray['name']] = $newData_subatomarray; 260 if (!is_integer($subatomarray['name']) || (count($subatomarray['subatoms']) != 1)) { 261 $allnumericnames = false; 246 262 break; 247 263 } 248 264 } 249 $atom_structure['data'] = $newData; 250 unset($atom_structure['subatoms']); 251 } 252 } 253 break; 254 255 case "\x00\x00\x00\x01": 256 case "\x00\x00\x00\x02": 257 case "\x00\x00\x00\x03": 258 case "\x00\x00\x00\x04": 259 case "\x00\x00\x00\x05": 260 $atomname = getid3_lib::BigEndian2Int($atomname); 261 $atom_structure['name'] = $atomname; 262 $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); 263 break; 264 265 case 'stbl': // Sample TaBLe container atom 266 $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); 267 $isVideo = false; 268 $framerate = 0; 269 $framecount = 0; 270 foreach ($atom_structure['subatoms'] as $key => $value_array) { 271 if (isset($value_array['sample_description_table'])) { 272 foreach ($value_array['sample_description_table'] as $key2 => $value_array2) { 273 if (isset($value_array2['data_format'])) { 274 switch ($value_array2['data_format']) { 275 case 'avc1': 276 case 'mp4v': 277 // video data 278 $isVideo = true; 279 break; 280 case 'mp4a': 281 // audio data 282 break; 265 if ($allnumericnames) { 266 $newData = array(); 267 foreach ($atom_structure['subatoms'] as $subatomarray) { 268 foreach ($subatomarray['subatoms'] as $newData_subatomarray) { 269 unset($newData_subatomarray['hierarchy'], $newData_subatomarray['name']); 270 $newData[$subatomarray['name']] = $newData_subatomarray; 271 break; 272 } 273 } 274 $atom_structure['data'] = $newData; 275 unset($atom_structure['subatoms']); 276 } 277 } 278 break; 279 280 case 'stbl': // Sample TaBLe container atom 281 $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); 282 $isVideo = false; 283 $framerate = 0; 284 $framecount = 0; 285 foreach ($atom_structure['subatoms'] as $key => $value_array) { 286 if (isset($value_array['sample_description_table'])) { 287 foreach ($value_array['sample_description_table'] as $key2 => $value_array2) { 288 if (isset($value_array2['data_format'])) { 289 switch ($value_array2['data_format']) { 290 case 'avc1': 291 case 'mp4v': 292 // video data 293 $isVideo = true; 294 break; 295 case 'mp4a': 296 // audio data 297 break; 298 } 299 } 300 } 301 } elseif (isset($value_array['time_to_sample_table'])) { 302 foreach ($value_array['time_to_sample_table'] as $key2 => $value_array2) { 303 if (isset($value_array2['sample_count']) && isset($value_array2['sample_duration']) && ($value_array2['sample_duration'] > 0)) { 304 $framerate = round($info['quicktime']['time_scale'] / $value_array2['sample_duration'], 3); 305 $framecount = $value_array2['sample_count']; 283 306 } 284 307 } 285 308 } 286 } elseif (isset($value_array['time_to_sample_table'])) { 287 foreach ($value_array['time_to_sample_table'] as $key2 => $value_array2) { 288 if (isset($value_array2['sample_count']) && isset($value_array2['sample_duration']) && ($value_array2['sample_duration'] > 0)) { 289 $framerate = round($info['quicktime']['time_scale'] / $value_array2['sample_duration'], 3); 290 $framecount = $value_array2['sample_count']; 291 } 292 } 293 } 294 } 295 if ($isVideo && $framerate) { 296 $info['quicktime']['video']['frame_rate'] = $framerate; 297 $info['video']['frame_rate'] = $info['quicktime']['video']['frame_rate']; 298 } 299 if ($isVideo && $framecount) { 300 $info['quicktime']['video']['frame_count'] = $framecount; 301 } 302 break; 303 304 305 case "\xA9".'alb': // ALBum 306 case "\xA9".'ART': // 307 case "\xA9".'art': // ARTist 308 case "\xA9".'aut': // 309 case "\xA9".'cmt': // CoMmenT 310 case "\xA9".'com': // COMposer 311 case "\xA9".'cpy': // 312 case "\xA9".'day': // content created year 313 case "\xA9".'dir': // 314 case "\xA9".'ed1': // 315 case "\xA9".'ed2': // 316 case "\xA9".'ed3': // 317 case "\xA9".'ed4': // 318 case "\xA9".'ed5': // 319 case "\xA9".'ed6': // 320 case "\xA9".'ed7': // 321 case "\xA9".'ed8': // 322 case "\xA9".'ed9': // 323 case "\xA9".'enc': // 324 case "\xA9".'fmt': // 325 case "\xA9".'gen': // GENre 326 case "\xA9".'grp': // GRouPing 327 case "\xA9".'hst': // 328 case "\xA9".'inf': // 329 case "\xA9".'lyr': // LYRics 330 case "\xA9".'mak': // 331 case "\xA9".'mod': // 332 case "\xA9".'nam': // full NAMe 333 case "\xA9".'ope': // 334 case "\xA9".'PRD': // 335 case "\xA9".'prf': // 336 case "\xA9".'req': // 337 case "\xA9".'src': // 338 case "\xA9".'swr': // 339 case "\xA9".'too': // encoder 340 case "\xA9".'trk': // TRacK 341 case "\xA9".'url': // 342 case "\xA9".'wrn': // 343 case "\xA9".'wrt': // WRiTer 344 case '----': // itunes specific 345 case 'aART': // Album ARTist 346 case 'akID': // iTunes store account type 347 case 'apID': // Purchase Account 348 case 'atID': // 349 case 'catg': // CaTeGory 350 case 'cmID': // 351 case 'cnID': // 352 case 'covr': // COVeR artwork 353 case 'cpil': // ComPILation 354 case 'cprt': // CoPyRighT 355 case 'desc': // DESCription 356 case 'disk': // DISK number 357 case 'egid': // Episode Global ID 358 case 'geID': // 359 case 'gnre': // GeNRE 360 case 'hdvd': // HD ViDeo 361 case 'keyw': // KEYWord 362 case 'ldes': // Long DEScription 363 case 'pcst': // PodCaST 364 case 'pgap': // GAPless Playback 365 case 'plID': // 366 case 'purd': // PURchase Date 367 case 'purl': // Podcast URL 368 case 'rati': // 369 case 'rndu': // 370 case 'rpdu': // 371 case 'rtng': // RaTiNG 372 case 'sfID': // iTunes store country 373 case 'soaa': // SOrt Album Artist 374 case 'soal': // SOrt ALbum 375 case 'soar': // SOrt ARtist 376 case 'soco': // SOrt COmposer 377 case 'sonm': // SOrt NaMe 378 case 'sosn': // SOrt Show Name 379 case 'stik': // 380 case 'tmpo': // TeMPO (BPM) 381 case 'trkn': // TRacK Number 382 case 'tven': // tvEpisodeID 383 case 'tves': // TV EpiSode 384 case 'tvnn': // TV Network Name 385 case 'tvsh': // TV SHow Name 386 case 'tvsn': // TV SeasoN 387 if ($atom_parent == 'udta') { 388 // User data atom handler 389 $atom_structure['data_length'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2)); 390 $atom_structure['language_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 2)); 391 $atom_structure['data'] = substr($atom_data, 4); 392 393 $atom_structure['language'] = $this->QuicktimeLanguageLookup($atom_structure['language_id']); 394 if (empty($info['comments']['language']) || (!in_array($atom_structure['language'], $info['comments']['language']))) { 395 $info['comments']['language'][] = $atom_structure['language']; 396 } 397 } else { 398 // Apple item list box atom handler 399 $atomoffset = 0; 400 if (substr($atom_data, 2, 2) == "\x10\xB5") { 401 // not sure what it means, but observed on iPhone4 data. 402 // Each $atom_data has 2 bytes of datasize, plus 0x10B5, then data 403 while ($atomoffset < strlen($atom_data)) { 404 $boxsmallsize = getid3_lib::BigEndian2Int(substr($atom_data, $atomoffset, 2)); 405 $boxsmalltype = substr($atom_data, $atomoffset + 2, 2); 406 $boxsmalldata = substr($atom_data, $atomoffset + 4, $boxsmallsize); 407 if ($boxsmallsize <= 1) { 408 $this->warning('Invalid QuickTime atom smallbox size "'.$boxsmallsize.'" in atom "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $atomname).'" at offset: '.($atom_structure['offset'] + $atomoffset)); 409 $atom_structure['data'] = null; 410 $atomoffset = strlen($atom_data); 411 break; 412 } 413 switch ($boxsmalltype) { 414 case "\x10\xB5": 415 $atom_structure['data'] = $boxsmalldata; 416 break; 417 default: 418 $this->warning('Unknown QuickTime smallbox type: "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $boxsmalltype).'" ('.trim(getid3_lib::PrintHexBytes($boxsmalltype)).') at offset '.$baseoffset); 419 $atom_structure['data'] = $atom_data; 420 break; 421 } 422 $atomoffset += (4 + $boxsmallsize); 309 } 310 if ($isVideo && $framerate) { 311 $info['quicktime']['video']['frame_rate'] = $framerate; 312 $info['video']['frame_rate'] = $info['quicktime']['video']['frame_rate']; 313 } 314 if ($isVideo && $framecount) { 315 $info['quicktime']['video']['frame_count'] = $framecount; 316 } 317 break; 318 319 320 case "\xA9".'alb': // ALBum 321 case "\xA9".'ART': // 322 case "\xA9".'art': // ARTist 323 case "\xA9".'aut': // 324 case "\xA9".'cmt': // CoMmenT 325 case "\xA9".'com': // COMposer 326 case "\xA9".'cpy': // 327 case "\xA9".'day': // content created year 328 case "\xA9".'dir': // 329 case "\xA9".'ed1': // 330 case "\xA9".'ed2': // 331 case "\xA9".'ed3': // 332 case "\xA9".'ed4': // 333 case "\xA9".'ed5': // 334 case "\xA9".'ed6': // 335 case "\xA9".'ed7': // 336 case "\xA9".'ed8': // 337 case "\xA9".'ed9': // 338 case "\xA9".'enc': // 339 case "\xA9".'fmt': // 340 case "\xA9".'gen': // GENre 341 case "\xA9".'grp': // GRouPing 342 case "\xA9".'hst': // 343 case "\xA9".'inf': // 344 case "\xA9".'lyr': // LYRics 345 case "\xA9".'mak': // 346 case "\xA9".'mod': // 347 case "\xA9".'nam': // full NAMe 348 case "\xA9".'ope': // 349 case "\xA9".'PRD': // 350 case "\xA9".'prf': // 351 case "\xA9".'req': // 352 case "\xA9".'src': // 353 case "\xA9".'swr': // 354 case "\xA9".'too': // encoder 355 case "\xA9".'trk': // TRacK 356 case "\xA9".'url': // 357 case "\xA9".'wrn': // 358 case "\xA9".'wrt': // WRiTer 359 case '----': // itunes specific 360 case 'aART': // Album ARTist 361 case 'akID': // iTunes store account type 362 case 'apID': // Purchase Account 363 case 'atID': // 364 case 'catg': // CaTeGory 365 case 'cmID': // 366 case 'cnID': // 367 case 'covr': // COVeR artwork 368 case 'cpil': // ComPILation 369 case 'cprt': // CoPyRighT 370 case 'desc': // DESCription 371 case 'disk': // DISK number 372 case 'egid': // Episode Global ID 373 case 'geID': // 374 case 'gnre': // GeNRE 375 case 'hdvd': // HD ViDeo 376 case 'keyw': // KEYWord 377 case 'ldes': // Long DEScription 378 case 'pcst': // PodCaST 379 case 'pgap': // GAPless Playback 380 case 'plID': // 381 case 'purd': // PURchase Date 382 case 'purl': // Podcast URL 383 case 'rati': // 384 case 'rndu': // 385 case 'rpdu': // 386 case 'rtng': // RaTiNG 387 case 'sfID': // iTunes store country 388 case 'soaa': // SOrt Album Artist 389 case 'soal': // SOrt ALbum 390 case 'soar': // SOrt ARtist 391 case 'soco': // SOrt COmposer 392 case 'sonm': // SOrt NaMe 393 case 'sosn': // SOrt Show Name 394 case 'stik': // 395 case 'tmpo': // TeMPO (BPM) 396 case 'trkn': // TRacK Number 397 case 'tven': // tvEpisodeID 398 case 'tves': // TV EpiSode 399 case 'tvnn': // TV Network Name 400 case 'tvsh': // TV SHow Name 401 case 'tvsn': // TV SeasoN 402 if ($atom_parent == 'udta') { 403 // User data atom handler 404 $atom_structure['data_length'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2)); 405 $atom_structure['language_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 2)); 406 $atom_structure['data'] = substr($atom_data, 4); 407 408 $atom_structure['language'] = $this->QuicktimeLanguageLookup($atom_structure['language_id']); 409 if (empty($info['comments']['language']) || (!in_array($atom_structure['language'], $info['comments']['language']))) { 410 $info['comments']['language'][] = $atom_structure['language']; 423 411 } 424 412 } else { 425 while ($atomoffset < strlen($atom_data)) { 426 $boxsize = getid3_lib::BigEndian2Int(substr($atom_data, $atomoffset, 4)); 427 $boxtype = substr($atom_data, $atomoffset + 4, 4); 428 $boxdata = substr($atom_data, $atomoffset + 8, $boxsize - 8); 429 if ($boxsize <= 1) { 430 $this->warning('Invalid QuickTime atom box size "'.$boxsize.'" in atom "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $atomname).'" at offset: '.($atom_structure['offset'] + $atomoffset)); 431 $atom_structure['data'] = null; 432 $atomoffset = strlen($atom_data); 433 break; 413 // Apple item list box atom handler 414 $atomoffset = 0; 415 if (substr($atom_data, 2, 2) == "\x10\xB5") { 416 // not sure what it means, but observed on iPhone4 data. 417 // Each $atom_data has 2 bytes of datasize, plus 0x10B5, then data 418 while ($atomoffset < strlen($atom_data)) { 419 $boxsmallsize = getid3_lib::BigEndian2Int(substr($atom_data, $atomoffset, 2)); 420 $boxsmalltype = substr($atom_data, $atomoffset + 2, 2); 421 $boxsmalldata = substr($atom_data, $atomoffset + 4, $boxsmallsize); 422 if ($boxsmallsize <= 1) { 423 $this->warning('Invalid QuickTime atom smallbox size "'.$boxsmallsize.'" in atom "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $atomname).'" at offset: '.($atom_structure['offset'] + $atomoffset)); 424 $atom_structure['data'] = null; 425 $atomoffset = strlen($atom_data); 426 break; 427 } 428 switch ($boxsmalltype) { 429 case "\x10\xB5": 430 $atom_structure['data'] = $boxsmalldata; 431 break; 432 default: 433 $this->warning('Unknown QuickTime smallbox type: "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $boxsmalltype).'" ('.trim(getid3_lib::PrintHexBytes($boxsmalltype)).') at offset '.$baseoffset); 434 $atom_structure['data'] = $atom_data; 435 break; 436 } 437 $atomoffset += (4 + $boxsmallsize); 434 438 } 435 $atomoffset += $boxsize; 436 437 switch ($boxtype) { 438 case 'mean': 439 case 'name': 440 $atom_structure[$boxtype] = substr($boxdata, 4); 439 } else { 440 while ($atomoffset < strlen($atom_data)) { 441 $boxsize = getid3_lib::BigEndian2Int(substr($atom_data, $atomoffset, 4)); 442 $boxtype = substr($atom_data, $atomoffset + 4, 4); 443 $boxdata = substr($atom_data, $atomoffset + 8, $boxsize - 8); 444 if ($boxsize <= 1) { 445 $this->warning('Invalid QuickTime atom box size "'.$boxsize.'" in atom "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $atomname).'" at offset: '.($atom_structure['offset'] + $atomoffset)); 446 $atom_structure['data'] = null; 447 $atomoffset = strlen($atom_data); 441 448 break; 442 443 case 'data': 444 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($boxdata, 0, 1)); 445 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($boxdata, 1, 3)); 446 switch ($atom_structure['flags_raw']) { 447 case 0: // data flag 448 case 21: // tmpo/cpil flag 449 switch ($atomname) { 450 case 'cpil': 451 case 'hdvd': 452 case 'pcst': 453 case 'pgap': 454 // 8-bit integer (boolean) 455 $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1)); 456 break; 457 458 case 'tmpo': 459 // 16-bit integer 460 $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 2)); 461 break; 462 463 case 'disk': 464 case 'trkn': 465 // binary 466 $num = getid3_lib::BigEndian2Int(substr($boxdata, 10, 2)); 467 $num_total = getid3_lib::BigEndian2Int(substr($boxdata, 12, 2)); 468 $atom_structure['data'] = empty($num) ? '' : $num; 469 $atom_structure['data'] .= empty($num_total) ? '' : '/'.$num_total; 470 break; 471 472 case 'gnre': 473 // enum 474 $GenreID = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4)); 475 $atom_structure['data'] = getid3_id3v1::LookupGenreName($GenreID - 1); 476 break; 477 478 case 'rtng': 479 // 8-bit integer 480 $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1)); 481 $atom_structure['data'] = $this->QuicktimeContentRatingLookup($atom_structure[$atomname]); 482 break; 483 484 case 'stik': 485 // 8-bit integer (enum) 486 $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1)); 487 $atom_structure['data'] = $this->QuicktimeSTIKLookup($atom_structure[$atomname]); 488 break; 489 490 case 'sfID': 491 // 32-bit integer 492 $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4)); 493 $atom_structure['data'] = $this->QuicktimeStoreFrontCodeLookup($atom_structure[$atomname]); 494 break; 495 496 case 'egid': 497 case 'purl': 498 $atom_structure['data'] = substr($boxdata, 8); 499 break; 500 501 case 'plID': 502 // 64-bit integer 503 $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 8)); 504 break; 505 506 case 'covr': 507 $atom_structure['data'] = substr($boxdata, 8); 449 } 450 $atomoffset += $boxsize; 451 452 switch ($boxtype) { 453 case 'mean': 454 case 'name': 455 $atom_structure[$boxtype] = substr($boxdata, 4); 456 break; 457 458 case 'data': 459 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($boxdata, 0, 1)); 460 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($boxdata, 1, 3)); 461 switch ($atom_structure['flags_raw']) { 462 case 0: // data flag 463 case 21: // tmpo/cpil flag 464 switch ($atomname) { 465 case 'cpil': 466 case 'hdvd': 467 case 'pcst': 468 case 'pgap': 469 // 8-bit integer (boolean) 470 $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1)); 471 break; 472 473 case 'tmpo': 474 // 16-bit integer 475 $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 2)); 476 break; 477 478 case 'disk': 479 case 'trkn': 480 // binary 481 $num = getid3_lib::BigEndian2Int(substr($boxdata, 10, 2)); 482 $num_total = getid3_lib::BigEndian2Int(substr($boxdata, 12, 2)); 483 $atom_structure['data'] = empty($num) ? '' : $num; 484 $atom_structure['data'] .= empty($num_total) ? '' : '/'.$num_total; 485 break; 486 487 case 'gnre': 488 // enum 489 $GenreID = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4)); 490 $atom_structure['data'] = getid3_id3v1::LookupGenreName($GenreID - 1); 491 break; 492 493 case 'rtng': 494 // 8-bit integer 495 $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1)); 496 $atom_structure['data'] = $this->QuicktimeContentRatingLookup($atom_structure[$atomname]); 497 break; 498 499 case 'stik': 500 // 8-bit integer (enum) 501 $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1)); 502 $atom_structure['data'] = $this->QuicktimeSTIKLookup($atom_structure[$atomname]); 503 break; 504 505 case 'sfID': 506 // 32-bit integer 507 $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4)); 508 $atom_structure['data'] = $this->QuicktimeStoreFrontCodeLookup($atom_structure[$atomname]); 509 break; 510 511 case 'egid': 512 case 'purl': 513 $atom_structure['data'] = substr($boxdata, 8); 514 break; 515 516 case 'plID': 517 // 64-bit integer 518 $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 8)); 519 break; 520 521 case 'covr': 522 $atom_structure['data'] = substr($boxdata, 8); 523 // not a foolproof check, but better than nothing 524 if (preg_match('#^\\xFF\\xD8\\xFF#', $atom_structure['data'])) { 525 $atom_structure['image_mime'] = 'image/jpeg'; 526 } elseif (preg_match('#^\\x89\\x50\\x4E\\x47\\x0D\\x0A\\x1A\\x0A#', $atom_structure['data'])) { 527 $atom_structure['image_mime'] = 'image/png'; 528 } elseif (preg_match('#^GIF#', $atom_structure['data'])) { 529 $atom_structure['image_mime'] = 'image/gif'; 530 } 531 break; 532 533 case 'atID': 534 case 'cnID': 535 case 'geID': 536 case 'tves': 537 case 'tvsn': 538 default: 539 // 32-bit integer 540 $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4)); 541 } 542 break; 543 544 case 1: // text flag 545 case 13: // image flag 546 default: 547 $atom_structure['data'] = substr($boxdata, 8); 548 if ($atomname == 'covr') { 508 549 // not a foolproof check, but better than nothing 509 550 if (preg_match('#^\\xFF\\xD8\\xFF#', $atom_structure['data'])) { … … 514 555 $atom_structure['image_mime'] = 'image/gif'; 515 556 } 516 break;517 518 case 'atID':519 case 'cnID':520 case 'geID':521 case 'tves':522 case 'tvsn':523 default:524 // 32-bit integer525 $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4));526 }527 break;528 529 case 1: // text flag530 case 13: // image flag531 default:532 $atom_structure['data'] = substr($boxdata, 8);533 if ($atomname == 'covr') {534 // not a foolproof check, but better than nothing535 if (preg_match('#^\\xFF\\xD8\\xFF#', $atom_structure['data'])) {536 $atom_structure['image_mime'] = 'image/jpeg';537 } elseif (preg_match('#^\\x89\\x50\\x4E\\x47\\x0D\\x0A\\x1A\\x0A#', $atom_structure['data'])) {538 $atom_structure['image_mime'] = 'image/png';539 } elseif (preg_match('#^GIF#', $atom_structure['data'])) {540 $atom_structure['image_mime'] = 'image/gif';541 557 } 542 }543 break; 544 545 }546 break; 547 548 default:549 $this->warning('Unknown QuickTime box type: "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $boxtype).'" ('.trim(getid3_lib::PrintHexBytes($boxtype)).') at offset '.$baseoffset);550 $atom_structure['data'] = $atom_data; 551 558 break; 559 560 } 561 break; 562 563 default: 564 $this->warning('Unknown QuickTime box type: "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $boxtype).'" ('.trim(getid3_lib::PrintHexBytes($boxtype)).') at offset '.$baseoffset); 565 $atom_structure['data'] = $atom_data; 566 567 } 552 568 } 553 569 } 554 570 } 555 } 556 $this->CopyToAppropriateCommentsSection($atomname, $atom_structure['data'], $atom_structure['name']); 557 break; 558 559 560 case 'play': // auto-PLAY atom 561 $atom_structure['autoplay'] = (bool) getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 562 563 $info['quicktime']['autoplay'] = $atom_structure['autoplay']; 564 break; 565 566 567 case 'WLOC': // Window LOCation atom 568 $atom_structure['location_x'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2)); 569 $atom_structure['location_y'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 2)); 570 break; 571 572 573 case 'LOOP': // LOOPing atom 574 case 'SelO': // play SELection Only atom 575 case 'AllF': // play ALL Frames atom 576 $atom_structure['data'] = getid3_lib::BigEndian2Int($atom_data); 577 break; 578 579 580 case 'name': // 581 case 'MCPS': // Media Cleaner PRo 582 case '@PRM': // adobe PReMiere version 583 case '@PRQ': // adobe PRemiere Quicktime version 584 $atom_structure['data'] = $atom_data; 585 break; 586 587 588 case 'cmvd': // Compressed MooV Data atom 589 // Code by ubergeekØubergeek*tv based on information from 590 // http://developer.apple.com/quicktime/icefloe/dispatch012.html 591 $atom_structure['unCompressedSize'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); 592 593 $CompressedFileData = substr($atom_data, 4); 594 if ($UncompressedHeader = @gzuncompress($CompressedFileData)) { 595 $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($UncompressedHeader, 0, $atomHierarchy, $ParseAllPossibleAtoms); 596 } else { 597 $this->warning('Error decompressing compressed MOV atom at offset '.$atom_structure['offset']); 598 } 599 break; 600 601 602 case 'dcom': // Data COMpression atom 603 $atom_structure['compression_id'] = $atom_data; 604 $atom_structure['compression_text'] = $this->QuicktimeDCOMLookup($atom_data); 605 break; 606 607 608 case 'rdrf': // Reference movie Data ReFerence atom 609 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 610 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); 611 $atom_structure['flags']['internal_data'] = (bool) ($atom_structure['flags_raw'] & 0x000001); 612 613 $atom_structure['reference_type_name'] = substr($atom_data, 4, 4); 614 $atom_structure['reference_length'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); 615 switch ($atom_structure['reference_type_name']) { 616 case 'url ': 617 $atom_structure['url'] = $this->NoNullString(substr($atom_data, 12)); 618 break; 619 620 case 'alis': 621 $atom_structure['file_alias'] = substr($atom_data, 12); 622 break; 623 624 case 'rsrc': 625 $atom_structure['resource_alias'] = substr($atom_data, 12); 626 break; 627 628 default: 629 $atom_structure['data'] = substr($atom_data, 12); 630 break; 631 } 632 break; 633 634 635 case 'rmqu': // Reference Movie QUality atom 636 $atom_structure['movie_quality'] = getid3_lib::BigEndian2Int($atom_data); 637 break; 638 639 640 case 'rmcs': // Reference Movie Cpu Speed atom 641 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 642 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 643 $atom_structure['cpu_speed_rating'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); 644 break; 645 646 647 case 'rmvc': // Reference Movie Version Check atom 648 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 649 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 650 $atom_structure['gestalt_selector'] = substr($atom_data, 4, 4); 651 $atom_structure['gestalt_value_mask'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); 652 $atom_structure['gestalt_value'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); 653 $atom_structure['gestalt_check_type'] = getid3_lib::BigEndian2Int(substr($atom_data, 14, 2)); 654 break; 655 656 657 case 'rmcd': // Reference Movie Component check atom 658 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 659 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 660 $atom_structure['component_type'] = substr($atom_data, 4, 4); 661 $atom_structure['component_subtype'] = substr($atom_data, 8, 4); 662 $atom_structure['component_manufacturer'] = substr($atom_data, 12, 4); 663 $atom_structure['component_flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); 664 $atom_structure['component_flags_mask'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 4)); 665 $atom_structure['component_min_version'] = getid3_lib::BigEndian2Int(substr($atom_data, 24, 4)); 666 break; 667 668 669 case 'rmdr': // Reference Movie Data Rate atom 670 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 671 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 672 $atom_structure['data_rate'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 673 674 $atom_structure['data_rate_bps'] = $atom_structure['data_rate'] * 10; 675 break; 676 677 678 case 'rmla': // Reference Movie Language Atom 679 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 680 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 681 $atom_structure['language_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); 682 683 $atom_structure['language'] = $this->QuicktimeLanguageLookup($atom_structure['language_id']); 684 if (empty($info['comments']['language']) || (!in_array($atom_structure['language'], $info['comments']['language']))) { 685 $info['comments']['language'][] = $atom_structure['language']; 686 } 687 break; 688 689 690 case 'rmla': // Reference Movie Language Atom 691 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 692 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 693 $atom_structure['track_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); 694 break; 695 696 697 case 'ptv ': // Print To Video - defines a movie's full screen mode 698 // http://developer.apple.com/documentation/QuickTime/APIREF/SOURCESIV/at_ptv-_pg.htm 699 $atom_structure['display_size_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2)); 700 $atom_structure['reserved_1'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 2)); // hardcoded: 0x0000 701 $atom_structure['reserved_2'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); // hardcoded: 0x0000 702 $atom_structure['slide_show_flag'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 1)); 703 $atom_structure['play_on_open_flag'] = getid3_lib::BigEndian2Int(substr($atom_data, 7, 1)); 704 705 $atom_structure['flags']['play_on_open'] = (bool) $atom_structure['play_on_open_flag']; 706 $atom_structure['flags']['slide_show'] = (bool) $atom_structure['slide_show_flag']; 707 708 $ptv_lookup[0] = 'normal'; 709 $ptv_lookup[1] = 'double'; 710 $ptv_lookup[2] = 'half'; 711 $ptv_lookup[3] = 'full'; 712 $ptv_lookup[4] = 'current'; 713 if (isset($ptv_lookup[$atom_structure['display_size_raw']])) { 714 $atom_structure['display_size'] = $ptv_lookup[$atom_structure['display_size_raw']]; 715 } else { 716 $this->warning('unknown "ptv " display constant ('.$atom_structure['display_size_raw'].')'); 717 } 718 break; 719 720 721 case 'stsd': // Sample Table Sample Description atom 722 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 723 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 724 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 725 726 // see: https://github.com/JamesHeinrich/getID3/issues/111 727 // Some corrupt files have been known to have high bits set in the number_entries field 728 // This field shouldn't really need to be 32-bits, values stores are likely in the range 1-100000 729 // Workaround: mask off the upper byte and throw a warning if it's nonzero 730 if ($atom_structure['number_entries'] > 0x000FFFFF) { 731 if ($atom_structure['number_entries'] > 0x00FFFFFF) { 732 $this->warning('"stsd" atom contains improbably large number_entries (0x'.getid3_lib::PrintHexBytes(substr($atom_data, 4, 4), true, false).' = '.$atom_structure['number_entries'].'), probably in error. Ignoring upper byte and interpreting this as 0x'.getid3_lib::PrintHexBytes(substr($atom_data, 5, 3), true, false).' = '.($atom_structure['number_entries'] & 0x00FFFFFF)); 733 $atom_structure['number_entries'] = ($atom_structure['number_entries'] & 0x00FFFFFF); 571 $this->CopyToAppropriateCommentsSection($atomname, $atom_structure['data'], $atom_structure['name']); 572 break; 573 574 575 case 'play': // auto-PLAY atom 576 $atom_structure['autoplay'] = (bool) getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 577 578 $info['quicktime']['autoplay'] = $atom_structure['autoplay']; 579 break; 580 581 582 case 'WLOC': // Window LOCation atom 583 $atom_structure['location_x'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2)); 584 $atom_structure['location_y'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 2)); 585 break; 586 587 588 case 'LOOP': // LOOPing atom 589 case 'SelO': // play SELection Only atom 590 case 'AllF': // play ALL Frames atom 591 $atom_structure['data'] = getid3_lib::BigEndian2Int($atom_data); 592 break; 593 594 595 case 'name': // 596 case 'MCPS': // Media Cleaner PRo 597 case '@PRM': // adobe PReMiere version 598 case '@PRQ': // adobe PRemiere Quicktime version 599 $atom_structure['data'] = $atom_data; 600 break; 601 602 603 case 'cmvd': // Compressed MooV Data atom 604 // Code by ubergeekØubergeek*tv based on information from 605 // http://developer.apple.com/quicktime/icefloe/dispatch012.html 606 $atom_structure['unCompressedSize'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); 607 608 $CompressedFileData = substr($atom_data, 4); 609 if ($UncompressedHeader = @gzuncompress($CompressedFileData)) { 610 $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($UncompressedHeader, 0, $atomHierarchy, $ParseAllPossibleAtoms); 734 611 } else { 735 $this->warning('"stsd" atom contains improbably large number_entries (0x'.getid3_lib::PrintHexBytes(substr($atom_data, 4, 4), true, false).' = '.$atom_structure['number_entries'].'), probably in error. Please report this to info@getid3.org referencing bug report #111'); 736 } 737 } 738 739 $stsdEntriesDataOffset = 8; 740 for ($i = 0; $i < $atom_structure['number_entries']; $i++) { 741 $atom_structure['sample_description_table'][$i]['size'] = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 4)); 742 $stsdEntriesDataOffset += 4; 743 $atom_structure['sample_description_table'][$i]['data_format'] = substr($atom_data, $stsdEntriesDataOffset, 4); 744 $stsdEntriesDataOffset += 4; 745 $atom_structure['sample_description_table'][$i]['reserved'] = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 6)); 746 $stsdEntriesDataOffset += 6; 747 $atom_structure['sample_description_table'][$i]['reference_index'] = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 2)); 748 $stsdEntriesDataOffset += 2; 749 $atom_structure['sample_description_table'][$i]['data'] = substr($atom_data, $stsdEntriesDataOffset, ($atom_structure['sample_description_table'][$i]['size'] - 4 - 4 - 6 - 2)); 750 $stsdEntriesDataOffset += ($atom_structure['sample_description_table'][$i]['size'] - 4 - 4 - 6 - 2); 751 752 $atom_structure['sample_description_table'][$i]['encoder_version'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 0, 2)); 753 $atom_structure['sample_description_table'][$i]['encoder_revision'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 2, 2)); 754 $atom_structure['sample_description_table'][$i]['encoder_vendor'] = substr($atom_structure['sample_description_table'][$i]['data'], 4, 4); 755 756 switch ($atom_structure['sample_description_table'][$i]['encoder_vendor']) { 757 758 case "\x00\x00\x00\x00": 759 // audio tracks 760 $atom_structure['sample_description_table'][$i]['audio_channels'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 8, 2)); 761 $atom_structure['sample_description_table'][$i]['audio_bit_depth'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 10, 2)); 762 $atom_structure['sample_description_table'][$i]['audio_compression_id'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 12, 2)); 763 $atom_structure['sample_description_table'][$i]['audio_packet_size'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 14, 2)); 764 $atom_structure['sample_description_table'][$i]['audio_sample_rate'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 16, 4)); 765 766 // video tracks 767 // http://developer.apple.com/library/mac/#documentation/QuickTime/QTFF/QTFFChap3/qtff3.html 768 $atom_structure['sample_description_table'][$i]['temporal_quality'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 8, 4)); 769 $atom_structure['sample_description_table'][$i]['spatial_quality'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 12, 4)); 770 $atom_structure['sample_description_table'][$i]['width'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 16, 2)); 771 $atom_structure['sample_description_table'][$i]['height'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 18, 2)); 772 $atom_structure['sample_description_table'][$i]['resolution_x'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 24, 4)); 773 $atom_structure['sample_description_table'][$i]['resolution_y'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 28, 4)); 774 $atom_structure['sample_description_table'][$i]['data_size'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 32, 4)); 775 $atom_structure['sample_description_table'][$i]['frame_count'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 36, 2)); 776 $atom_structure['sample_description_table'][$i]['compressor_name'] = substr($atom_structure['sample_description_table'][$i]['data'], 38, 4); 777 $atom_structure['sample_description_table'][$i]['pixel_depth'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 42, 2)); 778 $atom_structure['sample_description_table'][$i]['color_table_id'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 44, 2)); 779 780 switch ($atom_structure['sample_description_table'][$i]['data_format']) { 781 case '2vuY': 782 case 'avc1': 783 case 'cvid': 784 case 'dvc ': 785 case 'dvcp': 786 case 'gif ': 787 case 'h263': 788 case 'jpeg': 789 case 'kpcd': 790 case 'mjpa': 791 case 'mjpb': 792 case 'mp4v': 793 case 'png ': 794 case 'raw ': 795 case 'rle ': 796 case 'rpza': 797 case 'smc ': 798 case 'SVQ1': 799 case 'SVQ3': 800 case 'tiff': 801 case 'v210': 802 case 'v216': 803 case 'v308': 804 case 'v408': 805 case 'v410': 806 case 'yuv2': 807 $info['fileformat'] = 'mp4'; 808 $info['video']['fourcc'] = $atom_structure['sample_description_table'][$i]['data_format']; 809 // http://www.getid3.org/phpBB3/viewtopic.php?t=1550 810 //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 811 if (!empty($atom_structure['sample_description_table'][$i]['width']) && !empty($atom_structure['sample_description_table'][$i]['height'])) { 812 // assume that values stored here are more important than values stored in [tkhd] atom 813 $info['video']['resolution_x'] = $atom_structure['sample_description_table'][$i]['width']; 814 $info['video']['resolution_y'] = $atom_structure['sample_description_table'][$i]['height']; 815 $info['quicktime']['video']['resolution_x'] = $info['video']['resolution_x']; 816 $info['quicktime']['video']['resolution_y'] = $info['video']['resolution_y']; 817 } 818 break; 819 820 case 'qtvr': 821 $info['video']['dataformat'] = 'quicktimevr'; 822 break; 823 824 case 'mp4a': 825 default: 826 $info['quicktime']['audio']['codec'] = $this->QuicktimeAudioCodecLookup($atom_structure['sample_description_table'][$i]['data_format']); 827 $info['quicktime']['audio']['sample_rate'] = $atom_structure['sample_description_table'][$i]['audio_sample_rate']; 828 $info['quicktime']['audio']['channels'] = $atom_structure['sample_description_table'][$i]['audio_channels']; 829 $info['quicktime']['audio']['bit_depth'] = $atom_structure['sample_description_table'][$i]['audio_bit_depth']; 830 $info['audio']['codec'] = $info['quicktime']['audio']['codec']; 831 $info['audio']['sample_rate'] = $info['quicktime']['audio']['sample_rate']; 832 $info['audio']['channels'] = $info['quicktime']['audio']['channels']; 833 $info['audio']['bits_per_sample'] = $info['quicktime']['audio']['bit_depth']; 834 switch ($atom_structure['sample_description_table'][$i]['data_format']) { 835 case 'raw ': // PCM 836 case 'alac': // Apple Lossless Audio Codec 837 $info['audio']['lossless'] = true; 838 break; 839 default: 840 $info['audio']['lossless'] = false; 841 break; 842 } 843 break; 844 } 612 $this->warning('Error decompressing compressed MOV atom at offset '.$atom_structure['offset']); 613 } 614 break; 615 616 617 case 'dcom': // Data COMpression atom 618 $atom_structure['compression_id'] = $atom_data; 619 $atom_structure['compression_text'] = $this->QuicktimeDCOMLookup($atom_data); 620 break; 621 622 623 case 'rdrf': // Reference movie Data ReFerence atom 624 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 625 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); 626 $atom_structure['flags']['internal_data'] = (bool) ($atom_structure['flags_raw'] & 0x000001); 627 628 $atom_structure['reference_type_name'] = substr($atom_data, 4, 4); 629 $atom_structure['reference_length'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); 630 switch ($atom_structure['reference_type_name']) { 631 case 'url ': 632 $atom_structure['url'] = $this->NoNullString(substr($atom_data, 12)); 845 633 break; 846 634 635 case 'alis': 636 $atom_structure['file_alias'] = substr($atom_data, 12); 637 break; 638 639 case 'rsrc': 640 $atom_structure['resource_alias'] = substr($atom_data, 12); 641 break; 642 847 643 default: 848 switch ($atom_structure['sample_description_table'][$i]['data_format']) { 849 case 'mp4s': 850 $info['fileformat'] = 'mp4'; 851 break; 852 853 default: 854 // video atom 855 $atom_structure['sample_description_table'][$i]['video_temporal_quality'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 8, 4)); 856 $atom_structure['sample_description_table'][$i]['video_spatial_quality'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 12, 4)); 857 $atom_structure['sample_description_table'][$i]['video_frame_width'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 16, 2)); 858 $atom_structure['sample_description_table'][$i]['video_frame_height'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 18, 2)); 859 $atom_structure['sample_description_table'][$i]['video_resolution_x'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 20, 4)); 860 $atom_structure['sample_description_table'][$i]['video_resolution_y'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 24, 4)); 861 $atom_structure['sample_description_table'][$i]['video_data_size'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 28, 4)); 862 $atom_structure['sample_description_table'][$i]['video_frame_count'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 32, 2)); 863 $atom_structure['sample_description_table'][$i]['video_encoder_name_len'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 34, 1)); 864 $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']); 865 $atom_structure['sample_description_table'][$i]['video_pixel_color_depth'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 66, 2)); 866 $atom_structure['sample_description_table'][$i]['video_color_table_id'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 68, 2)); 867 868 $atom_structure['sample_description_table'][$i]['video_pixel_color_type'] = (($atom_structure['sample_description_table'][$i]['video_pixel_color_depth'] > 32) ? 'grayscale' : 'color'); 869 $atom_structure['sample_description_table'][$i]['video_pixel_color_name'] = $this->QuicktimeColorNameLookup($atom_structure['sample_description_table'][$i]['video_pixel_color_depth']); 870 871 if ($atom_structure['sample_description_table'][$i]['video_pixel_color_name'] != 'invalid') { 872 $info['quicktime']['video']['codec_fourcc'] = $atom_structure['sample_description_table'][$i]['data_format']; 873 $info['quicktime']['video']['codec_fourcc_lookup'] = $this->QuicktimeVideoCodecLookup($atom_structure['sample_description_table'][$i]['data_format']); 874 $info['quicktime']['video']['codec'] = (($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']); 875 $info['quicktime']['video']['color_depth'] = $atom_structure['sample_description_table'][$i]['video_pixel_color_depth']; 876 $info['quicktime']['video']['color_depth_name'] = $atom_structure['sample_description_table'][$i]['video_pixel_color_name']; 877 878 $info['video']['codec'] = $info['quicktime']['video']['codec']; 879 $info['video']['bits_per_sample'] = $info['quicktime']['video']['color_depth']; 880 } 881 $info['video']['lossless'] = false; 882 $info['video']['pixel_aspect_ratio'] = (float) 1; 883 break; 884 } 644 $atom_structure['data'] = substr($atom_data, 12); 885 645 break; 886 646 } 887 switch (strtolower($atom_structure['sample_description_table'][$i]['data_format'])) { 888 case 'mp4a': 889 $info['audio']['dataformat'] = 'mp4'; 890 $info['quicktime']['audio']['codec'] = 'mp4'; 891 break; 892 893 case '3ivx': 894 case '3iv1': 895 case '3iv2': 896 $info['video']['dataformat'] = '3ivx'; 897 break; 898 899 case 'xvid': 900 $info['video']['dataformat'] = 'xvid'; 901 break; 902 903 case 'mp4v': 904 $info['video']['dataformat'] = 'mpeg4'; 905 break; 906 907 case 'divx': 908 case 'div1': 909 case 'div2': 910 case 'div3': 911 case 'div4': 912 case 'div5': 913 case 'div6': 914 $info['video']['dataformat'] = 'divx'; 915 break; 916 917 default: 918 // do nothing 919 break; 920 } 921 unset($atom_structure['sample_description_table'][$i]['data']); 922 } 923 break; 924 925 926 case 'stts': // Sample Table Time-to-Sample atom 927 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 928 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 929 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 930 $sttsEntriesDataOffset = 8; 931 //$FrameRateCalculatorArray = array(); 932 $frames_count = 0; 933 934 $max_stts_entries_to_scan = ($info['php_memory_limit'] ? min(floor($this->getid3->memory_limit / 10000), $atom_structure['number_entries']) : $atom_structure['number_entries']); 935 if ($max_stts_entries_to_scan < $atom_structure['number_entries']) { 936 $this->warning('QuickTime atom "stts" has '.$atom_structure['number_entries'].' but only scanning the first '.$max_stts_entries_to_scan.' entries due to limited PHP memory available ('.floor($atom_structure['number_entries'] / 1048576).'MB).'); 937 } 938 for ($i = 0; $i < $max_stts_entries_to_scan; $i++) { 939 $atom_structure['time_to_sample_table'][$i]['sample_count'] = getid3_lib::BigEndian2Int(substr($atom_data, $sttsEntriesDataOffset, 4)); 940 $sttsEntriesDataOffset += 4; 941 $atom_structure['time_to_sample_table'][$i]['sample_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, $sttsEntriesDataOffset, 4)); 942 $sttsEntriesDataOffset += 4; 943 944 $frames_count += $atom_structure['time_to_sample_table'][$i]['sample_count']; 945 946 // THIS SECTION REPLACED WITH CODE IN "stbl" ATOM 947 //if (!empty($info['quicktime']['time_scale']) && ($atom_structure['time_to_sample_table'][$i]['sample_duration'] > 0)) { 948 // $stts_new_framerate = $info['quicktime']['time_scale'] / $atom_structure['time_to_sample_table'][$i]['sample_duration']; 949 // if ($stts_new_framerate <= 60) { 950 // // some atoms have durations of "1" giving a very large framerate, which probably is not right 951 // $info['video']['frame_rate'] = max($info['video']['frame_rate'], $stts_new_framerate); 952 // } 953 //} 954 // 955 //$FrameRateCalculatorArray[($info['quicktime']['time_scale'] / $atom_structure['time_to_sample_table'][$i]['sample_duration'])] += $atom_structure['time_to_sample_table'][$i]['sample_count']; 956 } 957 $info['quicktime']['stts_framecount'][] = $frames_count; 958 //$sttsFramesTotal = 0; 959 //$sttsSecondsTotal = 0; 960 //foreach ($FrameRateCalculatorArray as $frames_per_second => $frame_count) { 961 // if (($frames_per_second > 60) || ($frames_per_second < 1)) { 962 // // not video FPS information, probably audio information 963 // $sttsFramesTotal = 0; 964 // $sttsSecondsTotal = 0; 965 // break; 966 // } 967 // $sttsFramesTotal += $frame_count; 968 // $sttsSecondsTotal += $frame_count / $frames_per_second; 969 //} 970 //if (($sttsFramesTotal > 0) && ($sttsSecondsTotal > 0)) { 971 // if (($sttsFramesTotal / $sttsSecondsTotal) > $info['video']['frame_rate']) { 972 // $info['video']['frame_rate'] = $sttsFramesTotal / $sttsSecondsTotal; 973 // } 974 //} 975 break; 976 977 978 case 'stss': // Sample Table Sync Sample (key frames) atom 979 if ($ParseAllPossibleAtoms) { 647 break; 648 649 650 case 'rmqu': // Reference Movie QUality atom 651 $atom_structure['movie_quality'] = getid3_lib::BigEndian2Int($atom_data); 652 break; 653 654 655 case 'rmcs': // Reference Movie Cpu Speed atom 656 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 657 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 658 $atom_structure['cpu_speed_rating'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); 659 break; 660 661 662 case 'rmvc': // Reference Movie Version Check atom 663 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 664 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 665 $atom_structure['gestalt_selector'] = substr($atom_data, 4, 4); 666 $atom_structure['gestalt_value_mask'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); 667 $atom_structure['gestalt_value'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); 668 $atom_structure['gestalt_check_type'] = getid3_lib::BigEndian2Int(substr($atom_data, 14, 2)); 669 break; 670 671 672 case 'rmcd': // Reference Movie Component check atom 673 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 674 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 675 $atom_structure['component_type'] = substr($atom_data, 4, 4); 676 $atom_structure['component_subtype'] = substr($atom_data, 8, 4); 677 $atom_structure['component_manufacturer'] = substr($atom_data, 12, 4); 678 $atom_structure['component_flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); 679 $atom_structure['component_flags_mask'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 4)); 680 $atom_structure['component_min_version'] = getid3_lib::BigEndian2Int(substr($atom_data, 24, 4)); 681 break; 682 683 684 case 'rmdr': // Reference Movie Data Rate atom 685 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 686 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 687 $atom_structure['data_rate'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 688 689 $atom_structure['data_rate_bps'] = $atom_structure['data_rate'] * 10; 690 break; 691 692 693 case 'rmla': // Reference Movie Language Atom 694 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 695 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 696 $atom_structure['language_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); 697 698 $atom_structure['language'] = $this->QuicktimeLanguageLookup($atom_structure['language_id']); 699 if (empty($info['comments']['language']) || (!in_array($atom_structure['language'], $info['comments']['language']))) { 700 $info['comments']['language'][] = $atom_structure['language']; 701 } 702 break; 703 704 705 case 'ptv ': // Print To Video - defines a movie's full screen mode 706 // http://developer.apple.com/documentation/QuickTime/APIREF/SOURCESIV/at_ptv-_pg.htm 707 $atom_structure['display_size_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2)); 708 $atom_structure['reserved_1'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 2)); // hardcoded: 0x0000 709 $atom_structure['reserved_2'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); // hardcoded: 0x0000 710 $atom_structure['slide_show_flag'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 1)); 711 $atom_structure['play_on_open_flag'] = getid3_lib::BigEndian2Int(substr($atom_data, 7, 1)); 712 713 $atom_structure['flags']['play_on_open'] = (bool) $atom_structure['play_on_open_flag']; 714 $atom_structure['flags']['slide_show'] = (bool) $atom_structure['slide_show_flag']; 715 716 $ptv_lookup[0] = 'normal'; 717 $ptv_lookup[1] = 'double'; 718 $ptv_lookup[2] = 'half'; 719 $ptv_lookup[3] = 'full'; 720 $ptv_lookup[4] = 'current'; 721 if (isset($ptv_lookup[$atom_structure['display_size_raw']])) { 722 $atom_structure['display_size'] = $ptv_lookup[$atom_structure['display_size_raw']]; 723 } else { 724 $this->warning('unknown "ptv " display constant ('.$atom_structure['display_size_raw'].')'); 725 } 726 break; 727 728 729 case 'stsd': // Sample Table Sample Description atom 980 730 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 981 731 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 982 732 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 983 $stssEntriesDataOffset = 8; 733 734 // see: https://github.com/JamesHeinrich/getID3/issues/111 735 // Some corrupt files have been known to have high bits set in the number_entries field 736 // This field shouldn't really need to be 32-bits, values stores are likely in the range 1-100000 737 // Workaround: mask off the upper byte and throw a warning if it's nonzero 738 if ($atom_structure['number_entries'] > 0x000FFFFF) { 739 if ($atom_structure['number_entries'] > 0x00FFFFFF) { 740 $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)); 741 $atom_structure['number_entries'] = ($atom_structure['number_entries'] & 0x00FFFFFF); 742 } else { 743 $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'); 744 } 745 } 746 747 $stsdEntriesDataOffset = 8; 984 748 for ($i = 0; $i < $atom_structure['number_entries']; $i++) { 985 $atom_structure['time_to_sample_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stssEntriesDataOffset, 4)); 986 $stssEntriesDataOffset += 4; 987 } 988 } 989 break; 990 991 992 case 'stsc': // Sample Table Sample-to-Chunk atom 993 if ($ParseAllPossibleAtoms) { 749 $atom_structure['sample_description_table'][$i]['size'] = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 4)); 750 $stsdEntriesDataOffset += 4; 751 $atom_structure['sample_description_table'][$i]['data_format'] = substr($atom_data, $stsdEntriesDataOffset, 4); 752 $stsdEntriesDataOffset += 4; 753 $atom_structure['sample_description_table'][$i]['reserved'] = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 6)); 754 $stsdEntriesDataOffset += 6; 755 $atom_structure['sample_description_table'][$i]['reference_index'] = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 2)); 756 $stsdEntriesDataOffset += 2; 757 $atom_structure['sample_description_table'][$i]['data'] = substr($atom_data, $stsdEntriesDataOffset, ($atom_structure['sample_description_table'][$i]['size'] - 4 - 4 - 6 - 2)); 758 $stsdEntriesDataOffset += ($atom_structure['sample_description_table'][$i]['size'] - 4 - 4 - 6 - 2); 759 760 $atom_structure['sample_description_table'][$i]['encoder_version'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 0, 2)); 761 $atom_structure['sample_description_table'][$i]['encoder_revision'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 2, 2)); 762 $atom_structure['sample_description_table'][$i]['encoder_vendor'] = substr($atom_structure['sample_description_table'][$i]['data'], 4, 4); 763 764 switch ($atom_structure['sample_description_table'][$i]['encoder_vendor']) { 765 766 case "\x00\x00\x00\x00": 767 // audio tracks 768 $atom_structure['sample_description_table'][$i]['audio_channels'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 8, 2)); 769 $atom_structure['sample_description_table'][$i]['audio_bit_depth'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 10, 2)); 770 $atom_structure['sample_description_table'][$i]['audio_compression_id'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 12, 2)); 771 $atom_structure['sample_description_table'][$i]['audio_packet_size'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 14, 2)); 772 $atom_structure['sample_description_table'][$i]['audio_sample_rate'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 16, 4)); 773 774 // video tracks 775 // http://developer.apple.com/library/mac/#documentation/QuickTime/QTFF/QTFFChap3/qtff3.html 776 $atom_structure['sample_description_table'][$i]['temporal_quality'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 8, 4)); 777 $atom_structure['sample_description_table'][$i]['spatial_quality'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 12, 4)); 778 $atom_structure['sample_description_table'][$i]['width'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 16, 2)); 779 $atom_structure['sample_description_table'][$i]['height'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 18, 2)); 780 $atom_structure['sample_description_table'][$i]['resolution_x'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 24, 4)); 781 $atom_structure['sample_description_table'][$i]['resolution_y'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 28, 4)); 782 $atom_structure['sample_description_table'][$i]['data_size'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 32, 4)); 783 $atom_structure['sample_description_table'][$i]['frame_count'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 36, 2)); 784 $atom_structure['sample_description_table'][$i]['compressor_name'] = substr($atom_structure['sample_description_table'][$i]['data'], 38, 4); 785 $atom_structure['sample_description_table'][$i]['pixel_depth'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 42, 2)); 786 $atom_structure['sample_description_table'][$i]['color_table_id'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 44, 2)); 787 788 switch ($atom_structure['sample_description_table'][$i]['data_format']) { 789 case '2vuY': 790 case 'avc1': 791 case 'cvid': 792 case 'dvc ': 793 case 'dvcp': 794 case 'gif ': 795 case 'h263': 796 case 'jpeg': 797 case 'kpcd': 798 case 'mjpa': 799 case 'mjpb': 800 case 'mp4v': 801 case 'png ': 802 case 'raw ': 803 case 'rle ': 804 case 'rpza': 805 case 'smc ': 806 case 'SVQ1': 807 case 'SVQ3': 808 case 'tiff': 809 case 'v210': 810 case 'v216': 811 case 'v308': 812 case 'v408': 813 case 'v410': 814 case 'yuv2': 815 $info['fileformat'] = 'mp4'; 816 $info['video']['fourcc'] = $atom_structure['sample_description_table'][$i]['data_format']; 817 if ($this->QuicktimeVideoCodecLookup($info['video']['fourcc'])) { 818 $info['video']['fourcc_lookup'] = $this->QuicktimeVideoCodecLookup($info['video']['fourcc']); 819 } 820 821 // https://www.getid3.org/phpBB3/viewtopic.php?t=1550 822 //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 823 if (!empty($atom_structure['sample_description_table'][$i]['width']) && !empty($atom_structure['sample_description_table'][$i]['height'])) { 824 // assume that values stored here are more important than values stored in [tkhd] atom 825 $info['video']['resolution_x'] = $atom_structure['sample_description_table'][$i]['width']; 826 $info['video']['resolution_y'] = $atom_structure['sample_description_table'][$i]['height']; 827 $info['quicktime']['video']['resolution_x'] = $info['video']['resolution_x']; 828 $info['quicktime']['video']['resolution_y'] = $info['video']['resolution_y']; 829 } 830 break; 831 832 case 'qtvr': 833 $info['video']['dataformat'] = 'quicktimevr'; 834 break; 835 836 case 'mp4a': 837 default: 838 $info['quicktime']['audio']['codec'] = $this->QuicktimeAudioCodecLookup($atom_structure['sample_description_table'][$i]['data_format']); 839 $info['quicktime']['audio']['sample_rate'] = $atom_structure['sample_description_table'][$i]['audio_sample_rate']; 840 $info['quicktime']['audio']['channels'] = $atom_structure['sample_description_table'][$i]['audio_channels']; 841 $info['quicktime']['audio']['bit_depth'] = $atom_structure['sample_description_table'][$i]['audio_bit_depth']; 842 $info['audio']['codec'] = $info['quicktime']['audio']['codec']; 843 $info['audio']['sample_rate'] = $info['quicktime']['audio']['sample_rate']; 844 $info['audio']['channels'] = $info['quicktime']['audio']['channels']; 845 $info['audio']['bits_per_sample'] = $info['quicktime']['audio']['bit_depth']; 846 switch ($atom_structure['sample_description_table'][$i]['data_format']) { 847 case 'raw ': // PCM 848 case 'alac': // Apple Lossless Audio Codec 849 case 'sowt': // signed/two's complement (Little Endian) 850 case 'twos': // signed/two's complement (Big Endian) 851 case 'in24': // 24-bit Integer 852 case 'in32': // 32-bit Integer 853 case 'fl32': // 32-bit Floating Point 854 case 'fl64': // 64-bit Floating Point 855 $info['audio']['lossless'] = $info['quicktime']['audio']['lossless'] = true; 856 $info['audio']['bitrate'] = $info['quicktime']['audio']['bitrate'] = $info['audio']['channels'] * $info['audio']['bits_per_sample'] * $info['audio']['sample_rate']; 857 break; 858 default: 859 $info['audio']['lossless'] = false; 860 break; 861 } 862 break; 863 } 864 break; 865 866 default: 867 switch ($atom_structure['sample_description_table'][$i]['data_format']) { 868 case 'mp4s': 869 $info['fileformat'] = 'mp4'; 870 break; 871 872 default: 873 // video atom 874 $atom_structure['sample_description_table'][$i]['video_temporal_quality'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 8, 4)); 875 $atom_structure['sample_description_table'][$i]['video_spatial_quality'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 12, 4)); 876 $atom_structure['sample_description_table'][$i]['video_frame_width'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 16, 2)); 877 $atom_structure['sample_description_table'][$i]['video_frame_height'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 18, 2)); 878 $atom_structure['sample_description_table'][$i]['video_resolution_x'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 20, 4)); 879 $atom_structure['sample_description_table'][$i]['video_resolution_y'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 24, 4)); 880 $atom_structure['sample_description_table'][$i]['video_data_size'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 28, 4)); 881 $atom_structure['sample_description_table'][$i]['video_frame_count'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 32, 2)); 882 $atom_structure['sample_description_table'][$i]['video_encoder_name_len'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 34, 1)); 883 $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']); 884 $atom_structure['sample_description_table'][$i]['video_pixel_color_depth'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 66, 2)); 885 $atom_structure['sample_description_table'][$i]['video_color_table_id'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 68, 2)); 886 887 $atom_structure['sample_description_table'][$i]['video_pixel_color_type'] = (($atom_structure['sample_description_table'][$i]['video_pixel_color_depth'] > 32) ? 'grayscale' : 'color'); 888 $atom_structure['sample_description_table'][$i]['video_pixel_color_name'] = $this->QuicktimeColorNameLookup($atom_structure['sample_description_table'][$i]['video_pixel_color_depth']); 889 890 if ($atom_structure['sample_description_table'][$i]['video_pixel_color_name'] != 'invalid') { 891 $info['quicktime']['video']['codec_fourcc'] = $atom_structure['sample_description_table'][$i]['data_format']; 892 $info['quicktime']['video']['codec_fourcc_lookup'] = $this->QuicktimeVideoCodecLookup($atom_structure['sample_description_table'][$i]['data_format']); 893 $info['quicktime']['video']['codec'] = (($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']); 894 $info['quicktime']['video']['color_depth'] = $atom_structure['sample_description_table'][$i]['video_pixel_color_depth']; 895 $info['quicktime']['video']['color_depth_name'] = $atom_structure['sample_description_table'][$i]['video_pixel_color_name']; 896 897 $info['video']['codec'] = $info['quicktime']['video']['codec']; 898 $info['video']['bits_per_sample'] = $info['quicktime']['video']['color_depth']; 899 } 900 $info['video']['lossless'] = false; 901 $info['video']['pixel_aspect_ratio'] = (float) 1; 902 break; 903 } 904 break; 905 } 906 switch (strtolower($atom_structure['sample_description_table'][$i]['data_format'])) { 907 case 'mp4a': 908 $info['audio']['dataformat'] = 'mp4'; 909 $info['quicktime']['audio']['codec'] = 'mp4'; 910 break; 911 912 case '3ivx': 913 case '3iv1': 914 case '3iv2': 915 $info['video']['dataformat'] = '3ivx'; 916 break; 917 918 case 'xvid': 919 $info['video']['dataformat'] = 'xvid'; 920 break; 921 922 case 'mp4v': 923 $info['video']['dataformat'] = 'mpeg4'; 924 break; 925 926 case 'divx': 927 case 'div1': 928 case 'div2': 929 case 'div3': 930 case 'div4': 931 case 'div5': 932 case 'div6': 933 $info['video']['dataformat'] = 'divx'; 934 break; 935 936 default: 937 // do nothing 938 break; 939 } 940 unset($atom_structure['sample_description_table'][$i]['data']); 941 } 942 break; 943 944 945 case 'stts': // Sample Table Time-to-Sample atom 994 946 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 995 947 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 996 948 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 997 $stscEntriesDataOffset = 8; 998 for ($i = 0; $i < $atom_structure['number_entries']; $i++) { 999 $atom_structure['sample_to_chunk_table'][$i]['first_chunk'] = getid3_lib::BigEndian2Int(substr($atom_data, $stscEntriesDataOffset, 4)); 1000 $stscEntriesDataOffset += 4; 1001 $atom_structure['sample_to_chunk_table'][$i]['samples_per_chunk'] = getid3_lib::BigEndian2Int(substr($atom_data, $stscEntriesDataOffset, 4)); 1002 $stscEntriesDataOffset += 4; 1003 $atom_structure['sample_to_chunk_table'][$i]['sample_description'] = getid3_lib::BigEndian2Int(substr($atom_data, $stscEntriesDataOffset, 4)); 1004 $stscEntriesDataOffset += 4; 1005 } 1006 } 1007 break; 1008 1009 1010 case 'stsz': // Sample Table SiZe atom 1011 if ($ParseAllPossibleAtoms) { 1012 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1013 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 1014 $atom_structure['sample_size'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 1015 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); 1016 $stszEntriesDataOffset = 12; 1017 if ($atom_structure['sample_size'] == 0) { 949 $sttsEntriesDataOffset = 8; 950 //$FrameRateCalculatorArray = array(); 951 $frames_count = 0; 952 953 $max_stts_entries_to_scan = ($info['php_memory_limit'] ? min(floor($this->getid3->memory_limit / 10000), $atom_structure['number_entries']) : $atom_structure['number_entries']); 954 if ($max_stts_entries_to_scan < $atom_structure['number_entries']) { 955 $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).'); 956 } 957 for ($i = 0; $i < $max_stts_entries_to_scan; $i++) { 958 $atom_structure['time_to_sample_table'][$i]['sample_count'] = getid3_lib::BigEndian2Int(substr($atom_data, $sttsEntriesDataOffset, 4)); 959 $sttsEntriesDataOffset += 4; 960 $atom_structure['time_to_sample_table'][$i]['sample_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, $sttsEntriesDataOffset, 4)); 961 $sttsEntriesDataOffset += 4; 962 963 $frames_count += $atom_structure['time_to_sample_table'][$i]['sample_count']; 964 965 // THIS SECTION REPLACED WITH CODE IN "stbl" ATOM 966 //if (!empty($info['quicktime']['time_scale']) && ($atom_structure['time_to_sample_table'][$i]['sample_duration'] > 0)) { 967 // $stts_new_framerate = $info['quicktime']['time_scale'] / $atom_structure['time_to_sample_table'][$i]['sample_duration']; 968 // if ($stts_new_framerate <= 60) { 969 // // some atoms have durations of "1" giving a very large framerate, which probably is not right 970 // $info['video']['frame_rate'] = max($info['video']['frame_rate'], $stts_new_framerate); 971 // } 972 //} 973 // 974 //$FrameRateCalculatorArray[($info['quicktime']['time_scale'] / $atom_structure['time_to_sample_table'][$i]['sample_duration'])] += $atom_structure['time_to_sample_table'][$i]['sample_count']; 975 } 976 $info['quicktime']['stts_framecount'][] = $frames_count; 977 //$sttsFramesTotal = 0; 978 //$sttsSecondsTotal = 0; 979 //foreach ($FrameRateCalculatorArray as $frames_per_second => $frame_count) { 980 // if (($frames_per_second > 60) || ($frames_per_second < 1)) { 981 // // not video FPS information, probably audio information 982 // $sttsFramesTotal = 0; 983 // $sttsSecondsTotal = 0; 984 // break; 985 // } 986 // $sttsFramesTotal += $frame_count; 987 // $sttsSecondsTotal += $frame_count / $frames_per_second; 988 //} 989 //if (($sttsFramesTotal > 0) && ($sttsSecondsTotal > 0)) { 990 // if (($sttsFramesTotal / $sttsSecondsTotal) > $info['video']['frame_rate']) { 991 // $info['video']['frame_rate'] = $sttsFramesTotal / $sttsSecondsTotal; 992 // } 993 //} 994 break; 995 996 997 case 'stss': // Sample Table Sync Sample (key frames) atom 998 if ($ParseAllPossibleAtoms) { 999 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1000 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 1001 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 1002 $stssEntriesDataOffset = 8; 1018 1003 for ($i = 0; $i < $atom_structure['number_entries']; $i++) { 1019 $atom_structure[' sample_size_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stszEntriesDataOffset, 4));1020 $sts zEntriesDataOffset += 4;1004 $atom_structure['time_to_sample_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stssEntriesDataOffset, 4)); 1005 $stssEntriesDataOffset += 4; 1021 1006 } 1022 1007 } 1023 } 1024 break; 1025 1026 1027 case 'stco': // Sample Table Chunk Offset atom 1028 if ($ParseAllPossibleAtoms) { 1008 break; 1009 1010 1011 case 'stsc': // Sample Table Sample-to-Chunk atom 1012 if ($ParseAllPossibleAtoms) { 1013 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1014 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 1015 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 1016 $stscEntriesDataOffset = 8; 1017 for ($i = 0; $i < $atom_structure['number_entries']; $i++) { 1018 $atom_structure['sample_to_chunk_table'][$i]['first_chunk'] = getid3_lib::BigEndian2Int(substr($atom_data, $stscEntriesDataOffset, 4)); 1019 $stscEntriesDataOffset += 4; 1020 $atom_structure['sample_to_chunk_table'][$i]['samples_per_chunk'] = getid3_lib::BigEndian2Int(substr($atom_data, $stscEntriesDataOffset, 4)); 1021 $stscEntriesDataOffset += 4; 1022 $atom_structure['sample_to_chunk_table'][$i]['sample_description'] = getid3_lib::BigEndian2Int(substr($atom_data, $stscEntriesDataOffset, 4)); 1023 $stscEntriesDataOffset += 4; 1024 } 1025 } 1026 break; 1027 1028 1029 case 'stsz': // Sample Table SiZe atom 1030 if ($ParseAllPossibleAtoms) { 1031 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1032 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 1033 $atom_structure['sample_size'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 1034 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); 1035 $stszEntriesDataOffset = 12; 1036 if ($atom_structure['sample_size'] == 0) { 1037 for ($i = 0; $i < $atom_structure['number_entries']; $i++) { 1038 $atom_structure['sample_size_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stszEntriesDataOffset, 4)); 1039 $stszEntriesDataOffset += 4; 1040 } 1041 } 1042 } 1043 break; 1044 1045 1046 case 'stco': // Sample Table Chunk Offset atom 1047 if ($ParseAllPossibleAtoms) { 1048 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1049 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 1050 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 1051 $stcoEntriesDataOffset = 8; 1052 for ($i = 0; $i < $atom_structure['number_entries']; $i++) { 1053 $atom_structure['chunk_offset_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stcoEntriesDataOffset, 4)); 1054 $stcoEntriesDataOffset += 4; 1055 } 1056 } 1057 break; 1058 1059 1060 case 'co64': // Chunk Offset 64-bit (version of "stco" that supports > 2GB files) 1061 if ($ParseAllPossibleAtoms) { 1062 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1063 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 1064 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 1065 $stcoEntriesDataOffset = 8; 1066 for ($i = 0; $i < $atom_structure['number_entries']; $i++) { 1067 $atom_structure['chunk_offset_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stcoEntriesDataOffset, 8)); 1068 $stcoEntriesDataOffset += 8; 1069 } 1070 } 1071 break; 1072 1073 1074 case 'dref': // Data REFerence atom 1029 1075 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1030 1076 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 1031 1077 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 1032 $ stcoEntriesDataOffset = 8;1078 $drefDataOffset = 8; 1033 1079 for ($i = 0; $i < $atom_structure['number_entries']; $i++) { 1034 $atom_structure['chunk_offset_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stcoEntriesDataOffset, 4)); 1035 $stcoEntriesDataOffset += 4; 1036 } 1037 } 1038 break; 1039 1040 1041 case 'co64': // Chunk Offset 64-bit (version of "stco" that supports > 2GB files) 1042 if ($ParseAllPossibleAtoms) { 1080 $atom_structure['data_references'][$i]['size'] = getid3_lib::BigEndian2Int(substr($atom_data, $drefDataOffset, 4)); 1081 $drefDataOffset += 4; 1082 $atom_structure['data_references'][$i]['type'] = substr($atom_data, $drefDataOffset, 4); 1083 $drefDataOffset += 4; 1084 $atom_structure['data_references'][$i]['version'] = getid3_lib::BigEndian2Int(substr($atom_data, $drefDataOffset, 1)); 1085 $drefDataOffset += 1; 1086 $atom_structure['data_references'][$i]['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, $drefDataOffset, 3)); // hardcoded: 0x0000 1087 $drefDataOffset += 3; 1088 $atom_structure['data_references'][$i]['data'] = substr($atom_data, $drefDataOffset, ($atom_structure['data_references'][$i]['size'] - 4 - 4 - 1 - 3)); 1089 $drefDataOffset += ($atom_structure['data_references'][$i]['size'] - 4 - 4 - 1 - 3); 1090 1091 $atom_structure['data_references'][$i]['flags']['self_reference'] = (bool) ($atom_structure['data_references'][$i]['flags_raw'] & 0x001); 1092 } 1093 break; 1094 1095 1096 case 'gmin': // base Media INformation atom 1097 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1098 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 1099 $atom_structure['graphics_mode'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); 1100 $atom_structure['opcolor_red'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2)); 1101 $atom_structure['opcolor_green'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 2)); 1102 $atom_structure['opcolor_blue'] = getid3_lib::BigEndian2Int(substr($atom_data, 10, 2)); 1103 $atom_structure['balance'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 2)); 1104 $atom_structure['reserved'] = getid3_lib::BigEndian2Int(substr($atom_data, 14, 2)); 1105 break; 1106 1107 1108 case 'smhd': // Sound Media information HeaDer atom 1109 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1110 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 1111 $atom_structure['balance'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); 1112 $atom_structure['reserved'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2)); 1113 break; 1114 1115 1116 case 'vmhd': // Video Media information HeaDer atom 1117 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1118 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); 1119 $atom_structure['graphics_mode'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); 1120 $atom_structure['opcolor_red'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2)); 1121 $atom_structure['opcolor_green'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 2)); 1122 $atom_structure['opcolor_blue'] = getid3_lib::BigEndian2Int(substr($atom_data, 10, 2)); 1123 1124 $atom_structure['flags']['no_lean_ahead'] = (bool) ($atom_structure['flags_raw'] & 0x001); 1125 break; 1126 1127 1128 case 'hdlr': // HanDLeR reference atom 1129 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1130 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 1131 $atom_structure['component_type'] = substr($atom_data, 4, 4); 1132 $atom_structure['component_subtype'] = substr($atom_data, 8, 4); 1133 $atom_structure['component_manufacturer'] = substr($atom_data, 12, 4); 1134 $atom_structure['component_flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); 1135 $atom_structure['component_flags_mask'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 4)); 1136 $atom_structure['component_name'] = $this->Pascal2String(substr($atom_data, 24)); 1137 1138 if (($atom_structure['component_subtype'] == 'STpn') && ($atom_structure['component_manufacturer'] == 'zzzz')) { 1139 $info['video']['dataformat'] = 'quicktimevr'; 1140 } 1141 break; 1142 1143 1144 case 'mdhd': // MeDia HeaDer atom 1145 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1146 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 1147 $atom_structure['creation_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 1148 $atom_structure['modify_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); 1149 $atom_structure['time_scale'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); 1150 $atom_structure['duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); 1151 $atom_structure['language_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 2)); 1152 $atom_structure['quality'] = getid3_lib::BigEndian2Int(substr($atom_data, 22, 2)); 1153 1154 if ($atom_structure['time_scale'] == 0) { 1155 $this->error('Corrupt Quicktime file: mdhd.time_scale == zero'); 1156 return false; 1157 } 1158 $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']); 1159 1160 $atom_structure['creation_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['creation_time']); 1161 $atom_structure['modify_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['modify_time']); 1162 $atom_structure['playtime_seconds'] = $atom_structure['duration'] / $atom_structure['time_scale']; 1163 $atom_structure['language'] = $this->QuicktimeLanguageLookup($atom_structure['language_id']); 1164 if (empty($info['comments']['language']) || (!in_array($atom_structure['language'], $info['comments']['language']))) { 1165 $info['comments']['language'][] = $atom_structure['language']; 1166 } 1167 break; 1168 1169 1170 case 'pnot': // Preview atom 1171 $atom_structure['modification_date'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); // "standard Macintosh format" 1172 $atom_structure['version_number'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); // hardcoded: 0x00 1173 $atom_structure['atom_type'] = substr($atom_data, 6, 4); // usually: 'PICT' 1174 $atom_structure['atom_index'] = getid3_lib::BigEndian2Int(substr($atom_data, 10, 2)); // usually: 0x01 1175 1176 $atom_structure['modification_date_unix'] = getid3_lib::DateMac2Unix($atom_structure['modification_date']); 1177 break; 1178 1179 1180 case 'crgn': // Clipping ReGioN atom 1181 $atom_structure['region_size'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2)); // The Region size, Region boundary box, 1182 $atom_structure['boundary_box'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 8)); // and Clipping region data fields 1183 $atom_structure['clipping_data'] = substr($atom_data, 10); // constitute a QuickDraw region. 1184 break; 1185 1186 1187 case 'load': // track LOAD settings atom 1188 $atom_structure['preload_start_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); 1189 $atom_structure['preload_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 1190 $atom_structure['preload_flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); 1191 $atom_structure['default_hints_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); 1192 1193 $atom_structure['default_hints']['double_buffer'] = (bool) ($atom_structure['default_hints_raw'] & 0x0020); 1194 $atom_structure['default_hints']['high_quality'] = (bool) ($atom_structure['default_hints_raw'] & 0x0100); 1195 break; 1196 1197 1198 case 'tmcd': // TiMe CoDe atom 1199 case 'chap': // CHAPter list atom 1200 case 'sync': // SYNChronization atom 1201 case 'scpt': // tranSCriPT atom 1202 case 'ssrc': // non-primary SouRCe atom 1203 for ($i = 0; $i < strlen($atom_data); $i += 4) { 1204 @$atom_structure['track_id'][] = getid3_lib::BigEndian2Int(substr($atom_data, $i, 4)); 1205 } 1206 break; 1207 1208 1209 case 'elst': // Edit LiST atom 1043 1210 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1044 1211 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 1045 1212 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 1046 $stcoEntriesDataOffset = 8; 1047 for ($i = 0; $i < $atom_structure['number_entries']; $i++) { 1048 $atom_structure['chunk_offset_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stcoEntriesDataOffset, 8)); 1049 $stcoEntriesDataOffset += 8; 1050 } 1051 } 1052 break; 1053 1054 1055 case 'dref': // Data REFerence atom 1056 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1057 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 1058 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 1059 $drefDataOffset = 8; 1060 for ($i = 0; $i < $atom_structure['number_entries']; $i++) { 1061 $atom_structure['data_references'][$i]['size'] = getid3_lib::BigEndian2Int(substr($atom_data, $drefDataOffset, 4)); 1062 $drefDataOffset += 4; 1063 $atom_structure['data_references'][$i]['type'] = substr($atom_data, $drefDataOffset, 4); 1064 $drefDataOffset += 4; 1065 $atom_structure['data_references'][$i]['version'] = getid3_lib::BigEndian2Int(substr($atom_data, $drefDataOffset, 1)); 1066 $drefDataOffset += 1; 1067 $atom_structure['data_references'][$i]['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, $drefDataOffset, 3)); // hardcoded: 0x0000 1068 $drefDataOffset += 3; 1069 $atom_structure['data_references'][$i]['data'] = substr($atom_data, $drefDataOffset, ($atom_structure['data_references'][$i]['size'] - 4 - 4 - 1 - 3)); 1070 $drefDataOffset += ($atom_structure['data_references'][$i]['size'] - 4 - 4 - 1 - 3); 1071 1072 $atom_structure['data_references'][$i]['flags']['self_reference'] = (bool) ($atom_structure['data_references'][$i]['flags_raw'] & 0x001); 1073 } 1074 break; 1075 1076 1077 case 'gmin': // base Media INformation atom 1078 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1079 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 1080 $atom_structure['graphics_mode'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); 1081 $atom_structure['opcolor_red'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2)); 1082 $atom_structure['opcolor_green'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 2)); 1083 $atom_structure['opcolor_blue'] = getid3_lib::BigEndian2Int(substr($atom_data, 10, 2)); 1084 $atom_structure['balance'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 2)); 1085 $atom_structure['reserved'] = getid3_lib::BigEndian2Int(substr($atom_data, 14, 2)); 1086 break; 1087 1088 1089 case 'smhd': // Sound Media information HeaDer atom 1090 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1091 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 1092 $atom_structure['balance'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); 1093 $atom_structure['reserved'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2)); 1094 break; 1095 1096 1097 case 'vmhd': // Video Media information HeaDer atom 1098 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1099 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); 1100 $atom_structure['graphics_mode'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); 1101 $atom_structure['opcolor_red'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2)); 1102 $atom_structure['opcolor_green'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 2)); 1103 $atom_structure['opcolor_blue'] = getid3_lib::BigEndian2Int(substr($atom_data, 10, 2)); 1104 1105 $atom_structure['flags']['no_lean_ahead'] = (bool) ($atom_structure['flags_raw'] & 0x001); 1106 break; 1107 1108 1109 case 'hdlr': // HanDLeR reference atom 1110 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1111 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 1112 $atom_structure['component_type'] = substr($atom_data, 4, 4); 1113 $atom_structure['component_subtype'] = substr($atom_data, 8, 4); 1114 $atom_structure['component_manufacturer'] = substr($atom_data, 12, 4); 1115 $atom_structure['component_flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); 1116 $atom_structure['component_flags_mask'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 4)); 1117 $atom_structure['component_name'] = $this->Pascal2String(substr($atom_data, 24)); 1118 1119 if (($atom_structure['component_subtype'] == 'STpn') && ($atom_structure['component_manufacturer'] == 'zzzz')) { 1120 $info['video']['dataformat'] = 'quicktimevr'; 1121 } 1122 break; 1123 1124 1125 case 'mdhd': // MeDia HeaDer atom 1126 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1127 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 1128 $atom_structure['creation_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 1129 $atom_structure['modify_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); 1130 $atom_structure['time_scale'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); 1131 $atom_structure['duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); 1132 $atom_structure['language_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 2)); 1133 $atom_structure['quality'] = getid3_lib::BigEndian2Int(substr($atom_data, 22, 2)); 1134 1135 if ($atom_structure['time_scale'] == 0) { 1136 $this->error('Corrupt Quicktime file: mdhd.time_scale == zero'); 1137 return false; 1138 } 1139 $info['quicktime']['time_scale'] = ((isset($info['quicktime']['time_scale']) && ($info['quicktime']['time_scale'] < 1000)) ? max($info['quicktime']['time_scale'], $atom_structure['time_scale']) : $atom_structure['time_scale']); 1140 1141 $atom_structure['creation_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['creation_time']); 1142 $atom_structure['modify_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['modify_time']); 1143 $atom_structure['playtime_seconds'] = $atom_structure['duration'] / $atom_structure['time_scale']; 1144 $atom_structure['language'] = $this->QuicktimeLanguageLookup($atom_structure['language_id']); 1145 if (empty($info['comments']['language']) || (!in_array($atom_structure['language'], $info['comments']['language']))) { 1146 $info['comments']['language'][] = $atom_structure['language']; 1147 } 1148 break; 1149 1150 1151 case 'pnot': // Preview atom 1152 $atom_structure['modification_date'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); // "standard Macintosh format" 1153 $atom_structure['version_number'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); // hardcoded: 0x00 1154 $atom_structure['atom_type'] = substr($atom_data, 6, 4); // usually: 'PICT' 1155 $atom_structure['atom_index'] = getid3_lib::BigEndian2Int(substr($atom_data, 10, 2)); // usually: 0x01 1156 1157 $atom_structure['modification_date_unix'] = getid3_lib::DateMac2Unix($atom_structure['modification_date']); 1158 break; 1159 1160 1161 case 'crgn': // Clipping ReGioN atom 1162 $atom_structure['region_size'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2)); // The Region size, Region boundary box, 1163 $atom_structure['boundary_box'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 8)); // and Clipping region data fields 1164 $atom_structure['clipping_data'] = substr($atom_data, 10); // constitute a QuickDraw region. 1165 break; 1166 1167 1168 case 'load': // track LOAD settings atom 1169 $atom_structure['preload_start_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); 1170 $atom_structure['preload_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 1171 $atom_structure['preload_flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); 1172 $atom_structure['default_hints_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); 1173 1174 $atom_structure['default_hints']['double_buffer'] = (bool) ($atom_structure['default_hints_raw'] & 0x0020); 1175 $atom_structure['default_hints']['high_quality'] = (bool) ($atom_structure['default_hints_raw'] & 0x0100); 1176 break; 1177 1178 1179 case 'tmcd': // TiMe CoDe atom 1180 case 'chap': // CHAPter list atom 1181 case 'sync': // SYNChronization atom 1182 case 'scpt': // tranSCriPT atom 1183 case 'ssrc': // non-primary SouRCe atom 1184 for ($i = 0; $i < strlen($atom_data); $i += 4) { 1185 @$atom_structure['track_id'][] = getid3_lib::BigEndian2Int(substr($atom_data, $i, 4)); 1186 } 1187 break; 1188 1189 1190 case 'elst': // Edit LiST 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['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 1194 for ($i = 0; $i < $atom_structure['number_entries']; $i++ ) { 1195 $atom_structure['edit_list'][$i]['track_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($i * 12) + 0, 4)); 1196 $atom_structure['edit_list'][$i]['media_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($i * 12) + 4, 4)); 1197 $atom_structure['edit_list'][$i]['media_rate'] = getid3_lib::FixedPoint16_16(substr($atom_data, 8 + ($i * 12) + 8, 4)); 1198 } 1199 break; 1200 1201 1202 case 'kmat': // compressed MATte 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['matte_data_raw'] = substr($atom_data, 4); 1206 break; 1207 1208 1209 case 'ctab': // Color TABle atom 1210 $atom_structure['color_table_seed'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); // hardcoded: 0x00000000 1211 $atom_structure['color_table_flags'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); // hardcoded: 0x8000 1212 $atom_structure['color_table_size'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2)) + 1; 1213 for ($colortableentry = 0; $colortableentry < $atom_structure['color_table_size']; $colortableentry++) { 1214 $atom_structure['color_table'][$colortableentry]['alpha'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 0, 2)); 1215 $atom_structure['color_table'][$colortableentry]['red'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 2, 2)); 1216 $atom_structure['color_table'][$colortableentry]['green'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 4, 2)); 1217 $atom_structure['color_table'][$colortableentry]['blue'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 6, 2)); 1218 } 1219 break; 1220 1221 1222 case 'mvhd': // MoVie HeaDer 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)); 1225 $atom_structure['creation_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 1226 $atom_structure['modify_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); 1227 $atom_structure['time_scale'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); 1228 $atom_structure['duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); 1229 $atom_structure['preferred_rate'] = getid3_lib::FixedPoint16_16(substr($atom_data, 20, 4)); 1230 $atom_structure['preferred_volume'] = getid3_lib::FixedPoint8_8(substr($atom_data, 24, 2)); 1231 $atom_structure['reserved'] = substr($atom_data, 26, 10); 1232 $atom_structure['matrix_a'] = getid3_lib::FixedPoint16_16(substr($atom_data, 36, 4)); 1233 $atom_structure['matrix_b'] = getid3_lib::FixedPoint16_16(substr($atom_data, 40, 4)); 1234 $atom_structure['matrix_u'] = getid3_lib::FixedPoint2_30(substr($atom_data, 44, 4)); 1235 $atom_structure['matrix_c'] = getid3_lib::FixedPoint16_16(substr($atom_data, 48, 4)); 1236 $atom_structure['matrix_d'] = getid3_lib::FixedPoint16_16(substr($atom_data, 52, 4)); 1237 $atom_structure['matrix_v'] = getid3_lib::FixedPoint2_30(substr($atom_data, 56, 4)); 1238 $atom_structure['matrix_x'] = getid3_lib::FixedPoint16_16(substr($atom_data, 60, 4)); 1239 $atom_structure['matrix_y'] = getid3_lib::FixedPoint16_16(substr($atom_data, 64, 4)); 1240 $atom_structure['matrix_w'] = getid3_lib::FixedPoint2_30(substr($atom_data, 68, 4)); 1241 $atom_structure['preview_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 72, 4)); 1242 $atom_structure['preview_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 76, 4)); 1243 $atom_structure['poster_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 80, 4)); 1244 $atom_structure['selection_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 84, 4)); 1245 $atom_structure['selection_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 88, 4)); 1246 $atom_structure['current_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 92, 4)); 1247 $atom_structure['next_track_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 96, 4)); 1248 1249 if ($atom_structure['time_scale'] == 0) { 1250 $this->error('Corrupt Quicktime file: mvhd.time_scale == zero'); 1251 return false; 1252 } 1253 $atom_structure['creation_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['creation_time']); 1254 $atom_structure['modify_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['modify_time']); 1255 $info['quicktime']['time_scale'] = ((isset($info['quicktime']['time_scale']) && ($info['quicktime']['time_scale'] < 1000)) ? max($info['quicktime']['time_scale'], $atom_structure['time_scale']) : $atom_structure['time_scale']); 1256 $info['quicktime']['display_scale'] = $atom_structure['matrix_a']; 1257 $info['playtime_seconds'] = $atom_structure['duration'] / $atom_structure['time_scale']; 1258 break; 1259 1260 1261 case 'tkhd': // TracK HeaDer atom 1262 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1263 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); 1264 $atom_structure['creation_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 1265 $atom_structure['modify_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); 1266 $atom_structure['trackid'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); 1267 $atom_structure['reserved1'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); 1268 $atom_structure['duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 4)); 1269 $atom_structure['reserved2'] = getid3_lib::BigEndian2Int(substr($atom_data, 24, 8)); 1270 $atom_structure['layer'] = getid3_lib::BigEndian2Int(substr($atom_data, 32, 2)); 1271 $atom_structure['alternate_group'] = getid3_lib::BigEndian2Int(substr($atom_data, 34, 2)); 1272 $atom_structure['volume'] = getid3_lib::FixedPoint8_8(substr($atom_data, 36, 2)); 1273 $atom_structure['reserved3'] = getid3_lib::BigEndian2Int(substr($atom_data, 38, 2)); 1274 // http://developer.apple.com/library/mac/#documentation/QuickTime/RM/MovieBasics/MTEditing/K-Chapter/11MatrixFunctions.html 1275 // http://developer.apple.com/library/mac/#documentation/QuickTime/qtff/QTFFChap4/qtff4.html#//apple_ref/doc/uid/TP40000939-CH206-18737 1276 $atom_structure['matrix_a'] = getid3_lib::FixedPoint16_16(substr($atom_data, 40, 4)); 1277 $atom_structure['matrix_b'] = getid3_lib::FixedPoint16_16(substr($atom_data, 44, 4)); 1278 $atom_structure['matrix_u'] = getid3_lib::FixedPoint2_30(substr($atom_data, 48, 4)); 1279 $atom_structure['matrix_c'] = getid3_lib::FixedPoint16_16(substr($atom_data, 52, 4)); 1280 $atom_structure['matrix_d'] = getid3_lib::FixedPoint16_16(substr($atom_data, 56, 4)); 1281 $atom_structure['matrix_v'] = getid3_lib::FixedPoint2_30(substr($atom_data, 60, 4)); 1282 $atom_structure['matrix_x'] = getid3_lib::FixedPoint16_16(substr($atom_data, 64, 4)); 1283 $atom_structure['matrix_y'] = getid3_lib::FixedPoint16_16(substr($atom_data, 68, 4)); 1284 $atom_structure['matrix_w'] = getid3_lib::FixedPoint2_30(substr($atom_data, 72, 4)); 1285 $atom_structure['width'] = getid3_lib::FixedPoint16_16(substr($atom_data, 76, 4)); 1286 $atom_structure['height'] = getid3_lib::FixedPoint16_16(substr($atom_data, 80, 4)); 1287 $atom_structure['flags']['enabled'] = (bool) ($atom_structure['flags_raw'] & 0x0001); 1288 $atom_structure['flags']['in_movie'] = (bool) ($atom_structure['flags_raw'] & 0x0002); 1289 $atom_structure['flags']['in_preview'] = (bool) ($atom_structure['flags_raw'] & 0x0004); 1290 $atom_structure['flags']['in_poster'] = (bool) ($atom_structure['flags_raw'] & 0x0008); 1291 $atom_structure['creation_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['creation_time']); 1292 $atom_structure['modify_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['modify_time']); 1293 1294 if ($atom_structure['flags']['enabled'] == 1) { 1295 if (!isset($info['video']['resolution_x']) || !isset($info['video']['resolution_y'])) { 1296 $info['video']['resolution_x'] = $atom_structure['width']; 1297 $info['video']['resolution_y'] = $atom_structure['height']; 1298 } 1299 $info['video']['resolution_x'] = max($info['video']['resolution_x'], $atom_structure['width']); 1300 $info['video']['resolution_y'] = max($info['video']['resolution_y'], $atom_structure['height']); 1301 $info['quicktime']['video']['resolution_x'] = $info['video']['resolution_x']; 1302 $info['quicktime']['video']['resolution_y'] = $info['video']['resolution_y']; 1303 } else { 1304 // see: http://www.getid3.org/phpBB3/viewtopic.php?t=1295 1305 //if (isset($info['video']['resolution_x'])) { unset($info['video']['resolution_x']); } 1306 //if (isset($info['video']['resolution_y'])) { unset($info['video']['resolution_y']); } 1307 //if (isset($info['quicktime']['video'])) { unset($info['quicktime']['video']); } 1308 } 1309 break; 1310 1311 1312 case 'iods': // Initial Object DeScriptor atom 1313 // http://www.koders.com/c/fid1FAB3E762903DC482D8A246D4A4BF9F28E049594.aspx?s=windows.h 1314 // http://libquicktime.sourcearchive.com/documentation/1.0.2plus-pdebian/iods_8c-source.html 1315 $offset = 0; 1316 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); 1317 $offset += 1; 1318 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 3)); 1319 $offset += 3; 1320 $atom_structure['mp4_iod_tag'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); 1321 $offset += 1; 1322 $atom_structure['length'] = $this->quicktime_read_mp4_descr_length($atom_data, $offset); 1323 //$offset already adjusted by quicktime_read_mp4_descr_length() 1324 $atom_structure['object_descriptor_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 2)); 1325 $offset += 2; 1326 $atom_structure['od_profile_level'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); 1327 $offset += 1; 1328 $atom_structure['scene_profile_level'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); 1329 $offset += 1; 1330 $atom_structure['audio_profile_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); 1331 $offset += 1; 1332 $atom_structure['video_profile_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); 1333 $offset += 1; 1334 $atom_structure['graphics_profile_level'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); 1335 $offset += 1; 1336 1337 $atom_structure['num_iods_tracks'] = ($atom_structure['length'] - 7) / 6; // 6 bytes would only be right if all tracks use 1-byte length fields 1338 for ($i = 0; $i < $atom_structure['num_iods_tracks']; $i++) { 1339 $atom_structure['track'][$i]['ES_ID_IncTag'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); 1213 for ($i = 0; $i < $atom_structure['number_entries']; $i++ ) { 1214 $atom_structure['edit_list'][$i]['track_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($i * 12) + 0, 4)); 1215 $atom_structure['edit_list'][$i]['media_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($i * 12) + 4, 4)); 1216 $atom_structure['edit_list'][$i]['media_rate'] = getid3_lib::FixedPoint16_16(substr($atom_data, 8 + ($i * 12) + 8, 4)); 1217 } 1218 break; 1219 1220 1221 case 'kmat': // compressed MATte atom 1222 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1223 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 1224 $atom_structure['matte_data_raw'] = substr($atom_data, 4); 1225 break; 1226 1227 1228 case 'ctab': // Color TABle atom 1229 $atom_structure['color_table_seed'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); // hardcoded: 0x00000000 1230 $atom_structure['color_table_flags'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); // hardcoded: 0x8000 1231 $atom_structure['color_table_size'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2)) + 1; 1232 for ($colortableentry = 0; $colortableentry < $atom_structure['color_table_size']; $colortableentry++) { 1233 $atom_structure['color_table'][$colortableentry]['alpha'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 0, 2)); 1234 $atom_structure['color_table'][$colortableentry]['red'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 2, 2)); 1235 $atom_structure['color_table'][$colortableentry]['green'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 4, 2)); 1236 $atom_structure['color_table'][$colortableentry]['blue'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 6, 2)); 1237 } 1238 break; 1239 1240 1241 case 'mvhd': // MoVie HeaDer atom 1242 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1243 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); 1244 $atom_structure['creation_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 1245 $atom_structure['modify_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); 1246 $atom_structure['time_scale'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); 1247 $atom_structure['duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); 1248 $atom_structure['preferred_rate'] = getid3_lib::FixedPoint16_16(substr($atom_data, 20, 4)); 1249 $atom_structure['preferred_volume'] = getid3_lib::FixedPoint8_8(substr($atom_data, 24, 2)); 1250 $atom_structure['reserved'] = substr($atom_data, 26, 10); 1251 $atom_structure['matrix_a'] = getid3_lib::FixedPoint16_16(substr($atom_data, 36, 4)); 1252 $atom_structure['matrix_b'] = getid3_lib::FixedPoint16_16(substr($atom_data, 40, 4)); 1253 $atom_structure['matrix_u'] = getid3_lib::FixedPoint2_30(substr($atom_data, 44, 4)); 1254 $atom_structure['matrix_c'] = getid3_lib::FixedPoint16_16(substr($atom_data, 48, 4)); 1255 $atom_structure['matrix_d'] = getid3_lib::FixedPoint16_16(substr($atom_data, 52, 4)); 1256 $atom_structure['matrix_v'] = getid3_lib::FixedPoint2_30(substr($atom_data, 56, 4)); 1257 $atom_structure['matrix_x'] = getid3_lib::FixedPoint16_16(substr($atom_data, 60, 4)); 1258 $atom_structure['matrix_y'] = getid3_lib::FixedPoint16_16(substr($atom_data, 64, 4)); 1259 $atom_structure['matrix_w'] = getid3_lib::FixedPoint2_30(substr($atom_data, 68, 4)); 1260 $atom_structure['preview_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 72, 4)); 1261 $atom_structure['preview_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 76, 4)); 1262 $atom_structure['poster_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 80, 4)); 1263 $atom_structure['selection_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 84, 4)); 1264 $atom_structure['selection_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 88, 4)); 1265 $atom_structure['current_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 92, 4)); 1266 $atom_structure['next_track_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 96, 4)); 1267 1268 if ($atom_structure['time_scale'] == 0) { 1269 $this->error('Corrupt Quicktime file: mvhd.time_scale == zero'); 1270 return false; 1271 } 1272 $atom_structure['creation_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['creation_time']); 1273 $atom_structure['modify_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['modify_time']); 1274 $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']); 1275 $info['quicktime']['display_scale'] = $atom_structure['matrix_a']; 1276 $info['playtime_seconds'] = $atom_structure['duration'] / $atom_structure['time_scale']; 1277 break; 1278 1279 1280 case 'tkhd': // TracK HeaDer atom 1281 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1282 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); 1283 $atom_structure['creation_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 1284 $atom_structure['modify_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); 1285 $atom_structure['trackid'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); 1286 $atom_structure['reserved1'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); 1287 $atom_structure['duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 4)); 1288 $atom_structure['reserved2'] = getid3_lib::BigEndian2Int(substr($atom_data, 24, 8)); 1289 $atom_structure['layer'] = getid3_lib::BigEndian2Int(substr($atom_data, 32, 2)); 1290 $atom_structure['alternate_group'] = getid3_lib::BigEndian2Int(substr($atom_data, 34, 2)); 1291 $atom_structure['volume'] = getid3_lib::FixedPoint8_8(substr($atom_data, 36, 2)); 1292 $atom_structure['reserved3'] = getid3_lib::BigEndian2Int(substr($atom_data, 38, 2)); 1293 // http://developer.apple.com/library/mac/#documentation/QuickTime/RM/MovieBasics/MTEditing/K-Chapter/11MatrixFunctions.html 1294 // http://developer.apple.com/library/mac/#documentation/QuickTime/qtff/QTFFChap4/qtff4.html#//apple_ref/doc/uid/TP40000939-CH206-18737 1295 $atom_structure['matrix_a'] = getid3_lib::FixedPoint16_16(substr($atom_data, 40, 4)); 1296 $atom_structure['matrix_b'] = getid3_lib::FixedPoint16_16(substr($atom_data, 44, 4)); 1297 $atom_structure['matrix_u'] = getid3_lib::FixedPoint2_30(substr($atom_data, 48, 4)); 1298 $atom_structure['matrix_c'] = getid3_lib::FixedPoint16_16(substr($atom_data, 52, 4)); 1299 $atom_structure['matrix_d'] = getid3_lib::FixedPoint16_16(substr($atom_data, 56, 4)); 1300 $atom_structure['matrix_v'] = getid3_lib::FixedPoint2_30(substr($atom_data, 60, 4)); 1301 $atom_structure['matrix_x'] = getid3_lib::FixedPoint16_16(substr($atom_data, 64, 4)); 1302 $atom_structure['matrix_y'] = getid3_lib::FixedPoint16_16(substr($atom_data, 68, 4)); 1303 $atom_structure['matrix_w'] = getid3_lib::FixedPoint2_30(substr($atom_data, 72, 4)); 1304 $atom_structure['width'] = getid3_lib::FixedPoint16_16(substr($atom_data, 76, 4)); 1305 $atom_structure['height'] = getid3_lib::FixedPoint16_16(substr($atom_data, 80, 4)); 1306 $atom_structure['flags']['enabled'] = (bool) ($atom_structure['flags_raw'] & 0x0001); 1307 $atom_structure['flags']['in_movie'] = (bool) ($atom_structure['flags_raw'] & 0x0002); 1308 $atom_structure['flags']['in_preview'] = (bool) ($atom_structure['flags_raw'] & 0x0004); 1309 $atom_structure['flags']['in_poster'] = (bool) ($atom_structure['flags_raw'] & 0x0008); 1310 $atom_structure['creation_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['creation_time']); 1311 $atom_structure['modify_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['modify_time']); 1312 1313 // https://www.getid3.org/phpBB3/viewtopic.php?t=1908 1314 // attempt to compute rotation from matrix values 1315 // 2017-Dec-28: uncertain if 90/270 are correctly oriented; values returned by FixedPoint16_16 should perhaps be -1 instead of 65535(?) 1316 $matrixRotation = 0; 1317 switch ($atom_structure['matrix_a'].':'.$atom_structure['matrix_b'].':'.$atom_structure['matrix_c'].':'.$atom_structure['matrix_d']) { 1318 case '1:0:0:1': $matrixRotation = 0; break; 1319 case '0:1:65535:0': $matrixRotation = 90; break; 1320 case '65535:0:0:65535': $matrixRotation = 180; break; 1321 case '0:65535:1:0': $matrixRotation = 270; break; 1322 default: break; 1323 } 1324 1325 // https://www.getid3.org/phpBB3/viewtopic.php?t=2468 1326 // The rotation matrix can appear in the Quicktime file multiple times, at least once for each track, 1327 // and it's possible that only the video track (or, in theory, one of the video tracks) is flagged as 1328 // rotated while the other tracks (e.g. audio) is tagged as rotation=0 (behavior noted on iPhone 8 Plus) 1329 // The correct solution would be to check if the TrackID associated with the rotation matrix is indeed 1330 // a video track (or the main video track) and only set the rotation then, but since information about 1331 // what track is what is not trivially there to be examined, the lazy solution is to set the rotation 1332 // if it is found to be nonzero, on the assumption that tracks that don't need it will have rotation set 1333 // to zero (and be effectively ignored) and the video track will have rotation set correctly, which will 1334 // either be zero and automatically correct, or nonzero and be set correctly. 1335 if (!isset($info['video']['rotate']) || (($info['video']['rotate'] == 0) && ($matrixRotation > 0))) { 1336 $info['quicktime']['video']['rotate'] = $info['video']['rotate'] = $matrixRotation; 1337 } 1338 1339 if ($atom_structure['flags']['enabled'] == 1) { 1340 if (!isset($info['video']['resolution_x']) || !isset($info['video']['resolution_y'])) { 1341 $info['video']['resolution_x'] = $atom_structure['width']; 1342 $info['video']['resolution_y'] = $atom_structure['height']; 1343 } 1344 $info['video']['resolution_x'] = max($info['video']['resolution_x'], $atom_structure['width']); 1345 $info['video']['resolution_y'] = max($info['video']['resolution_y'], $atom_structure['height']); 1346 $info['quicktime']['video']['resolution_x'] = $info['video']['resolution_x']; 1347 $info['quicktime']['video']['resolution_y'] = $info['video']['resolution_y']; 1348 } else { 1349 // see: https://www.getid3.org/phpBB3/viewtopic.php?t=1295 1350 //if (isset($info['video']['resolution_x'])) { unset($info['video']['resolution_x']); } 1351 //if (isset($info['video']['resolution_y'])) { unset($info['video']['resolution_y']); } 1352 //if (isset($info['quicktime']['video'])) { unset($info['quicktime']['video']); } 1353 } 1354 break; 1355 1356 1357 case 'iods': // Initial Object DeScriptor atom 1358 // http://www.koders.com/c/fid1FAB3E762903DC482D8A246D4A4BF9F28E049594.aspx?s=windows.h 1359 // http://libquicktime.sourcearchive.com/documentation/1.0.2plus-pdebian/iods_8c-source.html 1360 $offset = 0; 1361 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); 1340 1362 $offset += 1; 1341 $atom_structure['track'][$i]['length'] = $this->quicktime_read_mp4_descr_length($atom_data, $offset); 1363 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 3)); 1364 $offset += 3; 1365 $atom_structure['mp4_iod_tag'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); 1366 $offset += 1; 1367 $atom_structure['length'] = $this->quicktime_read_mp4_descr_length($atom_data, $offset); 1342 1368 //$offset already adjusted by quicktime_read_mp4_descr_length() 1343 $atom_structure['track'][$i]['track_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 4)); 1344 $offset += 4; 1345 } 1346 1347 $atom_structure['audio_profile_name'] = $this->QuicktimeIODSaudioProfileName($atom_structure['audio_profile_id']); 1348 $atom_structure['video_profile_name'] = $this->QuicktimeIODSvideoProfileName($atom_structure['video_profile_id']); 1349 break; 1350 1351 case 'ftyp': // FileTYPe (?) atom (for MP4 it seems) 1352 $atom_structure['signature'] = substr($atom_data, 0, 4); 1353 $atom_structure['unknown_1'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 1354 $atom_structure['fourcc'] = substr($atom_data, 8, 4); 1355 break; 1356 1357 case 'mdat': // Media DATa atom 1358 // 'mdat' contains the actual data for the audio/video, possibly also subtitles 1359 1360 /* 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 */ 1361 1362 // first, skip any 'wide' padding, and second 'mdat' header (with specified size of zero?) 1363 $mdat_offset = 0; 1364 while (true) { 1365 if (substr($atom_data, $mdat_offset, 8) == "\x00\x00\x00\x08".'wide') { 1366 $mdat_offset += 8; 1367 } elseif (substr($atom_data, $mdat_offset, 8) == "\x00\x00\x00\x00".'mdat') { 1368 $mdat_offset += 8; 1369 } else { 1370 break; 1371 } 1372 } 1373 1374 // check to see if it looks like chapter titles, in the form of unterminated strings with a leading 16-bit size field 1375 while (($mdat_offset < (strlen($atom_data) - 8)) 1376 && ($chapter_string_length = getid3_lib::BigEndian2Int(substr($atom_data, $mdat_offset, 2))) 1377 && ($chapter_string_length < 1000) 1378 && ($chapter_string_length <= (strlen($atom_data) - $mdat_offset - 2)) 1379 && preg_match('#^([\x00-\xFF]{2})([\x20-\xFF]+)$#', substr($atom_data, $mdat_offset, $chapter_string_length + 2), $chapter_matches)) { 1380 list($dummy, $chapter_string_length_hex, $chapter_string) = $chapter_matches; 1381 $mdat_offset += (2 + $chapter_string_length); 1382 @$info['quicktime']['comments']['chapters'][] = $chapter_string; 1383 1384 // "encd" atom specifies encoding. In theory could be anything, almost always UTF-8, but may be UTF-16 with BOM (not currently handled) 1385 if (substr($atom_data, $mdat_offset, 12) == "\x00\x00\x00\x0C\x65\x6E\x63\x64\x00\x00\x01\x00") { // UTF-8 1386 $mdat_offset += 12; 1369 $atom_structure['object_descriptor_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 2)); 1370 $offset += 2; 1371 $atom_structure['od_profile_level'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); 1372 $offset += 1; 1373 $atom_structure['scene_profile_level'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); 1374 $offset += 1; 1375 $atom_structure['audio_profile_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); 1376 $offset += 1; 1377 $atom_structure['video_profile_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); 1378 $offset += 1; 1379 $atom_structure['graphics_profile_level'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); 1380 $offset += 1; 1381 1382 $atom_structure['num_iods_tracks'] = ($atom_structure['length'] - 7) / 6; // 6 bytes would only be right if all tracks use 1-byte length fields 1383 for ($i = 0; $i < $atom_structure['num_iods_tracks']; $i++) { 1384 $atom_structure['track'][$i]['ES_ID_IncTag'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); 1385 $offset += 1; 1386 $atom_structure['track'][$i]['length'] = $this->quicktime_read_mp4_descr_length($atom_data, $offset); 1387 //$offset already adjusted by quicktime_read_mp4_descr_length() 1388 $atom_structure['track'][$i]['track_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 4)); 1389 $offset += 4; 1390 } 1391 1392 $atom_structure['audio_profile_name'] = $this->QuicktimeIODSaudioProfileName($atom_structure['audio_profile_id']); 1393 $atom_structure['video_profile_name'] = $this->QuicktimeIODSvideoProfileName($atom_structure['video_profile_id']); 1394 break; 1395 1396 case 'ftyp': // FileTYPe (?) atom (for MP4 it seems) 1397 $atom_structure['signature'] = substr($atom_data, 0, 4); 1398 $atom_structure['unknown_1'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 1399 $atom_structure['fourcc'] = substr($atom_data, 8, 4); 1400 break; 1401 1402 case 'mdat': // Media DATa atom 1403 // 'mdat' contains the actual data for the audio/video, possibly also subtitles 1404 1405 /* 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 */ 1406 1407 // first, skip any 'wide' padding, and second 'mdat' header (with specified size of zero?) 1408 $mdat_offset = 0; 1409 while (true) { 1410 if (substr($atom_data, $mdat_offset, 8) == "\x00\x00\x00\x08".'wide') { 1411 $mdat_offset += 8; 1412 } elseif (substr($atom_data, $mdat_offset, 8) == "\x00\x00\x00\x00".'mdat') { 1413 $mdat_offset += 8; 1414 } else { 1415 break; 1387 1416 } 1388 } 1389 1390 1391 if (($atomsize > 8) && (!isset($info['avdataend_tmp']) || ($info['quicktime'][$atomname]['size'] > ($info['avdataend_tmp'] - $info['avdataoffset'])))) { 1392 1393 $info['avdataoffset'] = $atom_structure['offset'] + 8; // $info['quicktime'][$atomname]['offset'] + 8; 1394 $OldAVDataEnd = $info['avdataend']; 1395 $info['avdataend'] = $atom_structure['offset'] + $atom_structure['size']; // $info['quicktime'][$atomname]['offset'] + $info['quicktime'][$atomname]['size']; 1396 1397 $getid3_temp = new getID3(); 1398 $getid3_temp->openfile($this->getid3->filename); 1399 $getid3_temp->info['avdataoffset'] = $info['avdataoffset']; 1400 $getid3_temp->info['avdataend'] = $info['avdataend']; 1401 $getid3_mp3 = new getid3_mp3($getid3_temp); 1402 if ($getid3_mp3->MPEGaudioHeaderValid($getid3_mp3->MPEGaudioHeaderDecode($this->fread(4)))) { 1403 $getid3_mp3->getOnlyMPEGaudioInfo($getid3_temp->info['avdataoffset'], false); 1404 if (!empty($getid3_temp->info['warning'])) { 1405 foreach ($getid3_temp->info['warning'] as $value) { 1406 $this->warning($value); 1417 } 1418 if (substr($atom_data, $mdat_offset, 4) == 'GPRO') { 1419 $GOPRO_chunk_length = getid3_lib::LittleEndian2Int(substr($atom_data, $mdat_offset + 4, 4)); 1420 $GOPRO_offset = 8; 1421 $atom_structure['GPRO']['raw'] = substr($atom_data, $mdat_offset + 8, $GOPRO_chunk_length - 8); 1422 $atom_structure['GPRO']['firmware'] = substr($atom_structure['GPRO']['raw'], 0, 15); 1423 $atom_structure['GPRO']['unknown1'] = substr($atom_structure['GPRO']['raw'], 15, 16); 1424 $atom_structure['GPRO']['unknown2'] = substr($atom_structure['GPRO']['raw'], 31, 32); 1425 $atom_structure['GPRO']['unknown3'] = substr($atom_structure['GPRO']['raw'], 63, 16); 1426 $atom_structure['GPRO']['camera'] = substr($atom_structure['GPRO']['raw'], 79, 32); 1427 $info['quicktime']['camera']['model'] = rtrim($atom_structure['GPRO']['camera'], "\x00"); 1428 } 1429 1430 // check to see if it looks like chapter titles, in the form of unterminated strings with a leading 16-bit size field 1431 while (($mdat_offset < (strlen($atom_data) - 8)) 1432 && ($chapter_string_length = getid3_lib::BigEndian2Int(substr($atom_data, $mdat_offset, 2))) 1433 && ($chapter_string_length < 1000) 1434 && ($chapter_string_length <= (strlen($atom_data) - $mdat_offset - 2)) 1435 && preg_match('#^([\x00-\xFF]{2})([\x20-\xFF]+)$#', substr($atom_data, $mdat_offset, $chapter_string_length + 2), $chapter_matches)) { 1436 list($dummy, $chapter_string_length_hex, $chapter_string) = $chapter_matches; 1437 $mdat_offset += (2 + $chapter_string_length); 1438 @$info['quicktime']['comments']['chapters'][] = $chapter_string; 1439 1440 // "encd" atom specifies encoding. In theory could be anything, almost always UTF-8, but may be UTF-16 with BOM (not currently handled) 1441 if (substr($atom_data, $mdat_offset, 12) == "\x00\x00\x00\x0C\x65\x6E\x63\x64\x00\x00\x01\x00") { // UTF-8 1442 $mdat_offset += 12; 1443 } 1444 } 1445 1446 if (($atomsize > 8) && (!isset($info['avdataend_tmp']) || ($info['quicktime'][$atomname]['size'] > ($info['avdataend_tmp'] - $info['avdataoffset'])))) { 1447 1448 $info['avdataoffset'] = $atom_structure['offset'] + 8; // $info['quicktime'][$atomname]['offset'] + 8; 1449 $OldAVDataEnd = $info['avdataend']; 1450 $info['avdataend'] = $atom_structure['offset'] + $atom_structure['size']; // $info['quicktime'][$atomname]['offset'] + $info['quicktime'][$atomname]['size']; 1451 1452 $getid3_temp = new getID3(); 1453 $getid3_temp->openfile($this->getid3->filename); 1454 $getid3_temp->info['avdataoffset'] = $info['avdataoffset']; 1455 $getid3_temp->info['avdataend'] = $info['avdataend']; 1456 $getid3_mp3 = new getid3_mp3($getid3_temp); 1457 if ($getid3_mp3->MPEGaudioHeaderValid($getid3_mp3->MPEGaudioHeaderDecode($this->fread(4)))) { 1458 $getid3_mp3->getOnlyMPEGaudioInfo($getid3_temp->info['avdataoffset'], false); 1459 if (!empty($getid3_temp->info['warning'])) { 1460 foreach ($getid3_temp->info['warning'] as $value) { 1461 $this->warning($value); 1462 } 1463 } 1464 if (!empty($getid3_temp->info['mpeg'])) { 1465 $info['mpeg'] = $getid3_temp->info['mpeg']; 1466 if (isset($info['mpeg']['audio'])) { 1467 $info['audio']['dataformat'] = 'mp3'; 1468 $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'))); 1469 $info['audio']['sample_rate'] = $info['mpeg']['audio']['sample_rate']; 1470 $info['audio']['channels'] = $info['mpeg']['audio']['channels']; 1471 $info['audio']['bitrate'] = $info['mpeg']['audio']['bitrate']; 1472 $info['audio']['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']); 1473 $info['bitrate'] = $info['audio']['bitrate']; 1474 } 1407 1475 } 1408 1476 } 1409 if (!empty($getid3_temp->info['mpeg'])) { 1410 $info['mpeg'] = $getid3_temp->info['mpeg']; 1411 if (isset($info['mpeg']['audio'])) { 1412 $info['audio']['dataformat'] = 'mp3'; 1413 $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'))); 1414 $info['audio']['sample_rate'] = $info['mpeg']['audio']['sample_rate']; 1415 $info['audio']['channels'] = $info['mpeg']['audio']['channels']; 1416 $info['audio']['bitrate'] = $info['mpeg']['audio']['bitrate']; 1417 $info['audio']['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']); 1418 $info['bitrate'] = $info['audio']['bitrate']; 1477 unset($getid3_mp3, $getid3_temp); 1478 $info['avdataend'] = $OldAVDataEnd; 1479 unset($OldAVDataEnd); 1480 1481 } 1482 1483 unset($mdat_offset, $chapter_string_length, $chapter_matches); 1484 break; 1485 1486 case 'free': // FREE space atom 1487 case 'skip': // SKIP atom 1488 case 'wide': // 64-bit expansion placeholder atom 1489 // 'free', 'skip' and 'wide' are just padding, contains no useful data at all 1490 1491 // When writing QuickTime files, it is sometimes necessary to update an atom's size. 1492 // It is impossible to update a 32-bit atom to a 64-bit atom since the 32-bit atom 1493 // is only 8 bytes in size, and the 64-bit atom requires 16 bytes. Therefore, QuickTime 1494 // puts an 8-byte placeholder atom before any atoms it may have to update the size of. 1495 // In this way, if the atom needs to be converted from a 32-bit to a 64-bit atom, the 1496 // placeholder atom can be overwritten to obtain the necessary 8 extra bytes. 1497 // The placeholder atom has a type of kWideAtomPlaceholderType ( 'wide' ). 1498 break; 1499 1500 1501 case 'nsav': // NoSAVe atom 1502 // http://developer.apple.com/technotes/tn/tn2038.html 1503 $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); 1504 break; 1505 1506 case 'ctyp': // Controller TYPe atom (seen on QTVR) 1507 // http://homepages.slingshot.co.nz/~helmboy/quicktime/formats/qtm-layout.txt 1508 // some controller names are: 1509 // 0x00 + 'std' for linear movie 1510 // 'none' for no controls 1511 $atom_structure['ctyp'] = substr($atom_data, 0, 4); 1512 $info['quicktime']['controller'] = $atom_structure['ctyp']; 1513 switch ($atom_structure['ctyp']) { 1514 case 'qtvr': 1515 $info['video']['dataformat'] = 'quicktimevr'; 1516 break; 1517 } 1518 break; 1519 1520 case 'pano': // PANOrama track (seen on QTVR) 1521 $atom_structure['pano'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); 1522 break; 1523 1524 case 'hint': // HINT track 1525 case 'hinf': // 1526 case 'hinv': // 1527 case 'hnti': // 1528 $info['quicktime']['hinting'] = true; 1529 break; 1530 1531 case 'imgt': // IMaGe Track reference (kQTVRImageTrackRefType) (seen on QTVR) 1532 for ($i = 0; $i < ($atom_structure['size'] - 8); $i += 4) { 1533 $atom_structure['imgt'][] = getid3_lib::BigEndian2Int(substr($atom_data, $i, 4)); 1534 } 1535 break; 1536 1537 1538 // Observed-but-not-handled atom types are just listed here to prevent warnings being generated 1539 case 'FXTC': // Something to do with Adobe After Effects (?) 1540 case 'PrmA': 1541 case 'code': 1542 case 'FIEL': // this is NOT "fiel" (Field Ordering) as describe here: http://developer.apple.com/documentation/QuickTime/QTFF/QTFFChap3/chapter_4_section_2.html 1543 case 'tapt': // TrackApertureModeDimensionsAID - http://developer.apple.com/documentation/QuickTime/Reference/QT7-1_Update_Reference/Constants/Constants.html 1544 // tapt seems to be used to compute the video size [https://www.getid3.org/phpBB3/viewtopic.php?t=838] 1545 // * http://lists.apple.com/archives/quicktime-api/2006/Aug/msg00014.html 1546 // * http://handbrake.fr/irclogs/handbrake-dev/handbrake-dev20080128_pg2.html 1547 case 'ctts':// STCompositionOffsetAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html 1548 case 'cslg':// STCompositionShiftLeastGreatestAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html 1549 case 'sdtp':// STSampleDependencyAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html 1550 case 'stps':// STPartialSyncSampleAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html 1551 //$atom_structure['data'] = $atom_data; 1552 break; 1553 1554 case "\xA9".'xyz': // GPS latitude+longitude+altitude 1555 $atom_structure['data'] = $atom_data; 1556 if (preg_match('#([\\+\\-][0-9\\.]+)([\\+\\-][0-9\\.]+)([\\+\\-][0-9\\.]+)?/$#i', $atom_data, $matches)) { 1557 @list($all, $latitude, $longitude, $altitude) = $matches; 1558 $info['quicktime']['comments']['gps_latitude'][] = floatval($latitude); 1559 $info['quicktime']['comments']['gps_longitude'][] = floatval($longitude); 1560 if (!empty($altitude)) { 1561 $info['quicktime']['comments']['gps_altitude'][] = floatval($altitude); 1562 } 1563 } else { 1564 $this->warning('QuickTime atom "©xyz" data does not match expected data pattern at offset '.$baseoffset.'. Please report as getID3() bug.'); 1565 } 1566 break; 1567 1568 case 'NCDT': 1569 // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html 1570 // Nikon-specific QuickTime tags found in the NCDT atom of MOV videos from some Nikon cameras such as the Coolpix S8000 and D5100 1571 $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 4, $atomHierarchy, $ParseAllPossibleAtoms); 1572 break; 1573 case 'NCTH': // Nikon Camera THumbnail image 1574 case 'NCVW': // Nikon Camera preVieW image 1575 // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html 1576 if (preg_match('/^\xFF\xD8\xFF/', $atom_data)) { 1577 $atom_structure['data'] = $atom_data; 1578 $atom_structure['image_mime'] = 'image/jpeg'; 1579 $atom_structure['description'] = (($atomname == 'NCTH') ? 'Nikon Camera Thumbnail Image' : (($atomname == 'NCVW') ? 'Nikon Camera Preview Image' : 'Nikon preview image')); 1580 $info['quicktime']['comments']['picture'][] = array('image_mime'=>$atom_structure['image_mime'], 'data'=>$atom_data, 'description'=>$atom_structure['description']); 1581 } 1582 break; 1583 case 'NCTG': // Nikon - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html#NCTG 1584 $atom_structure['data'] = $this->QuicktimeParseNikonNCTG($atom_data); 1585 break; 1586 case 'NCHD': // Nikon:MakerNoteVersion - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html 1587 case 'NCDB': // Nikon - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html 1588 case 'CNCV': // Canon:CompressorVersion - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Canon.html 1589 $atom_structure['data'] = $atom_data; 1590 break; 1591 1592 case "\x00\x00\x00\x00": 1593 // some kind of metacontainer, may contain a big data dump such as: 1594 // 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 1595 // http://www.geocities.com/xhelmboyx/quicktime/formats/qti-layout.txt 1596 1597 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1598 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); 1599 $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom(substr($atom_data, 4), $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); 1600 //$atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); 1601 break; 1602 1603 case 'meta': // METAdata atom 1604 // https://developer.apple.com/library/mac/documentation/QuickTime/QTFF/Metadata/Metadata.html 1605 1606 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1607 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); 1608 $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); 1609 break; 1610 1611 case 'data': // metaDATA atom 1612 static $metaDATAkey = 1; // real ugly, but so is the QuickTime structure that stores keys and values in different multinested locations that are hard to relate to each other 1613 // seems to be 2 bytes language code (ASCII), 2 bytes unknown (set to 0x10B5 in sample I have), remainder is useful data 1614 $atom_structure['language'] = substr($atom_data, 4 + 0, 2); 1615 $atom_structure['unknown'] = getid3_lib::BigEndian2Int(substr($atom_data, 4 + 2, 2)); 1616 $atom_structure['data'] = substr($atom_data, 4 + 4); 1617 $atom_structure['key_name'] = @$info['quicktime']['temp_meta_key_names'][$metaDATAkey++]; 1618 1619 if ($atom_structure['key_name'] && $atom_structure['data']) { 1620 @$info['quicktime']['comments'][str_replace('com.apple.quicktime.', '', $atom_structure['key_name'])][] = $atom_structure['data']; 1621 } 1622 break; 1623 1624 case 'keys': // KEYS that may be present in the metadata atom. 1625 // https://developer.apple.com/library/mac/documentation/QuickTime/QTFF/Metadata/Metadata.html#//apple_ref/doc/uid/TP40000939-CH1-SW21 1626 // The metadata item keys atom holds a list of the metadata keys that may be present in the metadata atom. 1627 // 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". 1628 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1629 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); 1630 $atom_structure['entry_count'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 1631 $keys_atom_offset = 8; 1632 for ($i = 1; $i <= $atom_structure['entry_count']; $i++) { 1633 $atom_structure['keys'][$i]['key_size'] = getid3_lib::BigEndian2Int(substr($atom_data, $keys_atom_offset + 0, 4)); 1634 $atom_structure['keys'][$i]['key_namespace'] = substr($atom_data, $keys_atom_offset + 4, 4); 1635 $atom_structure['keys'][$i]['key_value'] = substr($atom_data, $keys_atom_offset + 8, $atom_structure['keys'][$i]['key_size'] - 8); 1636 $keys_atom_offset += $atom_structure['keys'][$i]['key_size']; // key_size includes the 4+4 bytes for key_size and key_namespace 1637 1638 $info['quicktime']['temp_meta_key_names'][$i] = $atom_structure['keys'][$i]['key_value']; 1639 } 1640 break; 1641 1642 case 'gps ': 1643 // https://dashcamtalk.com/forum/threads/script-to-extract-gps-data-from-novatek-mp4.20808/page-2#post-291730 1644 // The 'gps ' contains simple look up table made up of 8byte rows, that point to the 'free' atoms that contains the actual GPS data. 1645 // The first row is version/metadata/notsure, I skip that. 1646 // The following rows consist of 4byte address (absolute) and 4byte size (0x1000), these point to the GPS data in the file. 1647 1648 $GPS_rowsize = 8; // 4 bytes for offset, 4 bytes for size 1649 if (strlen($atom_data) > 0) { 1650 if ((strlen($atom_data) % $GPS_rowsize) == 0) { 1651 $atom_structure['gps_toc'] = array(); 1652 foreach (str_split($atom_data, $GPS_rowsize) as $counter => $datapair) { 1653 $atom_structure['gps_toc'][] = unpack('Noffset/Nsize', substr($atom_data, $counter * $GPS_rowsize, $GPS_rowsize)); 1419 1654 } 1655 1656 $atom_structure['gps_entries'] = array(); 1657 $previous_offset = $this->ftell(); 1658 foreach ($atom_structure['gps_toc'] as $key => $gps_pointer) { 1659 if ($key == 0) { 1660 // "The first row is version/metadata/notsure, I skip that." 1661 continue; 1662 } 1663 $this->fseek($gps_pointer['offset']); 1664 $GPS_free_data = $this->fread($gps_pointer['size']); 1665 1666 /* 1667 // 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 1668 1669 // https://dashcamtalk.com/forum/threads/script-to-extract-gps-data-from-novatek-mp4.20808/page-2#post-291730 1670 // The structure of the GPS data atom (the 'free' atoms mentioned above) is following: 1671 // hour,minute,second,year,month,day,active,latitude_b,longitude_b,unknown2,latitude,longitude,speed = struct.unpack_from('<IIIIIIssssfff',data, 48) 1672 // For those unfamiliar with python struct: 1673 // I = int 1674 // s = is string (size 1, in this case) 1675 // f = float 1676 1677 //$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)); 1678 */ 1679 1680 // $GPRMC,081836,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E*62 1681 // $GPRMC,183731,A,3907.482,N,12102.436,W,000.0,360.0,080301,015.5,E*67 1682 // $GPRMC,002454,A,3553.5295,N,13938.6570,E,0.0,43.1,180700,7.1,W,A*3F 1683 // $GPRMC,094347.000,A,5342.0061,N,00737.9908,W,0.01,156.75,140217,,,A*7D 1684 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)) { 1685 $GPS_this_GPRMC = array(); 1686 list( 1687 $GPS_this_GPRMC['raw']['gprmc'], 1688 $GPS_this_GPRMC['raw']['timestamp'], 1689 $GPS_this_GPRMC['raw']['status'], 1690 $GPS_this_GPRMC['raw']['latitude'], 1691 $GPS_this_GPRMC['raw']['latitude_direction'], 1692 $GPS_this_GPRMC['raw']['longitude'], 1693 $GPS_this_GPRMC['raw']['longitude_direction'], 1694 $GPS_this_GPRMC['raw']['knots'], 1695 $GPS_this_GPRMC['raw']['angle'], 1696 $GPS_this_GPRMC['raw']['datestamp'], 1697 $GPS_this_GPRMC['raw']['variation'], 1698 $GPS_this_GPRMC['raw']['variation_direction'], 1699 $dummy, 1700 $GPS_this_GPRMC['raw']['checksum'], 1701 ) = $matches; 1702 1703 $hour = substr($GPS_this_GPRMC['raw']['timestamp'], 0, 2); 1704 $minute = substr($GPS_this_GPRMC['raw']['timestamp'], 2, 2); 1705 $second = substr($GPS_this_GPRMC['raw']['timestamp'], 4, 2); 1706 $ms = substr($GPS_this_GPRMC['raw']['timestamp'], 6); // may contain decimal seconds 1707 $day = substr($GPS_this_GPRMC['raw']['datestamp'], 0, 2); 1708 $month = substr($GPS_this_GPRMC['raw']['datestamp'], 2, 2); 1709 $year = substr($GPS_this_GPRMC['raw']['datestamp'], 4, 2); 1710 $year += (($year > 90) ? 1900 : 2000); // complete lack of foresight: datestamps are stored with 2-digit years, take best guess 1711 $GPS_this_GPRMC['timestamp'] = $year.'-'.$month.'-'.$day.' '.$hour.':'.$minute.':'.$second.$ms; 1712 1713 $GPS_this_GPRMC['active'] = ($GPS_this_GPRMC['raw']['status'] == 'A'); // A=Active,V=Void 1714 1715 foreach (array('latitude','longitude') as $latlon) { 1716 preg_match('#^([0-9]{1,3})([0-9]{2}\\.[0-9]+)$#', $GPS_this_GPRMC['raw'][$latlon], $matches); 1717 list($dummy, $deg, $min) = $matches; 1718 $GPS_this_GPRMC[$latlon] = $deg + ($min / 60); 1719 } 1720 $GPS_this_GPRMC['latitude'] *= (($GPS_this_GPRMC['raw']['latitude_direction'] == 'S') ? -1 : 1); 1721 $GPS_this_GPRMC['longitude'] *= (($GPS_this_GPRMC['raw']['longitude_direction'] == 'W') ? -1 : 1); 1722 1723 $GPS_this_GPRMC['heading'] = $GPS_this_GPRMC['raw']['angle']; 1724 $GPS_this_GPRMC['speed_knot'] = $GPS_this_GPRMC['raw']['knots']; 1725 $GPS_this_GPRMC['speed_kmh'] = $GPS_this_GPRMC['raw']['knots'] * 1.852; 1726 if ($GPS_this_GPRMC['raw']['variation']) { 1727 $GPS_this_GPRMC['variation'] = $GPS_this_GPRMC['raw']['variation']; 1728 $GPS_this_GPRMC['variation'] *= (($GPS_this_GPRMC['raw']['variation_direction'] == 'W') ? -1 : 1); 1729 } 1730 1731 $atom_structure['gps_entries'][$key] = $GPS_this_GPRMC; 1732 1733 @$info['quicktime']['gps_track'][$GPS_this_GPRMC['timestamp']] = array( 1734 'latitude' => (float) $GPS_this_GPRMC['latitude'], 1735 'longitude' => (float) $GPS_this_GPRMC['longitude'], 1736 'speed_kmh' => (float) $GPS_this_GPRMC['speed_kmh'], 1737 'heading' => (float) $GPS_this_GPRMC['heading'], 1738 ); 1739 1740 } else { 1741 $this->warning('Unhandled GPS format in "free" atom at offset '.$gps_pointer['offset']); 1742 } 1743 } 1744 $this->fseek($previous_offset); 1745 1746 } else { 1747 $this->warning('QuickTime atom "'.$atomname.'" is not mod-8 bytes long ('.$atomsize.' bytes) at offset '.$baseoffset); 1420 1748 } 1421 } 1422 unset($getid3_mp3, $getid3_temp); 1423 $info['avdataend'] = $OldAVDataEnd; 1424 unset($OldAVDataEnd); 1425 1426 } 1427 1428 unset($mdat_offset, $chapter_string_length, $chapter_matches); 1429 break; 1430 1431 case 'free': // FREE space atom 1432 case 'skip': // SKIP atom 1433 case 'wide': // 64-bit expansion placeholder atom 1434 // 'free', 'skip' and 'wide' are just padding, contains no useful data at all 1435 1436 // When writing QuickTime files, it is sometimes necessary to update an atom's size. 1437 // It is impossible to update a 32-bit atom to a 64-bit atom since the 32-bit atom 1438 // is only 8 bytes in size, and the 64-bit atom requires 16 bytes. Therefore, QuickTime 1439 // puts an 8-byte placeholder atom before any atoms it may have to update the size of. 1440 // In this way, if the atom needs to be converted from a 32-bit to a 64-bit atom, the 1441 // placeholder atom can be overwritten to obtain the necessary 8 extra bytes. 1442 // The placeholder atom has a type of kWideAtomPlaceholderType ( 'wide' ). 1443 break; 1444 1445 1446 case 'nsav': // NoSAVe atom 1447 // http://developer.apple.com/technotes/tn/tn2038.html 1448 $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); 1449 break; 1450 1451 case 'ctyp': // Controller TYPe atom (seen on QTVR) 1452 // http://homepages.slingshot.co.nz/~helmboy/quicktime/formats/qtm-layout.txt 1453 // some controller names are: 1454 // 0x00 + 'std' for linear movie 1455 // 'none' for no controls 1456 $atom_structure['ctyp'] = substr($atom_data, 0, 4); 1457 $info['quicktime']['controller'] = $atom_structure['ctyp']; 1458 switch ($atom_structure['ctyp']) { 1459 case 'qtvr': 1460 $info['video']['dataformat'] = 'quicktimevr'; 1461 break; 1462 } 1463 break; 1464 1465 case 'pano': // PANOrama track (seen on QTVR) 1466 $atom_structure['pano'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); 1467 break; 1468 1469 case 'hint': // HINT track 1470 case 'hinf': // 1471 case 'hinv': // 1472 case 'hnti': // 1473 $info['quicktime']['hinting'] = true; 1474 break; 1475 1476 case 'imgt': // IMaGe Track reference (kQTVRImageTrackRefType) (seen on QTVR) 1477 for ($i = 0; $i < ($atom_structure['size'] - 8); $i += 4) { 1478 $atom_structure['imgt'][] = getid3_lib::BigEndian2Int(substr($atom_data, $i, 4)); 1479 } 1480 break; 1481 1482 1483 // Observed-but-not-handled atom types are just listed here to prevent warnings being generated 1484 case 'FXTC': // Something to do with Adobe After Effects (?) 1485 case 'PrmA': 1486 case 'code': 1487 case 'FIEL': // this is NOT "fiel" (Field Ordering) as describe here: http://developer.apple.com/documentation/QuickTime/QTFF/QTFFChap3/chapter_4_section_2.html 1488 case 'tapt': // TrackApertureModeDimensionsAID - http://developer.apple.com/documentation/QuickTime/Reference/QT7-1_Update_Reference/Constants/Constants.html 1489 // tapt seems to be used to compute the video size [http://www.getid3.org/phpBB3/viewtopic.php?t=838] 1490 // * http://lists.apple.com/archives/quicktime-api/2006/Aug/msg00014.html 1491 // * http://handbrake.fr/irclogs/handbrake-dev/handbrake-dev20080128_pg2.html 1492 case 'ctts':// STCompositionOffsetAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html 1493 case 'cslg':// STCompositionShiftLeastGreatestAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html 1494 case 'sdtp':// STSampleDependencyAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html 1495 case 'stps':// STPartialSyncSampleAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html 1496 //$atom_structure['data'] = $atom_data; 1497 break; 1498 1499 case "\xA9".'xyz': // GPS latitude+longitude+altitude 1500 $atom_structure['data'] = $atom_data; 1501 if (preg_match('#([\\+\\-][0-9\\.]+)([\\+\\-][0-9\\.]+)([\\+\\-][0-9\\.]+)?/$#i', $atom_data, $matches)) { 1502 @list($all, $latitude, $longitude, $altitude) = $matches; 1503 $info['quicktime']['comments']['gps_latitude'][] = floatval($latitude); 1504 $info['quicktime']['comments']['gps_longitude'][] = floatval($longitude); 1505 if (!empty($altitude)) { 1506 $info['quicktime']['comments']['gps_altitude'][] = floatval($altitude); 1507 } 1508 } else { 1509 $this->warning('QuickTime atom "©xyz" data does not match expected data pattern at offset '.$baseoffset.'. Please report as getID3() bug.'); 1510 } 1511 break; 1512 1513 case 'NCDT': 1514 // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html 1515 // Nikon-specific QuickTime tags found in the NCDT atom of MOV videos from some Nikon cameras such as the Coolpix S8000 and D5100 1516 $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 4, $atomHierarchy, $ParseAllPossibleAtoms); 1517 break; 1518 case 'NCTH': // Nikon Camera THumbnail image 1519 case 'NCVW': // Nikon Camera preVieW image 1520 // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html 1521 if (preg_match('/^\xFF\xD8\xFF/', $atom_data)) { 1749 } else { 1750 $this->warning('QuickTime atom "'.$atomname.'" is zero bytes long at offset '.$baseoffset); 1751 } 1752 break; 1753 1754 case 'loci':// 3GP location (El Loco) 1755 $loffset = 0; 1756 $info['quicktime']['comments']['gps_flags'] = array( getid3_lib::BigEndian2Int(substr($atom_data, 0, 4))); 1757 $info['quicktime']['comments']['gps_lang'] = array( getid3_lib::BigEndian2Int(substr($atom_data, 4, 2))); 1758 $info['quicktime']['comments']['gps_location'] = array( $this->LociString(substr($atom_data, 6), $loffset)); 1759 $loci_data = substr($atom_data, 6 + $loffset); 1760 $info['quicktime']['comments']['gps_role'] = array( getid3_lib::BigEndian2Int(substr($loci_data, 0, 1))); 1761 $info['quicktime']['comments']['gps_longitude'] = array(getid3_lib::FixedPoint16_16(substr($loci_data, 1, 4))); 1762 $info['quicktime']['comments']['gps_latitude'] = array(getid3_lib::FixedPoint16_16(substr($loci_data, 5, 4))); 1763 $info['quicktime']['comments']['gps_altitude'] = array(getid3_lib::FixedPoint16_16(substr($loci_data, 9, 4))); 1764 $info['quicktime']['comments']['gps_body'] = array( $this->LociString(substr($loci_data, 13 ), $loffset)); 1765 $info['quicktime']['comments']['gps_notes'] = array( $this->LociString(substr($loci_data, 13 + $loffset), $loffset)); 1766 break; 1767 1768 case 'chpl': // CHaPter List 1769 // https://www.adobe.com/content/dam/Adobe/en/devnet/flv/pdfs/video_file_format_spec_v10.pdf 1770 $chpl_version = getid3_lib::BigEndian2Int(substr($atom_data, 4, 1)); // Expected to be 0 1771 $chpl_flags = getid3_lib::BigEndian2Int(substr($atom_data, 5, 3)); // Reserved, set to 0 1772 $chpl_count = getid3_lib::BigEndian2Int(substr($atom_data, 8, 1)); 1773 $chpl_offset = 9; 1774 for ($i = 0; $i < $chpl_count; $i++) { 1775 if (($chpl_offset + 9) >= strlen($atom_data)) { 1776 $this->warning('QuickTime chapter '.$i.' extends beyond end of "chpl" atom'); 1777 break; 1778 } 1779 $info['quicktime']['chapters'][$i]['timestamp'] = getid3_lib::BigEndian2Int(substr($atom_data, $chpl_offset, 8)) / 10000000; // timestamps are stored as 100-nanosecond units 1780 $chpl_offset += 8; 1781 $chpl_title_size = getid3_lib::BigEndian2Int(substr($atom_data, $chpl_offset, 1)); 1782 $chpl_offset += 1; 1783 $info['quicktime']['chapters'][$i]['title'] = substr($atom_data, $chpl_offset, $chpl_title_size); 1784 $chpl_offset += $chpl_title_size; 1785 } 1786 break; 1787 1788 case 'FIRM': // FIRMware version(?), seen on GoPro Hero4 1789 $info['quicktime']['camera']['firmware'] = $atom_data; 1790 break; 1791 1792 case 'CAME': // FIRMware version(?), seen on GoPro Hero4 1793 $info['quicktime']['camera']['serial_hash'] = unpack('H*', $atom_data); 1794 break; 1795 1796 case 'dscp': 1797 case 'rcif': 1798 // https://www.getid3.org/phpBB3/viewtopic.php?t=1908 1799 if (substr($atom_data, 0, 7) == "\x00\x00\x00\x00\x55\xC4".'{') { 1800 if ($json_decoded = @json_decode(rtrim(substr($atom_data, 6), "\x00"), true)) { 1801 $info['quicktime']['camera'][$atomname] = $json_decoded; 1802 if (($atomname == 'rcif') && isset($info['quicktime']['camera']['rcif']['wxcamera']['rotate'])) { 1803 $info['video']['rotate'] = $info['quicktime']['video']['rotate'] = $info['quicktime']['camera']['rcif']['wxcamera']['rotate']; 1804 } 1805 } else { 1806 $this->warning('Failed to JSON decode atom "'.$atomname.'"'); 1807 $atom_structure['data'] = $atom_data; 1808 } 1809 unset($json_decoded); 1810 } else { 1811 $this->warning('Expecting 55 C4 7B at start of atom "'.$atomname.'", found '.getid3_lib::PrintHexBytes(substr($atom_data, 4, 3)).' instead'); 1812 $atom_structure['data'] = $atom_data; 1813 } 1814 break; 1815 1816 case 'frea': 1817 // https://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Kodak.html#frea 1818 // may contain "scra" (PreviewImage) and/or "thma" (ThumbnailImage) 1819 $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 4, $atomHierarchy, $ParseAllPossibleAtoms); 1820 break; 1821 case 'tima': // subatom to "frea" 1822 // no idea what this does, the one sample file I've seen has a value of 0x00000027 1522 1823 $atom_structure['data'] = $atom_data; 1523 $atom_structure['image_mime'] = 'image/jpeg'; 1524 $atom_structure['description'] = (($atomname == 'NCTH') ? 'Nikon Camera Thumbnail Image' : (($atomname == 'NCVW') ? 'Nikon Camera Preview Image' : 'Nikon preview image')); 1525 $info['quicktime']['comments']['picture'][] = array('image_mime'=>$atom_structure['image_mime'], 'data'=>$atom_data, 'description'=>$atom_structure['description']); 1526 } 1527 break; 1528 case 'NCTG': // Nikon - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html#NCTG 1529 $atom_structure['data'] = $this->QuicktimeParseNikonNCTG($atom_data); 1530 break; 1531 case 'NCHD': // Nikon:MakerNoteVersion - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html 1532 case 'NCDB': // Nikon - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html 1533 case 'CNCV': // Canon:CompressorVersion - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Canon.html 1534 $atom_structure['data'] = $atom_data; 1535 break; 1536 1537 case "\x00\x00\x00\x00": 1538 // some kind of metacontainer, may contain a big data dump such as: 1539 // mdta keys \005 mdtacom.apple.quicktime.make (mdtacom.apple.quicktime.creationdate ,mdtacom.apple.quicktime.location.ISO6709 $mdtacom.apple.quicktime.software !mdtacom.apple.quicktime.model ilst \01D \001 \015data \001DE\010Apple 0 \002 (data \001DE\0102011-05-11T17:54:04+0200 2 \003 *data \001DE\010+52.4936+013.3897+040.247/ \01D \004 \015data \001DE\0104.3.1 \005 \018data \001DE\010iPhone 4 1540 // http://www.geocities.com/xhelmboyx/quicktime/formats/qti-layout.txt 1541 1542 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1543 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); 1544 $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom(substr($atom_data, 4), $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); 1545 //$atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); 1546 break; 1547 1548 case 'meta': // METAdata atom 1549 // https://developer.apple.com/library/mac/documentation/QuickTime/QTFF/Metadata/Metadata.html 1550 1551 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1552 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); 1553 $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); 1554 break; 1555 1556 case 'data': // metaDATA atom 1557 static $metaDATAkey = 1; // real ugly, but so is the QuickTime structure that stores keys and values in different multinested locations that are hard to relate to each other 1558 // seems to be 2 bytes language code (ASCII), 2 bytes unknown (set to 0x10B5 in sample I have), remainder is useful data 1559 $atom_structure['language'] = substr($atom_data, 4 + 0, 2); 1560 $atom_structure['unknown'] = getid3_lib::BigEndian2Int(substr($atom_data, 4 + 2, 2)); 1561 $atom_structure['data'] = substr($atom_data, 4 + 4); 1562 $atom_structure['key_name'] = @$info['quicktime']['temp_meta_key_names'][$metaDATAkey++]; 1563 1564 if ($atom_structure['key_name'] && $atom_structure['data']) { 1565 @$info['quicktime']['comments'][str_replace('com.apple.quicktime.', '', $atom_structure['key_name'])][] = $atom_structure['data']; 1566 } 1567 break; 1568 1569 case 'keys': // KEYS that may be present in the metadata atom. 1570 // https://developer.apple.com/library/mac/documentation/QuickTime/QTFF/Metadata/Metadata.html#//apple_ref/doc/uid/TP40000939-CH1-SW21 1571 // The metadata item keys atom holds a list of the metadata keys that may be present in the metadata atom. 1572 // This list is indexed starting with 1; 0 is a reserved index value. The metadata item keys atom is a full atom with an atom type of "keys". 1573 $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); 1574 $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); 1575 $atom_structure['entry_count'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); 1576 $keys_atom_offset = 8; 1577 for ($i = 1; $i <= $atom_structure['entry_count']; $i++) { 1578 $atom_structure['keys'][$i]['key_size'] = getid3_lib::BigEndian2Int(substr($atom_data, $keys_atom_offset + 0, 4)); 1579 $atom_structure['keys'][$i]['key_namespace'] = substr($atom_data, $keys_atom_offset + 4, 4); 1580 $atom_structure['keys'][$i]['key_value'] = substr($atom_data, $keys_atom_offset + 8, $atom_structure['keys'][$i]['key_size'] - 8); 1581 $keys_atom_offset += $atom_structure['keys'][$i]['key_size']; // key_size includes the 4+4 bytes for key_size and key_namespace 1582 1583 $info['quicktime']['temp_meta_key_names'][$i] = $atom_structure['keys'][$i]['key_value']; 1584 } 1585 break; 1586 1587 case 'gps ': 1588 // https://dashcamtalk.com/forum/threads/script-to-extract-gps-data-from-novatek-mp4.20808/page-2#post-291730 1589 // The 'gps ' contains simple look up table made up of 8byte rows, that point to the 'free' atoms that contains the actual GPS data. 1590 // The first row is version/metadata/notsure, I skip that. 1591 // The following rows consist of 4byte address (absolute) and 4byte size (0x1000), these point to the GPS data in the file. 1592 1593 $GPS_rowsize = 8; // 4 bytes for offset, 4 bytes for size 1594 if (strlen($atom_data) > 0) { 1595 if ((strlen($atom_data) % $GPS_rowsize) == 0) { 1596 $atom_structure['gps_toc'] = array(); 1597 foreach (str_split($atom_data, $GPS_rowsize) as $counter => $datapair) { 1598 $atom_structure['gps_toc'][] = unpack('Noffset/Nsize', substr($atom_data, $counter * $GPS_rowsize, $GPS_rowsize)); 1599 } 1600 1601 $atom_structure['gps_entries'] = array(); 1602 $previous_offset = $this->ftell(); 1603 foreach ($atom_structure['gps_toc'] as $key => $gps_pointer) { 1604 if ($key == 0) { 1605 // "The first row is version/metadata/notsure, I skip that." 1606 continue; 1607 } 1608 $this->fseek($gps_pointer['offset']); 1609 $GPS_free_data = $this->fread($gps_pointer['size']); 1610 1611 /* 1612 // 2017-05-10: I see some of the data, notably the Hour-Minute-Second, but cannot reconcile the rest of the data. However, the NMEA "GPRMC" line is there and relatively easy to parse, so I'm using that instead 1613 1614 // https://dashcamtalk.com/forum/threads/script-to-extract-gps-data-from-novatek-mp4.20808/page-2#post-291730 1615 // The structure of the GPS data atom (the 'free' atoms mentioned above) is following: 1616 // hour,minute,second,year,month,day,active,latitude_b,longitude_b,unknown2,latitude,longitude,speed = struct.unpack_from('<IIIIIIssssfff',data, 48) 1617 // For those unfamiliar with python struct: 1618 // I = int 1619 // s = is string (size 1, in this case) 1620 // f = float 1621 1622 //$atom_structure['gps_entries'][$key] = unpack('Vhour/Vminute/Vsecond/Vyear/Vmonth/Vday/Vactive/Vlatitude_b/Vlongitude_b/Vunknown2/flatitude/flongitude/fspeed', substr($GPS_free_data, 48)); 1623 */ 1624 1625 // $GPRMC,081836,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E*62 1626 // $GPRMC,183731,A,3907.482,N,12102.436,W,000.0,360.0,080301,015.5,E*67 1627 // $GPRMC,002454,A,3553.5295,N,13938.6570,E,0.0,43.1,180700,7.1,W,A*3F 1628 // $GPRMC,094347.000,A,5342.0061,N,00737.9908,W,0.01,156.75,140217,,,A*7D 1629 if (preg_match('#\\$GPRMC,([0-9\\.]*),([AV]),([0-9\\.]*),([NS]),([0-9\\.]*),([EW]),([0-9\\.]*),([0-9\\.]*),([0-9]*),([0-9\\.]*),([EW]?)(,[A])?(\\*[0-9A-F]{2})#', $GPS_free_data, $matches)) { 1630 $GPS_this_GPRMC = array(); 1631 list( 1632 $GPS_this_GPRMC['raw']['gprmc'], 1633 $GPS_this_GPRMC['raw']['timestamp'], 1634 $GPS_this_GPRMC['raw']['status'], 1635 $GPS_this_GPRMC['raw']['latitude'], 1636 $GPS_this_GPRMC['raw']['latitude_direction'], 1637 $GPS_this_GPRMC['raw']['longitude'], 1638 $GPS_this_GPRMC['raw']['longitude_direction'], 1639 $GPS_this_GPRMC['raw']['knots'], 1640 $GPS_this_GPRMC['raw']['angle'], 1641 $GPS_this_GPRMC['raw']['datestamp'], 1642 $GPS_this_GPRMC['raw']['variation'], 1643 $GPS_this_GPRMC['raw']['variation_direction'], 1644 $dummy, 1645 $GPS_this_GPRMC['raw']['checksum'], 1646 ) = $matches; 1647 1648 $hour = substr($GPS_this_GPRMC['raw']['timestamp'], 0, 2); 1649 $minute = substr($GPS_this_GPRMC['raw']['timestamp'], 2, 2); 1650 $second = substr($GPS_this_GPRMC['raw']['timestamp'], 4, 2); 1651 $ms = substr($GPS_this_GPRMC['raw']['timestamp'], 6); // may contain decimal seconds 1652 $day = substr($GPS_this_GPRMC['raw']['datestamp'], 0, 2); 1653 $month = substr($GPS_this_GPRMC['raw']['datestamp'], 2, 2); 1654 $year = substr($GPS_this_GPRMC['raw']['datestamp'], 4, 2); 1655 $year += (($year > 90) ? 1900 : 2000); // complete lack of foresight: datestamps are stored with 2-digit years, take best guess 1656 $GPS_this_GPRMC['timestamp'] = $year.'-'.$month.'-'.$day.' '.$hour.':'.$minute.':'.$second.$ms; 1657 1658 $GPS_this_GPRMC['active'] = ($GPS_this_GPRMC['raw']['status'] == 'A'); // A=Active,V=Void 1659 1660 foreach (array('latitude','longitude') as $latlon) { 1661 preg_match('#^([0-9]{1,3})([0-9]{2}\\.[0-9]+)$#', $GPS_this_GPRMC['raw'][$latlon], $matches); 1662 list($dummy, $deg, $min) = $matches; 1663 $GPS_this_GPRMC[$latlon] = $deg + ($min / 60); 1664 } 1665 $GPS_this_GPRMC['latitude'] *= (($GPS_this_GPRMC['raw']['latitude_direction'] == 'S') ? -1 : 1); 1666 $GPS_this_GPRMC['longitude'] *= (($GPS_this_GPRMC['raw']['longitude_direction'] == 'W') ? -1 : 1); 1667 1668 $GPS_this_GPRMC['heading'] = $GPS_this_GPRMC['raw']['angle']; 1669 $GPS_this_GPRMC['speed_knot'] = $GPS_this_GPRMC['raw']['knots']; 1670 $GPS_this_GPRMC['speed_kmh'] = $GPS_this_GPRMC['raw']['knots'] * 1.852; 1671 if ($GPS_this_GPRMC['raw']['variation']) { 1672 $GPS_this_GPRMC['variation'] = $GPS_this_GPRMC['raw']['variation']; 1673 $GPS_this_GPRMC['variation'] *= (($GPS_this_GPRMC['raw']['variation_direction'] == 'W') ? -1 : 1); 1674 } 1675 1676 $atom_structure['gps_entries'][$key] = $GPS_this_GPRMC; 1677 1678 @$info['quicktime']['gps_track'][$GPS_this_GPRMC['timestamp']] = array( 1679 'latitude' => $GPS_this_GPRMC['latitude'], 1680 'longitude' => $GPS_this_GPRMC['longitude'], 1681 'speed_kmh' => $GPS_this_GPRMC['speed_kmh'], 1682 'heading' => $GPS_this_GPRMC['heading'], 1683 ); 1684 1685 } else { 1686 $this->warning('Unhandled GPS format in "free" atom at offset '.$gps_pointer['offset']); 1687 } 1688 } 1689 $this->fseek($previous_offset); 1690 1691 } else { 1692 $this->warning('QuickTime atom "'.$atomname.'" is not mod-8 bytes long ('.$atomsize.' bytes) at offset '.$baseoffset); 1693 } 1694 } else { 1695 $this->warning('QuickTime atom "'.$atomname.'" is zero bytes long at offset '.$baseoffset); 1696 } 1697 break; 1698 1699 case 'loci':// 3GP location (El Loco) 1700 $info['quicktime']['comments']['gps_flags'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); 1701 $info['quicktime']['comments']['gps_lang'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); 1702 $loffset = 0; 1703 $info['quicktime']['comments']['gps_location'] = $this->LociString(substr($atom_data, 6), $loffset); 1704 $loci_data=substr($atom_data, 6 + $loffset); 1705 $info['quicktime']['comments']['gps_role'] = getid3_lib::BigEndian2Int(substr($loci_data, 0, 1)); 1706 $info['quicktime']['comments']['gps_longitude'] = getid3_lib::FixedPoint16_16(substr($loci_data, 1, 4)); 1707 $info['quicktime']['comments']['gps_latitude'] = getid3_lib::FixedPoint16_16(substr($loci_data, 5, 4)); 1708 $info['quicktime']['comments']['gps_altitude'] = getid3_lib::FixedPoint16_16(substr($loci_data, 9, 4)); 1709 $info['quicktime']['comments']['gps_body'] = $this->LociString(substr($loci_data, 13), $loffset); 1710 $info['quicktime']['comments']['gps_notes'] = $this->LociString(substr($loci_data, 13 + $loffset), $loffset); 1711 break; 1712 1713 default: 1714 $this->warning('Unknown QuickTime atom type: "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $atomname).'" ('.trim(getid3_lib::PrintHexBytes($atomname)).') at offset '.$baseoffset); 1715 $atom_structure['data'] = $atom_data; 1716 break; 1824 break; 1825 case 'ver ': // subatom to "frea" 1826 // some kind of version number, the one sample file I've seen has a value of "3.00.073" 1827 $atom_structure['data'] = $atom_data; 1828 break; 1829 case 'thma': // subatom to "frea" -- "ThumbnailImage" 1830 // https://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Kodak.html#frea 1831 if (strlen($atom_data) > 0) { 1832 $info['quicktime']['comments']['picture'][] = array('data'=>$atom_data, 'image_mime'=>'image/jpeg'); 1833 } 1834 break; 1835 case 'scra': // subatom to "frea" -- "PreviewImage" 1836 // https://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Kodak.html#frea 1837 // but the only sample file I've seen has no useful data here 1838 if (strlen($atom_data) > 0) { 1839 $info['quicktime']['comments']['picture'][] = array('data'=>$atom_data, 'image_mime'=>'image/jpeg'); 1840 } 1841 break; 1842 1843 1844 default: 1845 $this->warning('Unknown QuickTime atom type: "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $atomname).'" ('.trim(getid3_lib::PrintHexBytes($atomname)).'), '.$atomsize.' bytes at offset '.$baseoffset); 1846 $atom_structure['data'] = $atom_data; 1847 break; 1848 } 1717 1849 } 1718 1850 array_pop($atomHierarchy); … … 1720 1852 } 1721 1853 1854 /** 1855 * @param string $atom_data 1856 * @param int $baseoffset 1857 * @param array $atomHierarchy 1858 * @param bool $ParseAllPossibleAtoms 1859 * 1860 * @return array|false 1861 */ 1722 1862 public function QuicktimeParseContainerAtom($atom_data, $baseoffset, &$atomHierarchy, $ParseAllPossibleAtoms) { 1723 //echo 'QuicktimeParseContainerAtom('.substr($atom_data, 4, 4).') @ '.$baseoffset.'<br><br>';1724 1863 $atom_structure = false; 1725 1864 $subatomoffset = 0; … … 1742 1881 return $atom_structure; 1743 1882 } 1744 1745 $atom_structure[$subatomcounter] = $this->QuicktimeParseAtom($subatomname, $subatomsize, $subatomdata, $baseoffset + $subatomoffset, $atomHierarchy, $ParseAllPossibleAtoms); 1746 1883 $atom_structure[$subatomcounter++] = $this->QuicktimeParseAtom($subatomname, $subatomsize, $subatomdata, $baseoffset + $subatomoffset, $atomHierarchy, $ParseAllPossibleAtoms); 1747 1884 $subatomoffset += $subatomsize; 1748 $subatomcounter++;1749 1885 } 1750 1886 return $atom_structure; 1751 1887 } 1752 1888 1753 1889 /** 1890 * @param string $data 1891 * @param int $offset 1892 * 1893 * @return int 1894 */ 1754 1895 public function quicktime_read_mp4_descr_length($data, &$offset) { 1755 1896 // http://libquicktime.sourcearchive.com/documentation/2:1.0.2plus-pdebian-2build1/esds_8c-source.html … … 1763 1904 } 1764 1905 1765 1906 /** 1907 * @param int $languageid 1908 * 1909 * @return string 1910 */ 1766 1911 public function QuicktimeLanguageLookup($languageid) { 1767 1912 // http://developer.apple.com/library/mac/#documentation/QuickTime/QTFF/QTFFChap4/qtff4.html#//apple_ref/doc/uid/TP40000939-CH206-34353 … … 1901 2046 } 1902 2047 2048 /** 2049 * @param string $codecid 2050 * 2051 * @return string 2052 */ 1903 2053 public function QuicktimeVideoCodecLookup($codecid) { 1904 2054 static $QuicktimeVideoCodecLookup = array(); … … 1960 2110 } 1961 2111 2112 /** 2113 * @param string $codecid 2114 * 2115 * @return mixed|string 2116 */ 1962 2117 public function QuicktimeAudioCodecLookup($codecid) { 1963 2118 static $QuicktimeAudioCodecLookup = array(); … … 2005 2160 } 2006 2161 2162 /** 2163 * @param string $compressionid 2164 * 2165 * @return string 2166 */ 2007 2167 public function QuicktimeDCOMLookup($compressionid) { 2008 2168 static $QuicktimeDCOMLookup = array(); … … 2014 2174 } 2015 2175 2176 /** 2177 * @param int $colordepthid 2178 * 2179 * @return string 2180 */ 2016 2181 public function QuicktimeColorNameLookup($colordepthid) { 2017 2182 static $QuicktimeColorNameLookup = array(); … … 2032 2197 } 2033 2198 2199 /** 2200 * @param int $stik 2201 * 2202 * @return string 2203 */ 2034 2204 public function QuicktimeSTIKLookup($stik) { 2035 2205 static $QuicktimeSTIKLookup = array(); … … 2049 2219 } 2050 2220 2221 /** 2222 * @param int $audio_profile_id 2223 * 2224 * @return string 2225 */ 2051 2226 public function QuicktimeIODSaudioProfileName($audio_profile_id) { 2052 2227 static $QuicktimeIODSaudioProfileNameLookup = array(); … … 2108 2283 } 2109 2284 2110 2285 /** 2286 * @param int $video_profile_id 2287 * 2288 * @return string 2289 */ 2111 2290 public function QuicktimeIODSvideoProfileName($video_profile_id) { 2112 2291 static $QuicktimeIODSvideoProfileNameLookup = array(); … … 2180 2359 } 2181 2360 2182 2361 /** 2362 * @param int $rtng 2363 * 2364 * @return string 2365 */ 2183 2366 public function QuicktimeContentRatingLookup($rtng) { 2184 2367 static $QuicktimeContentRatingLookup = array(); … … 2191 2374 } 2192 2375 2376 /** 2377 * @param int $akid 2378 * 2379 * @return string 2380 */ 2193 2381 public function QuicktimeStoreAccountTypeLookup($akid) { 2194 2382 static $QuicktimeStoreAccountTypeLookup = array(); … … 2200 2388 } 2201 2389 2390 /** 2391 * @param int $sfid 2392 * 2393 * @return string 2394 */ 2202 2395 public function QuicktimeStoreFrontCodeLookup($sfid) { 2203 2396 static $QuicktimeStoreFrontCodeLookup = array(); … … 2229 2422 } 2230 2423 2424 /** 2425 * @param string $atom_data 2426 * 2427 * @return array 2428 */ 2231 2429 public function QuicktimeParseNikonNCTG($atom_data) { 2232 2430 // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html#NCTG … … 2271 2469 2272 2470 $offset = 0; 2471 $data = null; 2273 2472 $datalength = strlen($atom_data); 2274 2473 $parsed = array(); 2275 2474 while ($offset < $datalength) { 2276 //echo getid3_lib::PrintHexBytes(substr($atom_data, $offset, 4)).'<br>';2277 2475 $record_type = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 4)); $offset += 4; 2278 2476 $data_size_type = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 2)); $offset += 2; … … 2329 2527 break; 2330 2528 default: 2331 echo 'QuicktimeParseNikonNCTG()::unknown $data_size_type: '.$data_size_type.'<br>';2529 echo 'QuicktimeParseNikonNCTG()::unknown $data_size_type: '.$data_size_type.'<br>'; 2332 2530 break 2; 2333 2531 } … … 2407 2605 } 2408 2606 2409 2607 /** 2608 * @param string $keyname 2609 * @param string|array $data 2610 * @param string $boxname 2611 * 2612 * @return bool 2613 */ 2410 2614 public function CopyToAppropriateCommentsSection($keyname, $data, $boxname='') { 2411 2615 static $handyatomtranslatorarray = array(); … … 2451 2655 $handyatomtranslatorarray["\xA9".'swr'] = 'software'; 2452 2656 $handyatomtranslatorarray["\xA9".'too'] = 'encoding_tool'; // iTunes 4.0 2453 $handyatomtranslatorarray["\xA9".'trk'] = 'track ';2657 $handyatomtranslatorarray["\xA9".'trk'] = 'track_number'; 2454 2658 $handyatomtranslatorarray["\xA9".'url'] = 'url'; 2455 2659 $handyatomtranslatorarray["\xA9".'wrn'] = 'warning'; … … 2504 2708 2505 2709 // http://age.hobba.nl/audio/tag_frame_reference.html 2506 $handyatomtranslatorarray['PLAY_COUNTER'] = 'play_counter'; // Foobar2000 - http ://www.getid3.org/phpBB3/viewtopic.php?t=13552507 $handyatomtranslatorarray['MEDIATYPE'] = 'mediatype'; // Foobar2000 - http ://www.getid3.org/phpBB3/viewtopic.php?t=13552710 $handyatomtranslatorarray['PLAY_COUNTER'] = 'play_counter'; // Foobar2000 - https://www.getid3.org/phpBB3/viewtopic.php?t=1355 2711 $handyatomtranslatorarray['MEDIATYPE'] = 'mediatype'; // Foobar2000 - https://www.getid3.org/phpBB3/viewtopic.php?t=1355 2508 2712 */ 2509 2713 } … … 2543 2747 } 2544 2748 2545 public function LociString($lstring, &$count) { 2546 // Loci strings are UTF-8 or UTF-16 and null (x00/x0000) terminated. UTF-16 has a BOM 2547 // Also need to return the number of bytes the string occupied so additional fields can be extracted 2548 $len = strlen($lstring); 2549 if ($len == 0) { 2550 $count = 0; 2551 return ''; 2552 } 2553 if ($lstring[0] == "\x00") { 2554 $count = 1; 2555 return ''; 2556 } 2557 //check for BOM 2558 if ($len > 2 && (($lstring[0] == "\xFE" && $lstring[1] == "\xFF") || ($lstring[0] == "\xFF" && $lstring[1] == "\xFE"))) { 2559 //UTF-16 2560 if (preg_match('/(.*)\x00/', $lstring, $lmatches)){ 2561 $count = strlen($lmatches[1]) * 2 + 2; //account for 2 byte characters and trailing \x0000 2562 return getid3_lib::iconv_fallback_utf16_utf8($lmatches[1]); 2563 } else { 2564 return ''; 2565 } 2566 } else { 2567 //UTF-8 2568 if (preg_match('/(.*)\x00/', $lstring, $lmatches)){ 2569 $count = strlen($lmatches[1]) + 1; //account for trailing \x00 2570 return $lmatches[1]; 2571 }else { 2572 return ''; 2573 } 2574 2575 } 2576 } 2577 2749 /** 2750 * @param string $lstring 2751 * @param int $count 2752 * 2753 * @return string 2754 */ 2755 public function LociString($lstring, &$count) { 2756 // Loci strings are UTF-8 or UTF-16 and null (x00/x0000) terminated. UTF-16 has a BOM 2757 // Also need to return the number of bytes the string occupied so additional fields can be extracted 2758 $len = strlen($lstring); 2759 if ($len == 0) { 2760 $count = 0; 2761 return ''; 2762 } 2763 if ($lstring[0] == "\x00") { 2764 $count = 1; 2765 return ''; 2766 } 2767 // check for BOM 2768 if (($len > 2) && ((($lstring[0] == "\xFE") && ($lstring[1] == "\xFF")) || (($lstring[0] == "\xFF") && ($lstring[1] == "\xFE")))) { 2769 // UTF-16 2770 if (preg_match('/(.*)\x00/', $lstring, $lmatches)) { 2771 $count = strlen($lmatches[1]) * 2 + 2; //account for 2 byte characters and trailing \x0000 2772 return getid3_lib::iconv_fallback_utf16_utf8($lmatches[1]); 2773 } else { 2774 return ''; 2775 } 2776 } 2777 // UTF-8 2778 if (preg_match('/(.*)\x00/', $lstring, $lmatches)) { 2779 $count = strlen($lmatches[1]) + 1; //account for trailing \x00 2780 return $lmatches[1]; 2781 } 2782 return ''; 2783 } 2784 2785 /** 2786 * @param string $nullterminatedstring 2787 * 2788 * @return string 2789 */ 2578 2790 public function NoNullString($nullterminatedstring) { 2579 2791 // remove the single null terminator on null terminated strings … … 2584 2796 } 2585 2797 2798 /** 2799 * @param string $pascalstring 2800 * 2801 * @return string 2802 */ 2586 2803 public function Pascal2String($pascalstring) { 2587 2804 // Pascal strings have 1 unsigned byte at the beginning saying how many chars (1-255) are in the string … … 2590 2807 2591 2808 2592 /* 2593 // helper functions for m4b audiobook chapters 2594 // code by Steffen Hartmann 2015-Nov-08 2595 */ 2809 /** 2810 * Helper functions for m4b audiobook chapters 2811 * code by Steffen Hartmann 2015-Nov-08. 2812 * 2813 * @param array $info 2814 * @param string $tag 2815 * @param string $history 2816 * @param array $result 2817 */ 2596 2818 public function search_tag_by_key($info, $tag, $history, &$result) { 2597 2819 foreach ($info as $key => $value) { … … 2607 2829 } 2608 2830 2831 /** 2832 * @param array $info 2833 * @param string $k 2834 * @param string $v 2835 * @param string $history 2836 * @param array $result 2837 */ 2609 2838 public function search_tag_by_pair($info, $k, $v, $history, &$result) { 2610 2839 foreach ($info as $key => $value) { … … 2620 2849 } 2621 2850 2851 /** 2852 * @param array $info 2853 * 2854 * @return array 2855 */ 2622 2856 public function quicktime_time_to_sample_table($info) { 2623 2857 $res = array(); … … 2637 2871 } 2638 2872 2639 function quicktime_bookmark_time_scale($info) { 2873 /** 2874 * @param array $info 2875 * 2876 * @return int 2877 */ 2878 public function quicktime_bookmark_time_scale($info) { 2640 2879 $time_scale = ''; 2641 2880 $ts_prefix_len = 0; … … 2648 2887 $ts_res = array(); 2649 2888 $this->search_tag_by_key($info['quicktime']['moov'], 'time_scale', 'quicktime/moov', $ts_res); 2650 foreach ($ts_res as $ value) {2651 $prefix = substr($ value[0], 0, -12);2889 foreach ($ts_res as $sub_value) { 2890 $prefix = substr($sub_value[0], 0, -12); 2652 2891 if ((substr($stbl_res[0][0], 0, strlen($prefix)) === $prefix) && ($ts_prefix_len < strlen($prefix))) { 2653 $time_scale = $ value[1]['time_scale'];2892 $time_scale = $sub_value[1]['time_scale']; 2654 2893 $ts_prefix_len = strlen($prefix); 2655 2894 }
Note: See TracChangeset
for help on using the changeset viewer.