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 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; }
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 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; }
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; }