function ParseRIFF(&$fd, $startoffset, $maxoffset, &$ThisFileInfo) { $maxoffset = min($maxoffset, $ThisFileInfo['avdataend']); $RIFFchunk = false; fseek($fd, $startoffset, SEEK_SET); while (ftell($fd) < $maxoffset) { $chunkname = fread($fd, 4); if (strlen($chunkname) < 4) { $ThisFileInfo['error'][] = 'Expecting chunk name at offset ' . (ftell($fd) - 4) . ' but found nothing. Aborting RIFF parsing.'; break; } $chunksize = getid3_riff::EitherEndian2Int($ThisFileInfo, fread($fd, 4)); if ($chunksize == 0) { $ThisFileInfo['error'][] = 'Chunk size at offset ' . (ftell($fd) - 4) . ' is zero. Aborting RIFF parsing.'; break; } if ($chunksize % 2 != 0) { // all structures are packed on word boundaries $chunksize++; } switch ($chunkname) { case 'LIST': $listname = fread($fd, 4); switch ($listname) { case 'movi': case 'rec ': $RIFFchunk[$listname]['offset'] = ftell($fd) - 4; $RIFFchunk[$listname]['size'] = $chunksize; static $ParsedAudioStream = false; if ($ParsedAudioStream) { // skip over } else { $WhereWeWere = ftell($fd); $AudioChunkHeader = fread($fd, 12); $AudioChunkStreamNum = substr($AudioChunkHeader, 0, 2); $AudioChunkStreamType = substr($AudioChunkHeader, 2, 2); $AudioChunkSize = getid3_lib::LittleEndian2Int(substr($AudioChunkHeader, 4, 4)); if ($AudioChunkStreamType == 'wb') { $FirstFourBytes = substr($AudioChunkHeader, 8, 4); if (preg_match('/^\\xFF[\\xE2-\\xE7\\xF2-\\xF7\\xFA-\\xFF][\\x00-\\xEB]/s', $FirstFourBytes)) { // MP3 if (getid3_mp3::MPEGaudioHeaderBytesValid($FirstFourBytes)) { $dummy = $ThisFileInfo; $dummy['avdataoffset'] = ftell($fd) - 4; $dummy['avdataend'] = ftell($fd) + $AudioChunkSize; getid3_mp3::getOnlyMPEGaudioInfo($fd, $dummy, $dummy['avdataoffset'], false); if (isset($dummy['mpeg']['audio'])) { $ThisFileInfo = $dummy; $ThisFileInfo['audio']['dataformat'] = 'mp' . $ThisFileInfo['mpeg']['audio']['layer']; $ThisFileInfo['audio']['sample_rate'] = $ThisFileInfo['mpeg']['audio']['sample_rate']; $ThisFileInfo['audio']['channels'] = $ThisFileInfo['mpeg']['audio']['channels']; $ThisFileInfo['audio']['bitrate'] = $ThisFileInfo['mpeg']['audio']['bitrate']; $ThisFileInfo['bitrate'] = $ThisFileInfo['audio']['bitrate']; $ThisFileInfo['audio']['bitrate_mode'] = strtolower($ThisFileInfo['mpeg']['audio']['bitrate_mode']); } } } elseif (preg_match('/^\\x0B\\x77/s', $FirstFourBytes)) { // AC3 $GETID3_ERRORARRAY =& $ThisFileInfo['warning']; if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.audio.ac3.php', __FILE__, false)) { $dummy = $ThisFileInfo; $dummy['avdataoffset'] = ftell($fd) - 4; $dummy['avdataend'] = ftell($fd) + $AudioChunkSize; $dummy['error'] = array(); $ac3_tag = new getid3_ac3($fd, $dummy); if (empty($dummy['error'])) { $ThisFileInfo['audio'] = $dummy['audio']; $ThisFileInfo['ac3'] = $dummy['ac3']; $ThisFileInfo['warning'] = $dummy['warning']; } } } } $ParsedAudioStream = true; fseek($fd, $WhereWeWere, SEEK_SET); } fseek($fd, $chunksize - 4, SEEK_CUR); break; default: if (!isset($RIFFchunk[$listname])) { $RIFFchunk[$listname] = array(); } $LISTchunkParent = $listname; $LISTchunkMaxOffset = ftell($fd) - 4 + $chunksize; if ($parsedChunk = getid3_riff::ParseRIFF($fd, ftell($fd), ftell($fd) + $chunksize - 4, $ThisFileInfo)) { $RIFFchunk[$listname] = array_merge_recursive($RIFFchunk[$listname], $parsedChunk); } break; } break; default: $thisindex = 0; if (isset($RIFFchunk[$chunkname]) && is_array($RIFFchunk[$chunkname])) { $thisindex = count($RIFFchunk[$chunkname]); } $RIFFchunk[$chunkname][$thisindex]['offset'] = ftell($fd) - 8; $RIFFchunk[$chunkname][$thisindex]['size'] = $chunksize; switch ($chunkname) { case 'data': $ThisFileInfo['avdataoffset'] = ftell($fd); $ThisFileInfo['avdataend'] = $ThisFileInfo['avdataoffset'] + $chunksize; $RIFFdataChunkContentsTest = fread($fd, 36); if (strlen($RIFFdataChunkContentsTest) > 0 && preg_match('/^\\xFF[\\xE2-\\xE7\\xF2-\\xF7\\xFA-\\xFF][\\x00-\\xEB]/s', substr($RIFFdataChunkContentsTest, 0, 4))) { // Probably is MP3 data if (getid3_mp3::MPEGaudioHeaderBytesValid(substr($RIFFdataChunkContentsTest, 0, 4))) { getid3_mp3::getOnlyMPEGaudioInfo($fd, $ThisFileInfo, $RIFFchunk[$chunkname][$thisindex]['offset'], false); } } elseif (strlen($RIFFdataChunkContentsTest) > 0 && substr($RIFFdataChunkContentsTest, 0, 2) == "\vw") { // This is probably AC-3 data $GETID3_ERRORARRAY =& $ThisFileInfo['warning']; if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.audio.ac3.php', __FILE__, false)) { $dummy = $ThisFileInfo; $dummy['avdataoffset'] = $RIFFchunk[$chunkname][$thisindex]['offset']; $dummy['avdataend'] = $dummy['avdataoffset'] + $RIFFchunk[$chunkname][$thisindex]['size']; $dummy['error'] = array(); $ac3_tag = new getid3_ac3($fd, $dummy); if (empty($dummy['error'])) { $ThisFileInfo['audio'] = $dummy['audio']; $ThisFileInfo['ac3'] = $dummy['ac3']; $ThisFileInfo['warning'] = $dummy['warning']; } } } elseif (strlen($RIFFdataChunkContentsTest) > 0 && substr($RIFFdataChunkContentsTest, 8, 2) == "w\v") { // Dolby Digital WAV // AC-3 content, but not encoded in same format as normal AC-3 file // For one thing, byte order is swapped $GETID3_ERRORARRAY =& $ThisFileInfo['warning']; if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.audio.ac3.php', __FILE__, false)) { // ok to use tmpfile here - only 56 bytes if ($fd_temp = tmpfile()) { for ($i = 0; $i < 28; $i += 2) { // swap byte order fwrite($fd_temp, substr($RIFFdataChunkContentsTest, 8 + $i + 1, 1)); fwrite($fd_temp, substr($RIFFdataChunkContentsTest, 8 + $i + 0, 1)); } $dummy = $ThisFileInfo; $dummy['avdataoffset'] = 0; $dummy['avdataend'] = 20; $dummy['error'] = array(); $ac3_tag = new getid3_ac3($fd_temp, $dummy); fclose($fd_temp); if (empty($dummy['error'])) { $ThisFileInfo['audio'] = $dummy['audio']; $ThisFileInfo['ac3'] = $dummy['ac3']; $ThisFileInfo['warning'] = $dummy['warning']; } else { $ThisFileInfo['error'][] = 'Errors parsing DolbyDigital WAV: ' . explode(';', $dummy['error']); } } else { $ThisFileInfo['error'][] = 'Could not create temporary file to analyze DolbyDigital WAV'; } } } elseif (strlen($RIFFdataChunkContentsTest) > 0 && substr($RIFFdataChunkContentsTest, 0, 4) == 'wvpk') { // This is WavPack data $ThisFileInfo['wavpack']['offset'] = $RIFFchunk[$chunkname][$thisindex]['offset']; $ThisFileInfo['wavpack']['size'] = getid3_lib::LittleEndian2Int(substr($RIFFdataChunkContentsTest, 4, 4)); getid3_riff::RIFFparseWavPackHeader(substr($RIFFdataChunkContentsTest, 8, 28), $ThisFileInfo); } else { // This is some other kind of data (quite possibly just PCM) // do nothing special, just skip it } fseek($fd, $RIFFchunk[$chunkname][$thisindex]['offset'] + 8 + $chunksize, SEEK_SET); break; case 'bext': case 'cart': case 'fmt ': case 'MEXT': case 'DISP': // always read data in $RIFFchunk[$chunkname][$thisindex]['data'] = fread($fd, $chunksize); break; default: if (!empty($LISTchunkParent) && $RIFFchunk[$chunkname][$thisindex]['offset'] + $RIFFchunk[$chunkname][$thisindex]['size'] <= $LISTchunkMaxOffset) { $RIFFchunk[$LISTchunkParent][$chunkname][$thisindex]['offset'] = $RIFFchunk[$chunkname][$thisindex]['offset']; $RIFFchunk[$LISTchunkParent][$chunkname][$thisindex]['size'] = $RIFFchunk[$chunkname][$thisindex]['size']; unset($RIFFchunk[$chunkname][$thisindex]['offset']); unset($RIFFchunk[$chunkname][$thisindex]['size']); if (isset($RIFFchunk[$chunkname][$thisindex]) && empty($RIFFchunk[$chunkname][$thisindex])) { unset($RIFFchunk[$chunkname][$thisindex]); } if (isset($RIFFchunk[$chunkname]) && empty($RIFFchunk[$chunkname])) { unset($RIFFchunk[$chunkname]); } $RIFFchunk[$LISTchunkParent][$chunkname][$thisindex]['data'] = fread($fd, $chunksize); } elseif ($chunksize < 2048) { // only read data in if smaller than 2kB $RIFFchunk[$chunkname][$thisindex]['data'] = fread($fd, $chunksize); } else { fseek($fd, $chunksize, SEEK_CUR); } break; } break; } } return $RIFFchunk; }
function Analyze() { $info =& $this->getid3->info; // http://www.matroska.org/technical/specs/index.html#EBMLBasics $offset = $info['avdataoffset']; $EBMLdata = ''; $EBMLdata_offset = $offset; if (!getid3_lib::intValueSupported($info['avdataend'])) { $this->warnings[] = 'This version of getID3() [' . $this->getid3->version() . '] may or may not correctly handle Matroska files larger than ' . round(PHP_INT_MAX / 1073741824) . 'GB'; } while ($offset < $info['avdataend']) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $top_element_offset = $offset; $top_element_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $top_element_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); if ($top_element_length === false) { $this->warnings[] = 'invalid chunk length at ' . $top_element_offset; $offset = PHP_INT_MAX + 1; break; } $top_element_endoffset = $offset + $top_element_length; switch ($top_element_id) { case EBML_ID_EBML: $info['fileformat'] = 'matroska'; $info['matroska']['header']['offset'] = $top_element_offset; $info['matroska']['header']['length'] = $top_element_length; while ($offset < $top_element_endoffset) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $element_data = array(); $element_data_offset = $offset; $element_data['id'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $element_data['id_name'] = $this->EBMLidName($element_data['id']); $element_data['length'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $end_offset = $offset + $element_data['length']; switch ($element_data['id']) { case EBML_ID_VOID: // padding, ignore break; case EBML_ID_EBMLVERSION: case EBML_ID_EBMLREADVERSION: case EBML_ID_EBMLMAXIDLENGTH: case EBML_ID_EBMLMAXSIZELENGTH: case EBML_ID_DOCTYPEVERSION: case EBML_ID_DOCTYPEREADVERSION: $element_data['data'] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $element_data['length'])); break; case EBML_ID_DOCTYPE: $element_data['data'] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $element_data['length']), ""); break; case EBML_ID_CRC32: // probably not useful, ignore unset($element_data); break; default: $this->warnings[] = 'Unhandled track.video element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $element_data['id'] . '::' . $element_data['id_name'] . ') at ' . $element_data_offset; break; } $offset = $end_offset; if (!empty($element_data)) { $info['matroska']['header']['elements'][] = $element_data; } } break; case EBML_ID_SEGMENT: $info['matroska']['segment'][0]['offset'] = $top_element_offset; $info['matroska']['segment'][0]['length'] = $top_element_length; $segment_key = -1; while ($offset < $info['avdataend']) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $element_data = array(); $element_data['offset'] = $offset; $element_data['id'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $element_data['id_name'] = $this->EBMLidName($element_data['id']); $element_data['length'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); if ($element_data['length'] === false) { $this->warnings[] = 'invalid chunk length at ' . $element_data['offset']; //$offset = PHP_INT_MAX + 1; $offset = $info['avdataend']; break; } $element_end = $offset + $element_data['length']; switch ($element_data['id']) { //case EBML_ID_CLUSTER: // // too many cluster entries, probably not useful // break; case false: $this->warnings[] = 'invalid ID at ' . $element_data['offset']; $offset = $element_end; continue 3; default: $info['matroska']['segments'][] = $element_data; break; } $segment_key++; switch ($element_data['id']) { case EBML_ID_SEEKHEAD: // Contains the position of other level 1 elements while ($offset < $element_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $seek_entry = array(); $seek_entry['offset'] = $offset; $seek_entry['id'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $seek_entry['id_name'] = $this->EBMLidName($seek_entry['id']); $seek_entry['length'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $seek_end_offset = $offset + $seek_entry['length']; switch ($seek_entry['id']) { case EBML_ID_SEEK: // Contains a single seek entry to an EBML element while ($offset < $seek_end_offset) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $value = substr($EBMLdata, $offset - $EBMLdata_offset, $length); $offset += $length; switch ($id) { case EBML_ID_SEEKID: $dummy = 0; $seek_entry['target_id'] = $this->readEBMLint($value, $dummy); $seek_entry['target_name'] = $this->EBMLidName($seek_entry['target_id']); break; case EBML_ID_SEEKPOSITION: $seek_entry['target_offset'] = $element_data['offset'] + getid3_lib::BigEndian2Int($value); break; case EBML_ID_CRC32: // probably not useful, ignore //$seek_entry['crc32'] = getid3_lib::PrintHexBytes($value, true, false, false); unset($seek_entry); break; default: $info['error'][] = 'Unhandled segment [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $id . ') at ' . $offset; break; } } if (!empty($seek_entry)) { $info['matroska']['seek'][] = $seek_entry; } //switch ($seek_entry['target_id']) { // case EBML_ID_CLUSTER: // // too many cluster seek points, probably not useful // break; // default: // $info['matroska']['seek'][] = $seek_entry; // break; //} break; case EBML_ID_CRC32: // probably not useful, ignore break; default: $this->warnings[] = 'Unhandled seekhead element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $seek_entry['id'] . '::' . $seek_entry['id_name'] . ') at ' . $offset; break; } $offset = $seek_end_offset; } break; case EBML_ID_TRACKS: // information about all tracks in segment $info['matroska']['tracks'] = $element_data; while ($offset < $element_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $track_entry = array(); $track_entry['offset'] = $offset; $track_entry['id'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $track_entry['id_name'] = $this->EBMLidName($track_entry['id']); $track_entry['length'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $track_entry_endoffset = $offset + $track_entry['length']; // $track_entry['offset'] is not the same as $offset, even though they were set equal a few lines up: $offset has been automagically incremented by readEMLint() switch ($track_entry['id']) { case EBML_ID_TRACKENTRY: //subelements: Describes a track with all elements. while ($offset < $track_entry_endoffset) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $subelement_offset = $offset; $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_idname = $this->EBMLidName($subelement_id); $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_end = $offset + $subelement_length; switch ($subelement_id) { case EBML_ID_TRACKNUMBER: case EBML_ID_TRACKUID: case EBML_ID_TRACKTYPE: case EBML_ID_MINCACHE: case EBML_ID_MAXCACHE: case EBML_ID_MAXBLOCKADDITIONID: case EBML_ID_DEFAULTDURATION: // nanoseconds per frame $track_entry[$subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); break; case EBML_ID_TRACKTIMECODESCALE: $track_entry[$subelement_idname] = getid3_lib::BigEndian2Float(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); break; case EBML_ID_CODECID: case EBML_ID_LANGUAGE: case EBML_ID_NAME: case EBML_ID_CODECNAME: case EBML_ID_CODECPRIVATE: $track_entry[$subelement_idname] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length), ""); break; // thought maybe it was a nice wFormatTag entry, but it's not :( //case EBML_ID_CODECPRIVATE: //$track_entry[$subelement_idname] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length), "\x00"); //if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, false)) { // $track_entry[$subelement_idname.'_decoded'] = getid3_riff::RIFFparseWAVEFORMATex($track_entry[$subelement_idname]); // if (isset($track_entry[$subelement_idname.'_decoded']['raw']['wFormatTag'])) { // } //} else { // $this->warnings[] = 'failed to include "module.audio-video.riff.php" for parsing codec private data'; //} //break; // thought maybe it was a nice wFormatTag entry, but it's not :( //case EBML_ID_CODECPRIVATE: //$track_entry[$subelement_idname] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length), "\x00"); //if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, false)) { // $track_entry[$subelement_idname.'_decoded'] = getid3_riff::RIFFparseWAVEFORMATex($track_entry[$subelement_idname]); // if (isset($track_entry[$subelement_idname.'_decoded']['raw']['wFormatTag'])) { // } //} else { // $this->warnings[] = 'failed to include "module.audio-video.riff.php" for parsing codec private data'; //} //break; case EBML_ID_FLAGENABLED: case EBML_ID_FLAGDEFAULT: case EBML_ID_FLAGFORCED: case EBML_ID_FLAGLACING: case EBML_ID_CODECDECODEALL: $track_entry[$subelement_idname] = (bool) getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); break; case EBML_ID_VIDEO: while ($offset < $subelement_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_offset = $offset; $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_end = $offset + $sub_subelement_length; switch ($sub_subelement_id) { case EBML_ID_PIXELWIDTH: case EBML_ID_PIXELHEIGHT: case EBML_ID_STEREOMODE: case EBML_ID_PIXELCROPBOTTOM: case EBML_ID_PIXELCROPTOP: case EBML_ID_PIXELCROPLEFT: case EBML_ID_PIXELCROPRIGHT: case EBML_ID_DISPLAYWIDTH: case EBML_ID_DISPLAYHEIGHT: case EBML_ID_DISPLAYUNIT: case EBML_ID_ASPECTRATIOTYPE: $track_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; case EBML_ID_FLAGINTERLACED: $track_entry[$sub_subelement_idname] = (bool) getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; case EBML_ID_GAMMAVALUE: $track_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Float(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; case EBML_ID_COLOURSPACE: $track_entry[$sub_subelement_idname] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length), ""); break; default: $this->warnings[] = 'Unhandled track.video element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $sub_subelement_id . '::' . $sub_subelement_idname . ') at ' . $sub_subelement_offset; break; } $offset = $sub_subelement_end; } if (isset($track_entry[$this->EBMLidName(EBML_ID_CODECID)]) && $track_entry[$this->EBMLidName(EBML_ID_CODECID)] == 'V_MS/VFW/FOURCC' && isset($track_entry[$this->EBMLidName(EBML_ID_CODECPRIVATE)])) { if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.audio-video.riff.php', __FILE__, false)) { $track_entry['codec_private_parsed'] = getid3_riff::ParseBITMAPINFOHEADER($track_entry[$this->EBMLidName(EBML_ID_CODECPRIVATE)]); } else { $this->warnings[] = 'Unable to parse codec private data [' . basename(__FILE__) . ':' . __LINE__ . '] because cannot include "module.audio-video.riff.php"'; } } break; case EBML_ID_AUDIO: while ($offset < $subelement_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_offset = $offset; $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_end = $offset + $sub_subelement_length; switch ($sub_subelement_id) { case EBML_ID_CHANNELS: case EBML_ID_BITDEPTH: $track_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; case EBML_ID_SAMPLINGFREQUENCY: case EBML_ID_OUTPUTSAMPLINGFREQUENCY: $track_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Float(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; case EBML_ID_CHANNELPOSITIONS: $track_entry[$sub_subelement_idname] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length), ""); break; default: $this->warnings[] = 'Unhandled track.audio element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $sub_subelement_id . '::' . $sub_subelement_idname . ') at ' . $sub_subelement_offset; break; } $offset = $sub_subelement_end; } break; case EBML_ID_CONTENTENCODINGS: while ($offset < $subelement_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_offset = $offset; $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_end = $offset + $sub_subelement_length; switch ($sub_subelement_id) { case EBML_ID_CONTENTENCODING: while ($offset < $sub_subelement_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_offset = $offset; $sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); $sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_end = $offset + $sub_sub_subelement_length; switch ($sub_sub_subelement_id) { case EBML_ID_CONTENTENCODINGORDER: case EBML_ID_CONTENTENCODINGSCOPE: case EBML_ID_CONTENTENCODINGTYPE: $track_entry[$sub_subelement_idname][$sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length)); break; case EBML_ID_CONTENTCOMPRESSION: while ($offset < $sub_sub_subelement_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_offset = $offset; $sub_sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); $sub_sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_end = $offset + $sub_sub_sub_subelement_length; switch ($sub_sub_sub_subelement_id) { case EBML_ID_CONTENTCOMPALGO: $track_entry[$sub_subelement_idname][$sub_sub_subelement_idname][$sub_sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_sub_subelement_length)); break; case EBML_ID_CONTENTCOMPSETTINGS: $track_entry[$sub_subelement_idname][$sub_sub_subelement_idname][$sub_sub_sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_sub_subelement_length); break; default: $this->warnings[] = 'Unhandled track.contentencodings.contentencoding.contentcompression element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $subelement_id . '::' . $subelement_idname . ' [' . $subelement_length . ' bytes]) at ' . $subelement_offset; break; } $offset = $sub_sub_sub_subelement_end; } break; case EBML_ID_CONTENTENCRYPTION: while ($offset < $sub_sub_subelement_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_offset = $offset; $sub_sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); $sub_sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_end = $offset + $sub_sub_sub_subelement_length; switch ($sub_sub_sub_subelement_id) { case EBML_ID_CONTENTENCALGO: case EBML_ID_CONTENTSIGALGO: case EBML_ID_CONTENTSIGHASHALGO: $track_entry[$sub_subelement_idname][$sub_sub_subelement_idname][$sub_sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_sub_subelement_length)); break; case EBML_ID_CONTENTENCKEYID: case EBML_ID_CONTENTSIGNATURE: case EBML_ID_CONTENTSIGKEYID: $track_entry[$sub_subelement_idname][$sub_sub_subelement_idname][$sub_sub_sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_sub_subelement_length); break; default: $this->warnings[] = 'Unhandled track.contentencodings.contentencoding.contentcompression element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $subelement_id . '::' . $subelement_idname . ' [' . $subelement_length . ' bytes]) at ' . $subelement_offset; break; } $offset = $sub_sub_sub_subelement_end; } break; default: $this->warnings[] = 'Unhandled track.contentencodings.contentencoding element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $subelement_id . '::' . $subelement_idname . ' [' . $subelement_length . ' bytes]) at ' . $subelement_offset; break; } $offset = $sub_sub_subelement_end; } break; default: $this->warnings[] = 'Unhandled track.contentencodings element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $subelement_id . '::' . $subelement_idname . ' [' . $subelement_length . ' bytes]) at ' . $subelement_offset; break; } $offset = $sub_subelement_end; } break; case EBML_ID_CRC32: // probably not useful, ignore break; default: $this->warnings[] = 'Unhandled track element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $subelement_id . '::' . $subelement_idname . ' [' . $subelement_length . ' bytes]) at ' . $subelement_offset; break; } $offset = $subelement_end; } break; case EBML_ID_CRC32: // probably not useful, ignore $offset = $track_entry_endoffset; break; default: $this->warnings[] = 'Unhandled track element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $track_entry['id'] . '::' . $track_entry['id_name'] . ') at ' . $track_entry['offset']; $offset = $track_entry_endoffset; break; } $info['matroska']['tracks']['tracks'][] = $track_entry; } break; case EBML_ID_INFO: // Contains the position of other level 1 elements $info_entry = array(); while ($offset < $element_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $subelement_offset = $offset; $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_idname = $this->EBMLidName($subelement_id); $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_end = $offset + $subelement_length; switch ($subelement_id) { case EBML_ID_CHAPTERTRANSLATEEDITIONUID: case EBML_ID_CHAPTERTRANSLATECODEC: case EBML_ID_TIMECODESCALE: $info_entry[$subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); break; case EBML_ID_DURATION: $info_entry[$subelement_idname] = getid3_lib::BigEndian2Float(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); break; case EBML_ID_DATEUTC: $info_entry[$subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); $info_entry[$subelement_idname . '_unix'] = $this->EBMLdate2unix($info_entry[$subelement_idname]); break; case EBML_ID_SEGMENTUID: case EBML_ID_PREVUID: case EBML_ID_NEXTUID: case EBML_ID_SEGMENTFAMILY: case EBML_ID_CHAPTERTRANSLATEID: $info_entry[$subelement_idname] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length), ""); break; case EBML_ID_SEGMENTFILENAME: case EBML_ID_PREVFILENAME: case EBML_ID_NEXTFILENAME: case EBML_ID_TITLE: case EBML_ID_MUXINGAPP: case EBML_ID_WRITINGAPP: $info_entry[$subelement_idname] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length), ""); $info['matroska']['comments'][strtolower($subelement_idname)][] = $info_entry[$subelement_idname]; break; case EBML_ID_CRC32: // probably not useful, ignore break; default: $this->warnings[] = 'Unhandled info element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $subelement_id . '::' . $subelement_idname . ' [' . $subelement_length . ' bytes]) at ' . $subelement_offset; break; } $offset = $subelement_end; } $info['matroska']['info'][] = $info_entry; break; case EBML_ID_CUES: $cues_entry = array(); while ($offset < $element_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $subelement_offset = $offset; $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_idname = $this->EBMLidName($subelement_id); $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_end = $offset + $subelement_length; switch ($subelement_id) { case EBML_ID_CUEPOINT: $cuepoint_entry = array(); while ($offset < $subelement_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_offset = $offset; $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_end = $offset + $sub_subelement_length; switch ($sub_subelement_id) { case EBML_ID_CUETRACKPOSITIONS: while ($offset < $sub_subelement_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_offset = $offset; $sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); $sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_end = $offset + $sub_sub_subelement_length; switch ($sub_sub_subelement_id) { case EBML_ID_CUETRACK: $cuepoint_entry[$sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length)); break; default: $this->warnings[] = 'Unhandled cues.cuepoint.cuetrackpositions element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $sub_sub_subelement_id . '::' . $sub_sub_subelement_idname . ') at ' . $sub_sub_subelement_offset; break; } $offset = $sub_subelement_end; } break; case EBML_ID_CUETIME: $cuepoint_entry[$subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; default: $this->warnings[] = 'Unhandled cues.cuepoint element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $sub_subelement_id . '::' . $sub_subelement_idname . ') at ' . $sub_subelement_offset; break; } $offset = $sub_subelement_end; } $cues_entry[] = $cuepoint_entry; $offset = $sub_subelement_end; break; case EBML_ID_CRC32: // probably not useful, ignore break; default: $this->warnings[] = 'Unhandled cues element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $subelement_id . '::' . $subelement_idname . ' [' . $subelement_length . ' bytes]) at ' . $subelement_offset; break; } $offset = $subelement_end; } $info['matroska']['cues'] = $cues_entry; break; case EBML_ID_TAGS: $tags_entry = array(); while ($offset < $element_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $subelement_offset = $offset; $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_idname = $this->EBMLidName($subelement_id); $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_end = $offset + $subelement_length; $tag_entry = array(); switch ($subelement_id) { case EBML_ID_WRITINGAPP: $tag_entry[$subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length); break; case EBML_ID_TAG: while ($offset < $subelement_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_offset = $offset; $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_end = $offset + $sub_subelement_length; switch ($sub_subelement_id) { case EBML_ID_TARGETS: $targets_entry = array(); while ($offset < $sub_subelement_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_offset = $offset; $sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); $sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_end = $offset + $sub_sub_subelement_length; switch ($sub_sub_subelement_id) { case EBML_ID_TARGETTYPEVALUE: $targets_entry[$sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length)); $targets_entry[strtolower($sub_sub_subelement_idname) . '_long'] = $this->MatroskaTargetTypeValue($targets_entry[$sub_sub_subelement_idname]); break; case EBML_ID_EDITIONUID: case EBML_ID_CHAPTERUID: case EBML_ID_ATTACHMENTUID: case EBML_ID_TAGTRACKUID: case EBML_ID_TAGCHAPTERUID: $targets_entry[$sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length)); break; default: $this->warnings[] = 'Unhandled tag.targets element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $sub_sub_subelement_id . '::' . $sub_sub_subelement_idname . ') at ' . $sub_sub_subelement_offset; break; } $offset = $sub_sub_subelement_end; } $tag_entry[$sub_subelement_idname][] = $targets_entry; break; case EBML_ID_SIMPLETAG: //$tag_entry[$sub_subelement_idname][] = $simpletag_entry; $tag_entry[$sub_subelement_idname][] = $this->Handle_EMBL_ID_SIMPLETAG($offset, $sub_subelement_end); break; case EBML_ID_TARGETTYPE: $tag_entry[$sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length); break; case EBML_ID_TRACKUID: $tag_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; default: $this->warnings[] = 'Unhandled tags.tag element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $sub_subelement_id . '::' . $sub_subelement_idname . ') at ' . $sub_subelement_offset; break; } $offset = $sub_subelement_end; } $offset = $sub_subelement_end; break; case EBML_ID_CRC32: // probably not useful, ignore break; default: $this->warnings[] = 'Unhandled tags element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $subelement_id . '::' . $subelement_idname . ' [' . $subelement_length . ' bytes]) at ' . $subelement_offset; break; } $tags_entry['tags'][] = $tag_entry; $offset = $subelement_end; } $info['matroska']['tags'] = $tags_entry['tags']; break; case EBML_ID_ATTACHMENTS: while ($offset < $element_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $subelement_offset = $offset; $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_idname = $this->EBMLidName($subelement_id); $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_end = $offset + $subelement_length; switch ($subelement_id) { case EBML_ID_ATTACHEDFILE: $attachedfile_entry = array(); while ($offset < $subelement_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_offset = $offset; $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_end = $offset + $sub_subelement_length; switch ($sub_subelement_id) { case EBML_ID_FILEDESCRIPTION: case EBML_ID_FILENAME: case EBML_ID_FILEMIMETYPE: $attachedfile_entry[$sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length); break; case EBML_ID_FILEDATA: $attachedfile_entry['data_offset'] = $offset; $attachedfile_entry['data_length'] = $sub_subelement_length; do { if ($this->inline_attachments === false) { // skip entirely break; } if ($this->inline_attachments === true) { // great } elseif (is_int($this->inline_attachments)) { if ($this->inline_attachments < $sub_subelement_length) { // too big, skip $this->warnings[] = 'attachment at ' . $sub_subelement_offset . ' is too large to process inline (' . number_format($sub_subelement_length) . ' bytes)'; break; } } elseif (is_string($this->inline_attachments)) { $this->inline_attachments = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->inline_attachments), DIRECTORY_SEPARATOR); if (!is_dir($this->inline_attachments) || !is_writable($this->inline_attachments)) { // cannot write, skip $this->warnings[] = 'attachment at ' . $sub_subelement_offset . ' cannot be saved to "' . $this->inline_attachments . '" (not writable)'; break; } } // if we get this far, must be OK $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset, $sub_subelement_length); $attachedfile_entry[$sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length); if (is_string($this->inline_attachments)) { $destination_filename = $this->inline_attachments . DIRECTORY_SEPARATOR . md5($info['filenamepath']) . '_' . $attachedfile_entry['data_offset']; if (!file_exists($destination_filename) || is_writable($destination_filename)) { file_put_contents($destination_filename, $attachedfile_entry[$sub_subelement_idname]); } else { $this->warnings[] = 'attachment at ' . $sub_subelement_offset . ' cannot be saved to "' . $destination_filename . '" (not writable)'; } $attachedfile_entry[$sub_subelement_idname . '_filename'] = $destination_filename; unset($attachedfile_entry[$sub_subelement_idname]); } } while (false); break; case EBML_ID_FILEUID: $attachedfile_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; default: $this->warnings[] = 'Unhandled attachment.attachedfile element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $sub_subelement_id . '::' . $sub_subelement_idname . ') at ' . $sub_subelement_offset; break; } $offset = $sub_subelement_end; } if (!empty($attachedfile_entry[$this->EBMLidName(EBML_ID_FILEDATA)]) && !empty($attachedfile_entry[$this->EBMLidName(EBML_ID_FILEMIMETYPE)]) && preg_match('#^image/#i', $attachedfile_entry[$this->EBMLidName(EBML_ID_FILEMIMETYPE)])) { if ($this->inline_attachments === true || is_int($this->inline_attachments) && $this->inline_attachments >= strlen($attachedfile_entry[$this->EBMLidName(EBML_ID_FILEDATA)])) { $attachedfile_entry['data'] = $attachedfile_entry[$this->EBMLidName(EBML_ID_FILEDATA)]; $attachedfile_entry['image_mime'] = $attachedfile_entry[$this->EBMLidName(EBML_ID_FILEMIMETYPE)]; $info['matroska']['comments']['picture'][] = array('data' => $attachedfile_entry['data'], 'image_mime' => $attachedfile_entry['image_mime'], 'filename' => !empty($attachedfile_entry[$this->EBMLidName(EBML_ID_FILENAME)]) ? $attachedfile_entry[$this->EBMLidName(EBML_ID_FILENAME)] : ''); unset($attachedfile_entry[$this->EBMLidName(EBML_ID_FILEDATA)], $attachedfile_entry[$this->EBMLidName(EBML_ID_FILEMIMETYPE)]); } } if (!empty($attachedfile_entry['image_mime']) && preg_match('#^image/#i', $attachedfile_entry['image_mime'])) { // don't add a second copy of attached images, which are grouped under the standard location [comments][picture] } else { $info['matroska']['attachments'][] = $attachedfile_entry; } $offset = $sub_subelement_end; break; case EBML_ID_CRC32: // probably not useful, ignore break; default: $this->warnings[] = 'Unhandled tags element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $subelement_id . '::' . $subelement_idname . ' [' . $subelement_length . ' bytes]) at ' . $subelement_offset; break; } $offset = $subelement_end; } break; case EBML_ID_CHAPTERS: // not important to us, contains mostly actual audio/video data, ignore while ($offset < $element_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $subelement_offset = $offset; $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_idname = $this->EBMLidName($subelement_id); $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_end = $offset + $subelement_length; switch ($subelement_id) { case EBML_ID_EDITIONENTRY: $editionentry_entry = array(); while ($offset < $subelement_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_offset = $offset; $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_end = $offset + $sub_subelement_length; switch ($sub_subelement_id) { case EBML_ID_EDITIONUID: $editionentry_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; case EBML_ID_EDITIONFLAGHIDDEN: case EBML_ID_EDITIONFLAGDEFAULT: case EBML_ID_EDITIONFLAGORDERED: $editionentry_entry[$sub_subelement_idname] = (bool) getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; case EBML_ID_CHAPTERATOM: $chapteratom_entry = array(); while ($offset < $sub_subelement_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_offset = $offset; $sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); $sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_end = $offset + $sub_sub_subelement_length; switch ($sub_sub_subelement_id) { case EBML_ID_CHAPTERSEGMENTUID: case EBML_ID_CHAPTERSEGMENTEDITIONUID: $chapteratom_entry[$sub_sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length); break; case EBML_ID_CHAPTERFLAGENABLED: case EBML_ID_CHAPTERFLAGHIDDEN: $chapteratom_entry[$sub_sub_subelement_idname] = (bool) getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length)); break; case EBML_ID_CHAPTERUID: case EBML_ID_CHAPTERTIMESTART: case EBML_ID_CHAPTERTIMEEND: $chapteratom_entry[$sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length)); break; case EBML_ID_CHAPTERTRACK: $chaptertrack_entry = array(); while ($offset < $sub_sub_subelement_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_offset = $offset; $sub_sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); $sub_sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_end = $offset + $sub_sub_sub_subelement_length; switch ($sub_sub_sub_subelement_id) { case EBML_ID_CHAPTERTRACKNUMBER: $chaptertrack_entry[$sub_sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_sub_subelement_length)); break; default: $this->warnings[] = 'Unhandled chapters.editionentry.chapteratom.chaptertrack element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $sub_sub_sub_subelement_id . '::' . $sub_sub_sub_subelement_idname . ') at ' . $sub_sub_sub_subelement_offset; break; } $offset = $sub_sub_sub_subelement_end; } $chapteratom_entry[$sub_sub_subelement_idname][] = $chaptertrack_entry; break; case EBML_ID_CHAPTERDISPLAY: $chapterdisplay_entry = array(); while ($offset < $sub_sub_subelement_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_offset = $offset; $sub_sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_idname = $this->EBMLidName($sub_sub_sub_subelement_id); $sub_sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_end = $offset + $sub_sub_sub_subelement_length; switch ($sub_sub_sub_subelement_id) { case EBML_ID_CHAPSTRING: case EBML_ID_CHAPLANGUAGE: case EBML_ID_CHAPCOUNTRY: $chapterdisplay_entry[$sub_sub_sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_sub_subelement_length); break; default: $this->warnings[] = 'Unhandled chapters.editionentry.chapteratom.chapterdisplay element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $sub_sub_sub_subelement_id . '::' . $sub_sub_sub_subelement_idname . ') at ' . $sub_sub_sub_subelement_offset; break; } $offset = $sub_sub_sub_subelement_end; } $chapteratom_entry[$sub_sub_subelement_idname][] = $chapterdisplay_entry; break; default: $this->warnings[] = 'Unhandled chapters.editionentry.chapteratom element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $sub_sub_subelement_id . '::' . $sub_sub_subelement_idname . ') at ' . $sub_sub_subelement_offset; break; } $offset = $sub_sub_subelement_end; } $editionentry_entry[$sub_subelement_idname][] = $chapteratom_entry; break; default: $this->warnings[] = 'Unhandled chapters.editionentry element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $sub_subelement_id . '::' . $sub_subelement_idname . ') at ' . $sub_subelement_offset; break; } $offset = $sub_subelement_end; } $info['matroska']['chapters'][] = $editionentry_entry; $offset = $sub_subelement_end; break; default: $this->warnings[] = 'Unhandled chapters element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $subelement_id . '::' . $subelement_idname . ' [' . $subelement_length . ' bytes]) at ' . $subelement_offset; break; } $offset = $subelement_end; } break; case EBML_ID_VOID: // padding, ignore $void_entry = array(); $void_entry['offset'] = $offset; $info['matroska']['void'][] = $void_entry; break; case EBML_ID_CLUSTER: // not important to us, contains mostly actual audio/video data, ignore $cluster_entry = array(); while ($offset < $element_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $subelement_offset = $offset; $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_idname = $this->EBMLidName($subelement_id); $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_end = $offset + $subelement_length; switch ($subelement_id) { case EBML_ID_CLUSTERTIMECODE: case EBML_ID_CLUSTERPOSITION: case EBML_ID_CLUSTERPREVSIZE: $cluster_entry[$subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); break; case EBML_ID_CLUSTERSILENTTRACKS: $cluster_silent_tracks = array(); while ($offset < $subelement_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_offset = $offset; $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_end = $offset + $sub_subelement_length; switch ($sub_subelement_id) { case EBML_ID_CLUSTERSILENTTRACKNUMBER: $cluster_silent_tracks[] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; default: $this->warnings[] = 'Unhandled clusters.silenttracks element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $sub_subelement_id . '::' . $sub_subelement_idname . ') at ' . $sub_subelement_offset; break; } $offset = $sub_subelement_end; } $cluster_entry[$subelement_idname][] = $cluster_silent_tracks; $offset = $sub_subelement_end; break; case EBML_ID_CLUSTERBLOCKGROUP: $cluster_block_group = array('offset' => $offset); while ($offset < $subelement_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_offset = $offset; $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_end = $offset + $sub_subelement_length; switch ($sub_subelement_id) { case EBML_ID_CLUSTERBLOCK: $cluster_block_data = array(); $cluster_block_data['tracknumber'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $cluster_block_data['timecode'] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset, 2)); $offset += 2; // unsure whether this is 1 octect or 2 octets? (http://matroska.org/technical/specs/index.html#block_structure) $cluster_block_data['flags_raw'] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset, 1)); $offset += 1; //$cluster_block_data['flags']['reserved1'] = (($cluster_block_data['flags_raw'] & 0xF0) >> 4); $cluster_block_data['flags']['invisible'] = (bool) (($cluster_block_data['flags_raw'] & 0x8) >> 3); $cluster_block_data['flags']['lacing'] = ($cluster_block_data['flags_raw'] & 0x6) >> 1; //$cluster_block_data['flags']['reserved2'] = (($cluster_block_data['flags_raw'] & 0x01) >> 0); $cluster_block_data['flags']['lacing_type'] = $this->MatroskaBlockLacingType($cluster_block_data['flags']['lacing']); if ($cluster_block_data['flags']['lacing'] != 0) { $cluster_block_data['lace_frames'] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset, 1)); // Number of frames in the lace-1 (uint8) $offset += 1; if ($cluster_block_data['flags']['lacing'] != 2) { $cluster_block_data['lace_frames'] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset, 1)); // Lace-coded size of each frame of the lace, except for the last one (multiple uint8). *This is not used with Fixed-size lacing as it is calculated automatically from (total size of lace) / (number of frames in lace). $offset += 1; } } if (!isset($info['matroska']['track_data_offsets'][$cluster_block_data['tracknumber']])) { $info['matroska']['track_data_offsets'][$cluster_block_data['tracknumber']]['offset'] = $offset; $info['matroska']['track_data_offsets'][$cluster_block_data['tracknumber']]['length'] = $subelement_length; } $cluster_block_group[$sub_subelement_idname] = $cluster_block_data; break; case EBML_ID_CLUSTERREFERENCEPRIORITY: // unsigned-int // unsigned-int case EBML_ID_CLUSTERBLOCKDURATION: // unsigned-int $cluster_block_group[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; case EBML_ID_CLUSTERREFERENCEBLOCK: // signed-int $cluster_block_group[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length), false, true); break; default: $this->warnings[] = 'Unhandled clusters.blockgroup element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $sub_subelement_id . '::' . $sub_subelement_idname . ') at ' . $sub_subelement_offset; break; } $offset = $sub_subelement_end; } $cluster_entry[$subelement_idname][] = $cluster_block_group; $offset = $sub_subelement_end; break; case EBML_ID_CLUSTERSIMPLEBLOCK: // http://www.matroska.org/technical/specs/index.html#simpleblock_structure $cluster_block_data = array(); $cluster_block_data['tracknumber'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $cluster_block_data['timecode'] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset, 2)); $offset += 2; $cluster_block_data['flags_raw'] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset, 1)); $offset += 1; $cluster_block_data['flags']['keyframe'] = ($cluster_block_data['flags_raw'] & 0x80) >> 7; $cluster_block_data['flags']['reserved1'] = ($cluster_block_data['flags_raw'] & 0x70) >> 4; $cluster_block_data['flags']['invisible'] = ($cluster_block_data['flags_raw'] & 0x8) >> 3; $cluster_block_data['flags']['lacing'] = ($cluster_block_data['flags_raw'] & 0x6) >> 1; // 00=no lacing; 01=Xiph lacing; 11=EBML lacing; 10=fixed-size lacing $cluster_block_data['flags']['discardable'] = $cluster_block_data['flags_raw'] & 0x1; if ($cluster_block_data['flags']['lacing'] > 0) { $cluster_block_data['lace_frames'] = 1 + getid3_lib::BigEndian2Int(substr($EBMLdata, $offset, 1)); $offset += 1; if ($cluster_block_data['flags']['lacing'] != 0x2) { // *This is not used with Fixed-size lacing as it is calculated automatically from (total size of lace) / (number of frames in lace). $cluster_block_data['lace_frame_size'] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset, 1)); $offset += 1; } } if (!isset($info['matroska']['track_data_offsets'][$cluster_block_data['tracknumber']])) { $info['matroska']['track_data_offsets'][$cluster_block_data['tracknumber']]['offset'] = $offset; $info['matroska']['track_data_offsets'][$cluster_block_data['tracknumber']]['length'] = $subelement_length; } $cluster_block_group[$sub_subelement_idname] = $cluster_block_data; break; default: $this->warnings[] = 'Unhandled cluster element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $subelement_id . '::' . $subelement_idname . ' [' . $subelement_length . ' bytes]) at ' . $subelement_offset; break; } $offset = $subelement_end; } $info['matroska']['cluster'][] = $cluster_entry; // check to see if all the data we need exists already, if so, break out of the loop if (isset($info['matroska']['info']) && is_array($info['matroska']['info'])) { if (isset($info['matroska']['tracks']['tracks']) && is_array($info['matroska']['tracks']['tracks'])) { break 2; } } break; default: if ($element_data['id_name'] == dechex($element_data['id'])) { $info['error'][] = 'Unhandled segment [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $element_data['id'] . ') at ' . $element_data_offset; } else { $this->warnings[] = 'Unhandled segment [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $element_data['id'] . '::' . $element_data['id_name'] . ') at ' . $element_data['offset']; } break; } $offset = $element_end; } break; default: $info['error'][] = 'Unhandled chunk [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $top_element_id . ') at ' . $offset; break; } $offset = $top_element_endoffset; } if (isset($info['matroska']['info']) && is_array($info['matroska']['info'])) { foreach ($info['matroska']['info'] as $key => $infoarray) { if (isset($infoarray['Duration'])) { // TimecodeScale is how many nanoseconds each Duration unit is $info['playtime_seconds'] = $infoarray['Duration'] * ((isset($infoarray['TimecodeScale']) ? $infoarray['TimecodeScale'] : 1000000) / 1000000000); break; } } } if (isset($info['matroska']['tags']) && is_array($info['matroska']['tags'])) { foreach ($info['matroska']['tags'] as $key => $infoarray) { $this->ExtractCommentsSimpleTag($infoarray); } } if (isset($info['matroska']['tracks']['tracks']) && is_array($info['matroska']['tracks']['tracks'])) { foreach ($info['matroska']['tracks']['tracks'] as $key => $trackarray) { $track_info = array(); if (isset($trackarray['FlagDefault'])) { $track_info['default'] = $trackarray['FlagDefault']; } switch (isset($trackarray['TrackType']) ? $trackarray['TrackType'] : '') { case 1: // Video if (!empty($trackarray['PixelWidth'])) { $track_info['resolution_x'] = $trackarray['PixelWidth']; } if (!empty($trackarray['PixelHeight'])) { $track_info['resolution_y'] = $trackarray['PixelHeight']; } if (!empty($trackarray['DisplayWidth'])) { $track_info['display_x'] = $trackarray['DisplayWidth']; } if (!empty($trackarray['DisplayHeight'])) { $track_info['display_y'] = $trackarray['DisplayHeight']; } if (!empty($trackarray['DefaultDuration'])) { $track_info['frame_rate'] = round(1000000000 / $trackarray['DefaultDuration'], 3); } if (!empty($trackarray['CodecID'])) { $track_info['dataformat'] = $this->MatroskaCodecIDtoCommonName($trackarray['CodecID']); } if (!empty($trackarray['codec_private_parsed']['fourcc'])) { $track_info['fourcc'] = $trackarray['codec_private_parsed']['fourcc']; } $info['video']['streams'][] = $track_info; if (isset($track_info['resolution_x']) && empty($info['video']['resolution_x'])) { foreach ($track_info as $key => $value) { $info['video'][$key] = $value; } } break; case 2: // Audio if (!empty($trackarray['CodecID'])) { $track_info['dataformat'] = $this->MatroskaCodecIDtoCommonName($trackarray['CodecID']); } if (!empty($trackarray['SamplingFrequency'])) { $track_info['sample_rate'] = $trackarray['SamplingFrequency']; } if (!empty($trackarray['Channels'])) { $track_info['channels'] = $trackarray['Channels']; } if (!empty($trackarray['BitDepth'])) { $track_info['bits_per_sample'] = $trackarray['BitDepth']; } if (!empty($trackarray['Language'])) { $track_info['language'] = $trackarray['Language']; } switch (isset($trackarray[$this->EBMLidName(EBML_ID_CODECID)]) ? $trackarray[$this->EBMLidName(EBML_ID_CODECID)] : '') { case 'A_PCM/INT/LIT': case 'A_PCM/INT/BIG': $track_info['bitrate'] = $trackarray['SamplingFrequency'] * $trackarray['Channels'] * $trackarray['BitDepth']; break; case 'A_AC3': if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.audio.ac3.php', __FILE__, false)) { if (isset($info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['offset'])) { $getid3_temp = new getID3(); $getid3_temp->openfile($this->getid3->filename); $getid3_temp->info['avdataoffset'] = $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['offset']; $getid3_ac3 = new getid3_ac3($getid3_temp); $getid3_ac3->Analyze(); unset($getid3_temp->info['ac3']['GETID3_VERSION']); $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $getid3_temp->info['ac3']; if (!empty($getid3_temp->info['error'])) { foreach ($getid3_temp->info['error'] as $newerror) { $this->warnings[] = 'getid3_ac3() says: [' . $newerror . ']'; } } if (!empty($getid3_temp->info['warning'])) { foreach ($getid3_temp->info['warning'] as $newerror) { $this->warnings[] = 'getid3_ac3() says: [' . $newerror . ']'; } } if (isset($getid3_temp->info['audio']) && is_array($getid3_temp->info['audio'])) { foreach ($getid3_temp->info['audio'] as $key => $value) { $track_info[$key] = $value; } } unset($getid3_temp, $getid3_ac3); } else { $this->warnings[] = 'Unable to parse audio data [' . basename(__FILE__) . ':' . __LINE__ . '] because $info[matroska][track_data_offsets][' . $trackarray['TrackNumber'] . '][offset] not set'; } } else { $this->warnings[] = 'Unable to parse audio data [' . basename(__FILE__) . ':' . __LINE__ . '] because cannot include "module.audio.ac3.php"'; } break; case 'A_DTS': $dts_offset = $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['offset']; // this is a NASTY hack, but sometimes audio data is off by a byte or two and not sure why, email info@getid3.org if you can explain better fseek($this->getid3->fp, $dts_offset, SEEK_SET); $magic_test = fread($this->getid3->fp, 8); for ($i = 0; $i < 4; $i++) { // look to see if DTS "magic" is here, if so adjust offset by that many bytes if (substr($magic_test, $i, 4) == "þ€") { $dts_offset += $i; break; } } if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.audio.dts.php', __FILE__, false)) { $getid3_temp = new getID3(); $getid3_temp->openfile($this->getid3->filename); $getid3_temp->info['avdataoffset'] = $dts_offset; $getid3_dts = new getid3_dts($getid3_temp); $getid3_dts->Analyze(); unset($getid3_temp->info['dts']['GETID3_VERSION']); $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $getid3_temp->info['dts']; if (!empty($getid3_temp->info['error'])) { foreach ($getid3_temp->info['error'] as $newerror) { $this->warnings[] = 'getid3_dts() says: [' . $newerror . ']'; } } if (!empty($getid3_temp->info['warning'])) { foreach ($getid3_temp->info['warning'] as $newerror) { $this->warnings[] = 'getid3_dts() says: [' . $newerror . ']'; } } if (isset($getid3_temp->info['audio']) && is_array($getid3_temp->info['audio'])) { foreach ($getid3_temp->info['audio'] as $key => $value) { $track_info[$key] = $value; } } unset($getid3_temp, $getid3_dts); } else { $this->warnings[] = 'Unable to parse audio data [' . basename(__FILE__) . ':' . __LINE__ . '] because cannot include "module.audio.dts.php"'; } break; case 'A_AAC': $this->warnings[] = 'This version of getID3() [v' . $this->getid3->version() . '] has problems parsing AAC audio in Matroska containers [' . basename(__FILE__) . ':' . __LINE__ . ']'; if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.audio.aac.php', __FILE__, false)) { $getid3_temp = new getID3(); $getid3_temp->openfile($this->getid3->filename); $getid3_temp->info['avdataoffset'] = $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['offset']; $getid3_aac = new getid3_aac($getid3_temp); $getid3_aac->Analyze(); unset($getid3_temp->info['aac']['GETID3_VERSION']); if (!empty($getid3_temp->info['audio']['dataformat'])) { $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $getid3_temp->info['aac']; if (isset($getid3_temp->info['audio']) && is_array($getid3_temp->info['audio'])) { foreach ($getid3_temp->info['audio'] as $key => $value) { $track_info[$key] = $value; } } } else { $this->warnings[] = 'Failed to parse ' . $trackarray[$this->EBMLidName(EBML_ID_CODECID)] . ' audio data [' . basename(__FILE__) . ':' . __LINE__ . ']'; } unset($getid3_temp, $getid3_aac); } else { $this->warnings[] = 'Unable to parse audio data [' . basename(__FILE__) . ':' . __LINE__ . '] because cannot include "module.audio.aac.php"'; } break; case 'A_MPEG/L3': if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.audio.mp3.php', __FILE__, false)) { $getid3_temp = new getID3(); $getid3_temp->openfile($this->getid3->filename); $getid3_temp->info['avdataoffset'] = $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['offset']; $getid3_temp->info['avdataend'] = $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['offset'] + $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['length']; $getid3_mp3 = new getid3_mp3($getid3_temp); $getid3_mp3->allow_bruteforce = true; $getid3_mp3->Analyze(); if (!empty($getid3_temp->info['mpeg'])) { unset($getid3_temp->info['mpeg']['GETID3_VERSION']); $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $getid3_temp->info['mpeg']; if (!empty($getid3_temp->info['error'])) { foreach ($getid3_temp->info['error'] as $newerror) { $this->warnings[] = 'getid3_mp3() says: [' . $newerror . ']'; } } if (!empty($getid3_temp->info['warning'])) { foreach ($getid3_temp->info['warning'] as $newerror) { $this->warnings[] = 'getid3_mp3() says: [' . $newerror . ']'; } } if (isset($getid3_temp->info['audio']) && is_array($getid3_temp->info['audio'])) { foreach ($getid3_temp->info['audio'] as $key => $value) { $track_info[$key] = $value; } } } else { $this->warnings[] = 'Unable to parse audio data [' . basename(__FILE__) . ':' . __LINE__ . '] because getid3_mp3::Analyze failed at offset ' . $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['offset']; } unset($getid3_temp, $getid3_mp3); } else { $this->warnings[] = 'Unable to parse audio data [' . basename(__FILE__) . ':' . __LINE__ . '] because cannot include "module.audio.mp3.php"'; } break; case 'A_VORBIS': if (isset($trackarray['CodecPrivate'])) { // this is a NASTY hack, email info@getid3.org if you have a better idea how to get this info out $found_vorbis = false; for ($vorbis_offset = 1; $vorbis_offset < 16; $vorbis_offset++) { if (substr($trackarray['CodecPrivate'], $vorbis_offset, 6) == 'vorbis') { $vorbis_offset--; $found_vorbis = true; break; } } if ($found_vorbis) { if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.audio.ogg.php', __FILE__, false)) { $oggpageinfo['page_seqno'] = 0; $getid3_temp = new getID3(); $getid3_temp->openfile($this->getid3->filename); $getid3_ogg = new getid3_ogg($getid3_temp); $getid3_ogg->ParseVorbisPageHeader($trackarray['CodecPrivate'], $vorbis_offset, $oggpageinfo); $vorbis_fileinfo = $getid3_temp->info; unset($getid3_temp, $getid3_ogg); if (isset($vorbis_fileinfo['audio'])) { $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']]['audio'] = $vorbis_fileinfo['audio']; } if (isset($vorbis_fileinfo['ogg'])) { $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']]['ogg'] = $vorbis_fileinfo['ogg']; } if (!empty($vorbis_fileinfo['error'])) { foreach ($vorbis_fileinfo['error'] as $newerror) { $this->warnings[] = 'getid3_ogg() says: [' . $newerror . ']'; } } if (!empty($vorbis_fileinfo['warning'])) { foreach ($vorbis_fileinfo['warning'] as $newerror) { $this->warnings[] = 'getid3_ogg() says: [' . $newerror . ']'; } } if (isset($vorbis_fileinfo['audio']) && is_array($vorbis_fileinfo['audio'])) { foreach ($vorbis_fileinfo['audio'] as $key => $value) { $track_info[$key] = $value; } } if (!empty($vorbis_fileinfo['ogg']['bitrate_average'])) { $track_info['bitrate'] = $vorbis_fileinfo['ogg']['bitrate_average']; } elseif (!empty($vorbis_fileinfo['ogg']['bitrate_nominal'])) { $track_info['bitrate'] = $vorbis_fileinfo['ogg']['bitrate_nominal']; } unset($vorbis_fileinfo); unset($oggpageinfo); } else { $this->warnings[] = 'Unable to parse audio data [' . basename(__FILE__) . ':' . __LINE__ . '] because cannot include "module.audio.ogg.php"'; } } else { } } else { } break; default: $this->warnings[] = 'Unhandled audio type "' . (isset($trackarray[$this->EBMLidName(EBML_ID_CODECID)]) ? $trackarray[$this->EBMLidName(EBML_ID_CODECID)] : '') . '"'; break; } $info['audio']['streams'][] = $track_info; if (isset($track_info['dataformat']) && empty($info['audio']['dataformat'])) { foreach ($track_info as $key => $value) { $info['audio'][$key] = $value; } } break; default: // ignore, do nothing break; } } } if ($this->hide_clusters) { // too much data returned that is usually not useful if (isset($info['matroska']['segments']) && is_array($info['matroska']['segments'])) { foreach ($info['matroska']['segments'] as $key => $segmentsarray) { if ($segmentsarray['id'] == EBML_ID_CLUSTER) { unset($info['matroska']['segments'][$key]); } } } if (isset($info['matroska']['seek']) && is_array($info['matroska']['seek'])) { foreach ($info['matroska']['seek'] as $key => $seekarray) { if ($seekarray['target_id'] == EBML_ID_CLUSTER) { unset($info['matroska']['seek'][$key]); } } } //unset($info['matroska']['cluster']); //unset($info['matroska']['track_data_offsets']); } if (!empty($info['video']['streams'])) { $info['mime_type'] = 'video/x-matroska'; } elseif (!empty($info['audio']['streams'])) { $info['mime_type'] = 'audio/x-matroska'; } elseif (isset($info['mime_type'])) { unset($info['mime_type']); } foreach ($this->warnings as $key => $value) { $info['warning'][] = $value; } return true; }
public function ParseRIFF($startoffset, $maxoffset) { $info =& $this->getid3->info; $RIFFchunk = false; $FoundAllChunksWeNeed = false; try { $this->fseek($startoffset); $maxoffset = min($maxoffset, $info['avdataend']); while ($this->ftell() < $maxoffset) { $chunknamesize = $this->fread(8); //$chunkname = substr($chunknamesize, 0, 4); $chunkname = str_replace("", '_', substr($chunknamesize, 0, 4)); // note: chunk names of 4 null bytes do appear to be legal (has been observed inside INFO and PRMI chunks, for example), but makes traversing array keys more difficult $chunksize = $this->EitherEndian2Int(substr($chunknamesize, 4, 4)); //if (strlen(trim($chunkname, "\x00")) < 4) { if (strlen($chunkname) < 4) { $this->error('Expecting chunk name at offset ' . ($this->ftell() - 8) . ' but found nothing. Aborting RIFF parsing.'); break; } if ($chunksize == 0 && $chunkname != 'JUNK') { $this->warning('Chunk (' . $chunkname . ') size at offset ' . ($this->ftell() - 4) . ' is zero. Aborting RIFF parsing.'); break; } if ($chunksize % 2 != 0) { // all structures are packed on word boundaries $chunksize++; } switch ($chunkname) { case 'LIST': $listname = $this->fread(4); if (preg_match('#^(movi|rec )$#i', $listname)) { $RIFFchunk[$listname]['offset'] = $this->ftell() - 4; $RIFFchunk[$listname]['size'] = $chunksize; if (!$FoundAllChunksWeNeed) { $WhereWeWere = $this->ftell(); $AudioChunkHeader = $this->fread(12); $AudioChunkStreamNum = substr($AudioChunkHeader, 0, 2); $AudioChunkStreamType = substr($AudioChunkHeader, 2, 2); $AudioChunkSize = getid3_lib::LittleEndian2Int(substr($AudioChunkHeader, 4, 4)); if ($AudioChunkStreamType == 'wb') { $FirstFourBytes = substr($AudioChunkHeader, 8, 4); if (preg_match('/^\\xFF[\\xE2-\\xE7\\xF2-\\xF7\\xFA-\\xFF][\\x00-\\xEB]/s', $FirstFourBytes)) { // MP3 if (getid3_mp3::MPEGaudioHeaderBytesValid($FirstFourBytes)) { $getid3_temp = new getID3(); $getid3_temp->openfile($this->getid3->filename); $getid3_temp->info['avdataoffset'] = $this->ftell() - 4; $getid3_temp->info['avdataend'] = $this->ftell() + $AudioChunkSize; $getid3_mp3 = new getid3_mp3($getid3_temp, __CLASS__); $getid3_mp3->getOnlyMPEGaudioInfo($getid3_temp->info['avdataoffset'], false); if (isset($getid3_temp->info['mpeg']['audio'])) { $info['mpeg']['audio'] = $getid3_temp->info['mpeg']['audio']; $info['audio'] = $getid3_temp->info['audio']; $info['audio']['dataformat'] = 'mp' . $info['mpeg']['audio']['layer']; $info['audio']['sample_rate'] = $info['mpeg']['audio']['sample_rate']; $info['audio']['channels'] = $info['mpeg']['audio']['channels']; $info['audio']['bitrate'] = $info['mpeg']['audio']['bitrate']; $info['audio']['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']); //$info['bitrate'] = $info['audio']['bitrate']; } unset($getid3_temp, $getid3_mp3); } } elseif (strpos($FirstFourBytes, getid3_ac3::syncword) === 0) { // AC3 $getid3_temp = new getID3(); $getid3_temp->openfile($this->getid3->filename); $getid3_temp->info['avdataoffset'] = $this->ftell() - 4; $getid3_temp->info['avdataend'] = $this->ftell() + $AudioChunkSize; $getid3_ac3 = new getid3_ac3($getid3_temp); $getid3_ac3->Analyze(); if (empty($getid3_temp->info['error'])) { $info['audio'] = $getid3_temp->info['audio']; $info['ac3'] = $getid3_temp->info['ac3']; if (!empty($getid3_temp->info['warning'])) { foreach ($getid3_temp->info['warning'] as $key => $value) { $info['warning'][] = $value; } } } unset($getid3_temp, $getid3_ac3); } } $FoundAllChunksWeNeed = true; $this->fseek($WhereWeWere); } $this->fseek($chunksize - 4, SEEK_CUR); } else { if (!isset($RIFFchunk[$listname])) { $RIFFchunk[$listname] = array(); } $LISTchunkParent = $listname; $LISTchunkMaxOffset = $this->ftell() - 4 + $chunksize; if ($parsedChunk = $this->ParseRIFF($this->ftell(), $LISTchunkMaxOffset)) { $RIFFchunk[$listname] = array_merge_recursive($RIFFchunk[$listname], $parsedChunk); } } break; default: if (preg_match('#^[0-9]{2}(wb|pc|dc|db)$#', $chunkname)) { $this->fseek($chunksize, SEEK_CUR); break; } $thisindex = 0; if (isset($RIFFchunk[$chunkname]) && is_array($RIFFchunk[$chunkname])) { $thisindex = count($RIFFchunk[$chunkname]); } $RIFFchunk[$chunkname][$thisindex]['offset'] = $this->ftell() - 8; $RIFFchunk[$chunkname][$thisindex]['size'] = $chunksize; switch ($chunkname) { case 'data': $info['avdataoffset'] = $this->ftell(); $info['avdataend'] = $info['avdataoffset'] + $chunksize; $testData = $this->fread(36); if ($testData === '') { break; } if (preg_match('/^\\xFF[\\xE2-\\xE7\\xF2-\\xF7\\xFA-\\xFF][\\x00-\\xEB]/s', substr($testData, 0, 4))) { // Probably is MP3 data if (getid3_mp3::MPEGaudioHeaderBytesValid(substr($testData, 0, 4))) { $getid3_temp = new getID3(); $getid3_temp->openfile($this->getid3->filename); $getid3_temp->info['avdataoffset'] = $info['avdataoffset']; $getid3_temp->info['avdataend'] = $info['avdataend']; $getid3_mp3 = new getid3_mp3($getid3_temp, __CLASS__); $getid3_mp3->getOnlyMPEGaudioInfo($info['avdataoffset'], false); if (empty($getid3_temp->info['error'])) { $info['audio'] = $getid3_temp->info['audio']; $info['mpeg'] = $getid3_temp->info['mpeg']; } unset($getid3_temp, $getid3_mp3); } } elseif (($isRegularAC3 = substr($testData, 0, 2) == getid3_ac3::syncword) || substr($testData, 8, 2) == strrev(getid3_ac3::syncword)) { // This is probably AC-3 data $getid3_temp = new getID3(); if ($isRegularAC3) { $getid3_temp->openfile($this->getid3->filename); $getid3_temp->info['avdataoffset'] = $info['avdataoffset']; $getid3_temp->info['avdataend'] = $info['avdataend']; } $getid3_ac3 = new getid3_ac3($getid3_temp); if ($isRegularAC3) { $getid3_ac3->Analyze(); } else { // Dolby Digital WAV // AC-3 content, but not encoded in same format as normal AC-3 file // For one thing, byte order is swapped $ac3_data = ''; for ($i = 0; $i < 28; $i += 2) { $ac3_data .= substr($testData, 8 + $i + 1, 1); $ac3_data .= substr($testData, 8 + $i + 0, 1); } $getid3_ac3->AnalyzeString($ac3_data); } if (empty($getid3_temp->info['error'])) { $info['audio'] = $getid3_temp->info['audio']; $info['ac3'] = $getid3_temp->info['ac3']; if (!empty($getid3_temp->info['warning'])) { foreach ($getid3_temp->info['warning'] as $newerror) { $this->warning('getid3_ac3() says: [' . $newerror . ']'); } } } unset($getid3_temp, $getid3_ac3); } elseif (preg_match('/^(' . implode('|', array_map('preg_quote', getid3_dts::$syncwords)) . ')/', $testData)) { // This is probably DTS data $getid3_temp = new getID3(); $getid3_temp->openfile($this->getid3->filename); $getid3_temp->info['avdataoffset'] = $info['avdataoffset']; $getid3_dts = new getid3_dts($getid3_temp); $getid3_dts->Analyze(); if (empty($getid3_temp->info['error'])) { $info['audio'] = $getid3_temp->info['audio']; $info['dts'] = $getid3_temp->info['dts']; $info['playtime_seconds'] = $getid3_temp->info['playtime_seconds']; // may not match RIFF calculations since DTS-WAV often used 14/16 bit-word packing if (!empty($getid3_temp->info['warning'])) { foreach ($getid3_temp->info['warning'] as $newerror) { $this->warning('getid3_dts() says: [' . $newerror . ']'); } } } unset($getid3_temp, $getid3_dts); } elseif (substr($testData, 0, 4) == 'wvpk') { // This is WavPack data $info['wavpack']['offset'] = $info['avdataoffset']; $info['wavpack']['size'] = getid3_lib::LittleEndian2Int(substr($testData, 4, 4)); $this->parseWavPackHeader(substr($testData, 8, 28)); } else { // This is some other kind of data (quite possibly just PCM) // do nothing special, just skip it } $nextoffset = $info['avdataend']; $this->fseek($nextoffset); break; case 'iXML': case 'bext': case 'cart': case 'fmt ': case 'strh': case 'strf': case 'indx': case 'MEXT': case 'DISP': // always read data in // always read data in case 'JUNK': // should be: never read data in // but some programs write their version strings in a JUNK chunk (e.g. VirtualDub, AVIdemux, etc) if ($chunksize < 1048576) { if ($chunksize > 0) { $RIFFchunk[$chunkname][$thisindex]['data'] = $this->fread($chunksize); if ($chunkname == 'JUNK') { if (preg_match('#^([\\x20-\\x7F]+)#', $RIFFchunk[$chunkname][$thisindex]['data'], $matches)) { // only keep text characters [chr(32)-chr(127)] $info['riff']['comments']['junk'][] = trim($matches[1]); } // but if nothing there, ignore // remove the key in either case unset($RIFFchunk[$chunkname][$thisindex]['data']); } } } else { $this->warning('Chunk "' . $chunkname . '" at offset ' . $this->ftell() . ' is unexpectedly larger than 1MB (claims to be ' . number_format($chunksize) . ' bytes), skipping data'); $this->fseek($chunksize, SEEK_CUR); } break; //case 'IDVX': // $info['divxtag']['comments'] = self::ParseDIVXTAG($this->fread($chunksize)); // break; //case 'IDVX': // $info['divxtag']['comments'] = self::ParseDIVXTAG($this->fread($chunksize)); // break; default: if (!empty($LISTchunkParent) && $RIFFchunk[$chunkname][$thisindex]['offset'] + $RIFFchunk[$chunkname][$thisindex]['size'] <= $LISTchunkMaxOffset) { $RIFFchunk[$LISTchunkParent][$chunkname][$thisindex]['offset'] = $RIFFchunk[$chunkname][$thisindex]['offset']; $RIFFchunk[$LISTchunkParent][$chunkname][$thisindex]['size'] = $RIFFchunk[$chunkname][$thisindex]['size']; unset($RIFFchunk[$chunkname][$thisindex]['offset']); unset($RIFFchunk[$chunkname][$thisindex]['size']); if (isset($RIFFchunk[$chunkname][$thisindex]) && empty($RIFFchunk[$chunkname][$thisindex])) { unset($RIFFchunk[$chunkname][$thisindex]); } if (isset($RIFFchunk[$chunkname]) && empty($RIFFchunk[$chunkname])) { unset($RIFFchunk[$chunkname]); } $RIFFchunk[$LISTchunkParent][$chunkname][$thisindex]['data'] = $this->fread($chunksize); } elseif ($chunksize < 2048) { // only read data in if smaller than 2kB $RIFFchunk[$chunkname][$thisindex]['data'] = $this->fread($chunksize); } else { $this->fseek($chunksize, SEEK_CUR); } break; } break; } } } catch (getid3_exception $e) { if ($e->getCode() == 10) { $this->warning('RIFF parser: ' . $e->getMessage()); } else { throw $e; } } return $RIFFchunk; }
function MPEGaudioHeaderValid($rawarray, $echoerrors = false, $allowBitrate15 = false) { if (($rawarray['synch'] & 0xffe) != 0xffe) { return false; } static $MPEGaudioVersionLookup; static $MPEGaudioLayerLookup; static $MPEGaudioBitrateLookup; static $MPEGaudioFrequencyLookup; static $MPEGaudioChannelModeLookup; static $MPEGaudioModeExtensionLookup; static $MPEGaudioEmphasisLookup; if (empty($MPEGaudioVersionLookup)) { $MPEGaudioVersionLookup = getid3_mp3::MPEGaudioVersionArray(); $MPEGaudioLayerLookup = getid3_mp3::MPEGaudioLayerArray(); $MPEGaudioBitrateLookup = getid3_mp3::MPEGaudioBitrateArray(); $MPEGaudioFrequencyLookup = getid3_mp3::MPEGaudioFrequencyArray(); $MPEGaudioChannelModeLookup = getid3_mp3::MPEGaudioChannelModeArray(); $MPEGaudioModeExtensionLookup = getid3_mp3::MPEGaudioModeExtensionArray(); $MPEGaudioEmphasisLookup = getid3_mp3::MPEGaudioEmphasisArray(); } if (isset($MPEGaudioVersionLookup[$rawarray['version']])) { $decodedVersion = $MPEGaudioVersionLookup[$rawarray['version']]; } else { echo $echoerrors ? "\n" . 'invalid Version (' . $rawarray['version'] . ')' : ''; return false; } if (isset($MPEGaudioLayerLookup[$rawarray['layer']])) { $decodedLayer = $MPEGaudioLayerLookup[$rawarray['layer']]; } else { echo $echoerrors ? "\n" . 'invalid Layer (' . $rawarray['layer'] . ')' : ''; return false; } if (!isset($MPEGaudioBitrateLookup[$decodedVersion][$decodedLayer][$rawarray['bitrate']])) { echo $echoerrors ? "\n" . 'invalid Bitrate (' . $rawarray['bitrate'] . ')' : ''; if ($rawarray['bitrate'] == 15) { // known issue in LAME 3.90 - 3.93.1 where free-format has bitrate ID of 15 instead of 0 // let it go through here otherwise file will not be identified if (!$allowBitrate15) { return false; } } else { return false; } } if (!isset($MPEGaudioFrequencyLookup[$decodedVersion][$rawarray['sample_rate']])) { echo $echoerrors ? "\n" . 'invalid Frequency (' . $rawarray['sample_rate'] . ')' : ''; return false; } if (!isset($MPEGaudioChannelModeLookup[$rawarray['channelmode']])) { echo $echoerrors ? "\n" . 'invalid ChannelMode (' . $rawarray['channelmode'] . ')' : ''; return false; } if (!isset($MPEGaudioModeExtensionLookup[$decodedLayer][$rawarray['modeextension']])) { echo $echoerrors ? "\n" . 'invalid Mode Extension (' . $rawarray['modeextension'] . ')' : ''; return false; } if (!isset($MPEGaudioEmphasisLookup[$rawarray['emphasis']])) { echo $echoerrors ? "\n" . 'invalid Emphasis (' . $rawarray['emphasis'] . ')' : ''; return false; } // These are just either set or not set, you can't mess that up :) // $rawarray['protection']; // $rawarray['padding']; // $rawarray['private']; // $rawarray['copyright']; // $rawarray['original']; return true; }
echo '<td><a href="' . htmlentities($_SERVER['PHP_SELF'] . '?encodedbydistribution=' . urlencode($string) . '&showfiles=1') . '">' . $string . '</a></td></tr>'; } echo '</table></td></tr>'; } echo '</table>'; } } elseif (!empty($_REQUEST['audiobitrates'])) { getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.audio.mp3.php', __FILE__, true); $BitrateDistribution = array(); $SQLquery = 'SELECT ROUND(audio_bitrate / 1000) AS `RoundBitrate`, COUNT(*) AS `num`'; $SQLquery .= ' FROM `' . mysql_real_escape_string(GETID3_DB_TABLE) . '`'; $SQLquery .= ' WHERE (`audio_bitrate` > 0)'; $SQLquery .= ' GROUP BY `RoundBitrate`'; $result = mysql_query_safe($SQLquery); while ($row = mysql_fetch_array($result)) { $this_bitrate = getid3_mp3::ClosestStandardMP3Bitrate($row['RoundBitrate'] * 1000); if (isset($BitrateDistribution[$this_bitrate])) { $BitrateDistribution[$this_bitrate] += $row['num']; } else { $BitrateDistribution[$this_bitrate] = $row['num']; } } echo '<table border="1" cellspacing="0" cellpadding="3">'; echo '<tr><th>Bitrate</th><th>Count</th></tr>'; foreach ($BitrateDistribution as $Bitrate => $Count) { echo '<tr>'; echo '<td align="right">' . round($Bitrate / 1000) . ' kbps</td>'; echo '<td align="right">' . number_format($Count) . '</td>'; echo '</tr>'; } echo '</table>';
echo '<td><a href="' . $_SERVER['SCRIPT_NAME'] . '?encodedbydistribution=' . urlencode($string) . '&showfiles=1">' . $string . '</a></td></tr>'; } echo '</table></td></tr>'; } echo '</table>'; } } elseif (!empty($_REQUEST['audiobitrates'])) { getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.audio.mp3.php', __FILE__, true); $BitrateDistribution = array(); $SQLquery = 'SELECT ROUND(audio_bitrate / 1000) AS `RoundBitrate`, COUNT(*) AS `num`'; $SQLquery .= ' FROM `' . GETID3_DB_TABLE . '`'; $SQLquery .= ' WHERE (`audio_bitrate` > 0)'; $SQLquery .= ' GROUP BY `RoundBitrate`'; $result = safe_mysql_query($SQLquery); while ($row = mysql_fetch_array($result)) { @($BitrateDistribution[getid3_mp3::ClosestStandardMP3Bitrate($row['RoundBitrate'] * 1000)] += $row['num']); // safe_inc } echo '<table border="1" cellspacing="0" cellpadding="3">'; echo '<tr><th>Bitrate</th><th>Count</th></tr>'; foreach ($BitrateDistribution as $Bitrate => $Count) { echo '<tr>'; echo '<TD ALIGN="RIGHT">' . round($Bitrate / 1000) . ' kbps</td>'; echo '<TD ALIGN="RIGHT">' . number_format($Count) . '</td>'; echo '</tr>'; } echo '</table>'; } elseif (!empty($_REQUEST['emptygenres'])) { $SQLquery = 'SELECT `fileformat`, `filename`, `genre`'; $SQLquery .= ' FROM `' . GETID3_DB_TABLE . '`'; $SQLquery .= ' WHERE (`genre` = "")';
public function QuicktimeParseAtom($atomname, $atomsize, $atom_data, $baseoffset, &$atomHierarchy, $ParseAllPossibleAtoms) { // http://developer.apple.com/techpubs/quicktime/qtdevdocs/APIREF/INDEX/atomalphaindex.htm $info =& $this->getid3->info; $atom_parent = end($atomHierarchy); // not array_pop($atomHierarchy); see http://www.getid3.org/phpBB3/viewtopic.php?t=1717 array_push($atomHierarchy, $atomname); $atom_structure['hierarchy'] = implode(' ', $atomHierarchy); $atom_structure['name'] = $atomname; $atom_structure['size'] = $atomsize; $atom_structure['offset'] = $baseoffset; switch ($atomname) { case 'moov': // MOVie container atom // MOVie container atom case 'trak': // TRAcK container atom // TRAcK container atom case 'clip': // CLIPping container atom // CLIPping container atom case 'matt': // track MATTe container atom // track MATTe container atom case 'edts': // EDiTS container atom // EDiTS container atom case 'tref': // Track REFerence container atom // Track REFerence container atom case 'mdia': // MeDIA container atom // MeDIA container atom case 'minf': // Media INFormation container atom // Media INFormation container atom case 'dinf': // Data INFormation container atom // Data INFormation container atom case 'udta': // User DaTA container atom // User DaTA container atom case 'cmov': // Compressed MOVie container atom // Compressed MOVie container atom case 'rmra': // Reference Movie Record Atom // Reference Movie Record Atom case 'rmda': // Reference Movie Descriptor Atom // Reference Movie Descriptor Atom case 'gmhd': // Generic Media info HeaDer atom (seen on QTVR) $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); break; case 'ilst': // Item LiST container atom if ($atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms)) { // some "ilst" atoms contain data atoms that have a numeric name, and the data is far more accessible if the returned array is compacted $allnumericnames = true; foreach ($atom_structure['subatoms'] as $subatomarray) { if (!is_integer($subatomarray['name']) || count($subatomarray['subatoms']) != 1) { $allnumericnames = false; break; } } if ($allnumericnames) { $newData = array(); foreach ($atom_structure['subatoms'] as $subatomarray) { foreach ($subatomarray['subatoms'] as $newData_subatomarray) { unset($newData_subatomarray['hierarchy'], $newData_subatomarray['name']); $newData[$subatomarray['name']] = $newData_subatomarray; break; } } $atom_structure['data'] = $newData; unset($atom_structure['subatoms']); } } break; case "": case "": case "": case "": case "": $atomname = getid3_lib::BigEndian2Int($atomname); $atom_structure['name'] = $atomname; $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); break; case 'stbl': // Sample TaBLe container atom $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); $isVideo = false; $framerate = 0; $framecount = 0; foreach ($atom_structure['subatoms'] as $key => $value_array) { if (isset($value_array['sample_description_table'])) { foreach ($value_array['sample_description_table'] as $key2 => $value_array2) { if (isset($value_array2['data_format'])) { switch ($value_array2['data_format']) { case 'avc1': case 'mp4v': // video data $isVideo = true; break; case 'mp4a': // audio data break; } } } } elseif (isset($value_array['time_to_sample_table'])) { foreach ($value_array['time_to_sample_table'] as $key2 => $value_array2) { if (isset($value_array2['sample_count']) && isset($value_array2['sample_duration']) && $value_array2['sample_duration'] > 0) { $framerate = round($info['quicktime']['time_scale'] / $value_array2['sample_duration'], 3); $framecount = $value_array2['sample_count']; } } } } if ($isVideo && $framerate) { $info['quicktime']['video']['frame_rate'] = $framerate; $info['video']['frame_rate'] = $info['quicktime']['video']['frame_rate']; } if ($isVideo && $framecount) { $info['quicktime']['video']['frame_count'] = $framecount; } break; case 'aART': // Album ARTist // Album ARTist case 'catg': // CaTeGory // CaTeGory case 'covr': // COVeR artwork // COVeR artwork case 'cpil': // ComPILation // ComPILation case 'cprt': // CoPyRighT // CoPyRighT case 'desc': // DESCription // DESCription case 'disk': // DISK number // DISK number case 'egid': // Episode Global ID // Episode Global ID case 'gnre': // GeNRE // GeNRE case 'keyw': // KEYWord // KEYWord case 'ldes': case 'pcst': // PodCaST // PodCaST case 'pgap': // GAPless Playback // GAPless Playback case 'purd': // PURchase Date // PURchase Date case 'purl': // Podcast URL // Podcast URL case 'rati': case 'rndu': case 'rpdu': case 'rtng': // RaTiNG // RaTiNG case 'stik': case 'tmpo': // TeMPO (BPM) // TeMPO (BPM) case 'trkn': // TRacK Number // TRacK Number case 'tves': // TV EpiSode // TV EpiSode case 'tvnn': // TV Network Name // TV Network Name case 'tvsh': // TV SHow Name // TV SHow Name case 'tvsn': // TV SeasoN // TV SeasoN case 'akID': // iTunes store account type // iTunes store account type case 'apID': case 'atID': case 'cmID': case 'cnID': case 'geID': case 'plID': case 'sfID': // iTunes store country // iTunes store country case "©" . 'alb': // ALBum // ALBum case "©" . 'art': // ARTist // ARTist case "©" . 'ART': case "©" . 'aut': case "©" . 'cmt': // CoMmenT // CoMmenT case "©" . 'com': // COMposer // COMposer case "©" . 'cpy': case "©" . 'day': // content created year // content created year case "©" . 'dir': case "©" . 'ed1': case "©" . 'ed2': case "©" . 'ed3': case "©" . 'ed4': case "©" . 'ed5': case "©" . 'ed6': case "©" . 'ed7': case "©" . 'ed8': case "©" . 'ed9': case "©" . 'enc': case "©" . 'fmt': case "©" . 'gen': // GENre // GENre case "©" . 'grp': // GRouPing // GRouPing case "©" . 'hst': case "©" . 'inf': case "©" . 'lyr': // LYRics // LYRics case "©" . 'mak': case "©" . 'mod': case "©" . 'nam': // full NAMe // full NAMe case "©" . 'ope': case "©" . 'PRD': case "©" . 'prd': case "©" . 'prf': case "©" . 'req': case "©" . 'src': case "©" . 'swr': case "©" . 'too': // encoder // encoder case "©" . 'trk': // TRacK // TRacK case "©" . 'url': case "©" . 'wrn': case "©" . 'wrt': // WRiTer // WRiTer case '----': // itunes specific if ($atom_parent == 'udta') { // User data atom handler $atom_structure['data_length'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2)); $atom_structure['language_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 2)); $atom_structure['data'] = substr($atom_data, 4); $atom_structure['language'] = $this->QuicktimeLanguageLookup($atom_structure['language_id']); if (empty($info['comments']['language']) || !in_array($atom_structure['language'], $info['comments']['language'])) { $info['comments']['language'][] = $atom_structure['language']; } } else { // Apple item list box atom handler $atomoffset = 0; if (substr($atom_data, 2, 2) == "µ") { // not sure what it means, but observed on iPhone4 data. // Each $atom_data has 2 bytes of datasize, plus 0x10B5, then data while ($atomoffset < strlen($atom_data)) { $boxsmallsize = getid3_lib::BigEndian2Int(substr($atom_data, $atomoffset, 2)); $boxsmalltype = substr($atom_data, $atomoffset + 2, 2); $boxsmalldata = substr($atom_data, $atomoffset + 4, $boxsmallsize); if ($boxsmallsize <= 1) { $info['warning'][] = 'Invalid QuickTime atom smallbox size "' . $boxsmallsize . '" in atom "' . $atomname . '" at offset: ' . ($atom_structure['offset'] + $atomoffset); $atom_structure['data'] = null; $atomoffset = strlen($atom_data); break; } switch ($boxsmalltype) { case "µ": $atom_structure['data'] = $boxsmalldata; break; default: $info['warning'][] = 'Unknown QuickTime smallbox type: "' . getid3_lib::PrintHexBytes($boxsmalltype) . '" at offset ' . $baseoffset; $atom_structure['data'] = $atom_data; break; } $atomoffset += 4 + $boxsmallsize; } } else { while ($atomoffset < strlen($atom_data)) { $boxsize = getid3_lib::BigEndian2Int(substr($atom_data, $atomoffset, 4)); $boxtype = substr($atom_data, $atomoffset + 4, 4); $boxdata = substr($atom_data, $atomoffset + 8, $boxsize - 8); if ($boxsize <= 1) { $info['warning'][] = 'Invalid QuickTime atom box size "' . $boxsize . '" in atom "' . $atomname . '" at offset: ' . ($atom_structure['offset'] + $atomoffset); $atom_structure['data'] = null; $atomoffset = strlen($atom_data); break; } $atomoffset += $boxsize; switch ($boxtype) { case 'mean': case 'name': $atom_structure[$boxtype] = substr($boxdata, 4); break; case 'data': $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($boxdata, 0, 1)); $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($boxdata, 1, 3)); switch ($atom_structure['flags_raw']) { case 0: // data flag // data flag case 21: // tmpo/cpil flag switch ($atomname) { case 'cpil': case 'pcst': case 'pgap': $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1)); break; case 'tmpo': $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 2)); break; case 'disk': case 'trkn': $num = getid3_lib::BigEndian2Int(substr($boxdata, 10, 2)); $num_total = getid3_lib::BigEndian2Int(substr($boxdata, 12, 2)); $atom_structure['data'] = empty($num) ? '' : $num; $atom_structure['data'] .= empty($num_total) ? '' : '/' . $num_total; break; case 'gnre': $GenreID = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4)); $atom_structure['data'] = getid3_id3v1::LookupGenreName($GenreID - 1); break; case 'rtng': $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1)); $atom_structure['data'] = $this->QuicktimeContentRatingLookup($atom_structure[$atomname]); break; case 'stik': $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1)); $atom_structure['data'] = $this->QuicktimeSTIKLookup($atom_structure[$atomname]); break; case 'sfID': $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4)); $atom_structure['data'] = $this->QuicktimeStoreFrontCodeLookup($atom_structure[$atomname]); break; case 'egid': case 'purl': $atom_structure['data'] = substr($boxdata, 8); break; default: $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4)); } break; case 1: // text flag // text flag case 13: // image flag // image flag default: $atom_structure['data'] = substr($boxdata, 8); if ($atomname == 'covr') { // not a foolproof check, but better than nothing if (preg_match('#^\\xFF\\xD8\\xFF#', $atom_structure['data'])) { $atom_structure['image_mime'] = 'image/jpeg'; } elseif (preg_match('#^\\x89\\x50\\x4E\\x47\\x0D\\x0A\\x1A\\x0A#', $atom_structure['data'])) { $atom_structure['image_mime'] = 'image/png'; } elseif (preg_match('#^GIF#', $atom_structure['data'])) { $atom_structure['image_mime'] = 'image/gif'; } } break; } break; default: $info['warning'][] = 'Unknown QuickTime box type: "' . getid3_lib::PrintHexBytes($boxtype) . '" at offset ' . $baseoffset; $atom_structure['data'] = $atom_data; } } } } $this->CopyToAppropriateCommentsSection($atomname, $atom_structure['data'], $atom_structure['name']); break; case 'play': // auto-PLAY atom $atom_structure['autoplay'] = (bool) getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); $info['quicktime']['autoplay'] = $atom_structure['autoplay']; break; case 'WLOC': // Window LOCation atom $atom_structure['location_x'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2)); $atom_structure['location_y'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 2)); break; case 'LOOP': // LOOPing atom // LOOPing atom case 'SelO': // play SELection Only atom // play SELection Only atom case 'AllF': // play ALL Frames atom $atom_structure['data'] = getid3_lib::BigEndian2Int($atom_data); break; case 'name': // // case 'MCPS': // Media Cleaner PRo // Media Cleaner PRo case '@PRM': // adobe PReMiere version // adobe PReMiere version case '@PRQ': // adobe PRemiere Quicktime version $atom_structure['data'] = $atom_data; break; case 'cmvd': // Compressed MooV Data atom // Code by ubergeekØubergeek*tv based on information from // http://developer.apple.com/quicktime/icefloe/dispatch012.html $atom_structure['unCompressedSize'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); $CompressedFileData = substr($atom_data, 4); if ($UncompressedHeader = @gzuncompress($CompressedFileData)) { $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($UncompressedHeader, 0, $atomHierarchy, $ParseAllPossibleAtoms); } else { $info['warning'][] = 'Error decompressing compressed MOV atom at offset ' . $atom_structure['offset']; } break; case 'dcom': // Data COMpression atom $atom_structure['compression_id'] = $atom_data; $atom_structure['compression_text'] = $this->QuicktimeDCOMLookup($atom_data); break; case 'rdrf': // Reference movie Data ReFerence atom $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); $atom_structure['flags']['internal_data'] = (bool) ($atom_structure['flags_raw'] & 0x1); $atom_structure['reference_type_name'] = substr($atom_data, 4, 4); $atom_structure['reference_length'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); switch ($atom_structure['reference_type_name']) { case 'url ': $atom_structure['url'] = $this->NoNullString(substr($atom_data, 12)); break; case 'alis': $atom_structure['file_alias'] = substr($atom_data, 12); break; case 'rsrc': $atom_structure['resource_alias'] = substr($atom_data, 12); break; default: $atom_structure['data'] = substr($atom_data, 12); break; } break; case 'rmqu': // Reference Movie QUality atom $atom_structure['movie_quality'] = getid3_lib::BigEndian2Int($atom_data); break; case 'rmcs': // Reference Movie Cpu Speed atom $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 $atom_structure['cpu_speed_rating'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); break; case 'rmvc': // Reference Movie Version Check atom $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 $atom_structure['gestalt_selector'] = substr($atom_data, 4, 4); $atom_structure['gestalt_value_mask'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); $atom_structure['gestalt_value'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); $atom_structure['gestalt_check_type'] = getid3_lib::BigEndian2Int(substr($atom_data, 14, 2)); break; case 'rmcd': // Reference Movie Component check atom $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 $atom_structure['component_type'] = substr($atom_data, 4, 4); $atom_structure['component_subtype'] = substr($atom_data, 8, 4); $atom_structure['component_manufacturer'] = substr($atom_data, 12, 4); $atom_structure['component_flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); $atom_structure['component_flags_mask'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 4)); $atom_structure['component_min_version'] = getid3_lib::BigEndian2Int(substr($atom_data, 24, 4)); break; case 'rmdr': // Reference Movie Data Rate atom $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 $atom_structure['data_rate'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); $atom_structure['data_rate_bps'] = $atom_structure['data_rate'] * 10; break; case 'rmla': // Reference Movie Language Atom $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 $atom_structure['language_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); $atom_structure['language'] = $this->QuicktimeLanguageLookup($atom_structure['language_id']); if (empty($info['comments']['language']) || !in_array($atom_structure['language'], $info['comments']['language'])) { $info['comments']['language'][] = $atom_structure['language']; } break; case 'rmla': // Reference Movie Language Atom $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 $atom_structure['track_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); break; case 'ptv ': // Print To Video - defines a movie's full screen mode // http://developer.apple.com/documentation/QuickTime/APIREF/SOURCESIV/at_ptv-_pg.htm $atom_structure['display_size_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2)); $atom_structure['reserved_1'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 2)); // hardcoded: 0x0000 $atom_structure['reserved_2'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); // hardcoded: 0x0000 $atom_structure['slide_show_flag'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 1)); $atom_structure['play_on_open_flag'] = getid3_lib::BigEndian2Int(substr($atom_data, 7, 1)); $atom_structure['flags']['play_on_open'] = (bool) $atom_structure['play_on_open_flag']; $atom_structure['flags']['slide_show'] = (bool) $atom_structure['slide_show_flag']; $ptv_lookup[0] = 'normal'; $ptv_lookup[1] = 'double'; $ptv_lookup[2] = 'half'; $ptv_lookup[3] = 'full'; $ptv_lookup[4] = 'current'; if (isset($ptv_lookup[$atom_structure['display_size_raw']])) { $atom_structure['display_size'] = $ptv_lookup[$atom_structure['display_size_raw']]; } else { $info['warning'][] = 'unknown "ptv " display constant (' . $atom_structure['display_size_raw'] . ')'; } break; case 'stsd': // Sample Table Sample Description atom $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); $stsdEntriesDataOffset = 8; for ($i = 0; $i < $atom_structure['number_entries']; $i++) { $atom_structure['sample_description_table'][$i]['size'] = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 4)); $stsdEntriesDataOffset += 4; $atom_structure['sample_description_table'][$i]['data_format'] = substr($atom_data, $stsdEntriesDataOffset, 4); $stsdEntriesDataOffset += 4; $atom_structure['sample_description_table'][$i]['reserved'] = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 6)); $stsdEntriesDataOffset += 6; $atom_structure['sample_description_table'][$i]['reference_index'] = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 2)); $stsdEntriesDataOffset += 2; $atom_structure['sample_description_table'][$i]['data'] = substr($atom_data, $stsdEntriesDataOffset, $atom_structure['sample_description_table'][$i]['size'] - 4 - 4 - 6 - 2); $stsdEntriesDataOffset += $atom_structure['sample_description_table'][$i]['size'] - 4 - 4 - 6 - 2; $atom_structure['sample_description_table'][$i]['encoder_version'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 0, 2)); $atom_structure['sample_description_table'][$i]['encoder_revision'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 2, 2)); $atom_structure['sample_description_table'][$i]['encoder_vendor'] = substr($atom_structure['sample_description_table'][$i]['data'], 4, 4); switch ($atom_structure['sample_description_table'][$i]['encoder_vendor']) { case "": // audio tracks $atom_structure['sample_description_table'][$i]['audio_channels'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 8, 2)); $atom_structure['sample_description_table'][$i]['audio_bit_depth'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 10, 2)); $atom_structure['sample_description_table'][$i]['audio_compression_id'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 12, 2)); $atom_structure['sample_description_table'][$i]['audio_packet_size'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 14, 2)); $atom_structure['sample_description_table'][$i]['audio_sample_rate'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 16, 4)); // video tracks // http://developer.apple.com/library/mac/#documentation/QuickTime/QTFF/QTFFChap3/qtff3.html $atom_structure['sample_description_table'][$i]['temporal_quality'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 8, 4)); $atom_structure['sample_description_table'][$i]['spatial_quality'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 12, 4)); $atom_structure['sample_description_table'][$i]['width'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 16, 2)); $atom_structure['sample_description_table'][$i]['height'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 18, 2)); $atom_structure['sample_description_table'][$i]['resolution_x'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 24, 4)); $atom_structure['sample_description_table'][$i]['resolution_y'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 28, 4)); $atom_structure['sample_description_table'][$i]['data_size'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 32, 4)); $atom_structure['sample_description_table'][$i]['frame_count'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 36, 2)); $atom_structure['sample_description_table'][$i]['compressor_name'] = substr($atom_structure['sample_description_table'][$i]['data'], 38, 4); $atom_structure['sample_description_table'][$i]['pixel_depth'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 42, 2)); $atom_structure['sample_description_table'][$i]['color_table_id'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 44, 2)); switch ($atom_structure['sample_description_table'][$i]['data_format']) { case '2vuY': case 'avc1': case 'cvid': case 'dvc ': case 'dvcp': case 'gif ': case 'h263': case 'jpeg': case 'kpcd': case 'mjpa': case 'mjpb': case 'mp4v': case 'png ': case 'raw ': case 'rle ': case 'rpza': case 'smc ': case 'SVQ1': case 'SVQ3': case 'tiff': case 'v210': case 'v216': case 'v308': case 'v408': case 'v410': case 'yuv2': $info['fileformat'] = 'mp4'; $info['video']['fourcc'] = $atom_structure['sample_description_table'][$i]['data_format']; // http://www.getid3.org/phpBB3/viewtopic.php?t=1550 //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 if (!empty($atom_structure['sample_description_table'][$i]['width']) && !empty($atom_structure['sample_description_table'][$i]['height'])) { // assume that values stored here are more important than values stored in [tkhd] atom $info['video']['resolution_x'] = $atom_structure['sample_description_table'][$i]['width']; $info['video']['resolution_y'] = $atom_structure['sample_description_table'][$i]['height']; $info['quicktime']['video']['resolution_x'] = $info['video']['resolution_x']; $info['quicktime']['video']['resolution_y'] = $info['video']['resolution_y']; } break; case 'qtvr': $info['video']['dataformat'] = 'quicktimevr'; break; case 'mp4a': default: $info['quicktime']['audio']['codec'] = $this->QuicktimeAudioCodecLookup($atom_structure['sample_description_table'][$i]['data_format']); $info['quicktime']['audio']['sample_rate'] = $atom_structure['sample_description_table'][$i]['audio_sample_rate']; $info['quicktime']['audio']['channels'] = $atom_structure['sample_description_table'][$i]['audio_channels']; $info['quicktime']['audio']['bit_depth'] = $atom_structure['sample_description_table'][$i]['audio_bit_depth']; $info['audio']['codec'] = $info['quicktime']['audio']['codec']; $info['audio']['sample_rate'] = $info['quicktime']['audio']['sample_rate']; $info['audio']['channels'] = $info['quicktime']['audio']['channels']; $info['audio']['bits_per_sample'] = $info['quicktime']['audio']['bit_depth']; switch ($atom_structure['sample_description_table'][$i]['data_format']) { case 'raw ': // PCM // PCM case 'alac': // Apple Lossless Audio Codec $info['audio']['lossless'] = true; break; default: $info['audio']['lossless'] = false; break; } break; } break; default: switch ($atom_structure['sample_description_table'][$i]['data_format']) { case 'mp4s': $info['fileformat'] = 'mp4'; break; default: // video atom $atom_structure['sample_description_table'][$i]['video_temporal_quality'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 8, 4)); $atom_structure['sample_description_table'][$i]['video_spatial_quality'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 12, 4)); $atom_structure['sample_description_table'][$i]['video_frame_width'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 16, 2)); $atom_structure['sample_description_table'][$i]['video_frame_height'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 18, 2)); $atom_structure['sample_description_table'][$i]['video_resolution_x'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 20, 4)); $atom_structure['sample_description_table'][$i]['video_resolution_y'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 24, 4)); $atom_structure['sample_description_table'][$i]['video_data_size'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 28, 4)); $atom_structure['sample_description_table'][$i]['video_frame_count'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 32, 2)); $atom_structure['sample_description_table'][$i]['video_encoder_name_len'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 34, 1)); $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']); $atom_structure['sample_description_table'][$i]['video_pixel_color_depth'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 66, 2)); $atom_structure['sample_description_table'][$i]['video_color_table_id'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 68, 2)); $atom_structure['sample_description_table'][$i]['video_pixel_color_type'] = $atom_structure['sample_description_table'][$i]['video_pixel_color_depth'] > 32 ? 'grayscale' : 'color'; $atom_structure['sample_description_table'][$i]['video_pixel_color_name'] = $this->QuicktimeColorNameLookup($atom_structure['sample_description_table'][$i]['video_pixel_color_depth']); if ($atom_structure['sample_description_table'][$i]['video_pixel_color_name'] != 'invalid') { $info['quicktime']['video']['codec_fourcc'] = $atom_structure['sample_description_table'][$i]['data_format']; $info['quicktime']['video']['codec_fourcc_lookup'] = $this->QuicktimeVideoCodecLookup($atom_structure['sample_description_table'][$i]['data_format']); $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']; $info['quicktime']['video']['color_depth'] = $atom_structure['sample_description_table'][$i]['video_pixel_color_depth']; $info['quicktime']['video']['color_depth_name'] = $atom_structure['sample_description_table'][$i]['video_pixel_color_name']; $info['video']['codec'] = $info['quicktime']['video']['codec']; $info['video']['bits_per_sample'] = $info['quicktime']['video']['color_depth']; } $info['video']['lossless'] = false; $info['video']['pixel_aspect_ratio'] = (double) 1; break; } break; } switch (strtolower($atom_structure['sample_description_table'][$i]['data_format'])) { case 'mp4a': $info['audio']['dataformat'] = 'mp4'; $info['quicktime']['audio']['codec'] = 'mp4'; break; case '3ivx': case '3iv1': case '3iv2': $info['video']['dataformat'] = '3ivx'; break; case 'xvid': $info['video']['dataformat'] = 'xvid'; break; case 'mp4v': $info['video']['dataformat'] = 'mpeg4'; break; case 'divx': case 'div1': case 'div2': case 'div3': case 'div4': case 'div5': case 'div6': $info['video']['dataformat'] = 'divx'; break; default: // do nothing break; } unset($atom_structure['sample_description_table'][$i]['data']); } break; case 'stts': // Sample Table Time-to-Sample atom $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); $sttsEntriesDataOffset = 8; //$FrameRateCalculatorArray = array(); $frames_count = 0; for ($i = 0; $i < $atom_structure['number_entries']; $i++) { $atom_structure['time_to_sample_table'][$i]['sample_count'] = getid3_lib::BigEndian2Int(substr($atom_data, $sttsEntriesDataOffset, 4)); $sttsEntriesDataOffset += 4; $atom_structure['time_to_sample_table'][$i]['sample_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, $sttsEntriesDataOffset, 4)); $sttsEntriesDataOffset += 4; $frames_count += $atom_structure['time_to_sample_table'][$i]['sample_count']; // THIS SECTION REPLACED WITH CODE IN "stbl" ATOM //if (!empty($info['quicktime']['time_scale']) && ($atom_structure['time_to_sample_table'][$i]['sample_duration'] > 0)) { // $stts_new_framerate = $info['quicktime']['time_scale'] / $atom_structure['time_to_sample_table'][$i]['sample_duration']; // if ($stts_new_framerate <= 60) { // // some atoms have durations of "1" giving a very large framerate, which probably is not right // $info['video']['frame_rate'] = max($info['video']['frame_rate'], $stts_new_framerate); // } //} // //$FrameRateCalculatorArray[($info['quicktime']['time_scale'] / $atom_structure['time_to_sample_table'][$i]['sample_duration'])] += $atom_structure['time_to_sample_table'][$i]['sample_count']; } $info['quicktime']['stts_framecount'][] = $frames_count; //$sttsFramesTotal = 0; //$sttsSecondsTotal = 0; //foreach ($FrameRateCalculatorArray as $frames_per_second => $frame_count) { // if (($frames_per_second > 60) || ($frames_per_second < 1)) { // // not video FPS information, probably audio information // $sttsFramesTotal = 0; // $sttsSecondsTotal = 0; // break; // } // $sttsFramesTotal += $frame_count; // $sttsSecondsTotal += $frame_count / $frames_per_second; //} //if (($sttsFramesTotal > 0) && ($sttsSecondsTotal > 0)) { // if (($sttsFramesTotal / $sttsSecondsTotal) > $info['video']['frame_rate']) { // $info['video']['frame_rate'] = $sttsFramesTotal / $sttsSecondsTotal; // } //} break; case 'stss': // Sample Table Sync Sample (key frames) atom if ($ParseAllPossibleAtoms) { $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); $stssEntriesDataOffset = 8; for ($i = 0; $i < $atom_structure['number_entries']; $i++) { $atom_structure['time_to_sample_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stssEntriesDataOffset, 4)); $stssEntriesDataOffset += 4; } } break; case 'stsc': // Sample Table Sample-to-Chunk atom if ($ParseAllPossibleAtoms) { $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); $stscEntriesDataOffset = 8; for ($i = 0; $i < $atom_structure['number_entries']; $i++) { $atom_structure['sample_to_chunk_table'][$i]['first_chunk'] = getid3_lib::BigEndian2Int(substr($atom_data, $stscEntriesDataOffset, 4)); $stscEntriesDataOffset += 4; $atom_structure['sample_to_chunk_table'][$i]['samples_per_chunk'] = getid3_lib::BigEndian2Int(substr($atom_data, $stscEntriesDataOffset, 4)); $stscEntriesDataOffset += 4; $atom_structure['sample_to_chunk_table'][$i]['sample_description'] = getid3_lib::BigEndian2Int(substr($atom_data, $stscEntriesDataOffset, 4)); $stscEntriesDataOffset += 4; } } break; case 'stsz': // Sample Table SiZe atom if ($ParseAllPossibleAtoms) { $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 $atom_structure['sample_size'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); $stszEntriesDataOffset = 12; if ($atom_structure['sample_size'] == 0) { for ($i = 0; $i < $atom_structure['number_entries']; $i++) { $atom_structure['sample_size_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stszEntriesDataOffset, 4)); $stszEntriesDataOffset += 4; } } } break; case 'stco': // Sample Table Chunk Offset atom if ($ParseAllPossibleAtoms) { $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); $stcoEntriesDataOffset = 8; for ($i = 0; $i < $atom_structure['number_entries']; $i++) { $atom_structure['chunk_offset_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stcoEntriesDataOffset, 4)); $stcoEntriesDataOffset += 4; } } break; case 'co64': // Chunk Offset 64-bit (version of "stco" that supports > 2GB files) if ($ParseAllPossibleAtoms) { $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); $stcoEntriesDataOffset = 8; for ($i = 0; $i < $atom_structure['number_entries']; $i++) { $atom_structure['chunk_offset_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stcoEntriesDataOffset, 8)); $stcoEntriesDataOffset += 8; } } break; case 'dref': // Data REFerence atom $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); $drefDataOffset = 8; for ($i = 0; $i < $atom_structure['number_entries']; $i++) { $atom_structure['data_references'][$i]['size'] = getid3_lib::BigEndian2Int(substr($atom_data, $drefDataOffset, 4)); $drefDataOffset += 4; $atom_structure['data_references'][$i]['type'] = substr($atom_data, $drefDataOffset, 4); $drefDataOffset += 4; $atom_structure['data_references'][$i]['version'] = getid3_lib::BigEndian2Int(substr($atom_data, $drefDataOffset, 1)); $drefDataOffset += 1; $atom_structure['data_references'][$i]['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, $drefDataOffset, 3)); // hardcoded: 0x0000 $drefDataOffset += 3; $atom_structure['data_references'][$i]['data'] = substr($atom_data, $drefDataOffset, $atom_structure['data_references'][$i]['size'] - 4 - 4 - 1 - 3); $drefDataOffset += $atom_structure['data_references'][$i]['size'] - 4 - 4 - 1 - 3; $atom_structure['data_references'][$i]['flags']['self_reference'] = (bool) ($atom_structure['data_references'][$i]['flags_raw'] & 0x1); } break; case 'gmin': // base Media INformation atom $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 $atom_structure['graphics_mode'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); $atom_structure['opcolor_red'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2)); $atom_structure['opcolor_green'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 2)); $atom_structure['opcolor_blue'] = getid3_lib::BigEndian2Int(substr($atom_data, 10, 2)); $atom_structure['balance'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 2)); $atom_structure['reserved'] = getid3_lib::BigEndian2Int(substr($atom_data, 14, 2)); break; case 'smhd': // Sound Media information HeaDer atom $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 $atom_structure['balance'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); $atom_structure['reserved'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2)); break; case 'vmhd': // Video Media information HeaDer atom $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); $atom_structure['graphics_mode'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); $atom_structure['opcolor_red'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2)); $atom_structure['opcolor_green'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 2)); $atom_structure['opcolor_blue'] = getid3_lib::BigEndian2Int(substr($atom_data, 10, 2)); $atom_structure['flags']['no_lean_ahead'] = (bool) ($atom_structure['flags_raw'] & 0x1); break; case 'hdlr': // HanDLeR reference atom $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 $atom_structure['component_type'] = substr($atom_data, 4, 4); $atom_structure['component_subtype'] = substr($atom_data, 8, 4); $atom_structure['component_manufacturer'] = substr($atom_data, 12, 4); $atom_structure['component_flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); $atom_structure['component_flags_mask'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 4)); $atom_structure['component_name'] = $this->Pascal2String(substr($atom_data, 24)); if ($atom_structure['component_subtype'] == 'STpn' && $atom_structure['component_manufacturer'] == 'zzzz') { $info['video']['dataformat'] = 'quicktimevr'; } break; case 'mdhd': // MeDia HeaDer atom $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 $atom_structure['creation_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); $atom_structure['modify_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); $atom_structure['time_scale'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); $atom_structure['duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); $atom_structure['language_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 2)); $atom_structure['quality'] = getid3_lib::BigEndian2Int(substr($atom_data, 22, 2)); if ($atom_structure['time_scale'] == 0) { $info['error'][] = 'Corrupt Quicktime file: mdhd.time_scale == zero'; return false; } $info['quicktime']['time_scale'] = isset($info['quicktime']['time_scale']) ? max($info['quicktime']['time_scale'], $atom_structure['time_scale']) : $atom_structure['time_scale']; $atom_structure['creation_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['creation_time']); $atom_structure['modify_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['modify_time']); $atom_structure['playtime_seconds'] = $atom_structure['duration'] / $atom_structure['time_scale']; $atom_structure['language'] = $this->QuicktimeLanguageLookup($atom_structure['language_id']); if (empty($info['comments']['language']) || !in_array($atom_structure['language'], $info['comments']['language'])) { $info['comments']['language'][] = $atom_structure['language']; } break; case 'pnot': // Preview atom $atom_structure['modification_date'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); // "standard Macintosh format" $atom_structure['version_number'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); // hardcoded: 0x00 $atom_structure['atom_type'] = substr($atom_data, 6, 4); // usually: 'PICT' $atom_structure['atom_index'] = getid3_lib::BigEndian2Int(substr($atom_data, 10, 2)); // usually: 0x01 $atom_structure['modification_date_unix'] = getid3_lib::DateMac2Unix($atom_structure['modification_date']); break; case 'crgn': // Clipping ReGioN atom $atom_structure['region_size'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2)); // The Region size, Region boundary box, $atom_structure['boundary_box'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 8)); // and Clipping region data fields $atom_structure['clipping_data'] = substr($atom_data, 10); // constitute a QuickDraw region. break; case 'load': // track LOAD settings atom $atom_structure['preload_start_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); $atom_structure['preload_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); $atom_structure['preload_flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); $atom_structure['default_hints_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); $atom_structure['default_hints']['double_buffer'] = (bool) ($atom_structure['default_hints_raw'] & 0x20); $atom_structure['default_hints']['high_quality'] = (bool) ($atom_structure['default_hints_raw'] & 0x100); break; case 'tmcd': // TiMe CoDe atom // TiMe CoDe atom case 'chap': // CHAPter list atom // CHAPter list atom case 'sync': // SYNChronization atom // SYNChronization atom case 'scpt': // tranSCriPT atom // tranSCriPT atom case 'ssrc': // non-primary SouRCe atom for ($i = 0; $i < strlen($atom_data); $i += 4) { @($atom_structure['track_id'][] = getid3_lib::BigEndian2Int(substr($atom_data, $i, 4))); } break; case 'elst': // Edit LiST atom $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); for ($i = 0; $i < $atom_structure['number_entries']; $i++) { $atom_structure['edit_list'][$i]['track_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + $i * 12 + 0, 4)); $atom_structure['edit_list'][$i]['media_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + $i * 12 + 4, 4)); $atom_structure['edit_list'][$i]['media_rate'] = getid3_lib::FixedPoint16_16(substr($atom_data, 8 + $i * 12 + 8, 4)); } break; case 'kmat': // compressed MATte atom $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 $atom_structure['matte_data_raw'] = substr($atom_data, 4); break; case 'ctab': // Color TABle atom $atom_structure['color_table_seed'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); // hardcoded: 0x00000000 $atom_structure['color_table_flags'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); // hardcoded: 0x8000 $atom_structure['color_table_size'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2)) + 1; for ($colortableentry = 0; $colortableentry < $atom_structure['color_table_size']; $colortableentry++) { $atom_structure['color_table'][$colortableentry]['alpha'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + $colortableentry * 8 + 0, 2)); $atom_structure['color_table'][$colortableentry]['red'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + $colortableentry * 8 + 2, 2)); $atom_structure['color_table'][$colortableentry]['green'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + $colortableentry * 8 + 4, 2)); $atom_structure['color_table'][$colortableentry]['blue'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + $colortableentry * 8 + 6, 2)); } break; case 'mvhd': // MoVie HeaDer atom $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); $atom_structure['creation_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); $atom_structure['modify_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); $atom_structure['time_scale'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); $atom_structure['duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); $atom_structure['preferred_rate'] = getid3_lib::FixedPoint16_16(substr($atom_data, 20, 4)); $atom_structure['preferred_volume'] = getid3_lib::FixedPoint8_8(substr($atom_data, 24, 2)); $atom_structure['reserved'] = substr($atom_data, 26, 10); $atom_structure['matrix_a'] = getid3_lib::FixedPoint16_16(substr($atom_data, 36, 4)); $atom_structure['matrix_b'] = getid3_lib::FixedPoint16_16(substr($atom_data, 40, 4)); $atom_structure['matrix_u'] = getid3_lib::FixedPoint2_30(substr($atom_data, 44, 4)); $atom_structure['matrix_c'] = getid3_lib::FixedPoint16_16(substr($atom_data, 48, 4)); $atom_structure['matrix_d'] = getid3_lib::FixedPoint16_16(substr($atom_data, 52, 4)); $atom_structure['matrix_v'] = getid3_lib::FixedPoint2_30(substr($atom_data, 56, 4)); $atom_structure['matrix_x'] = getid3_lib::FixedPoint16_16(substr($atom_data, 60, 4)); $atom_structure['matrix_y'] = getid3_lib::FixedPoint16_16(substr($atom_data, 64, 4)); $atom_structure['matrix_w'] = getid3_lib::FixedPoint2_30(substr($atom_data, 68, 4)); $atom_structure['preview_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 72, 4)); $atom_structure['preview_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 76, 4)); $atom_structure['poster_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 80, 4)); $atom_structure['selection_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 84, 4)); $atom_structure['selection_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 88, 4)); $atom_structure['current_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 92, 4)); $atom_structure['next_track_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 96, 4)); if ($atom_structure['time_scale'] == 0) { $info['error'][] = 'Corrupt Quicktime file: mvhd.time_scale == zero'; return false; } $atom_structure['creation_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['creation_time']); $atom_structure['modify_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['modify_time']); $info['quicktime']['time_scale'] = isset($info['quicktime']['time_scale']) ? max($info['quicktime']['time_scale'], $atom_structure['time_scale']) : $atom_structure['time_scale']; $info['quicktime']['display_scale'] = $atom_structure['matrix_a']; $info['playtime_seconds'] = $atom_structure['duration'] / $atom_structure['time_scale']; break; case 'tkhd': // TracK HeaDer atom $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); $atom_structure['creation_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); $atom_structure['modify_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); $atom_structure['trackid'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); $atom_structure['reserved1'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); $atom_structure['duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 4)); $atom_structure['reserved2'] = getid3_lib::BigEndian2Int(substr($atom_data, 24, 8)); $atom_structure['layer'] = getid3_lib::BigEndian2Int(substr($atom_data, 32, 2)); $atom_structure['alternate_group'] = getid3_lib::BigEndian2Int(substr($atom_data, 34, 2)); $atom_structure['volume'] = getid3_lib::FixedPoint8_8(substr($atom_data, 36, 2)); $atom_structure['reserved3'] = getid3_lib::BigEndian2Int(substr($atom_data, 38, 2)); // http://developer.apple.com/library/mac/#documentation/QuickTime/RM/MovieBasics/MTEditing/K-Chapter/11MatrixFunctions.html // http://developer.apple.com/library/mac/#documentation/QuickTime/qtff/QTFFChap4/qtff4.html#//apple_ref/doc/uid/TP40000939-CH206-18737 $atom_structure['matrix_a'] = getid3_lib::FixedPoint16_16(substr($atom_data, 40, 4)); $atom_structure['matrix_b'] = getid3_lib::FixedPoint16_16(substr($atom_data, 44, 4)); $atom_structure['matrix_u'] = getid3_lib::FixedPoint2_30(substr($atom_data, 48, 4)); $atom_structure['matrix_c'] = getid3_lib::FixedPoint16_16(substr($atom_data, 52, 4)); $atom_structure['matrix_d'] = getid3_lib::FixedPoint16_16(substr($atom_data, 56, 4)); $atom_structure['matrix_v'] = getid3_lib::FixedPoint2_30(substr($atom_data, 60, 4)); $atom_structure['matrix_x'] = getid3_lib::FixedPoint16_16(substr($atom_data, 64, 4)); $atom_structure['matrix_y'] = getid3_lib::FixedPoint16_16(substr($atom_data, 68, 4)); $atom_structure['matrix_w'] = getid3_lib::FixedPoint2_30(substr($atom_data, 72, 4)); $atom_structure['width'] = getid3_lib::FixedPoint16_16(substr($atom_data, 76, 4)); $atom_structure['height'] = getid3_lib::FixedPoint16_16(substr($atom_data, 80, 4)); $atom_structure['flags']['enabled'] = (bool) ($atom_structure['flags_raw'] & 0x1); $atom_structure['flags']['in_movie'] = (bool) ($atom_structure['flags_raw'] & 0x2); $atom_structure['flags']['in_preview'] = (bool) ($atom_structure['flags_raw'] & 0x4); $atom_structure['flags']['in_poster'] = (bool) ($atom_structure['flags_raw'] & 0x8); $atom_structure['creation_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['creation_time']); $atom_structure['modify_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['modify_time']); if ($atom_structure['flags']['enabled'] == 1) { if (!isset($info['video']['resolution_x']) || !isset($info['video']['resolution_y'])) { $info['video']['resolution_x'] = $atom_structure['width']; $info['video']['resolution_y'] = $atom_structure['height']; } $info['video']['resolution_x'] = max($info['video']['resolution_x'], $atom_structure['width']); $info['video']['resolution_y'] = max($info['video']['resolution_y'], $atom_structure['height']); $info['quicktime']['video']['resolution_x'] = $info['video']['resolution_x']; $info['quicktime']['video']['resolution_y'] = $info['video']['resolution_y']; } else { // see: http://www.getid3.org/phpBB3/viewtopic.php?t=1295 //if (isset($info['video']['resolution_x'])) { unset($info['video']['resolution_x']); } //if (isset($info['video']['resolution_y'])) { unset($info['video']['resolution_y']); } //if (isset($info['quicktime']['video'])) { unset($info['quicktime']['video']); } } break; case 'iods': // Initial Object DeScriptor atom // http://www.koders.com/c/fid1FAB3E762903DC482D8A246D4A4BF9F28E049594.aspx?s=windows.h // http://libquicktime.sourcearchive.com/documentation/1.0.2plus-pdebian/iods_8c-source.html $offset = 0; $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); $offset += 1; $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 3)); $offset += 3; $atom_structure['mp4_iod_tag'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); $offset += 1; $atom_structure['length'] = $this->quicktime_read_mp4_descr_length($atom_data, $offset); //$offset already adjusted by quicktime_read_mp4_descr_length() $atom_structure['object_descriptor_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 2)); $offset += 2; $atom_structure['od_profile_level'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); $offset += 1; $atom_structure['scene_profile_level'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); $offset += 1; $atom_structure['audio_profile_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); $offset += 1; $atom_structure['video_profile_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); $offset += 1; $atom_structure['graphics_profile_level'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); $offset += 1; $atom_structure['num_iods_tracks'] = ($atom_structure['length'] - 7) / 6; // 6 bytes would only be right if all tracks use 1-byte length fields for ($i = 0; $i < $atom_structure['num_iods_tracks']; $i++) { $atom_structure['track'][$i]['ES_ID_IncTag'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); $offset += 1; $atom_structure['track'][$i]['length'] = $this->quicktime_read_mp4_descr_length($atom_data, $offset); //$offset already adjusted by quicktime_read_mp4_descr_length() $atom_structure['track'][$i]['track_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 4)); $offset += 4; } $atom_structure['audio_profile_name'] = $this->QuicktimeIODSaudioProfileName($atom_structure['audio_profile_id']); $atom_structure['video_profile_name'] = $this->QuicktimeIODSvideoProfileName($atom_structure['video_profile_id']); break; case 'ftyp': // FileTYPe (?) atom (for MP4 it seems) $atom_structure['signature'] = substr($atom_data, 0, 4); $atom_structure['unknown_1'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); $atom_structure['fourcc'] = substr($atom_data, 8, 4); break; case 'mdat': // Media DATa atom // 'mdat' contains the actual data for the audio/video, possibly also subtitles /* 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 */ // first, skip any 'wide' padding, and second 'mdat' header (with specified size of zero?) $mdat_offset = 0; while (true) { if (substr($atom_data, $mdat_offset, 8) == "" . 'wide') { $mdat_offset += 8; } elseif (substr($atom_data, $mdat_offset, 8) == "" . 'mdat') { $mdat_offset += 8; } else { break; } } // check to see if it looks like chapter titles, in the form of unterminated strings with a leading 16-bit size field while (($chapter_string_length = getid3_lib::BigEndian2Int(substr($atom_data, $mdat_offset, 2))) && $chapter_string_length < 1000 && $chapter_string_length <= strlen($atom_data) - $mdat_offset - 2 && preg_match('#^[\\x20-\\xFF]+$#', substr($atom_data, $mdat_offset + 2, $chapter_string_length), $chapter_matches)) { $mdat_offset += 2 + $chapter_string_length; @($info['quicktime']['comments']['chapters'][] = $chapter_matches[0]); } if ($atomsize > 8 && (!isset($info['avdataend_tmp']) || $info['quicktime'][$atomname]['size'] > $info['avdataend_tmp'] - $info['avdataoffset'])) { $info['avdataoffset'] = $atom_structure['offset'] + 8; // $info['quicktime'][$atomname]['offset'] + 8; $OldAVDataEnd = $info['avdataend']; $info['avdataend'] = $atom_structure['offset'] + $atom_structure['size']; // $info['quicktime'][$atomname]['offset'] + $info['quicktime'][$atomname]['size']; $getid3_temp = new getID3(); $getid3_temp->openfile($this->getid3->filename); $getid3_temp->info['avdataoffset'] = $info['avdataoffset']; $getid3_temp->info['avdataend'] = $info['avdataend']; $getid3_mp3 = new getid3_mp3($getid3_temp); if ($getid3_mp3->MPEGaudioHeaderValid($getid3_mp3->MPEGaudioHeaderDecode($this->fread(4)))) { $getid3_mp3->getOnlyMPEGaudioInfo($getid3_temp->info['avdataoffset'], false); if (!empty($getid3_temp->info['warning'])) { foreach ($getid3_temp->info['warning'] as $value) { $info['warning'][] = $value; } } if (!empty($getid3_temp->info['mpeg'])) { $info['mpeg'] = $getid3_temp->info['mpeg']; if (isset($info['mpeg']['audio'])) { $info['audio']['dataformat'] = 'mp3'; $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')); $info['audio']['sample_rate'] = $info['mpeg']['audio']['sample_rate']; $info['audio']['channels'] = $info['mpeg']['audio']['channels']; $info['audio']['bitrate'] = $info['mpeg']['audio']['bitrate']; $info['audio']['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']); $info['bitrate'] = $info['audio']['bitrate']; } } } unset($getid3_mp3, $getid3_temp); $info['avdataend'] = $OldAVDataEnd; unset($OldAVDataEnd); } unset($mdat_offset, $chapter_string_length, $chapter_matches); break; case 'free': // FREE space atom // FREE space atom case 'skip': // SKIP atom // SKIP atom case 'wide': // 64-bit expansion placeholder atom // 'free', 'skip' and 'wide' are just padding, contains no useful data at all // When writing QuickTime files, it is sometimes necessary to update an atom's size. // It is impossible to update a 32-bit atom to a 64-bit atom since the 32-bit atom // is only 8 bytes in size, and the 64-bit atom requires 16 bytes. Therefore, QuickTime // puts an 8-byte placeholder atom before any atoms it may have to update the size of. // In this way, if the atom needs to be converted from a 32-bit to a 64-bit atom, the // placeholder atom can be overwritten to obtain the necessary 8 extra bytes. // The placeholder atom has a type of kWideAtomPlaceholderType ( 'wide' ). break; case 'nsav': // NoSAVe atom // http://developer.apple.com/technotes/tn/tn2038.html $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); break; case 'ctyp': // Controller TYPe atom (seen on QTVR) // http://homepages.slingshot.co.nz/~helmboy/quicktime/formats/qtm-layout.txt // some controller names are: // 0x00 + 'std' for linear movie // 'none' for no controls $atom_structure['ctyp'] = substr($atom_data, 0, 4); $info['quicktime']['controller'] = $atom_structure['ctyp']; switch ($atom_structure['ctyp']) { case 'qtvr': $info['video']['dataformat'] = 'quicktimevr'; break; } break; case 'pano': // PANOrama track (seen on QTVR) $atom_structure['pano'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); break; case 'hint': // HINT track // HINT track case 'hinf': // // case 'hinv': // // case 'hnti': // $info['quicktime']['hinting'] = true; break; case 'imgt': // IMaGe Track reference (kQTVRImageTrackRefType) (seen on QTVR) for ($i = 0; $i < $atom_structure['size'] - 8; $i += 4) { $atom_structure['imgt'][] = getid3_lib::BigEndian2Int(substr($atom_data, $i, 4)); } break; // Observed-but-not-handled atom types are just listed here to prevent warnings being generated // Observed-but-not-handled atom types are just listed here to prevent warnings being generated case 'FXTC': // Something to do with Adobe After Effects (?) // Something to do with Adobe After Effects (?) case 'PrmA': case 'code': case 'FIEL': // this is NOT "fiel" (Field Ordering) as describe here: http://developer.apple.com/documentation/QuickTime/QTFF/QTFFChap3/chapter_4_section_2.html // this is NOT "fiel" (Field Ordering) as describe here: http://developer.apple.com/documentation/QuickTime/QTFF/QTFFChap3/chapter_4_section_2.html case 'tapt': // TrackApertureModeDimensionsAID - http://developer.apple.com/documentation/QuickTime/Reference/QT7-1_Update_Reference/Constants/Constants.html // tapt seems to be used to compute the video size [http://www.getid3.org/phpBB3/viewtopic.php?t=838] // * http://lists.apple.com/archives/quicktime-api/2006/Aug/msg00014.html // * http://handbrake.fr/irclogs/handbrake-dev/handbrake-dev20080128_pg2.html // TrackApertureModeDimensionsAID - http://developer.apple.com/documentation/QuickTime/Reference/QT7-1_Update_Reference/Constants/Constants.html // tapt seems to be used to compute the video size [http://www.getid3.org/phpBB3/viewtopic.php?t=838] // * http://lists.apple.com/archives/quicktime-api/2006/Aug/msg00014.html // * http://handbrake.fr/irclogs/handbrake-dev/handbrake-dev20080128_pg2.html case 'ctts': // STCompositionOffsetAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html // STCompositionOffsetAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html case 'cslg': // STCompositionShiftLeastGreatestAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html // STCompositionShiftLeastGreatestAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html case 'sdtp': // STSampleDependencyAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html // STSampleDependencyAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html case 'stps': // STPartialSyncSampleAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html //$atom_structure['data'] = $atom_data; break; case "©" . 'xyz': // GPS latitude+longitude+altitude $atom_structure['data'] = $atom_data; if (preg_match('#([\\+\\-][0-9\\.]+)([\\+\\-][0-9\\.]+)([\\+\\-][0-9\\.]+)?/$#i', $atom_data, $matches)) { @(list($all, $latitude, $longitude, $altitude) = $matches); $info['quicktime']['comments']['gps_latitude'][] = floatval($latitude); $info['quicktime']['comments']['gps_longitude'][] = floatval($longitude); if (!empty($altitude)) { $info['quicktime']['comments']['gps_altitude'][] = floatval($altitude); } } else { $info['warning'][] = 'QuickTime atom "©xyz" data does not match expected data pattern at offset ' . $baseoffset . '. Please report as getID3() bug.'; } break; case 'NCDT': // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html // Nikon-specific QuickTime tags found in the NCDT atom of MOV videos from some Nikon cameras such as the Coolpix S8000 and D5100 $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 4, $atomHierarchy, $ParseAllPossibleAtoms); break; case 'NCTH': // Nikon Camera THumbnail image // Nikon Camera THumbnail image case 'NCVW': // Nikon Camera preVieW image // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html if (preg_match('/^\\xFF\\xD8\\xFF/', $atom_data)) { $atom_structure['data'] = $atom_data; $atom_structure['image_mime'] = 'image/jpeg'; $atom_structure['description'] = $atomname == 'NCTH' ? 'Nikon Camera Thumbnail Image' : ($atomname == 'NCVW' ? 'Nikon Camera Preview Image' : 'Nikon preview image'); $info['quicktime']['comments']['picture'][] = array('image_mime' => $atom_structure['image_mime'], 'data' => $atom_data, 'description' => $atom_structure['description']); } break; case 'NCTG': // Nikon - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html#NCTG $atom_structure['data'] = $this->QuicktimeParseNikonNCTG($atom_data); break; case 'NCHD': // Nikon:MakerNoteVersion - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html // Nikon:MakerNoteVersion - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html case 'NCDB': // Nikon - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html // Nikon - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html case 'CNCV': // Canon:CompressorVersion - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Canon.html $atom_structure['data'] = $atom_data; break; case "": case 'meta': // METAdata atom // some kind of metacontainer, may contain a big data dump such as: // mdta keys mdtacom.apple.quicktime.make (mdtacom.apple.quicktime.creationdate ,mdtacom.apple.quicktime.location.ISO6709 $mdtacom.apple.quicktime.software !mdtacom.apple.quicktime.model ilst data DEApple 0 (data DE2011-05-11T17:54:04+0200 2 *data DE+52.4936+013.3897+040.247/ data DE4.3.1 data DEiPhone 4 // http://www.geocities.com/xhelmboyx/quicktime/formats/qti-layout.txt $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom(substr($atom_data, 4), $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); //$atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); break; case 'data': // metaDATA atom // seems to be 2 bytes language code (ASCII), 2 bytes unknown (set to 0x10B5 in sample I have), remainder is useful data $atom_structure['language'] = substr($atom_data, 4 + 0, 2); $atom_structure['unknown'] = getid3_lib::BigEndian2Int(substr($atom_data, 4 + 2, 2)); $atom_structure['data'] = substr($atom_data, 4 + 4); break; default: $info['warning'][] = 'Unknown QuickTime atom type: "' . $atomname . '" (' . trim(getid3_lib::PrintHexBytes($atomname)) . ') at offset ' . $baseoffset; $atom_structure['data'] = $atom_data; break; } array_pop($atomHierarchy); return $atom_structure; }
function getid3_quicktime(&$fd, &$ThisFileInfo, $ReturnAtomData = true, $ParseAllPossibleAtoms = false) { $ThisFileInfo['fileformat'] = 'quicktime'; $ThisFileInfo['quicktime']['hinting'] = false; fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); $offset = 0; $atomcounter = 0; while ($offset < $ThisFileInfo['avdataend']) { if ($offset >= pow(2, 31)) { $ThisFileInfo['error'][] = 'Unable to parse atom at offset ' . $offset . ' because beyond 2GB limit of PHP filesystem functions'; break; } fseek($fd, $offset, SEEK_SET); $AtomHeader = fread($fd, 8); $atomsize = getid3_lib::BigEndian2Int(substr($AtomHeader, 0, 4)); $atomname = substr($AtomHeader, 4, 4); // 64-bit MOV patch by jlegate�ktnc*com if ($atomsize == 1) { $atomsize = getid3_lib::BigEndian2Int(fread($fd, 8)); } $ThisFileInfo['quicktime'][$atomname]['name'] = $atomname; $ThisFileInfo['quicktime'][$atomname]['size'] = $atomsize; $ThisFileInfo['quicktime'][$atomname]['offset'] = $offset; if ($offset + $atomsize > $ThisFileInfo['avdataend']) { $ThisFileInfo['error'][] = 'Atom at offset ' . $offset . ' claims to go beyond end-of-file (length: ' . $atomsize . ' bytes)'; return false; } if ($atomsize == 0) { // Furthermore, for historical reasons the list of atoms is optionally // terminated by a 32-bit integer set to 0. If you are writing a program // to read user data atoms, you should allow for the terminating 0. break; } switch ($atomname) { case 'mdat': // Media DATa atom // 'mdat' contains the actual data for the audio/video if ($atomsize > 8 && (!isset($ThisFileInfo['avdataend_tmp']) || $ThisFileInfo['quicktime'][$atomname]['size'] > $ThisFileInfo['avdataend_tmp'] - $ThisFileInfo['avdataoffset'])) { $ThisFileInfo['avdataoffset'] = $ThisFileInfo['quicktime'][$atomname]['offset'] + 8; $OldAVDataEnd = $ThisFileInfo['avdataend']; $ThisFileInfo['avdataend'] = $ThisFileInfo['quicktime'][$atomname]['offset'] + $ThisFileInfo['quicktime'][$atomname]['size']; if (getid3_mp3::MPEGaudioHeaderValid(getid3_mp3::MPEGaudioHeaderDecode(fread($fd, 4)))) { getid3_mp3::getOnlyMPEGaudioInfo($fd, $ThisFileInfo, $ThisFileInfo['avdataoffset'], false); if (isset($ThisFileInfo['mpeg']['audio'])) { $ThisFileInfo['audio']['dataformat'] = 'mp3'; $ThisFileInfo['audio']['codec'] = !empty($ThisFileInfo['mpeg']['audio']['encoder']) ? $ThisFileInfo['mpeg']['audio']['encoder'] : (!empty($ThisFileInfo['mpeg']['audio']['codec']) ? $ThisFileInfo['mpeg']['audio']['codec'] : (!empty($ThisFileInfo['mpeg']['audio']['LAME']) ? 'LAME' : 'mp3')); $ThisFileInfo['audio']['sample_rate'] = $ThisFileInfo['mpeg']['audio']['sample_rate']; $ThisFileInfo['audio']['channels'] = $ThisFileInfo['mpeg']['audio']['channels']; $ThisFileInfo['audio']['bitrate'] = $ThisFileInfo['mpeg']['audio']['bitrate']; $ThisFileInfo['audio']['bitrate_mode'] = strtolower($ThisFileInfo['mpeg']['audio']['bitrate_mode']); $ThisFileInfo['bitrate'] = $ThisFileInfo['audio']['bitrate']; } } $ThisFileInfo['avdataend'] = $OldAVDataEnd; unset($OldAVDataEnd); } break; case 'free': // FREE space atom // FREE space atom case 'skip': // SKIP atom // SKIP atom case 'wide': // 64-bit expansion placeholder atom // 'free', 'skip' and 'wide' are just padding, contains no useful data at all break; default: $atomHierarchy = array(); $ThisFileInfo['quicktime'][$atomname] = $this->QuicktimeParseAtom($atomname, $atomsize, fread($fd, $atomsize), $ThisFileInfo, $offset, $atomHierarchy, $ParseAllPossibleAtoms); break; } $offset += $atomsize; $atomcounter++; } if (!empty($ThisFileInfo['avdataend_tmp'])) { // this value is assigned to a temp value and then erased because // otherwise any atoms beyond the 'mdat' atom would not get parsed $ThisFileInfo['avdataend'] = $ThisFileInfo['avdataend_tmp']; unset($ThisFileInfo['avdataend_tmp']); } if (!isset($ThisFileInfo['bitrate']) && isset($ThisFileInfo['playtime_seconds'])) { $ThisFileInfo['bitrate'] = ($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8 / $ThisFileInfo['playtime_seconds']; } if (isset($ThisFileInfo['bitrate']) && !isset($ThisFileInfo['audio']['bitrate']) && !isset($ThisFileInfo['quicktime']['video'])) { $ThisFileInfo['audio']['bitrate'] = $ThisFileInfo['bitrate']; } if (@$ThisFileInfo['playtime_seconds'] && !isset($ThisFileInfo['video']['frame_rate']) && !empty($ThisFileInfo['quicktime']['stts_framecount'])) { foreach ($ThisFileInfo['quicktime']['stts_framecount'] as $key => $samples_count) { $samples_per_second = $samples_count / $ThisFileInfo['playtime_seconds']; if ($samples_per_second > 240) { // has to be audio samples } else { $ThisFileInfo['video']['frame_rate'] = $samples_per_second; break; } } } if ($ThisFileInfo['audio']['dataformat'] == 'mp4' && empty($ThisFileInfo['video']['resolution_x'])) { $ThisFileInfo['fileformat'] = 'mp4'; $ThisFileInfo['mime_type'] = 'audio/mp4'; unset($ThisFileInfo['video']['dataformat']); } if (!$ReturnAtomData) { unset($ThisFileInfo['quicktime']['moov']); } if (empty($ThisFileInfo['audio']['dataformat']) && !empty($ThisFileInfo['quicktime']['audio'])) { $ThisFileInfo['audio']['dataformat'] = 'quicktime'; } if (empty($ThisFileInfo['video']['dataformat']) && !empty($ThisFileInfo['quicktime']['video'])) { $ThisFileInfo['video']['dataformat'] = 'quicktime'; } return true; }
public function Analyze() { $getid3 = $this->getid3; $getid3->info['mpeg']['video']['raw'] = array(); $info_mpeg_video =& $getid3->info['mpeg']['video']; $info_mpeg_video_raw =& $info_mpeg_video['raw']; $getid3->info['video'] = array(); $info_video =& $getid3->info['video']; $getid3->include_module('audio.mp3'); if ($getid3->info['avdataend'] <= $getid3->info['avdataoffset']) { throw new getid3_exception('"avdataend" (' . $getid3->info['avdataend'] . ') is unexpectedly less-than-or-equal-to "avdataoffset" (' . $getid3->info['avdataoffset'] . ')'); } $getid3->info['fileformat'] = 'mpeg'; fseek($getid3->fp, $getid3->info['avdataoffset'], SEEK_SET); $mpeg_stream_data = fread($getid3->fp, min(100000, $getid3->info['avdataend'] - $getid3->info['avdataoffset'])); $mpeg_stream_data_length = strlen($mpeg_stream_data); $video_chunk_offset = 0; while (substr($mpeg_stream_data, $video_chunk_offset++, 4) !== getid3_mpeg::VIDEO_SEQUENCE_HEADER) { if ($video_chunk_offset >= $mpeg_stream_data_length) { throw new getid3_exception('Could not find start of video block in the first 100,000 bytes (or before end of file) - this might not be an MPEG-video file?'); } } // Start code 32 bits // horizontal frame size 12 bits // vertical frame size 12 bits // pixel aspect ratio 4 bits // frame rate 4 bits // bitrate 18 bits // marker bit 1 bit // VBV buffer size 10 bits // constrained parameter flag 1 bit // intra quant. matrix flag 1 bit // intra quant. matrix values 512 bits (present if matrix flag == 1) // non-intra quant. matrix flag 1 bit // non-intra quant. matrix values 512 bits (present if matrix flag == 1) $info_video['dataformat'] = 'mpeg'; $video_chunk_offset += strlen(getid3_mpeg::VIDEO_SEQUENCE_HEADER) - 1; $frame_size_dword = getid3_lib::BigEndian2Int(substr($mpeg_stream_data, $video_chunk_offset, 3)); $video_chunk_offset += 3; $aspect_ratio_frame_rate_dword = getid3_lib::BigEndian2Int(substr($mpeg_stream_data, $video_chunk_offset, 1)); $video_chunk_offset += 1; $assorted_information = getid3_lib::BigEndian2Bin(substr($mpeg_stream_data, $video_chunk_offset, 4)); $video_chunk_offset += 4; $info_mpeg_video_raw['framesize_horizontal'] = ($frame_size_dword & 0xfff000) >> 12; // 12 bits for horizontal frame size $info_mpeg_video_raw['framesize_vertical'] = $frame_size_dword & 0xfff; // 12 bits for vertical frame size $info_mpeg_video_raw['pixel_aspect_ratio'] = ($aspect_ratio_frame_rate_dword & 0xf0) >> 4; $info_mpeg_video_raw['frame_rate'] = $aspect_ratio_frame_rate_dword & 0xf; $info_mpeg_video['framesize_horizontal'] = $info_mpeg_video_raw['framesize_horizontal']; $info_mpeg_video['framesize_vertical'] = $info_mpeg_video_raw['framesize_vertical']; $info_mpeg_video['pixel_aspect_ratio'] = $this->MPEGvideoAspectRatioLookup($info_mpeg_video_raw['pixel_aspect_ratio']); $info_mpeg_video['pixel_aspect_ratio_text'] = $this->MPEGvideoAspectRatioTextLookup($info_mpeg_video_raw['pixel_aspect_ratio']); $info_mpeg_video['frame_rate'] = $this->MPEGvideoFramerateLookup($info_mpeg_video_raw['frame_rate']); $info_mpeg_video_raw['bitrate'] = bindec(substr($assorted_information, 0, 18)); $info_mpeg_video_raw['marker_bit'] = (bool) bindec($assorted_information[18]); $info_mpeg_video_raw['vbv_buffer_size'] = bindec(substr($assorted_information, 19, 10)); $info_mpeg_video_raw['constrained_param_flag'] = (bool) bindec($assorted_information[29]); $info_mpeg_video_raw['intra_quant_flag'] = (bool) bindec($assorted_information[30]); if ($info_mpeg_video_raw['intra_quant_flag']) { // read 512 bits $info_mpeg_video_raw['intra_quant'] = getid3_lib::BigEndian2Bin(substr($mpeg_stream_data, $video_chunk_offset, 64)); $video_chunk_offset += 64; $info_mpeg_video_raw['non_intra_quant_flag'] = (bool) bindec($info_mpeg_video_raw['intra_quant'][511]); $info_mpeg_video_raw['intra_quant'] = bindec($assorted_information[31]) . substr(getid3_lib::BigEndian2Bin(substr($mpeg_stream_data, $video_chunk_offset, 64)), 0, 511); if ($info_mpeg_video_raw['non_intra_quant_flag']) { $info_mpeg_video_raw['non_intra_quant'] = substr($mpeg_stream_data, $video_chunk_offset, 64); $video_chunk_offset += 64; } } else { $info_mpeg_video_raw['non_intra_quant_flag'] = (bool) bindec($assorted_information[31]); if ($info_mpeg_video_raw['non_intra_quant_flag']) { $info_mpeg_video_raw['non_intra_quant'] = substr($mpeg_stream_data, $video_chunk_offset, 64); $video_chunk_offset += 64; } } if ($info_mpeg_video_raw['bitrate'] == 0x3ffff) { // 18 set bits $getid3->warning('This version of getID3() cannot determine average bitrate of VBR MPEG video files'); $info_mpeg_video['bitrate_mode'] = 'vbr'; } else { $info_mpeg_video['bitrate'] = $info_mpeg_video_raw['bitrate'] * 400; $info_mpeg_video['bitrate_mode'] = 'cbr'; $info_video['bitrate'] = $info_mpeg_video['bitrate']; } $info_video['resolution_x'] = $info_mpeg_video['framesize_horizontal']; $info_video['resolution_y'] = $info_mpeg_video['framesize_vertical']; $info_video['frame_rate'] = $info_mpeg_video['frame_rate']; $info_video['bitrate_mode'] = $info_mpeg_video['bitrate_mode']; $info_video['pixel_aspect_ratio'] = $info_mpeg_video['pixel_aspect_ratio']; $info_video['lossless'] = false; $info_video['bits_per_sample'] = 24; //0x000001B3 begins the sequence_header of every MPEG video stream. //But in MPEG-2, this header must immediately be followed by an //extension_start_code (0x000001B5) with a sequence_extension ID (1). //(This extension contains all the additional MPEG-2 stuff.) //MPEG-1 doesn't have this extension, so that's a sure way to tell the //difference between MPEG-1 and MPEG-2 video streams. $info_video['codec'] = substr($mpeg_stream_data, $video_chunk_offset, 4) == getid3_mpeg::VIDEO_EXTENSION_START ? 'MPEG-2' : 'MPEG-1'; $audio_chunk_offset = 0; while (true) { while (substr($mpeg_stream_data, $audio_chunk_offset++, 4) !== getid3_mpeg::AUDIO_START) { if ($audio_chunk_offset >= $mpeg_stream_data_length) { break 2; } } for ($i = 0; $i <= 7; $i++) { // some files have the MPEG-audio header 8 bytes after the end of the $00 $00 $01 $C0 signature, some have it up to 13 bytes (or more?) after // I have no idea why or what the difference is, so this is a stupid hack. // If anybody has any better idea of what's going on, please let me know - info@getid3.org // make copy of info $dummy = $getid3->info; // clone getid3 - better safe than sorry $clone = clone $this->getid3; // check $mp3 = new getid3_mp3($clone); if ($mp3->decodeMPEGaudioHeader($getid3->fp, $audio_chunk_offset + 3 + 8 + $i, $dummy, false)) { $getid3->info = $dummy; $getid3->info['audio']['bitrate_mode'] = 'cbr'; $getid3->info['audio']['lossless'] = false; break 2; } // destroy copy unset($dummy); } } // Temporary hack to account for interleaving overhead: if (!empty($info_video['bitrate']) && !empty($getid3->info['audio']['bitrate'])) { $getid3->info['playtime_seconds'] = ($getid3->info['avdataend'] - $getid3->info['avdataoffset']) * 8 / ($info_video['bitrate'] + $getid3->info['audio']['bitrate']); // Interleaved MPEG audio/video files have a certain amount of overhead that varies // by both video and audio bitrates, and not in any sensible, linear/logarithmic patter // Use interpolated lookup tables to approximately guess how much is overhead, because // playtime is calculated as filesize / total-bitrate $getid3->info['playtime_seconds'] *= $this->MPEGsystemNonOverheadPercentage($info_video['bitrate'], $getid3->info['audio']['bitrate']); //switch ($info_video['bitrate']) { // case('5000000'): // $multiplier = 0.93292642112380355828048824319889; // break; // case('5500000'): // $multiplier = 0.93582895375200989965359777343219; // break; // case('6000000'): // $multiplier = 0.93796247714820932532911373859139; // break; // case('7000000'): // $multiplier = 0.9413264083635103463010117778776; // break; // default: // $multiplier = 1; // break; //} //$getid3->info['playtime_seconds'] *= $multiplier; //$getid3->warning('Interleaved MPEG audio/video playtime may be inaccurate. With current hack should be within a few seconds of accurate. Report to info@getid3.org if off by more than 10 seconds.'); if ($info_video['bitrate'] < 50000) { $getid3->warning('Interleaved MPEG audio/video playtime may be slightly inaccurate for video bitrates below 100kbps. Except in extreme low-bitrate situations, error should be less than 1%. Report to info@getid3.org if greater than this.'); } } return true; }
public function Analyze() { $getid3 = $this->getid3; $info =& $getid3->info; $getid3->include_module('audio.mp3'); $info['quicktime'] = array(); $info_quicktime =& $info['quicktime']; $info['fileformat'] = 'quicktime'; $info_quicktime['hinting'] = false; fseek($getid3->fp, $info['avdataoffset'], SEEK_SET); $offset = $atom_counter = 0; while ($offset < $info['avdataend']) { fseek($getid3->fp, $offset, SEEK_SET); $atom_header = fread($getid3->fp, 8); $atom_size = getid3_lib::BigEndian2Int(substr($atom_header, 0, 4)); $atom_name = substr($atom_header, 4, 4); $info_quicktime[$atom_name]['name'] = $atom_name; $info_quicktime[$atom_name]['size'] = $atom_size; $info_quicktime[$atom_name]['offset'] = $offset; if ($offset + $atom_size > $info['avdataend']) { throw new getid3_exception('Atom at offset ' . $offset . ' claims to go beyond end-of-file (length: ' . $atom_size . ' bytes)'); } if ($atom_size == 0) { // Furthermore, for historical reasons the list of atoms is optionally // terminated by a 32-bit integer set to 0. If you are writing a program // to read user data atoms, you should allow for the terminating 0. break; } switch ($atom_name) { case 'mdat': // Media DATa atom // 'mdat' contains the actual data for the audio/video if ($atom_size > 8 && (!isset($info['avdataend_tmp']) || $info_quicktime[$atom_name]['size'] > $info['avdataend_tmp'] - $info['avdataoffset'])) { $info['avdataoffset'] = $info_quicktime[$atom_name]['offset'] + 8; $old_av_data_end = $info['avdataend']; $info['avdataend'] = $info_quicktime[$atom_name]['offset'] + $info_quicktime[$atom_name]['size']; //// MP3 if (!$getid3->include_module_optional('audio.mp3')) { $getid3->warning('MP3 skipped because mpeg module is missing.'); } else { // Clone getid3 - messing with offsets - better safe than sorry $clone = clone $getid3; if (getid3_mp3::MPEGaudioHeaderValid(getid3_mp3::MPEGaudioHeaderDecode(fread($clone->fp, 4)))) { $mp3 = new getid3_mp3($clone); $mp3->AnalyzeMPEGaudioInfo(); // Import from clone and destroy if (isset($clone->info['mpeg']['audio'])) { $info['mpeg']['audio'] = $clone->info['mpeg']['audio']; $info['audio']['dataformat'] = 'mp3'; $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')); $info['audio']['sample_rate'] = $info['mpeg']['audio']['sample_rate']; $info['audio']['channels'] = $info['mpeg']['audio']['channels']; $info['audio']['bitrate'] = $info['mpeg']['audio']['bitrate']; $info['audio']['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']); $info['bitrate'] = $info['audio']['bitrate']; $getid3->warning($clone->warnings()); unset($clone); } } } $info['avdataend'] = $old_av_data_end; unset($old_av_data_end); } break; case 'free': // FREE space atom // FREE space atom case 'skip': // SKIP atom // SKIP atom case 'wide': // 64-bit expansion placeholder atom // 'free', 'skip' and 'wide' are just padding, contains no useful data at all break; default: $atom_hierarchy = array(); $info_quicktime[$atom_name] = $this->QuicktimeParseAtom($atom_name, $atom_size, fread($getid3->fp, $atom_size), $offset, $atom_hierarchy); break; } $offset += $atom_size; $atom_counter++; } if (!empty($info['avdataend_tmp'])) { // this value is assigned to a temp value and then erased because // otherwise any atoms beyond the 'mdat' atom would not get parsed $info['avdataend'] = $info['avdataend_tmp']; unset($info['avdataend_tmp']); } if (!isset($info['bitrate']) && isset($info['playtime_seconds'])) { $info['bitrate'] = ($info['avdataend'] - $info['avdataoffset']) * 8 / $info['playtime_seconds']; } if (isset($info['bitrate']) && !isset($info['audio']['bitrate']) && !isset($info_quicktime['video'])) { $info['audio']['bitrate'] = $info['bitrate']; } if (@$info['audio']['dataformat'] == 'mp4' && empty($info['video']['resolution_x'])) { $info['fileformat'] = 'mp4'; $info['mime_type'] = 'audio/mp4'; unset($info['video']['dataformat']); } if (!$getid3->option_extra_info) { unset($info_quicktime['moov']); } if (empty($info['audio']['dataformat']) && !empty($info_quicktime['audio'])) { $info['audio']['dataformat'] = 'quicktime'; } if (empty($info['video']['dataformat']) && !empty($info_quicktime['video'])) { $info['video']['dataformat'] = 'quicktime'; } return true; }
public function ParseRIFF($start_offset, $max_offset) { $getid3 = $this->getid3; $info =& $getid3->info; $endian_function = $this->endian_function; $max_offset = min($max_offset, $info['avdataend']); $riff_chunk = false; $this->fseek($start_offset, SEEK_SET); while ($this->ftell() < $max_offset) { $chunk_name = $this->fread(4); if (strlen($chunk_name) < 4) { throw new getid3_exception('Expecting chunk name at offset ' . ($this->ftell() - 4) . ' but found nothing. Aborting RIFF parsing.'); } $chunk_size = getid3_lib::$endian_function($this->fread(4)); if ($chunk_size == 0) { continue; throw new getid3_exception('Chunk size at offset ' . ($this->ftell() - 4) . ' is zero. Aborting RIFF parsing.'); } if ($chunk_size % 2 != 0) { // all structures are packed on word boundaries $chunk_size++; } switch ($chunk_name) { case 'LIST': $list_name = $this->fread(4); switch ($list_name) { case 'movi': case 'rec ': $riff_chunk[$list_name]['offset'] = $this->ftell() - 4; $riff_chunk[$list_name]['size'] = $chunk_size; static $parsed_audio_stream = false; if (!$parsed_audio_stream) { $where_we_were = $this->ftell(); $audio_chunk_header = $this->fread(12); $audio_chunk_stream_num = substr($audio_chunk_header, 0, 2); $audio_chunk_stream_type = substr($audio_chunk_header, 2, 2); $audio_chunk_size = getid3_lib::LittleEndian2Int(substr($audio_chunk_header, 4, 4)); if ($audio_chunk_stream_type == 'wb') { $first_four_bytes = substr($audio_chunk_header, 8, 4); //// MPEG if (preg_match('/^\\xFF[\\xE2-\\xE7\\xF2-\\xF7\\xFA-\\xFF][\\x00-\\xEB]/s', $first_four_bytes)) { if (!$getid3->include_module_optional('audio.mp3')) { $getid3->warning('MP3 skipped because mp3 module is missing.'); } elseif (getid3_mp3::MPEGaudioHeaderBytesValid($first_four_bytes)) { // Clone getid3 - messing with offsets - better safe than sorry $clone = clone $getid3; $clone->info['avdataoffset'] = $this->ftell() - 4; $clone->info['avdataend'] = $this->ftell() + $audio_chunk_size; $mp3 = new getid3_mp3($clone); $mp3->AnalyzeMPEGaudioInfo(); // Import from clone and destroy if (isset($clone->info['mpeg']['audio'])) { $info['mpeg']['audio'] = $clone->info['mpeg']['audio']; $info['audio']['dataformat'] = 'mp' . $info['mpeg']['audio']['layer']; $info['audio']['sample_rate'] = $info['mpeg']['audio']['sample_rate']; $info['audio']['channels'] = $info['mpeg']['audio']['channels']; $info['audio']['bitrate'] = $info['mpeg']['audio']['bitrate']; $info['audio']['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']); $info['bitrate'] = $info['audio']['bitrate']; $getid3->warning($clone->warnings()); unset($clone); } } } elseif (preg_match('/^\\x0B\\x77/s', $first_four_bytes)) { if (!$getid3->include_module_optional('audio.ac3')) { $getid3->warning('AC3 skipped because ac3 module is missing.'); } else { // Clone getid3 - messing with offsets - better safe than sorry $clone = clone $getid3; $clone->info['avdataoffset'] = $this->ftell() - 4; $clone->info['avdataend'] = $this->ftell() + $audio_chunk_size; // Analyze clone by fp $ac3 = new getid3_ac3($clone); $ac3->Analyze(); // Import from clone and destroy $info['audio'] = $clone->info['audio']; $info['ac3'] = $clone->info['ac3']; $getid3->warning($clone->warnings()); unset($clone); } } } $parsed_audio_stream = true; $this->fseek($where_we_were, SEEK_SET); } $this->fseek($chunk_size - 4, SEEK_CUR); break; default: if (!isset($riff_chunk[$list_name])) { $riff_chunk[$list_name] = array(); } $list_chunk_parent = $list_name; $list_chunk_max_offset = $this->ftell() - 4 + $chunk_size; if ($parsed_chunk = $this->ParseRIFF($this->ftell(), $this->ftell() + $chunk_size - 4)) { $riff_chunk[$list_name] = array_merge_recursive($riff_chunk[$list_name], $parsed_chunk); } break; } break; default: $this_index = 0; if (isset($riff_chunk[$chunk_name]) && is_array($riff_chunk[$chunk_name])) { $this_index = count($riff_chunk[$chunk_name]); } $riff_chunk[$chunk_name][$this_index]['offset'] = $this->ftell() - 8; $riff_chunk[$chunk_name][$this_index]['size'] = $chunk_size; switch ($chunk_name) { case 'data': $info['avdataoffset'] = $this->ftell(); $info['avdataend'] = $info['avdataoffset'] + $chunk_size; $riff_data_chunk_contents_test = $this->fread(36); //// This is probably MP3 data if (strlen($riff_data_chunk_contents_test) > 0 && preg_match('/^\\xFF[\\xE2-\\xE7\\xF2-\\xF7\\xFA-\\xFF][\\x00-\\xEB]/s', substr($riff_data_chunk_contents_test, 0, 4))) { try { if (!$getid3->include_module_optional('audio.mp3')) { $getid3->warning('MP3 skipped because mp3 module is missing.'); } // Clone getid3 - messing with offsets - better safe than sorry $clone = clone $getid3; if (getid3_mp3::MPEGaudioHeaderBytesValid(substr($riff_data_chunk_contents_test, 0, 4))) { $mp3 = new getid3_mp3($clone); $mp3->AnalyzeMPEGaudioInfo(); // Import from clone and destroy if (isset($clone->info['mpeg']['audio'])) { $info['mpeg']['audio'] = $clone->info['mpeg']['audio']; $info['audio']['sample_rate'] = $info['mpeg']['audio']['sample_rate']; $info['audio']['channels'] = $info['mpeg']['audio']['channels']; $info['audio']['bitrate'] = $info['mpeg']['audio']['bitrate']; $info['audio']['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']); $info['bitrate'] = $info['audio']['bitrate']; $getid3->warning($clone->warnings()); unset($clone); } } } catch (Exception $e) { // do nothing - not MP3 data } } elseif (strlen($riff_data_chunk_contents_test) > 0 && substr($riff_data_chunk_contents_test, 0, 2) == "\vw") { if (!$getid3->include_module_optional('audio.ac3')) { $getid3->warning('AC3 skipped because ac3 module is missing.'); } else { // Clone getid3 - messing with offsets - better safe than sorry $clone = clone $getid3; $clone->info['avdataoffset'] = $riff_chunk[$chunk_name][$this_index]['offset']; $clone->info['avdataend'] = $clone->info['avdataoffset'] + $riff_chunk[$chunk_name][$this_index]['size']; // Analyze clone by fp $ac3 = new getid3_ac3($clone); $ac3->Analyze(); // Import from clone and destroy $info['audio'] = $clone->info['audio']; $info['ac3'] = $clone->info['ac3']; $getid3->warning($clone->warnings()); unset($clone); } } elseif (strlen($riff_data_chunk_contents_test) > 0 && substr($riff_data_chunk_contents_test, 8, 2) == "w\v") { if (!$getid3->include_module_optional('audio.ac3')) { $getid3->warning('AC3 skipped because ac3 module is missing.'); } else { // Extract ac3 data to string $ac3_data = ''; for ($i = 0; $i < 28; $i += 2) { // swap byte order $ac3_data .= substr($riff_data_chunk_contents_test, 8 + $i + 1, 1); $ac3_data .= substr($riff_data_chunk_contents_test, 8 + $i + 0, 1); } // Clone getid3 - messing with offsets - better safe than sorry $clone = clone $getid3; $clone->info['avdataoffset'] = 0; $clone->info['avdataend'] = 20; // Analyse clone by string $ac3 = new getid3_ac3($clone); $ac3->AnalyzeString($ac3_data); // Import from clone and destroy $info['audio'] = $clone->info['audio']; $info['ac3'] = $clone->info['ac3']; $getid3->warning($clone->warnings()); unset($clone); } } if (strlen($riff_data_chunk_contents_test) > 0 && substr($riff_data_chunk_contents_test, 0, 4) == 'wvpk') { // This is WavPack data $info['wavpack']['offset'] = $riff_chunk[$chunk_name][$this_index]['offset']; $info['wavpack']['size'] = getid3_lib::LittleEndian2Int(substr($riff_data_chunk_contents_test, 4, 4)); $this->RIFFparseWavPackHeader(substr($riff_data_chunk_contents_test, 8, 28)); } else { // This is some other kind of data (quite possibly just PCM) // do nothing special, just skip it } $this->fseek($riff_chunk[$chunk_name][$this_index]['offset'] + 8 + $chunk_size, SEEK_SET); break; case 'bext': case 'cart': case 'fmt ': case 'MEXT': case 'DISP': // always read data in $riff_chunk[$chunk_name][$this_index]['data'] = $this->fread($chunk_size); break; default: if (!empty($list_chunk_parent) && $riff_chunk[$chunk_name][$this_index]['offset'] + $riff_chunk[$chunk_name][$this_index]['size'] <= $list_chunk_max_offset) { $riff_chunk[$list_chunk_parent][$chunk_name][$this_index]['offset'] = $riff_chunk[$chunk_name][$this_index]['offset']; $riff_chunk[$list_chunk_parent][$chunk_name][$this_index]['size'] = $riff_chunk[$chunk_name][$this_index]['size']; unset($riff_chunk[$chunk_name][$this_index]['offset']); unset($riff_chunk[$chunk_name][$this_index]['size']); if (isset($riff_chunk[$chunk_name][$this_index]) && empty($riff_chunk[$chunk_name][$this_index])) { unset($riff_chunk[$chunk_name][$this_index]); } if (isset($riff_chunk[$chunk_name]) && empty($riff_chunk[$chunk_name])) { unset($riff_chunk[$chunk_name]); } $riff_chunk[$list_chunk_parent][$chunk_name][$this_index]['data'] = $this->fread($chunk_size); } elseif ($chunk_size < 2048) { // only read data in if smaller than 2kB $riff_chunk[$chunk_name][$this_index]['data'] = $this->fread($chunk_size); } else { $this->fseek($chunk_size, SEEK_CUR); } break; } break; } } return $riff_chunk; }
function ParseRIFF($startoffset, $maxoffset) { $info =& $this->getid3->info; $maxoffset = min($maxoffset, $info['avdataend']); $RIFFchunk = false; $FoundAllChunksWeNeed = false; if ($startoffset < 0 || !getid3_lib::intValueSupported($startoffset)) { $info['warning'][] = 'Unable to ParseRIFF() at ' . $startoffset . ' because beyond ' . round(PHP_INT_MAX / 1073741824) . 'GB limit of PHP filesystem functions'; return false; } $max_usable_offset = min(PHP_INT_MAX - 1024, $maxoffset); if ($maxoffset > $max_usable_offset) { $info['warning'][] = 'ParseRIFF() may return incomplete data for chunk starting at ' . $startoffset . ' because beyond it extends to ' . $maxoffset . ', which is beyond the ' . round(PHP_INT_MAX / 1073741824) . 'GB limit of PHP filesystem functions'; } fseek($this->getid3->fp, $startoffset, SEEK_SET); while (ftell($this->getid3->fp) < $max_usable_offset) { $chunknamesize = fread($this->getid3->fp, 8); $chunkname = substr($chunknamesize, 0, 4); $chunksize = $this->EitherEndian2Int(substr($chunknamesize, 4, 4)); if (strlen($chunkname) < 4) { $info['error'][] = 'Expecting chunk name at offset ' . (ftell($this->getid3->fp) - 4) . ' but found nothing. Aborting RIFF parsing.'; break; } if ($chunksize == 0) { if ($chunkname == 'JUNK') { // we'll allow zero-size JUNK frames } else { $info['warning'][] = 'Chunk size at offset ' . (ftell($this->getid3->fp) - 4) . ' is zero. Aborting RIFF parsing.'; break; } } if ($chunksize % 2 != 0) { // all structures are packed on word boundaries $chunksize++; } switch ($chunkname) { case 'LIST': $listname = fread($this->getid3->fp, 4); if (preg_match('#^(movi|rec )$#i', $listname)) { $RIFFchunk[$listname]['offset'] = ftell($this->getid3->fp) - 4; $RIFFchunk[$listname]['size'] = $chunksize; if ($FoundAllChunksWeNeed) { // skip over } else { $WhereWeWere = ftell($this->getid3->fp); $AudioChunkHeader = fread($this->getid3->fp, 12); $AudioChunkStreamNum = substr($AudioChunkHeader, 0, 2); $AudioChunkStreamType = substr($AudioChunkHeader, 2, 2); $AudioChunkSize = getid3_lib::LittleEndian2Int(substr($AudioChunkHeader, 4, 4)); if ($AudioChunkStreamType == 'wb') { $FirstFourBytes = substr($AudioChunkHeader, 8, 4); if (preg_match('/^\\xFF[\\xE2-\\xE7\\xF2-\\xF7\\xFA-\\xFF][\\x00-\\xEB]/s', $FirstFourBytes)) { // MP3 if (getid3_mp3::MPEGaudioHeaderBytesValid($FirstFourBytes)) { $getid3_temp = new getID3(); $getid3_temp->openfile($this->getid3->filename); $getid3_temp->info['avdataoffset'] = ftell($this->getid3->fp) - 4; $getid3_temp->info['avdataend'] = ftell($this->getid3->fp) + $AudioChunkSize; $getid3_mp3 = new getid3_mp3($getid3_temp); $getid3_mp3->getOnlyMPEGaudioInfo($getid3_temp->info['avdataoffset'], false); if (isset($getid3_temp->info['mpeg']['audio'])) { $info['mpeg']['audio'] = $getid3_temp->info['mpeg']['audio']; $info['audio'] = $getid3_temp->info['audio']; $info['audio']['dataformat'] = 'mp' . $info['mpeg']['audio']['layer']; $info['audio']['sample_rate'] = $info['mpeg']['audio']['sample_rate']; $info['audio']['channels'] = $info['mpeg']['audio']['channels']; $info['audio']['bitrate'] = $info['mpeg']['audio']['bitrate']; $info['audio']['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']); //$info['bitrate'] = $info['audio']['bitrate']; } unset($getid3_temp, $getid3_mp3); } } elseif (preg_match('/^\\x0B\\x77/s', $FirstFourBytes)) { // AC3 if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.audio.ac3.php', __FILE__, false)) { $getid3_temp = new getID3(); $getid3_temp->openfile($this->getid3->filename); $getid3_temp->info['avdataoffset'] = ftell($this->getid3->fp) - 4; $getid3_temp->info['avdataend'] = ftell($this->getid3->fp) + $AudioChunkSize; $getid3_ac3 = new getid3_ac3($getid3_temp); $getid3_ac3->Analyze(); if (empty($getid3_temp->info['error'])) { $info['audio'] = $getid3_temp->info['audio']; $info['ac3'] = $getid3_temp->info['ac3']; if (!empty($getid3_temp->info['warning'])) { foreach ($getid3_temp->info['warning'] as $key => $value) { $info['warning'][] = $value; } } } unset($getid3_temp, $getid3_ac3); } } } $FoundAllChunksWeNeed = true; fseek($this->getid3->fp, $WhereWeWere, SEEK_SET); } fseek($this->getid3->fp, $chunksize - 4, SEEK_CUR); //} elseif (preg_match('#^[0-9]{2}(wb|pc|dc|db)$#i', $listname)) { // // // data chunk, ignore // } else { if (!isset($RIFFchunk[$listname])) { $RIFFchunk[$listname] = array(); } $LISTchunkParent = $listname; $LISTchunkMaxOffset = ftell($this->getid3->fp) - 4 + $chunksize; if ($parsedChunk = $this->ParseRIFF(ftell($this->getid3->fp), ftell($this->getid3->fp) + $chunksize - 4)) { $RIFFchunk[$listname] = array_merge_recursive($RIFFchunk[$listname], $parsedChunk); } } break; default: if (preg_match('#^[0-9]{2}(wb|pc|dc|db)$#', $chunkname)) { $nextoffset = ftell($this->getid3->fp) + $chunksize; if ($nextoffset < 0 || !getid3_lib::intValueSupported($nextoffset)) { $info['warning'][] = 'Unable to parse chunk at offset ' . $nextoffset . ' because beyond ' . round(PHP_INT_MAX / 1073741824) . 'GB limit of PHP filesystem functions'; break 2; } fseek($this->getid3->fp, $nextoffset, SEEK_SET); break; } $thisindex = 0; if (isset($RIFFchunk[$chunkname]) && is_array($RIFFchunk[$chunkname])) { $thisindex = count($RIFFchunk[$chunkname]); } $RIFFchunk[$chunkname][$thisindex]['offset'] = ftell($this->getid3->fp) - 8; $RIFFchunk[$chunkname][$thisindex]['size'] = $chunksize; switch ($chunkname) { case 'data': $info['avdataoffset'] = ftell($this->getid3->fp); $info['avdataend'] = $info['avdataoffset'] + $chunksize; $RIFFdataChunkContentsTest = fread($this->getid3->fp, 36); if (strlen($RIFFdataChunkContentsTest) > 0 && preg_match('/^\\xFF[\\xE2-\\xE7\\xF2-\\xF7\\xFA-\\xFF][\\x00-\\xEB]/s', substr($RIFFdataChunkContentsTest, 0, 4))) { // Probably is MP3 data if (getid3_mp3::MPEGaudioHeaderBytesValid(substr($RIFFdataChunkContentsTest, 0, 4))) { $getid3_temp = new getID3(); $getid3_temp->openfile($this->getid3->filename); $getid3_temp->info['avdataoffset'] = $RIFFchunk[$chunkname][$thisindex]['offset']; $getid3_temp->info['avdataend'] = $RIFFchunk[$chunkname][$thisindex]['offset'] + $RIFFchunk[$chunkname][$thisindex]['size']; $getid3_mp3 = new getid3_mp3($getid3_temp); $getid3_mp3->getOnlyMPEGaudioInfo($RIFFchunk[$chunkname][$thisindex]['offset'], false); if (empty($getid3_temp->info['error'])) { $info['mpeg'] = $getid3_temp->info['mpeg']; $info['audio'] = $getid3_temp->info['audio']; } unset($getid3_temp, $getid3_mp3); } } elseif (strlen($RIFFdataChunkContentsTest) > 0 && substr($RIFFdataChunkContentsTest, 0, 2) == "\vw") { // This is probably AC-3 data if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.audio.ac3.php', __FILE__, false)) { $getid3_temp = new getID3(); $getid3_temp->openfile($this->getid3->filename); $getid3_temp->info['avdataoffset'] = $RIFFchunk[$chunkname][$thisindex]['offset']; $getid3_temp->info['avdataend'] = $RIFFchunk[$chunkname][$thisindex]['offset'] + $RIFFchunk[$chunkname][$thisindex]['size']; $getid3_ac3 = new getid3_ac3($getid3_temp); $getid3_ac3->Analyze(); if (empty($getid3_temp->info['error'])) { $info['audio'] = $getid3_temp->info['audio']; $info['ac3'] = $getid3_temp->info['ac3']; $info['warning'] = $getid3_temp->info['warning']; } unset($getid3_temp, $getid3_ac3); } } elseif (strlen($RIFFdataChunkContentsTest) > 0 && substr($RIFFdataChunkContentsTest, 8, 2) == "w\v") { // Dolby Digital WAV // AC-3 content, but not encoded in same format as normal AC-3 file // For one thing, byte order is swapped if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.audio.ac3.php', __FILE__, false)) { // ok to use tmpfile here - only 56 bytes if ($RIFFtempfilename = tempnam(GETID3_TEMP_DIR, 'id3')) { if ($fd_temp = fopen($RIFFtempfilename, 'wb')) { for ($i = 0; $i < 28; $i += 2) { // swap byte order fwrite($fd_temp, substr($RIFFdataChunkContentsTest, 8 + $i + 1, 1)); fwrite($fd_temp, substr($RIFFdataChunkContentsTest, 8 + $i + 0, 1)); } fclose($fd_temp); $getid3_temp = new getID3(); $getid3_temp->openfile($RIFFtempfilename); $getid3_temp->info['avdataend'] = 20; $getid3_ac3 = new getid3_ac3($getid3_temp); $getid3_ac3->Analyze(); if (empty($getid3_temp->info['error'])) { $info['audio'] = $getid3_temp->info['audio']; $info['ac3'] = $getid3_temp->info['ac3']; $info['warning'] = $getid3_temp->info['warning']; } else { $info['error'][] = 'Error parsing Dolby Digital WAV (AC3-in-RIFF): ' . implode(';', $getid3_temp->info['error']); } unset($getid3_ac3, $getid3_temp); } else { $info['error'][] = 'Error parsing Dolby Digital WAV (AC3-in-RIFF): failed to write temp file'; } unlink($RIFFtempfilename); } else { $info['error'][] = 'Error parsing Dolby Digital WAV (AC3-in-RIFF): failed to write temp file'; } } } elseif (strlen($RIFFdataChunkContentsTest) > 0 && substr($RIFFdataChunkContentsTest, 0, 4) == 'wvpk') { // This is WavPack data $info['wavpack']['offset'] = $RIFFchunk[$chunkname][$thisindex]['offset']; $info['wavpack']['size'] = getid3_lib::LittleEndian2Int(substr($RIFFdataChunkContentsTest, 4, 4)); $this->RIFFparseWavPackHeader(substr($RIFFdataChunkContentsTest, 8, 28)); } else { // This is some other kind of data (quite possibly just PCM) // do nothing special, just skip it } $nextoffset = $RIFFchunk[$chunkname][$thisindex]['offset'] + 8 + $chunksize; if ($nextoffset < 0 || !getid3_lib::intValueSupported($nextoffset)) { $info['warning'][] = 'Unable to parse chunk at offset ' . $nextoffset . ' because beyond ' . round(PHP_INT_MAX / 1073741824) . 'GB limit of PHP filesystem functions'; break 3; } fseek($this->getid3->fp, $RIFFchunk[$chunkname][$thisindex]['offset'] + 8 + $chunksize, SEEK_SET); break; case 'iXML': case 'bext': case 'cart': case 'fmt ': case 'strh': case 'strf': case 'indx': case 'MEXT': case 'DISP': // always read data in // always read data in case 'JUNK': // should be: never read data in // but some programs write their version strings in a JUNK chunk (e.g. VirtualDub, AVIdemux, etc) if ($chunksize < 1048576) { if ($chunksize > 0) { $RIFFchunk[$chunkname][$thisindex]['data'] = fread($this->getid3->fp, $chunksize); if ($chunkname == 'JUNK') { if (preg_match('#^([\\x20-\\x7F]+)#', $RIFFchunk[$chunkname][$thisindex]['data'], $matches)) { // only keep text characters [chr(32)-chr(127)] $info['riff']['comments']['junk'][] = trim($matches[1]); } // but if nothing there, ignore // remove the key in either case unset($RIFFchunk[$chunkname][$thisindex]['data']); } } } else { $info['warning'][] = 'chunk "' . $chunkname . '" at offset ' . ftell($this->getid3->fp) . ' is unexpectedly larger than 1MB (claims to be ' . number_format($chunksize) . ' bytes), skipping data'; $nextoffset = ftell($this->getid3->fp) + $chunksize; if ($nextoffset < 0 || !getid3_lib::intValueSupported($nextoffset)) { $info['warning'][] = 'Unable to parse chunk at offset ' . $nextoffset . ' because beyond ' . round(PHP_INT_MAX / 1073741824) . 'GB limit of PHP filesystem functions'; break 3; } fseek($this->getid3->fp, $nextoffset, SEEK_SET); } break; default: if (!preg_match('#^[0-9]{2}(wb|pc|dc|db)$#', $chunkname) && !empty($LISTchunkParent) && $RIFFchunk[$chunkname][$thisindex]['offset'] + $RIFFchunk[$chunkname][$thisindex]['size'] <= $LISTchunkMaxOffset) { $RIFFchunk[$LISTchunkParent][$chunkname][$thisindex]['offset'] = $RIFFchunk[$chunkname][$thisindex]['offset']; $RIFFchunk[$LISTchunkParent][$chunkname][$thisindex]['size'] = $RIFFchunk[$chunkname][$thisindex]['size']; unset($RIFFchunk[$chunkname][$thisindex]['offset']); unset($RIFFchunk[$chunkname][$thisindex]['size']); if (isset($RIFFchunk[$chunkname][$thisindex]) && empty($RIFFchunk[$chunkname][$thisindex])) { unset($RIFFchunk[$chunkname][$thisindex]); } if (isset($RIFFchunk[$chunkname]) && empty($RIFFchunk[$chunkname])) { unset($RIFFchunk[$chunkname]); } $RIFFchunk[$LISTchunkParent][$chunkname][$thisindex]['data'] = fread($this->getid3->fp, $chunksize); //} elseif (in_array($chunkname, array('ID3 ')) || (($chunksize > 0) && ($chunksize < 2048))) { } elseif ($chunksize > 0 && $chunksize < 2048) { // only read data in if smaller than 2kB $RIFFchunk[$chunkname][$thisindex]['data'] = fread($this->getid3->fp, $chunksize); } else { $nextoffset = ftell($this->getid3->fp) + $chunksize; if ($nextoffset < 0 || !getid3_lib::intValueSupported($nextoffset)) { $info['warning'][] = 'Unable to parse chunk at offset ' . $nextoffset . ' because beyond ' . round(PHP_INT_MAX / 1073741824) . 'GB limit of PHP filesystem functions'; break 3; } fseek($this->getid3->fp, $nextoffset, SEEK_SET); } break; } break; } } return $RIFFchunk; }
public function Analyze() { $info =& $this->getid3->info; $info['fileformat'] = 'mpeg'; $this->fseek($info['avdataoffset']); $MPEGstreamData = $this->fread($this->getid3->option_fread_buffer_size); $MPEGstreamBaseOffset = 0; // how far are we from the beginning of the file data ($info['avdataoffset']) $MPEGstreamDataOffset = 0; // how far are we from the beginning of the buffer data (~32kB) $StartCodeValue = false; $prevStartCodeValue = false; $GOPcounter = -1; $FramesByGOP = array(); $ParsedAVchannels = array(); do { //echo $MPEGstreamDataOffset.' vs '.(strlen($MPEGstreamData) - 1024).'<Br>'; if ($MPEGstreamDataOffset > strlen($MPEGstreamData) - 16384) { // buffer running low, get more data //echo 'reading more data<br>'; $MPEGstreamData .= $this->fread($this->getid3->option_fread_buffer_size); if (strlen($MPEGstreamData) > $this->getid3->option_fread_buffer_size) { $MPEGstreamData = substr($MPEGstreamData, $MPEGstreamDataOffset); $MPEGstreamBaseOffset += $MPEGstreamDataOffset; $MPEGstreamDataOffset = 0; } } if (($StartCodeOffset = strpos($MPEGstreamData, self::START_CODE_BASE, $MPEGstreamDataOffset)) === false) { //echo 'no more start codes found.<br>'; break; } else { $MPEGstreamDataOffset = $StartCodeOffset; $prevStartCodeValue = $StartCodeValue; $StartCodeValue = ord(substr($MPEGstreamData, $StartCodeOffset + 3, 1)); //echo 'Found "'.strtoupper(dechex($StartCodeValue)).'" at offset '.($MPEGstreamBaseOffset + $StartCodeOffset).' ($MPEGstreamDataOffset = '.$MPEGstreamDataOffset.')<br>'; } $MPEGstreamDataOffset += 4; switch ($StartCodeValue) { case 0x0: // picture_start_code if ($info['mpeg']['video']['bitrate_mode'] == 'vbr') { $bitstream = getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $StartCodeOffset + 4, 4)); $bitstreamoffset = 0; $PictureHeader = array(); $PictureHeader['temporal_reference'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 10); // 10-bit unsigned integer associated with each input picture. It is incremented by one, modulo 1024, for each input frame. When a frame is coded as two fields the temporal reference in the picture header of both fields is the same. Following a group start header the temporal reference of the earliest picture (in display order) shall be reset to zero. $PictureHeader['picture_coding_type'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 3); // 3 bits for picture_coding_type $PictureHeader['vbv_delay'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 16); // 16 bits for vbv_delay //... etc $FramesByGOP[$GOPcounter][] = $PictureHeader; } break; case 0xb3: // sequence_header_code /* Note: purposely doing the less-pretty (and probably a bit slower) method of using string of bits rather than bitwise operations. Mostly because PHP 32-bit doesn't handle unsigned integers well for bitwise operation. Also the MPEG stream is designed as a bitstream and often doesn't align nicely with byte boundaries. */ $info['video']['codec'] = 'MPEG-1'; // will be updated if extension_start_code found $bitstream = getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $StartCodeOffset + 4, 8)); $bitstreamoffset = 0; $info['mpeg']['video']['raw']['horizontal_size_value'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 12); // 12 bits for horizontal frame size. Note: horizontal_size_extension, if present, will add 2 most-significant bits to this value $info['mpeg']['video']['raw']['vertical_size_value'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 12); // 12 bits for vertical frame size. Note: vertical_size_extension, if present, will add 2 most-significant bits to this value $info['mpeg']['video']['raw']['aspect_ratio_information'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 4); // 4 bits for aspect_ratio_information $info['mpeg']['video']['raw']['frame_rate_code'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 4); // 4 bits for Frame Rate id code $info['mpeg']['video']['raw']['bitrate'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 18); // 18 bits for bit_rate_value (18 set bits = VBR, otherwise bitrate = this value * 400) $marker_bit = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // The term "marker_bit" indicates a one bit field in which the value zero is forbidden. These marker bits are introduced at several points in the syntax to avoid start code emulation. $info['mpeg']['video']['raw']['vbv_buffer_size'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 10); // 10 bits vbv_buffer_size_value $info['mpeg']['video']['raw']['constrained_param_flag'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: constrained_param_flag $info['mpeg']['video']['raw']['load_intra_quantiser_matrix'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: load_intra_quantiser_matrix if ($info['mpeg']['video']['raw']['load_intra_quantiser_matrix']) { $bitstream .= getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $StartCodeOffset + 12, 64)); for ($i = 0; $i < 64; $i++) { $info['mpeg']['video']['raw']['intra_quantiser_matrix'][$i] = self::readBitsFromStream($bitstream, $bitstreamoffset, 8); } } $info['mpeg']['video']['raw']['load_non_intra_quantiser_matrix'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); if ($info['mpeg']['video']['raw']['load_non_intra_quantiser_matrix']) { $bitstream .= getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $StartCodeOffset + 12 + ($info['mpeg']['video']['raw']['load_intra_quantiser_matrix'] ? 64 : 0), 64)); for ($i = 0; $i < 64; $i++) { $info['mpeg']['video']['raw']['non_intra_quantiser_matrix'][$i] = self::readBitsFromStream($bitstream, $bitstreamoffset, 8); } } $info['mpeg']['video']['pixel_aspect_ratio'] = self::videoAspectRatioLookup($info['mpeg']['video']['raw']['aspect_ratio_information']); $info['mpeg']['video']['pixel_aspect_ratio_text'] = self::videoAspectRatioTextLookup($info['mpeg']['video']['raw']['aspect_ratio_information']); $info['mpeg']['video']['frame_rate'] = self::videoFramerateLookup($info['mpeg']['video']['raw']['frame_rate_code']); if ($info['mpeg']['video']['raw']['bitrate'] == 0x3ffff) { // 18 set bits = VBR //$this->warning('This version of getID3() ['.$this->getid3->version().'] cannot determine average bitrate of VBR MPEG video files'); $info['mpeg']['video']['bitrate_mode'] = 'vbr'; } else { $info['mpeg']['video']['bitrate'] = $info['mpeg']['video']['raw']['bitrate'] * 400; $info['mpeg']['video']['bitrate_mode'] = 'cbr'; $info['video']['bitrate'] = $info['mpeg']['video']['bitrate']; } $info['video']['resolution_x'] = $info['mpeg']['video']['raw']['horizontal_size_value']; $info['video']['resolution_y'] = $info['mpeg']['video']['raw']['vertical_size_value']; $info['video']['frame_rate'] = $info['mpeg']['video']['frame_rate']; $info['video']['bitrate_mode'] = $info['mpeg']['video']['bitrate_mode']; $info['video']['pixel_aspect_ratio'] = $info['mpeg']['video']['pixel_aspect_ratio']; $info['video']['lossless'] = false; $info['video']['bits_per_sample'] = 24; break; case 0xb5: // extension_start_code $info['video']['codec'] = 'MPEG-2'; $bitstream = getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $StartCodeOffset + 4, 8)); // 48 bits for Sequence Extension ID; 61 bits for Sequence Display Extension ID; 59 bits for Sequence Scalable Extension ID $bitstreamoffset = 0; $info['mpeg']['video']['raw']['extension_start_code_identifier'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 4); // 4 bits for extension_start_code_identifier //echo $info['mpeg']['video']['raw']['extension_start_code_identifier'].'<br>'; switch ($info['mpeg']['video']['raw']['extension_start_code_identifier']) { case 1: // 0001 Sequence Extension ID $info['mpeg']['video']['raw']['profile_and_level_indication'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 8); // 8 bits for profile_and_level_indication $info['mpeg']['video']['raw']['progressive_sequence'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: progressive_sequence $info['mpeg']['video']['raw']['chroma_format'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 2); // 2 bits for chroma_format $info['mpeg']['video']['raw']['horizontal_size_extension'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 2); // 2 bits for horizontal_size_extension $info['mpeg']['video']['raw']['vertical_size_extension'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 2); // 2 bits for vertical_size_extension $info['mpeg']['video']['raw']['bit_rate_extension'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 12); // 12 bits for bit_rate_extension $marker_bit = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // The term "marker_bit" indicates a one bit field in which the value zero is forbidden. These marker bits are introduced at several points in the syntax to avoid start code emulation. $info['mpeg']['video']['raw']['vbv_buffer_size_extension'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 8); // 8 bits for vbv_buffer_size_extension $info['mpeg']['video']['raw']['low_delay'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: low_delay $info['mpeg']['video']['raw']['frame_rate_extension_n'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 2); // 2 bits for frame_rate_extension_n $info['mpeg']['video']['raw']['frame_rate_extension_d'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 5); // 5 bits for frame_rate_extension_d $info['video']['resolution_x'] = $info['mpeg']['video']['raw']['horizontal_size_extension'] << 12 | $info['mpeg']['video']['raw']['horizontal_size_value']; $info['video']['resolution_y'] = $info['mpeg']['video']['raw']['vertical_size_extension'] << 12 | $info['mpeg']['video']['raw']['vertical_size_value']; $info['video']['interlaced'] = !$info['mpeg']['video']['raw']['progressive_sequence']; $info['mpeg']['video']['interlaced'] = !$info['mpeg']['video']['raw']['progressive_sequence']; $info['mpeg']['video']['chroma_format'] = self::chromaFormatTextLookup($info['mpeg']['video']['raw']['chroma_format']); break; case 2: // 0010 Sequence Display Extension ID $info['mpeg']['video']['raw']['video_format'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 3); // 3 bits for video_format $info['mpeg']['video']['raw']['colour_description'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: colour_description if ($info['mpeg']['video']['raw']['colour_description']) { $info['mpeg']['video']['raw']['colour_primaries'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 8); // 8 bits for colour_primaries $info['mpeg']['video']['raw']['transfer_characteristics'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 8); // 8 bits for transfer_characteristics $info['mpeg']['video']['raw']['matrix_coefficients'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 8); // 8 bits for matrix_coefficients } $info['mpeg']['video']['raw']['display_horizontal_size'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 14); // 14 bits for display_horizontal_size $marker_bit = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // The term "marker_bit" indicates a one bit field in which the value zero is forbidden. These marker bits are introduced at several points in the syntax to avoid start code emulation. $info['mpeg']['video']['raw']['display_vertical_size'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 14); // 14 bits for display_vertical_size $info['mpeg']['video']['video_format'] = self::videoFormatTextLookup($info['mpeg']['video']['raw']['video_format']); break; case 3: // 0011 Quant Matrix Extension ID break; case 5: // 0101 Sequence Scalable Extension ID $info['mpeg']['video']['raw']['scalable_mode'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 2); // 2 bits for scalable_mode $info['mpeg']['video']['raw']['layer_id'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 4); // 4 bits for layer_id if ($info['mpeg']['video']['raw']['scalable_mode'] == 1) { // "spatial scalability" $info['mpeg']['video']['raw']['lower_layer_prediction_horizontal_size'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 14); // 14 bits for lower_layer_prediction_horizontal_size $marker_bit = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // The term "marker_bit" indicates a one bit field in which the value zero is forbidden. These marker bits are introduced at several points in the syntax to avoid start code emulation. $info['mpeg']['video']['raw']['lower_layer_prediction_vertical_size'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 14); // 14 bits for lower_layer_prediction_vertical_size $info['mpeg']['video']['raw']['horizontal_subsampling_factor_m'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 5); // 5 bits for horizontal_subsampling_factor_m $info['mpeg']['video']['raw']['horizontal_subsampling_factor_n'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 5); // 5 bits for horizontal_subsampling_factor_n $info['mpeg']['video']['raw']['vertical_subsampling_factor_m'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 5); // 5 bits for vertical_subsampling_factor_m $info['mpeg']['video']['raw']['vertical_subsampling_factor_n'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 5); // 5 bits for vertical_subsampling_factor_n } elseif ($info['mpeg']['video']['raw']['scalable_mode'] == 3) { // "temporal scalability" $info['mpeg']['video']['raw']['picture_mux_enable'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: picture_mux_enable if ($info['mpeg']['video']['raw']['picture_mux_enable']) { $info['mpeg']['video']['raw']['mux_to_progressive_sequence'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: mux_to_progressive_sequence } $info['mpeg']['video']['raw']['picture_mux_order'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 3); // 3 bits for picture_mux_order $info['mpeg']['video']['raw']['picture_mux_factor'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 3); // 3 bits for picture_mux_factor } $info['mpeg']['video']['scalable_mode'] = self::scalableModeTextLookup($info['mpeg']['video']['raw']['scalable_mode']); break; case 7: // 0111 Picture Display Extension ID break; case 8: // 1000 Picture Coding Extension ID $info['mpeg']['video']['raw']['f_code_00'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 4); // 4 bits for f_code[0][0] (forward horizontal) $info['mpeg']['video']['raw']['f_code_01'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 4); // 4 bits for f_code[0][1] (forward vertical) $info['mpeg']['video']['raw']['f_code_10'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 4); // 4 bits for f_code[1][0] (backward horizontal) $info['mpeg']['video']['raw']['f_code_11'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 4); // 4 bits for f_code[1][1] (backward vertical) $info['mpeg']['video']['raw']['intra_dc_precision'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 2); // 2 bits for intra_dc_precision $info['mpeg']['video']['raw']['picture_structure'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 2); // 2 bits for picture_structure $info['mpeg']['video']['raw']['top_field_first'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: top_field_first $info['mpeg']['video']['raw']['frame_pred_frame_dct'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: frame_pred_frame_dct $info['mpeg']['video']['raw']['concealment_motion_vectors'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: concealment_motion_vectors $info['mpeg']['video']['raw']['q_scale_type'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: q_scale_type $info['mpeg']['video']['raw']['intra_vlc_format'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: intra_vlc_format $info['mpeg']['video']['raw']['alternate_scan'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: alternate_scan $info['mpeg']['video']['raw']['repeat_first_field'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: repeat_first_field $info['mpeg']['video']['raw']['chroma_420_type'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: chroma_420_type $info['mpeg']['video']['raw']['progressive_frame'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: progressive_frame $info['mpeg']['video']['raw']['composite_display_flag'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: composite_display_flag if ($info['mpeg']['video']['raw']['composite_display_flag']) { $info['mpeg']['video']['raw']['v_axis'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: v_axis $info['mpeg']['video']['raw']['field_sequence'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 3); // 3 bits for field_sequence $info['mpeg']['video']['raw']['sub_carrier'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: sub_carrier $info['mpeg']['video']['raw']['burst_amplitude'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 7); // 7 bits for burst_amplitude $info['mpeg']['video']['raw']['sub_carrier_phase'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 8); // 8 bits for sub_carrier_phase } $info['mpeg']['video']['intra_dc_precision_bits'] = $info['mpeg']['video']['raw']['intra_dc_precision'] + 8; $info['mpeg']['video']['picture_structure'] = self::pictureStructureTextLookup($info['mpeg']['video']['raw']['picture_structure']); break; case 9: // 1001 Picture Spatial Scalable Extension ID break; case 10: // 1010 Picture Temporal Scalable Extension ID break; default: $this->warning('Unexpected $info[mpeg][video][raw][extension_start_code_identifier] value of ' . $info['mpeg']['video']['raw']['extension_start_code_identifier']); break; } break; case 0xb8: // group_of_pictures_header $GOPcounter++; if ($info['mpeg']['video']['bitrate_mode'] == 'vbr') { $bitstream = getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $StartCodeOffset + 4, 4)); // 27 bits needed for group_of_pictures_header $bitstreamoffset = 0; $GOPheader = array(); $GOPheader['byte_offset'] = $MPEGstreamBaseOffset + $StartCodeOffset; $GOPheader['drop_frame_flag'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: drop_frame_flag $GOPheader['time_code_hours'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 5); // 5 bits for time_code_hours $GOPheader['time_code_minutes'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 6); // 6 bits for time_code_minutes $marker_bit = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // The term "marker_bit" indicates a one bit field in which the value zero is forbidden. These marker bits are introduced at several points in the syntax to avoid start code emulation. $GOPheader['time_code_seconds'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 6); // 6 bits for time_code_seconds $GOPheader['time_code_pictures'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 6); // 6 bits for time_code_pictures $GOPheader['closed_gop'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: closed_gop $GOPheader['broken_link'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: broken_link $time_code_separator = $GOPheader['drop_frame_flag'] ? ';' : ':'; // While non-drop time code is displayed with colons separating the digit pairs—"HH:MM:SS:FF"—drop frame is usually represented with a semi-colon (;) or period (.) as the divider between all the digit pairs—"HH;MM;SS;FF", "HH.MM.SS.FF" $GOPheader['time_code'] = sprintf('%02d' . $time_code_separator . '%02d' . $time_code_separator . '%02d' . $time_code_separator . '%02d', $GOPheader['time_code_hours'], $GOPheader['time_code_minutes'], $GOPheader['time_code_seconds'], $GOPheader['time_code_pictures']); $info['mpeg']['group_of_pictures'][] = $GOPheader; } break; case 0xc0: // audio stream // audio stream case 0xc1: // audio stream // audio stream case 0xc2: // audio stream // audio stream case 0xc3: // audio stream // audio stream case 0xc4: // audio stream // audio stream case 0xc5: // audio stream // audio stream case 0xc6: // audio stream // audio stream case 0xc7: // audio stream // audio stream case 0xc8: // audio stream // audio stream case 0xc9: // audio stream // audio stream case 0xca: // audio stream // audio stream case 0xcb: // audio stream // audio stream case 0xcc: // audio stream // audio stream case 0xcd: // audio stream // audio stream case 0xce: // audio stream // audio stream case 0xcf: // audio stream // audio stream case 0xd0: // audio stream // audio stream case 0xd1: // audio stream // audio stream case 0xd2: // audio stream // audio stream case 0xd3: // audio stream // audio stream case 0xd4: // audio stream // audio stream case 0xd5: // audio stream // audio stream case 0xd6: // audio stream // audio stream case 0xd7: // audio stream // audio stream case 0xd8: // audio stream // audio stream case 0xd9: // audio stream // audio stream case 0xda: // audio stream // audio stream case 0xdb: // audio stream // audio stream case 0xdc: // audio stream // audio stream case 0xdd: // audio stream // audio stream case 0xde: // audio stream // audio stream case 0xdf: // audio stream //case 0xE0: // video stream //case 0xE1: // video stream //case 0xE2: // video stream //case 0xE3: // video stream //case 0xE4: // video stream //case 0xE5: // video stream //case 0xE6: // video stream //case 0xE7: // video stream //case 0xE8: // video stream //case 0xE9: // video stream //case 0xEA: // video stream //case 0xEB: // video stream //case 0xEC: // video stream //case 0xED: // video stream //case 0xEE: // video stream //case 0xEF: // video stream if (isset($ParsedAVchannels[$StartCodeValue])) { break; } $ParsedAVchannels[$StartCodeValue] = $StartCodeValue; // http://en.wikipedia.org/wiki/Packetized_elementary_stream // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html /* $PackedElementaryStream = array(); if ($StartCodeValue >= 0xE0) { $PackedElementaryStream['stream_type'] = 'video'; $PackedElementaryStream['stream_id'] = $StartCodeValue - 0xE0; } else { $PackedElementaryStream['stream_type'] = 'audio'; $PackedElementaryStream['stream_id'] = $StartCodeValue - 0xC0; } $PackedElementaryStream['packet_length'] = getid3_lib::BigEndian2Int(substr($MPEGstreamData, $StartCodeOffset + 4, 2)); $bitstream = getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $StartCodeOffset + 6, 3)); // more may be needed below $bitstreamoffset = 0; $PackedElementaryStream['marker_bits'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 2); // 2 bits for marker_bits -- should be "10" = 2 echo 'marker_bits = '.$PackedElementaryStream['marker_bits'].'<br>'; $PackedElementaryStream['scrambling_control'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 2); // 2 bits for scrambling_control -- 00 implies not scrambled $PackedElementaryStream['priority'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: priority $PackedElementaryStream['data_alignment_indicator'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: data_alignment_indicator -- 1 indicates that the PES packet header is immediately followed by the video start code or audio syncword $PackedElementaryStream['copyright'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: copyright -- 1 implies copyrighted $PackedElementaryStream['original_or_copy'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: original_or_copy -- 1 implies original $PackedElementaryStream['pts_flag'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: pts_flag -- Presentation Time Stamp $PackedElementaryStream['dts_flag'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: dts_flag -- Decode Time Stamp $PackedElementaryStream['escr_flag'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: escr_flag -- Elementary Stream Clock Reference $PackedElementaryStream['es_rate_flag'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: es_rate_flag -- Elementary Stream [data] Rate $PackedElementaryStream['dsm_trick_mode_flag'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: dsm_trick_mode_flag -- DSM trick mode - not used by DVD $PackedElementaryStream['additional_copy_info_flag'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: additional_copy_info_flag $PackedElementaryStream['crc_flag'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: crc_flag $PackedElementaryStream['extension_flag'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: extension_flag $PackedElementaryStream['pes_remain_header_length'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 8); // 1 bit flag: priority $additional_header_bytes = 0; $additional_header_bytes += ($PackedElementaryStream['pts_flag'] ? 5 : 0); $additional_header_bytes += ($PackedElementaryStream['dts_flag'] ? 5 : 0); $additional_header_bytes += ($PackedElementaryStream['escr_flag'] ? 6 : 0); $additional_header_bytes += ($PackedElementaryStream['es_rate_flag'] ? 3 : 0); $additional_header_bytes += ($PackedElementaryStream['additional_copy_info_flag'] ? 1 : 0); $additional_header_bytes += ($PackedElementaryStream['crc_flag'] ? 2 : 0); $additional_header_bytes += ($PackedElementaryStream['extension_flag'] ? 1 : 0); $PackedElementaryStream['additional_header_bytes'] = $additional_header_bytes; $bitstream .= getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $StartCodeOffset + 9, $additional_header_bytes)); $info['mpeg']['packed_elementary_streams'][$PackedElementaryStream['stream_type']][$PackedElementaryStream['stream_id']][] = $PackedElementaryStream; */ $getid3_temp = new getID3(); $getid3_temp->openfile($this->getid3->filename); $getid3_temp->info = $info; $getid3_mp3 = new getid3_mp3($getid3_temp); for ($i = 0; $i <= 7; $i++) { // some files have the MPEG-audio header 8 bytes after the end of the $00 $00 $01 $C0 signature, some have it up to 13 bytes (or more?) after // I have no idea why or what the difference is, so this is a stupid hack. // If anybody has any better idea of what's going on, please let me know - info@getid3.org $getid3_temp->info = $info; // only overwrite real data if valid header found //echo 'audio at? '.($MPEGstreamBaseOffset + $StartCodeOffset + 4 + 8 + $i).'<br>'; if ($getid3_mp3->decodeMPEGaudioHeader($MPEGstreamBaseOffset + $StartCodeOffset + 4 + 8 + $i, $getid3_temp->info, false)) { //echo 'yes!<br>'; $info = $getid3_temp->info; $info['audio']['bitrate_mode'] = 'cbr'; $info['audio']['lossless'] = false; break; } } unset($getid3_temp, $getid3_mp3); break; case 0xbc: // Program Stream Map // Program Stream Map case 0xbd: // Private stream 1 (non MPEG audio, subpictures) // Private stream 1 (non MPEG audio, subpictures) case 0xbe: // Padding stream // Padding stream case 0xbf: // Private stream 2 (navigation data) // Private stream 2 (navigation data) case 0xf0: // ECM stream // ECM stream case 0xf1: // EMM stream // EMM stream case 0xf2: // DSM-CC stream // DSM-CC stream case 0xf3: // ISO/IEC_13522_stream // ISO/IEC_13522_stream case 0xf4: // ITU-I Rec. H.222.1 type A // ITU-I Rec. H.222.1 type A case 0xf5: // ITU-I Rec. H.222.1 type B // ITU-I Rec. H.222.1 type B case 0xf6: // ITU-I Rec. H.222.1 type C // ITU-I Rec. H.222.1 type C case 0xf7: // ITU-I Rec. H.222.1 type D // ITU-I Rec. H.222.1 type D case 0xf8: // ITU-I Rec. H.222.1 type E // ITU-I Rec. H.222.1 type E case 0xf9: // ancilliary stream // ancilliary stream case 0xfa: // ISO/IEC 14496-1 SL-packtized stream // ISO/IEC 14496-1 SL-packtized stream case 0xfb: // ISO/IEC 14496-1 FlexMux stream // ISO/IEC 14496-1 FlexMux stream case 0xfc: // metadata stream // metadata stream case 0xfd: // extended stream ID // extended stream ID case 0xfe: // reserved data stream // reserved data stream case 0xff: // program stream directory // ignore break; default: // ignore break; } } while (true); // // Temporary hack to account for interleaving overhead: // if (!empty($info['video']['bitrate']) && !empty($info['audio']['bitrate'])) { // $info['playtime_seconds'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / ($info['video']['bitrate'] + $info['audio']['bitrate']); // // // Interleaved MPEG audio/video files have a certain amount of overhead that varies // // by both video and audio bitrates, and not in any sensible, linear/logarithmic pattern // // Use interpolated lookup tables to approximately guess how much is overhead, because // // playtime is calculated as filesize / total-bitrate // $info['playtime_seconds'] *= self::systemNonOverheadPercentage($info['video']['bitrate'], $info['audio']['bitrate']); // // //switch ($info['video']['bitrate']) { // // case('5000000'): // // $multiplier = 0.93292642112380355828048824319889; // // break; // // case('5500000'): // // $multiplier = 0.93582895375200989965359777343219; // // break; // // case('6000000'): // // $multiplier = 0.93796247714820932532911373859139; // // break; // // case('7000000'): // // $multiplier = 0.9413264083635103463010117778776; // // break; // // default: // // $multiplier = 1; // // break; // //} // //$info['playtime_seconds'] *= $multiplier; // //$info['warning'][] = 'Interleaved MPEG audio/video playtime may be inaccurate. With current hack should be within a few seconds of accurate. Report to info@getid3.org if off by more than 10 seconds.'; // if ($info['video']['bitrate'] < 50000) { // $this->warning('Interleaved MPEG audio/video playtime may be slightly inaccurate for video bitrates below 100kbps. Except in extreme low-bitrate situations, error should be less than 1%. Report to info@getid3.org if greater than this.'); // } // } // /* $time_prev = 0; $byte_prev = 0; $vbr_bitrates = array(); foreach ($info['mpeg']['group_of_pictures'] as $gopkey => $gopdata) { $time_this = ($gopdata['time_code_hours'] * 3600) + ($gopdata['time_code_minutes'] * 60) + $gopdata['time_code_seconds'] + ($gopdata['time_code_seconds'] / 30); $byte_this = $gopdata['byte_offset']; if ($gopkey > 0) { if ($time_this > $time_prev) { $bytedelta = $byte_this - $byte_prev; $timedelta = $time_this - $time_prev; $this_bitrate = ($bytedelta * 8) / $timedelta; echo $gopkey.': ('.number_format($time_prev, 2).'-'.number_format($time_this, 2).') '.number_format($bytedelta).' bytes over '.number_format($timedelta, 3).' seconds = '.number_format($this_bitrate / 1000, 2).'kbps<br>'; $time_prev = $time_this; $byte_prev = $byte_this; $vbr_bitrates[] = $this_bitrate; } } } echo 'average_File_bitrate = '.number_format(array_sum($vbr_bitrates) / count($vbr_bitrates), 1).'<br>'; */ //echo '<pre>'.print_r($FramesByGOP, true).'</pre>'; if ($info['mpeg']['video']['bitrate_mode'] == 'vbr') { $last_GOP_id = max(array_keys($FramesByGOP)); $frames_in_last_GOP = count($FramesByGOP[$last_GOP_id]); $gopdata =& $info['mpeg']['group_of_pictures'][$last_GOP_id]; $info['playtime_seconds'] = $gopdata['time_code_hours'] * 3600 + $gopdata['time_code_minutes'] * 60 + $gopdata['time_code_seconds'] + ($gopdata['time_code_pictures'] + $frames_in_last_GOP + 1) / $info['mpeg']['video']['frame_rate']; if (!isset($info['video']['bitrate'])) { $overall_bitrate = ($info['avdataend'] - $info['avdataoffset']) * 8 / $info['playtime_seconds']; $info['video']['bitrate'] = $overall_bitrate - (isset($info['audio']['bitrate']) ? $info['audio']['bitrate'] : 0); } unset($info['mpeg']['group_of_pictures']); } return true; }
function Analyze() { $info =& $this->getid3->info; $info['fileformat'] = 'quicktime'; $info['quicktime']['hinting'] = false; $info['quicktime']['controller'] = 'standard'; // may be overridden if 'ctyp' atom is present fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); $offset = 0; $atomcounter = 0; while ($offset < $info['avdataend']) { if (!getid3_lib::intValueSupported($offset)) { $info['error'][] = 'Unable to parse atom at offset ' . $offset . ' because beyond ' . round(PHP_INT_MAX / 1073741824) . 'GB limit of PHP filesystem functions'; break; } fseek($this->getid3->fp, $offset, SEEK_SET); $AtomHeader = fread($this->getid3->fp, 8); $atomsize = getid3_lib::BigEndian2Int(substr($AtomHeader, 0, 4)); $atomname = substr($AtomHeader, 4, 4); // 64-bit MOV patch by jlegateØktnc*com if ($atomsize == 1) { $atomsize = getid3_lib::BigEndian2Int(fread($this->getid3->fp, 8)); } $info['quicktime'][$atomname]['name'] = $atomname; $info['quicktime'][$atomname]['size'] = $atomsize; $info['quicktime'][$atomname]['offset'] = $offset; if ($offset + $atomsize > $info['avdataend']) { $info['error'][] = 'Atom at offset ' . $offset . ' claims to go beyond end-of-file (length: ' . $atomsize . ' bytes)'; return false; } if ($atomsize == 0) { // Furthermore, for historical reasons the list of atoms is optionally // terminated by a 32-bit integer set to 0. If you are writing a program // to read user data atoms, you should allow for the terminating 0. break; } switch ($atomname) { case 'mdat': // Media DATa atom // 'mdat' contains the actual data for the audio/video if ($atomsize > 8 && (!isset($info['avdataend_tmp']) || $info['quicktime'][$atomname]['size'] > $info['avdataend_tmp'] - $info['avdataoffset'])) { $info['avdataoffset'] = $info['quicktime'][$atomname]['offset'] + 8; $OldAVDataEnd = $info['avdataend']; $info['avdataend'] = $info['quicktime'][$atomname]['offset'] + $info['quicktime'][$atomname]['size']; $getid3_temp = new getID3(); $getid3_temp->openfile($this->getid3->filename); $getid3_temp->info['avdataoffset'] = $info['avdataoffset']; $getid3_temp->info['avdataend'] = $info['avdataend']; $getid3_mp3 = new getid3_mp3($getid3_temp); if ($getid3_mp3->MPEGaudioHeaderValid($getid3_mp3->MPEGaudioHeaderDecode(fread($this->getid3->fp, 4)))) { $getid3_mp3->getOnlyMPEGaudioInfo($getid3_temp->info['avdataoffset'], false); if (!empty($getid3_temp->info['warning'])) { foreach ($getid3_temp->info['warning'] as $value) { $info['warning'][] = $value; } } if (!empty($getid3_temp->info['mpeg'])) { $info['mpeg'] = $getid3_temp->info['mpeg']; if (isset($info['mpeg']['audio'])) { $info['audio']['dataformat'] = 'mp3'; $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')); $info['audio']['sample_rate'] = $info['mpeg']['audio']['sample_rate']; $info['audio']['channels'] = $info['mpeg']['audio']['channels']; $info['audio']['bitrate'] = $info['mpeg']['audio']['bitrate']; $info['audio']['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']); $info['bitrate'] = $info['audio']['bitrate']; } } } unset($getid3_mp3, $getid3_temp); $info['avdataend'] = $OldAVDataEnd; unset($OldAVDataEnd); } break; case 'free': // FREE space atom // FREE space atom case 'skip': // SKIP atom // SKIP atom case 'wide': // 64-bit expansion placeholder atom // 'free', 'skip' and 'wide' are just padding, contains no useful data at all break; default: $atomHierarchy = array(); $info['quicktime'][$atomname] = $this->QuicktimeParseAtom($atomname, $atomsize, fread($this->getid3->fp, $atomsize), $offset, $atomHierarchy, $this->ParseAllPossibleAtoms); break; } $offset += $atomsize; $atomcounter++; } if (!empty($info['avdataend_tmp'])) { // this value is assigned to a temp value and then erased because // otherwise any atoms beyond the 'mdat' atom would not get parsed $info['avdataend'] = $info['avdataend_tmp']; unset($info['avdataend_tmp']); } if (!isset($info['bitrate']) && isset($info['playtime_seconds'])) { $info['bitrate'] = ($info['avdataend'] - $info['avdataoffset']) * 8 / $info['playtime_seconds']; } if (isset($info['bitrate']) && !isset($info['audio']['bitrate']) && !isset($info['quicktime']['video'])) { $info['audio']['bitrate'] = $info['bitrate']; } if (!empty($info['playtime_seconds']) && !isset($info['video']['frame_rate']) && !empty($info['quicktime']['stts_framecount'])) { foreach ($info['quicktime']['stts_framecount'] as $key => $samples_count) { $samples_per_second = $samples_count / $info['playtime_seconds']; if ($samples_per_second > 240) { // has to be audio samples } else { $info['video']['frame_rate'] = $samples_per_second; break; } } } if ($info['audio']['dataformat'] == 'mp4' && empty($info['video']['resolution_x'])) { $info['fileformat'] = 'mp4'; $info['mime_type'] = 'audio/mp4'; unset($info['video']['dataformat']); } if (!$this->ReturnAtomData) { unset($info['quicktime']['moov']); } if (empty($info['audio']['dataformat']) && !empty($info['quicktime']['audio'])) { $info['audio']['dataformat'] = 'quicktime'; } if (empty($info['video']['dataformat']) && !empty($info['quicktime']['video'])) { $info['video']['dataformat'] = 'quicktime'; } return true; }
static function ParseRIFF(&$fd, $startoffset, $maxoffset, &$ThisFileInfo) { $maxoffset = min($maxoffset, $ThisFileInfo['avdataend']); $RIFFchunk = false; $FoundAllChunksWeNeed = false; if ($startoffset < 0 || !getid3_lib::intValueSupported($startoffset)) { $ThisFileInfo['warning'][] = 'Unable to ParseRIFF() at ' . $startoffset . ' because beyond ' . round(PHP_INT_MAX / 1073741824) . 'GB limit of PHP filesystem functions'; return false; } $max_usable_offset = min(PHP_INT_MAX - 1024, $maxoffset); if ($maxoffset > $max_usable_offset) { $ThisFileInfo['warning'][] = 'ParseRIFF() may return incomplete data for chunk starting at ' . $startoffset . ' because beyond it extends to ' . $maxoffset . ', which is beyond the ' . round(PHP_INT_MAX / 1073741824) . 'GB limit of PHP filesystem functions'; } fseek($fd, $startoffset, SEEK_SET); while (ftell($fd) < $max_usable_offset) { $chunknamesize = fread($fd, 8); $chunkname = substr($chunknamesize, 0, 4); $chunksize = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($chunknamesize, 4, 4)); if (strlen($chunkname) < 4) { $ThisFileInfo['error'][] = 'Expecting chunk name at offset ' . (ftell($fd) - 4) . ' but found nothing. Aborting RIFF parsing.'; break; } if ($chunksize == 0) { if ($chunkname == 'JUNK') { // we'll allow zero-size JUNK frames } else { $ThisFileInfo['warning'][] = 'Chunk size at offset ' . (ftell($fd) - 4) . ' is zero. Aborting RIFF parsing.'; break; } } if ($chunksize % 2 != 0) { // all structures are packed on word boundaries $chunksize++; } switch ($chunkname) { case 'LIST': $listname = fread($fd, 4); if (preg_match('#^(movi|rec )$#i', $listname)) { $RIFFchunk[$listname]['offset'] = ftell($fd) - 4; $RIFFchunk[$listname]['size'] = $chunksize; if ($FoundAllChunksWeNeed) { // skip over } else { $WhereWeWere = ftell($fd); $AudioChunkHeader = fread($fd, 12); $AudioChunkStreamNum = substr($AudioChunkHeader, 0, 2); $AudioChunkStreamType = substr($AudioChunkHeader, 2, 2); $AudioChunkSize = getid3_lib::LittleEndian2Int(substr($AudioChunkHeader, 4, 4)); if ($AudioChunkStreamType == 'wb') { $FirstFourBytes = substr($AudioChunkHeader, 8, 4); if (preg_match('/^\\xFF[\\xE2-\\xE7\\xF2-\\xF7\\xFA-\\xFF][\\x00-\\xEB]/s', $FirstFourBytes)) { // MP3 if (getid3_mp3::MPEGaudioHeaderBytesValid($FirstFourBytes)) { $dummy = $ThisFileInfo; $dummy['avdataoffset'] = ftell($fd) - 4; $dummy['avdataend'] = ftell($fd) + $AudioChunkSize; getid3_mp3::getOnlyMPEGaudioInfo($fd, $dummy, $dummy['avdataoffset'], false); if (isset($dummy['mpeg']['audio'])) { $ThisFileInfo = $dummy; $ThisFileInfo['audio']['dataformat'] = 'mp' . $ThisFileInfo['mpeg']['audio']['layer']; $ThisFileInfo['audio']['sample_rate'] = $ThisFileInfo['mpeg']['audio']['sample_rate']; $ThisFileInfo['audio']['channels'] = $ThisFileInfo['mpeg']['audio']['channels']; $ThisFileInfo['audio']['bitrate'] = $ThisFileInfo['mpeg']['audio']['bitrate']; $ThisFileInfo['bitrate'] = $ThisFileInfo['audio']['bitrate']; $ThisFileInfo['audio']['bitrate_mode'] = strtolower($ThisFileInfo['mpeg']['audio']['bitrate_mode']); } unset($dummy); } } elseif (preg_match('/^\\x0B\\x77/s', $FirstFourBytes)) { // AC3 $GETID3_ERRORARRAY =& $ThisFileInfo['warning']; if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.audio.ac3.php', __FILE__, false)) { $dummy = $ThisFileInfo; $dummy['avdataoffset'] = ftell($fd) - 4; $dummy['avdataend'] = ftell($fd) + $AudioChunkSize; $dummy['error'] = array(); $ac3_tag = new getid3_ac3($fd, $dummy); if (empty($dummy['error'])) { $ThisFileInfo['audio'] = $dummy['audio']; $ThisFileInfo['ac3'] = $dummy['ac3']; $ThisFileInfo['warning'] = $dummy['warning']; } unset($ac3_tag); } } } $FoundAllChunksWeNeed = true; fseek($fd, $WhereWeWere, SEEK_SET); } fseek($fd, $chunksize - 4, SEEK_CUR); //} elseif (preg_match('#^[0-9]{2}(wb|pc|dc|db)$#i', $listname)) { // // // data chunk, ignore // } else { if (!isset($RIFFchunk[$listname])) { $RIFFchunk[$listname] = array(); } $LISTchunkParent = $listname; $LISTchunkMaxOffset = ftell($fd) - 4 + $chunksize; if ($parsedChunk = getid3_riff::ParseRIFF($fd, ftell($fd), ftell($fd) + $chunksize - 4, $ThisFileInfo)) { $RIFFchunk[$listname] = array_merge_recursive($RIFFchunk[$listname], $parsedChunk); } } break; default: if (preg_match('#^[0-9]{2}(wb|pc|dc|db)$#', $chunkname)) { $nextoffset = ftell($fd) + $chunksize; if ($nextoffset < 0 || !getid3_lib::intValueSupported($nextoffset)) { $ThisFileInfo['warning'][] = 'Unable to parse chunk at offset ' . $nextoffset . ' because beyond ' . round(PHP_INT_MAX / 1073741824) . 'GB limit of PHP filesystem functions'; break 2; } fseek($fd, $nextoffset, SEEK_SET); break; } $thisindex = 0; if (isset($RIFFchunk[$chunkname]) && is_array($RIFFchunk[$chunkname])) { $thisindex = count($RIFFchunk[$chunkname]); } $RIFFchunk[$chunkname][$thisindex]['offset'] = ftell($fd) - 8; $RIFFchunk[$chunkname][$thisindex]['size'] = $chunksize; switch ($chunkname) { case 'data': $ThisFileInfo['avdataoffset'] = ftell($fd); $ThisFileInfo['avdataend'] = $ThisFileInfo['avdataoffset'] + $chunksize; $RIFFdataChunkContentsTest = fread($fd, 36); if (strlen($RIFFdataChunkContentsTest) > 0 && preg_match('/^\\xFF[\\xE2-\\xE7\\xF2-\\xF7\\xFA-\\xFF][\\x00-\\xEB]/s', substr($RIFFdataChunkContentsTest, 0, 4))) { // Probably is MP3 data if (getid3_mp3::MPEGaudioHeaderBytesValid(substr($RIFFdataChunkContentsTest, 0, 4))) { $dummy = $ThisFileInfo; // copy info array, only use if there's no error $getid3_mp3 = new getid3_mp3($fd, $dummy); $dummy = $ThisFileInfo; // copy info array, only use if there's no error $getid3_mp3->getOnlyMPEGaudioInfo($fd, $dummy, $RIFFchunk[$chunkname][$thisindex]['offset'], false); // use dummy array unless error if (empty($dummy['error'])) { $ThisFileInfo = $dummy; } } } elseif (strlen($RIFFdataChunkContentsTest) > 0 && substr($RIFFdataChunkContentsTest, 0, 2) == "\vw") { // This is probably AC-3 data $GETID3_ERRORARRAY =& $ThisFileInfo['warning']; if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.audio.ac3.php', __FILE__, false)) { $dummy = $ThisFileInfo; $dummy['avdataoffset'] = $RIFFchunk[$chunkname][$thisindex]['offset']; $dummy['avdataend'] = $dummy['avdataoffset'] + $RIFFchunk[$chunkname][$thisindex]['size']; $dummy['error'] = array(); $ac3_tag = new getid3_ac3($fd, $dummy); if (empty($dummy['error'])) { $ThisFileInfo['audio'] = $dummy['audio']; $ThisFileInfo['ac3'] = $dummy['ac3']; $ThisFileInfo['warning'] = $dummy['warning']; } unset($ac3_tag); } } elseif (strlen($RIFFdataChunkContentsTest) > 0 && substr($RIFFdataChunkContentsTest, 8, 2) == "w\v") { // Dolby Digital WAV // AC-3 content, but not encoded in same format as normal AC-3 file // For one thing, byte order is swapped $GETID3_ERRORARRAY =& $ThisFileInfo['warning']; if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.audio.ac3.php', __FILE__, false)) { // ok to use tmpfile here - only 56 bytes if ($fd_temp = tmpfile()) { for ($i = 0; $i < 28; $i += 2) { // swap byte order fwrite($fd_temp, substr($RIFFdataChunkContentsTest, 8 + $i + 1, 1)); fwrite($fd_temp, substr($RIFFdataChunkContentsTest, 8 + $i + 0, 1)); } $dummy = $ThisFileInfo; $dummy['avdataoffset'] = 0; $dummy['avdataend'] = 20; $dummy['error'] = array(); $ac3_tag = new getid3_ac3($fd_temp, $dummy); fclose($fd_temp); if (empty($dummy['error'])) { $ThisFileInfo['audio'] = $dummy['audio']; $ThisFileInfo['ac3'] = $dummy['ac3']; $ThisFileInfo['warning'] = $dummy['warning']; } else { $ThisFileInfo['error'][] = 'Errors parsing DolbyDigital WAV: ' . explode(';', $dummy['error']); } unset($ac3_tag); } else { $ThisFileInfo['error'][] = 'Could not create temporary file to analyze DolbyDigital WAV'; } } } elseif (strlen($RIFFdataChunkContentsTest) > 0 && substr($RIFFdataChunkContentsTest, 0, 4) == 'wvpk') { // This is WavPack data $ThisFileInfo['wavpack']['offset'] = $RIFFchunk[$chunkname][$thisindex]['offset']; $ThisFileInfo['wavpack']['size'] = getid3_lib::LittleEndian2Int(substr($RIFFdataChunkContentsTest, 4, 4)); getid3_riff::RIFFparseWavPackHeader(substr($RIFFdataChunkContentsTest, 8, 28), $ThisFileInfo); } else { // This is some other kind of data (quite possibly just PCM) // do nothing special, just skip it } $nextoffset = $RIFFchunk[$chunkname][$thisindex]['offset'] + 8 + $chunksize; if ($nextoffset < 0 || !getid3_lib::intValueSupported($nextoffset)) { $ThisFileInfo['warning'][] = 'Unable to parse chunk at offset ' . $nextoffset . ' because beyond ' . round(PHP_INT_MAX / 1073741824) . 'GB limit of PHP filesystem functions'; break 3; } fseek($fd, $RIFFchunk[$chunkname][$thisindex]['offset'] + 8 + $chunksize, SEEK_SET); break; case 'bext': case 'cart': case 'fmt ': case 'strh': case 'strf': case 'indx': case 'MEXT': case 'DISP': // always read data in $RIFFchunk[$chunkname][$thisindex]['data'] = fread($fd, $chunksize); break; case 'JUNK': // never read data in $nextoffset = ftell($fd) + $chunksize; if ($nextoffset < 0 || !getid3_lib::intValueSupported($nextoffset)) { $ThisFileInfo['warning'][] = 'Unable to parse chunk at offset ' . $nextoffset . ' because beyond ' . round(PHP_INT_MAX / 1073741824) . 'GB limit of PHP filesystem functions'; break 3; } fseek($fd, $nextoffset, SEEK_SET); break; default: if (!preg_match('#^[0-9]{2}(wb|pc|dc|db)$#', $chunkname) && !empty($LISTchunkParent) && $RIFFchunk[$chunkname][$thisindex]['offset'] + $RIFFchunk[$chunkname][$thisindex]['size'] <= $LISTchunkMaxOffset) { $RIFFchunk[$LISTchunkParent][$chunkname][$thisindex]['offset'] = $RIFFchunk[$chunkname][$thisindex]['offset']; $RIFFchunk[$LISTchunkParent][$chunkname][$thisindex]['size'] = $RIFFchunk[$chunkname][$thisindex]['size']; unset($RIFFchunk[$chunkname][$thisindex]['offset']); unset($RIFFchunk[$chunkname][$thisindex]['size']); if (isset($RIFFchunk[$chunkname][$thisindex]) && empty($RIFFchunk[$chunkname][$thisindex])) { unset($RIFFchunk[$chunkname][$thisindex]); } if (isset($RIFFchunk[$chunkname]) && empty($RIFFchunk[$chunkname])) { unset($RIFFchunk[$chunkname]); } $RIFFchunk[$LISTchunkParent][$chunkname][$thisindex]['data'] = fread($fd, $chunksize); } elseif ($chunksize > 0 && $chunksize < 2048) { // only read data in if smaller than 2kB $RIFFchunk[$chunkname][$thisindex]['data'] = fread($fd, $chunksize); } else { $nextoffset = ftell($fd) + $chunksize; if ($nextoffset < 0 || !getid3_lib::intValueSupported($nextoffset)) { $ThisFileInfo['warning'][] = 'Unable to parse chunk at offset ' . $nextoffset . ' because beyond ' . round(PHP_INT_MAX / 1073741824) . 'GB limit of PHP filesystem functions'; break 3; } fseek($fd, $nextoffset, SEEK_SET); } break; } break; } } return $RIFFchunk; }
public function ParseRIFF($startoffset, $maxoffset) { $info =& $this->getid3->info; $RIFFchunk = false; $FoundAllChunksWeNeed = false; try { $this->fseek($startoffset); $maxoffset = min($maxoffset, $info['avdataend']); while ($this->ftell() < $maxoffset) { $chunknamesize = $this->fread(8); $chunkname = substr($chunknamesize, 0, 4); $chunksize = $this->EitherEndian2Int(substr($chunknamesize, 4, 4)); if (strlen(trim($chunkname, "")) < 4) { $this->error('Expecting chunk name at offset ' . ($this->ftell() - 8) . ' but found nothing. Aborting RIFF parsing.'); break; } if ($chunksize == 0 && $chunkname != 'JUNK') { $this->warning('Chunk (' . $chunkname . ') size at offset ' . ($this->ftell() - 4) . ' is zero. Aborting RIFF parsing.'); break; } if ($chunksize % 2 != 0) { // all structures are packed on word boundaries $chunksize++; } switch ($chunkname) { case 'LIST': $listname = $this->fread(4); if (preg_match('#^(movi|rec )$#i', $listname)) { $RIFFchunk[$listname]['offset'] = $this->ftell() - 4; $RIFFchunk[$listname]['size'] = $chunksize; if (!$FoundAllChunksWeNeed) { $WhereWeWere = $this->ftell(); $AudioChunkHeader = $this->fread(12); $AudioChunkStreamNum = substr($AudioChunkHeader, 0, 2); $AudioChunkStreamType = substr($AudioChunkHeader, 2, 2); $AudioChunkSize = getid3_lib::LittleEndian2Int(substr($AudioChunkHeader, 4, 4)); if ($AudioChunkStreamType == 'wb') { $FirstFourBytes = substr($AudioChunkHeader, 8, 4); if (preg_match('/^\\xFF[\\xE2-\\xE7\\xF2-\\xF7\\xFA-\\xFF][\\x00-\\xEB]/s', $FirstFourBytes)) { // MP3 if (getid3_mp3::MPEGaudioHeaderBytesValid($FirstFourBytes)) { $getid3_temp = new getID3(); $getid3_temp->openfile($this->getid3->filename); $getid3_temp->info['avdataoffset'] = $this->ftell() - 4; $getid3_temp->info['avdataend'] = $this->ftell() + $AudioChunkSize; $getid3_mp3 = new getid3_mp3($getid3_temp); $getid3_mp3->getOnlyMPEGaudioInfo($getid3_temp->info['avdataoffset'], false); if (isset($getid3_temp->info['mpeg']['audio'])) { $info['mpeg']['audio'] = $getid3_temp->info['mpeg']['audio']; $info['audio'] = $getid3_temp->info['audio']; $info['audio']['dataformat'] = 'mp' . $info['mpeg']['audio']['layer']; $info['audio']['sample_rate'] = $info['mpeg']['audio']['sample_rate']; $info['audio']['channels'] = $info['mpeg']['audio']['channels']; $info['audio']['bitrate'] = $info['mpeg']['audio']['bitrate']; $info['audio']['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']); //$info['bitrate'] = $info['audio']['bitrate']; } unset($getid3_temp, $getid3_mp3); } } elseif (strpos($FirstFourBytes, getid3_ac3::syncword) === 0) { // AC3 $getid3_temp = new getID3(); $getid3_temp->openfile($this->getid3->filename); $getid3_temp->info['avdataoffset'] = $this->ftell() - 4; $getid3_temp->info['avdataend'] = $this->ftell() + $AudioChunkSize; $getid3_ac3 = new getid3_ac3($getid3_temp); $getid3_ac3->Analyze(); if (empty($getid3_temp->info['error'])) { $info['audio'] = $getid3_temp->info['audio']; $info['ac3'] = $getid3_temp->info['ac3']; if (!empty($getid3_temp->info['warning'])) { foreach ($getid3_temp->info['warning'] as $key => $value) { $info['warning'][] = $value; } } } unset($getid3_temp, $getid3_ac3); } } $FoundAllChunksWeNeed = true; $this->fseek($WhereWeWere); } $this->fseek($chunksize - 4, SEEK_CUR); } else { if (!isset($RIFFchunk[$listname])) { $RIFFchunk[$listname] = array(); } $LISTchunkParent = $listname; $LISTchunkMaxOffset = $this->ftell() - 4 + $chunksize; if ($parsedChunk = $this->ParseRIFF($this->ftell(), $LISTchunkMaxOffset)) { $RIFFchunk[$listname] = array_merge_recursive($RIFFchunk[$listname], $parsedChunk); } } break; default: if (preg_match('#^[0-9]{2}(wb|pc|dc|db)$#', $chunkname)) { $this->fseek($chunksize, SEEK_CUR); break; } $thisindex = 0; if (isset($RIFFchunk[$chunkname]) && is_array($RIFFchunk[$chunkname])) { $thisindex = count($RIFFchunk[$chunkname]); } $RIFFchunk[$chunkname][$thisindex]['offset'] = $this->ftell() - 8; $RIFFchunk[$chunkname][$thisindex]['size'] = $chunksize; switch ($chunkname) { case 'data': $info['avdataoffset'] = $this->ftell(); $info['avdataend'] = $info['avdataoffset'] + $chunksize; $RIFFdataChunkContentsTest = $this->fread(36); if (strlen($RIFFdataChunkContentsTest) > 0 && preg_match('/^\\xFF[\\xE2-\\xE7\\xF2-\\xF7\\xFA-\\xFF][\\x00-\\xEB]/s', substr($RIFFdataChunkContentsTest, 0, 4))) { // Probably is MP3 data if (getid3_mp3::MPEGaudioHeaderBytesValid(substr($RIFFdataChunkContentsTest, 0, 4))) { $getid3_temp = new getID3(); $getid3_temp->openfile($this->getid3->filename); $getid3_temp->info['avdataoffset'] = $RIFFchunk[$chunkname][$thisindex]['offset']; $getid3_temp->info['avdataend'] = $RIFFchunk[$chunkname][$thisindex]['offset'] + $RIFFchunk[$chunkname][$thisindex]['size']; $getid3_mp3 = new getid3_mp3($getid3_temp); $getid3_mp3->getOnlyMPEGaudioInfo($RIFFchunk[$chunkname][$thisindex]['offset'], false); if (empty($getid3_temp->info['error'])) { $info['mpeg'] = $getid3_temp->info['mpeg']; $info['audio'] = $getid3_temp->info['audio']; } unset($getid3_temp, $getid3_mp3); } } elseif (strlen($RIFFdataChunkContentsTest) > 0 && substr($RIFFdataChunkContentsTest, 0, 2) == getid3_ac3::syncword) { // This is probably AC-3 data $getid3_temp = new getID3(); $getid3_temp->openfile($this->getid3->filename); $getid3_temp->info['avdataoffset'] = $RIFFchunk[$chunkname][$thisindex]['offset']; $getid3_temp->info['avdataend'] = $RIFFchunk[$chunkname][$thisindex]['offset'] + $RIFFchunk[$chunkname][$thisindex]['size']; $getid3_ac3 = new getid3_ac3($getid3_temp); $getid3_ac3->Analyze(); if (empty($getid3_temp->info['error'])) { $info['audio'] = $getid3_temp->info['audio']; $info['ac3'] = $getid3_temp->info['ac3']; $info['warning'] = $getid3_temp->info['warning']; } unset($getid3_temp, $getid3_ac3); } elseif (strlen($RIFFdataChunkContentsTest) > 0 && substr($RIFFdataChunkContentsTest, 8, 2) == getid3_ac3::syncword) { // Dolby Digital WAV // AC-3 content, but not encoded in same format as normal AC-3 file // For one thing, byte order is swapped if ($RIFFtempfilename = tempnam(GETID3_TEMP_DIR, 'id3')) { // ok to use tmpfile here - only 56 bytes if ($fd_temp = fopen($RIFFtempfilename, 'wb')) { for ($i = 0; $i < 28; $i += 2) { // swap byte order fwrite($fd_temp, substr($RIFFdataChunkContentsTest, 8 + $i + 1, 1)); fwrite($fd_temp, substr($RIFFdataChunkContentsTest, 8 + $i + 0, 1)); } fclose($fd_temp); $getid3_temp = new getID3(); $getid3_temp->openfile($RIFFtempfilename); $getid3_temp->info['avdataend'] = 20; $getid3_ac3 = new getid3_ac3($getid3_temp); $getid3_ac3->Analyze(); if (empty($getid3_temp->info['error'])) { $info['audio'] = $getid3_temp->info['audio']; $info['ac3'] = $getid3_temp->info['ac3']; $info['warning'] = $getid3_temp->info['warning']; } else { $info['error'][] = 'Error parsing Dolby Digital WAV (AC3-in-RIFF): ' . implode(';', $getid3_temp->info['error']); } unset($getid3_ac3, $getid3_temp); } else { $info['error'][] = 'Error parsing Dolby Digital WAV (AC3-in-RIFF): failed to write temp file'; } unlink($RIFFtempfilename); } else { $info['error'][] = 'Error parsing Dolby Digital WAV (AC3-in-RIFF): failed to write temp file'; } } elseif (strlen($RIFFdataChunkContentsTest) > 0 && substr($RIFFdataChunkContentsTest, 0, 4) == 'wvpk') { // This is WavPack data $info['wavpack']['offset'] = $RIFFchunk[$chunkname][$thisindex]['offset']; $info['wavpack']['size'] = getid3_lib::LittleEndian2Int(substr($RIFFdataChunkContentsTest, 4, 4)); self::parseWavPackHeader(substr($RIFFdataChunkContentsTest, 8, 28)); } else { // This is some other kind of data (quite possibly just PCM) // do nothing special, just skip it } $nextoffset = $RIFFchunk[$chunkname][$thisindex]['offset'] + 8 + $chunksize; $this->fseek($nextoffset); break; case 'iXML': case 'bext': case 'cart': case 'fmt ': case 'strh': case 'strf': case 'indx': case 'MEXT': case 'DISP': // always read data in // always read data in case 'JUNK': // should be: never read data in // but some programs write their version strings in a JUNK chunk (e.g. VirtualDub, AVIdemux, etc) if ($chunksize < 1048576) { if ($chunksize > 0) { $RIFFchunk[$chunkname][$thisindex]['data'] = $this->fread($chunksize); if ($chunkname == 'JUNK') { if (preg_match('#^([\\x20-\\x7F]+)#', $RIFFchunk[$chunkname][$thisindex]['data'], $matches)) { // only keep text characters [chr(32)-chr(127)] $info['riff']['comments']['junk'][] = trim($matches[1]); } // but if nothing there, ignore // remove the key in either case unset($RIFFchunk[$chunkname][$thisindex]['data']); } } } else { $this->warning('Chunk "' . $chunkname . '" at offset ' . $this->ftell() . ' is unexpectedly larger than 1MB (claims to be ' . number_format($chunksize) . ' bytes), skipping data'); $this->fseek($chunksize, SEEK_CUR); } break; //case 'IDVX': // $info['divxtag']['comments'] = self::ParseDIVXTAG($this->fread($chunksize)); // break; //case 'IDVX': // $info['divxtag']['comments'] = self::ParseDIVXTAG($this->fread($chunksize)); // break; default: if (!empty($LISTchunkParent) && $RIFFchunk[$chunkname][$thisindex]['offset'] + $RIFFchunk[$chunkname][$thisindex]['size'] <= $LISTchunkMaxOffset) { $RIFFchunk[$LISTchunkParent][$chunkname][$thisindex]['offset'] = $RIFFchunk[$chunkname][$thisindex]['offset']; $RIFFchunk[$LISTchunkParent][$chunkname][$thisindex]['size'] = $RIFFchunk[$chunkname][$thisindex]['size']; unset($RIFFchunk[$chunkname][$thisindex]['offset']); unset($RIFFchunk[$chunkname][$thisindex]['size']); if (isset($RIFFchunk[$chunkname][$thisindex]) && empty($RIFFchunk[$chunkname][$thisindex])) { unset($RIFFchunk[$chunkname][$thisindex]); } if (isset($RIFFchunk[$chunkname]) && empty($RIFFchunk[$chunkname])) { unset($RIFFchunk[$chunkname]); } $RIFFchunk[$LISTchunkParent][$chunkname][$thisindex]['data'] = $this->fread($chunksize); } elseif ($chunksize < 2048) { // only read data in if smaller than 2kB $RIFFchunk[$chunkname][$thisindex]['data'] = $this->fread($chunksize); } else { $this->fseek($chunksize, SEEK_CUR); } break; } break; } } } catch (getid3_exception $e) { if ($e->getCode() == 10) { $this->warning('RIFF parser: ' . $e->getMessage()); } else { throw $e; } } return $RIFFchunk; }