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