Esempio n. 1
0
 /**
  * @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;
 }
Esempio n. 2
0
 /**
  * @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;
 }