/** * @param type $startoffset * @param type $maxoffset * * @return bool */ public function ParseRIFF($startoffset, $maxoffset) { $info =& $this->getid3->info; $maxoffset = min($maxoffset, $info['avdataend']); $RIFFchunk = false; $FoundAllChunksWeNeed = false; if ($startoffset < 0 || !Helper::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 = Helper::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 (Mp3::MPEGaudioHeaderBytesValid($FirstFourBytes)) { $getid3_temp = new GetId3Core(); $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 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 (class_exists('Helpers\\GetId3\\Module\\Audio\\Ac3')) { $getid3_temp = new GetId3Core(); $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 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 || !Helper::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 (Mp3::MPEGaudioHeaderBytesValid(substr($RIFFdataChunkContentsTest, 0, 4))) { $getid3_temp = new GetId3Core(); $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 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 (class_exists('Helpers\\GetId3\\Module\\Audio\\Ac3')) { $getid3_temp = new GetId3Core(); $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 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 (class_exists('Helpers\\GetId3\\Module\\Audio\\Ac3')) { // ok to use tmpfile here - only 56 bytes if ($RIFFtempfilename = tempnam(GetId3Core::getTempDir(), '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 GetId3Core(); $getid3_temp->openfile($RIFFtempfilename); $getid3_temp->info['avdataend'] = 20; $getid3_ac3 = new 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'] = Helper::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 || !Helper::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 || !Helper::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 || !Helper::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; }
/** * @return bool */ public function analyze() { $info =& $this->getid3->info; $info['fileformat'] = 'quicktime'; $info['quicktime']['hinting'] = false; $info['quicktime']['controller'] = 'standard'; // may be overridden if 'ctyp' atom is present fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); $offset = 0; $atomcounter = 0; while ($offset < $info['avdataend']) { if (!Helper::intValueSupported($offset)) { $info['error'][] = 'Unable to parse atom at offset ' . $offset . ' because beyond ' . round(PHP_INT_MAX / 1073741824) . 'GB limit of PHP filesystem functions'; break; } fseek($this->getid3->fp, $offset, SEEK_SET); $AtomHeader = fread($this->getid3->fp, 8); $atomsize = Helper::BigEndian2Int(substr($AtomHeader, 0, 4)); $atomname = substr($AtomHeader, 4, 4); // 64-bit MOV patch by jlegateØktnc*com if ($atomsize == 1) { $atomsize = Helper::BigEndian2Int(fread($this->getid3->fp, 8)); } $info['quicktime'][$atomname]['name'] = $atomname; $info['quicktime'][$atomname]['size'] = $atomsize; $info['quicktime'][$atomname]['offset'] = $offset; if ($offset + $atomsize > $info['avdataend']) { $info['error'][] = 'Atom at offset ' . $offset . ' claims to go beyond end-of-file (length: ' . $atomsize . ' bytes)'; return false; } if ($atomsize == 0) { // Furthermore, for historical reasons the list of atoms is optionally // terminated by a 32-bit integer set to 0. If you are writing a program // to read user data atoms, you should allow for the terminating 0. break; } switch ($atomname) { case 'mdat': // Media DATa atom // 'mdat' contains the actual data for the audio/video if ($atomsize > 8 && (!isset($info['avdataend_tmp']) || $info['quicktime'][$atomname]['size'] > $info['avdataend_tmp'] - $info['avdataoffset'])) { $info['avdataoffset'] = $info['quicktime'][$atomname]['offset'] + 8; $OldAVDataEnd = $info['avdataend']; $info['avdataend'] = $info['quicktime'][$atomname]['offset'] + $info['quicktime'][$atomname]['size']; $getid3_temp = new GetId3Core(); $getid3_temp->openfile($this->getid3->filename); $getid3_temp->info['avdataoffset'] = $info['avdataoffset']; $getid3_temp->info['avdataend'] = $info['avdataend']; $getid3_mp3 = new Module\Audio\Mp3($getid3_temp); if ($getid3_mp3->MPEGaudioHeaderValid($getid3_mp3->MPEGaudioHeaderDecode(fread($this->getid3->fp, 4)))) { $getid3_mp3->getOnlyMPEGaudioInfo($getid3_temp->info['avdataoffset'], false); if (!empty($getid3_temp->info['warning'])) { foreach ($getid3_temp->info['warning'] as $value) { $info['warning'][] = $value; } } if (!empty($getid3_temp->info['mpeg'])) { $info['mpeg'] = $getid3_temp->info['mpeg']; if (isset($info['mpeg']['audio'])) { $info['audio']['dataformat'] = 'mp3'; $info['audio']['codec'] = !empty($info['mpeg']['audio']['encoder']) ? $info['mpeg']['audio']['encoder'] : (!empty($info['mpeg']['audio']['codec']) ? $info['mpeg']['audio']['codec'] : (!empty($info['mpeg']['audio']['LAME']) ? 'LAME' : 'mp3')); $info['audio']['sample_rate'] = $info['mpeg']['audio']['sample_rate']; $info['audio']['channels'] = $info['mpeg']['audio']['channels']; $info['audio']['bitrate'] = $info['mpeg']['audio']['bitrate']; $info['audio']['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']); $info['bitrate'] = $info['audio']['bitrate']; } } } unset($getid3_mp3, $getid3_temp); $info['avdataend'] = $OldAVDataEnd; unset($OldAVDataEnd); } break; case 'free': // FREE space atom // FREE space atom case 'skip': // SKIP atom // SKIP atom case 'wide': // 64-bit expansion placeholder atom // 'free', 'skip' and 'wide' are just padding, contains no useful data at all break; default: $atomHierarchy = array(); $info['quicktime'][$atomname] = $this->QuicktimeParseAtom($atomname, $atomsize, fread($this->getid3->fp, $atomsize), $offset, $atomHierarchy, $this->ParseAllPossibleAtoms); break; } $offset += $atomsize; ++$atomcounter; } if (!empty($info['avdataend_tmp'])) { // this value is assigned to a temp value and then erased because // otherwise any atoms beyond the 'mdat' atom would not get parsed $info['avdataend'] = $info['avdataend_tmp']; unset($info['avdataend_tmp']); } if (!isset($info['bitrate']) && isset($info['playtime_seconds'])) { $info['bitrate'] = ($info['avdataend'] - $info['avdataoffset']) * 8 / $info['playtime_seconds']; } if (isset($info['bitrate']) && !isset($info['audio']['bitrate']) && !isset($info['quicktime']['video'])) { $info['audio']['bitrate'] = $info['bitrate']; } if (!empty($info['playtime_seconds']) && !isset($info['video']['frame_rate']) && !empty($info['quicktime']['stts_framecount'])) { foreach ($info['quicktime']['stts_framecount'] as $key => $samples_count) { $samples_per_second = $samples_count / $info['playtime_seconds']; if ($samples_per_second > 240) { // has to be audio samples } else { $info['video']['frame_rate'] = $samples_per_second; break; } } } if ($info['audio']['dataformat'] == 'mp4' && empty($info['video']['resolution_x'])) { $info['fileformat'] = 'mp4'; $info['mime_type'] = 'audio/mp4'; unset($info['video']['dataformat']); } if (!$this->ReturnAtomData) { unset($info['quicktime']['moov']); } if (empty($info['audio']['dataformat']) && !empty($info['quicktime']['audio'])) { $info['audio']['dataformat'] = 'quicktime'; } if (empty($info['video']['dataformat']) && !empty($info['quicktime']['video'])) { $info['video']['dataformat'] = 'quicktime'; } return true; }
/** * @return bool */ public function analyze() { $info =& $this->getid3->info; if ($info['avdataend'] <= $info['avdataoffset']) { $info['error'][] = '"avdataend" (' . $info['avdataend'] . ') is unexpectedly less-than-or-equal-to "avdataoffset" (' . $info['avdataoffset'] . ')'; return false; } $info['fileformat'] = 'mpeg'; fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); $MPEGstreamData = fread($this->getid3->fp, min(100000, $info['avdataend'] - $info['avdataoffset'])); $MPEGstreamDataLength = strlen($MPEGstreamData); $foundVideo = true; $VideoChunkOffset = 0; while (substr($MPEGstreamData, $VideoChunkOffset++, 4) !== self::GETID3_MPEG_VIDEO_SEQUENCE_HEADER) { if ($VideoChunkOffset >= $MPEGstreamDataLength) { $foundVideo = false; break; } } if ($foundVideo) { // Start code 32 bits // horizontal frame size 12 bits // vertical frame size 12 bits // pixel aspect ratio 4 bits // frame rate 4 bits // bitrate 18 bits // marker bit 1 bit // VBV buffer size 10 bits // constrained parameter flag 1 bit // intra quant. matrix flag 1 bit // intra quant. matrix values 512 bits (present if matrix flag == 1) // non-intra quant. matrix flag 1 bit // non-intra quant. matrix values 512 bits (present if matrix flag == 1) $info['video']['dataformat'] = 'mpeg'; $VideoChunkOffset += strlen(self::GETID3_MPEG_VIDEO_SEQUENCE_HEADER) - 1; $FrameSizeDWORD = Helper::BigEndian2Int(substr($MPEGstreamData, $VideoChunkOffset, 3)); $VideoChunkOffset += 3; $AspectRatioFrameRateDWORD = Helper::BigEndian2Int(substr($MPEGstreamData, $VideoChunkOffset, 1)); $VideoChunkOffset += 1; $assortedinformation = Helper::BigEndian2Bin(substr($MPEGstreamData, $VideoChunkOffset, 4)); $VideoChunkOffset += 4; $info['mpeg']['video']['raw']['framesize_horizontal'] = ($FrameSizeDWORD & 0xfff000) >> 12; // 12 bits for horizontal frame size $info['mpeg']['video']['raw']['framesize_vertical'] = $FrameSizeDWORD & 0xfff; // 12 bits for vertical frame size $info['mpeg']['video']['raw']['pixel_aspect_ratio'] = ($AspectRatioFrameRateDWORD & 0xf0) >> 4; $info['mpeg']['video']['raw']['frame_rate'] = $AspectRatioFrameRateDWORD & 0xf; $info['mpeg']['video']['framesize_horizontal'] = $info['mpeg']['video']['raw']['framesize_horizontal']; $info['mpeg']['video']['framesize_vertical'] = $info['mpeg']['video']['raw']['framesize_vertical']; $info['mpeg']['video']['pixel_aspect_ratio'] = $this->MPEGvideoAspectRatioLookup($info['mpeg']['video']['raw']['pixel_aspect_ratio']); $info['mpeg']['video']['pixel_aspect_ratio_text'] = $this->MPEGvideoAspectRatioTextLookup($info['mpeg']['video']['raw']['pixel_aspect_ratio']); $info['mpeg']['video']['frame_rate'] = $this->MPEGvideoFramerateLookup($info['mpeg']['video']['raw']['frame_rate']); $info['mpeg']['video']['raw']['bitrate'] = Helper::Bin2Dec(substr($assortedinformation, 0, 18)); $info['mpeg']['video']['raw']['marker_bit'] = (bool) Helper::Bin2Dec(substr($assortedinformation, 18, 1)); $info['mpeg']['video']['raw']['vbv_buffer_size'] = Helper::Bin2Dec(substr($assortedinformation, 19, 10)); $info['mpeg']['video']['raw']['constrained_param_flag'] = (bool) Helper::Bin2Dec(substr($assortedinformation, 29, 1)); $info['mpeg']['video']['raw']['intra_quant_flag'] = (bool) Helper::Bin2Dec(substr($assortedinformation, 30, 1)); if ($info['mpeg']['video']['raw']['intra_quant_flag']) { // read 512 bits $info['mpeg']['video']['raw']['intra_quant'] = Helper::BigEndian2Bin(substr($MPEGstreamData, $VideoChunkOffset, 64)); $VideoChunkOffset += 64; $info['mpeg']['video']['raw']['non_intra_quant_flag'] = (bool) Helper::Bin2Dec(substr($info['mpeg']['video']['raw']['intra_quant'], 511, 1)); $info['mpeg']['video']['raw']['intra_quant'] = Helper::Bin2Dec(substr($assortedinformation, 31, 1)) . substr(Helper::BigEndian2Bin(substr($MPEGstreamData, $VideoChunkOffset, 64)), 0, 511); if ($info['mpeg']['video']['raw']['non_intra_quant_flag']) { $info['mpeg']['video']['raw']['non_intra_quant'] = substr($MPEGstreamData, $VideoChunkOffset, 64); $VideoChunkOffset += 64; } } else { $info['mpeg']['video']['raw']['non_intra_quant_flag'] = (bool) Helper::Bin2Dec(substr($assortedinformation, 31, 1)); if ($info['mpeg']['video']['raw']['non_intra_quant_flag']) { $info['mpeg']['video']['raw']['non_intra_quant'] = substr($MPEGstreamData, $VideoChunkOffset, 64); $VideoChunkOffset += 64; } } if ($info['mpeg']['video']['raw']['bitrate'] == 0x3ffff) { // 18 set bits $info['warning'][] = 'This version of GetId3Core() [' . $this->getid3->version() . '] cannot determine average bitrate of VBR MPEG video files'; $info['mpeg']['video']['bitrate_mode'] = 'vbr'; } else { $info['mpeg']['video']['bitrate'] = $info['mpeg']['video']['raw']['bitrate'] * 400; $info['mpeg']['video']['bitrate_mode'] = 'cbr'; $info['video']['bitrate'] = $info['mpeg']['video']['bitrate']; } $info['video']['resolution_x'] = $info['mpeg']['video']['framesize_horizontal']; $info['video']['resolution_y'] = $info['mpeg']['video']['framesize_vertical']; $info['video']['frame_rate'] = $info['mpeg']['video']['frame_rate']; $info['video']['bitrate_mode'] = $info['mpeg']['video']['bitrate_mode']; $info['video']['pixel_aspect_ratio'] = $info['mpeg']['video']['pixel_aspect_ratio']; $info['video']['lossless'] = false; $info['video']['bits_per_sample'] = 24; } else { $info['error'][] = 'Could not find start of video block in the first 100,000 bytes (or before end of file) - this might not be an MPEG-video file?'; } //0x000001B3 begins the sequence_header of every MPEG video stream. //But in MPEG-2, this header must immediately be followed by an //extension_start_code (0x000001B5) with a sequence_extension ID (1). //(This extension contains all the additional MPEG-2 stuff.) //MPEG-1 doesn't have this extension, so that's a sure way to tell the //difference between MPEG-1 and MPEG-2 video streams. if (substr($MPEGstreamData, $VideoChunkOffset, 4) == self::GETID3_MPEG_VIDEO_EXTENSION_START) { $info['video']['codec'] = 'MPEG-2'; } else { $info['video']['codec'] = 'MPEG-1'; } $AudioChunkOffset = 0; while (true) { while (substr($MPEGstreamData, $AudioChunkOffset++, 4) !== self::GETID3_MPEG_AUDIO_START) { if ($AudioChunkOffset >= $MPEGstreamDataLength) { break 2; } } $getid3_temp = new GetId3Core(); $getid3_temp->openfile($this->getid3->filename); $getid3_temp->info = $info; $getid3_mp3 = new Mp3($getid3_temp); for ($i = 0; $i <= 7; ++$i) { // some files have the MPEG-audio header 8 bytes after the end of the $00 $00 $01 $C0 signature, some have it up to 13 bytes (or more?) after // I have no idea why or what the difference is, so this is a stupid hack. // If anybody has any better idea of what's going on, please let me know - info@getid3.org fseek($getid3_temp->fp, ftell($this->getid3->fp), SEEK_SET); $getid3_temp->info = $info; // only overwrite real data if valid header found if ($getid3_mp3->decodeMPEGaudioHeader($AudioChunkOffset + 3 + 8 + $i, $getid3_temp->info, false)) { $info = $getid3_temp->info; $info['audio']['bitrate_mode'] = 'cbr'; $info['audio']['lossless'] = false; unset($getid3_temp, $getid3_mp3); break 2; } } unset($getid3_temp, $getid3_mp3); } // Temporary hack to account for interleaving overhead: if (!empty($info['video']['bitrate']) && !empty($info['audio']['bitrate'])) { $info['playtime_seconds'] = ($info['avdataend'] - $info['avdataoffset']) * 8 / ($info['video']['bitrate'] + $info['audio']['bitrate']); // Interleaved MPEG audio/video files have a certain amount of overhead that varies // by both video and audio bitrates, and not in any sensible, linear/logarithmic patter // Use interpolated lookup tables to approximately guess how much is overhead, because // playtime is calculated as filesize / total-bitrate $info['playtime_seconds'] *= $this->MPEGsystemNonOverheadPercentage($info['video']['bitrate'], $info['audio']['bitrate']); //switch ($info['video']['bitrate']) { // case('5000000'): // $multiplier = 0.93292642112380355828048824319889; // break; // case('5500000'): // $multiplier = 0.93582895375200989965359777343219; // break; // case('6000000'): // $multiplier = 0.93796247714820932532911373859139; // break; // case('7000000'): // $multiplier = 0.9413264083635103463010117778776; // break; // default: // $multiplier = 1; // break; //} //$info['playtime_seconds'] *= $multiplier; //$info['warning'][] = 'Interleaved MPEG audio/video playtime may be inaccurate. With current hack should be within a few seconds of accurate. Report to info@getid3.org if off by more than 10 seconds.'; if ($info['video']['bitrate'] < 50000) { $info['warning'][] = 'Interleaved MPEG audio/video playtime may be slightly inaccurate for video bitrates below 100kbps. Except in extreme low-bitrate situations, error should be less than 1%. Report to info@getid3.org if greater than this.'; } } return true; }