Вход Регистрация
Файл: moduls/PEAR/Ogg.php
Строк: 530
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4: */
// +----------------------------------------------------------------------------+
// | File_Ogg PEAR Package for Accessing Ogg Bitstreams                         |
// | Copyright (c) 2005-2007                                                    |
// | David Grant <david@grant.org.uk>                                           |
// | Tim Starling <tstarling@wikimedia.org>                                     |
// +----------------------------------------------------------------------------+
// | This library is free software; you can redistribute it and/or              |
// | modify it under the terms of the GNU Lesser General Public                 |
// | License as published by the Free Software Foundation; either               |
// | version 2.1 of the License, or (at your option) any later version.         |
// |                                                                            |
// | This library is distributed in the hope that it will be useful,            |
// | but WITHOUT ANY WARRANTY; without even the implied warranty of             |
// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU          |
// | Lesser General Public License for more details.                            |
// |                                                                            |
// | You should have received a copy of the GNU Lesser General Public           |
// | License along with this library; if not, write to the Free Software        |
// | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA |
// +----------------------------------------------------------------------------+

/**
 * @author      David Grant <david@grant.org.uk>, Tim Starling <tstarling@wikimedia.org>
 * @category    File
 * @copyright   David Grant <david@grant.org.uk>, Tim Starling <tstarling@wikimedia.org>
 * @license     http://www.gnu.org/copyleft/lesser.html GNU LGPL
 * @link        http://pear.php.net/package/File_Ogg
 * @package     File_Ogg
 * @version     CVS: $Id: Ogg.php,v 1.15 2008/01/31 04:41:44 tstarling Exp $
 */

/**
 * @access  public
 */
define("OGG_STREAM_VORBIS",     1);
/**
 * @access  public
 */
define("OGG_STREAM_THEORA",     2);
/**
 * @access  public
 */
define("OGG_STREAM_SPEEX",      3);
/**
 * @access  public
 */
define("OGG_STREAM_FLAC",       4);

/**
 * Capture pattern to determine if a file is an Ogg physical stream.
 * 
 * @access  private
 */
define("OGG_CAPTURE_PATTERN", "OggS");
/**
 * Maximum size of an Ogg stream page plus four.  This value is specified to allow
 * efficient parsing of the physical stream.  The extra four is a paranoid measure
 * to make sure a capture pattern is not split into two parts accidentally.
 * 
 * @access  private
 */
define("OGG_MAXIMUM_PAGE_SIZE", 65311);
/**
 * Capture pattern for an Ogg Vorbis logical stream.
 * 
 * @access  private
 */
define("OGG_STREAM_CAPTURE_VORBIS", "vorbis");
/**
 * Capture pattern for an Ogg Speex logical stream.
 * @access  private
 */
define("OGG_STREAM_CAPTURE_SPEEX",  "Speex   ");
/**
 * Capture pattern for an Ogg FLAC logical stream.
 * 
 * @access  private
 */
define("OGG_STREAM_CAPTURE_FLAC",   "FLAC");
/**
 * Capture pattern for an Ogg Theora logical stream.
 * 
 * @access  private
 */
define("OGG_STREAM_CAPTURE_THEORA", "theora");
/**
 * Error thrown if the file location passed is nonexistant or unreadable.
 * 
 * @access  private
 */
define("OGG_ERROR_INVALID_FILE", 1);
/**
 * Error thrown if the user attempts to extract an unsupported logical stream.
 * 
 * @access  private
 */
define("OGG_ERROR_UNSUPPORTED",  2);
/**
 * Error thrown if the user attempts to extract an logical stream with no
 * corresponding serial number.
 * 
 * @access  private
 */
define("OGG_ERROR_BAD_SERIAL",   3);
/**
 * Error thrown if the stream appears to be corrupted.
 * 
 * @access  private
 */
define("OGG_ERROR_UNDECODABLE",      4);

require_once(
'PEAR.php');
require_once(
'Exception.php');
require_once(
'Ogg/Bitstream.php');
require_once(
"Ogg/Flac.php");
require_once(
"Ogg/Speex.php");
require_once(
"Ogg/Theora.php");
require_once(
"Ogg/Vorbis.php");


/**
 * Class for parsing a ogg bitstream.
 *
 * This class provides a means to access several types of logical bitstreams (e.g. Vorbis)
 * within a Ogg physical bitstream.
 *
 * @link    http://www.xiph.org/ogg/doc/
 * @package File_Ogg
 */
class File_Ogg
{
    
/**
     * File pointer to Ogg container.
     *
     * This is the file pointer used for extracting data from the Ogg stream.  It is
     * the result of a standard fopen call.
     *
     * @var     pointer
     * @access  private
     */
    
var $_filePointer;

    
/**
     * The container for all logical streams.
     *
     * List of all of the unique streams in the Ogg physical stream.  The key
     * used is the unique serial number assigned to the logical stream by the
     * encoding application.
     *
     * @var     array
     * @access  private
     */
    
var $_streamList = array();
    var 
$_streams = array();

    
/**
     * Length in seconds of each stream group
     */
    
var $_groupLengths = array();

    
/**
     * Total length in seconds of the entire file
     */
    
var $_totalLength;

    
/**
     * Returns an interface to an Ogg physical stream.
     *
     * This method takes the path to a local file and examines it for a physical
     * ogg bitsream.  After instantiation, the user should query the object for
     * the logical bitstreams held within the ogg container.
     *
     * @access  public
     * @param   string  $fileLocation   The path of the file to be examined.
     */
    
function File_Ogg($fileLocation)
    {
        
clearstatcache();
        if (! 
file_exists($fileLocation)) {
            throw new 
PEAR_Exception("Couldn't Open File.  Check File Path.", OGG_ERROR_INVALID_FILE);
        }

        
// Open this file as a binary, and split the file into streams.
        
$this->_filePointer = fopen($fileLocation, "rb");
        if (!
is_resource($this->_filePointer))
            throw new 
PEAR_Exception("Couldn't Open File.  Check File Permissions.", OGG_ERROR_INVALID_FILE);

        
// Check for a stream at the start
        
$magic = fread($this->_filePointer, strlen(OGG_CAPTURE_PATTERN));
        if (
$magic !== OGG_CAPTURE_PATTERN) {
            throw new 
PEAR_Exception("Couldn't read file: Incorrect magic number.", OGG_ERROR_UNDECODABLE);
        }
        
fseek($this->_filePointer, 0, SEEK_SET);

        
$this->_splitStreams();
        
fclose($this->_filePointer);
    }

    
/**
     * Little-endian equivalent for bin2hex
     * @static
     */
    
static function _littleEndianBin2Hex( $bin ) {
        
$bigEndian = bin2hex( $bin );
        
// Reverse entire string
        
$reversed = strrev( $bigEndian );
        
// Swap nibbles back
        
for ( $i = 0, $j = strlen ( $bigEndian ); $i < $j; $i += 2 ) {
            
$temp = $reversed[$i];
            
$reversed[$i] = $reversed[$i+1];
            
$reversed[$i+1] = $temp;
        }
        return 
$reversed;
    }


    
/**
     * Read a binary structure from a file. An array of unsigned integers are read.
     * Large integers are upgraded to floating point on overflow.
     *
     * Format is big-endian as per Theora bit packing convention, this function 
     * won't work for Vorbis.
     *
     * @param   resource    $file
     * @param   array       $fields Associative array mapping name to length in bits
     */
    
static function _readBigEndian($file, $fields) 
    {
        
$bufferLength = ceil(array_sum($fields) / 8);
        
$buffer = fread($file, $bufferLength);
        if (
strlen($buffer) != $bufferLength) {
            throw new 
PEAR_Exception('Unexpected end of file', OGG_ERROR_UNDECODABLE);
        }
        
$bytePos = 0;
        
$bitPos = 0;
        
$byteValue = ord($buffer[0]);
        
$output = array();
        foreach (
$fields as $name => $width) {
            if (
$width % 8 == 0 && $bitPos == 0) {
                
// Byte aligned case
                
$bytes = $width / 8;
                
$endBytePos = $bytePos + $bytes;
                
$value = 0;
                while (
$bytePos < $endBytePos) {
                    
$value = ($value * 256) + ord($buffer[$bytePos]);
                    
$bytePos++;
                }
                if (
$bytePos < strlen($buffer)) {
                    
$byteValue = ord($buffer[$bytePos]);
                }
            } else {
                
// General case
                
$bitsRemaining = $width;
                
$value = 0;
                while (
$bitsRemaining > 0) {
                    
$bitsToRead = min($bitsRemaining, 8 - $bitPos);
                    
$byteValue <<= $bitsToRead;
                    
$overflow = ($byteValue & 0xff00) >> 8;
                    
$byteValue &= $byteValue & 0xff;

                    
$bitPos += $bitsToRead;
                    
$bitsRemaining -= $bitsToRead;
                    
$value += $overflow * pow(2, $bitsRemaining);

                    if (
$bitPos >= 8) {
                        
$bitPos = 0;
                        
$bytePos++;
                        if (
$bitsRemaining <= 0) {
                            break;
                        }
                        
$byteValue = ord($buffer[$bytePos]);
                    }
                }
            }
            
$output[$name] = $value;
            
assert($bytePos <= $bufferLength);
        }
        return 
$output;
    }

    
/**
     * Read a binary structure from a file. An array of unsigned integers are read.
     * Large integers are upgraded to floating point on overflow.
     *
     * Format is little-endian as per Vorbis bit packing convention.
     *
     * @param   resource    $file
     * @param   array       $fields Associative array mapping name to length in bits
     */
    
static function _readLittleEndian( $file, $fields ) {
        
$bufferLength = ceil(array_sum($fields) / 8);
        
$buffer = fread($file, $bufferLength);
        if (
strlen($buffer) != $bufferLength) {
            throw new 
PEAR_Exception('Unexpected end of file', OGG_ERROR_UNDECODABLE);
        }

        
$bytePos = 0;
        
$bitPos = 0;
        
$byteValue = ord($buffer[0]) << 8;
        
$output = array();
        foreach (
$fields as $name => $width) {
            if (
$width % 8 == 0 && $bitPos == 0) {
                
// Byte aligned case
                
$bytes = $width / 8;
                
$value = 0;
                for (
$i = 0; $i < $bytes; $i++, $bytePos++) {
                    
$value += pow(256, $i) * ord($buffer[$bytePos]);
                }
                if (
$bytePos < strlen($buffer)) {
                    
$byteValue = ord($buffer[$bytePos]) << 8;
                }
            } else {
                
// General case
                
$bitsRemaining = $width;
                
$value = 0;
                while (
$bitsRemaining > 0) {
                    
$bitsToRead = min($bitsRemaining, 8 - $bitPos);
                    
$byteValue >>= $bitsToRead;
                    
$overflow = ($byteValue & 0xff) >> (8 - $bitsToRead);
                    
$byteValue &= 0xff00;

                    
$value += $overflow * pow(2, $width - $bitsRemaining);
                    
$bitPos += $bitsToRead;
                    
$bitsRemaining -= $bitsToRead;

                    if (
$bitPos >= 8) {
                        
$bitPos = 0;
                        
$bytePos++;
                        if (
$bitsRemaining <= 0) {
                            break;
                        }
                        
$byteValue = ord($buffer[$bytePos]) << 8;
                    }
                }
            }
            
$output[$name] = $value;
            
assert($bytePos <= $bufferLength);
        }
        return 
$output;
    }


    
/**
     * @access  private
     */
    
function _decodePageHeader($pageData, $pageOffset, $groupId)
    {
        
// Extract the various bits and pieces found in each packet header.
        
if (substr($pageData, 0, 4) != OGG_CAPTURE_PATTERN)
            return (
false);

        
$stream_version = unpack("C1data", substr($pageData, 4, 1));
        if (
$stream_version['data'] != 0x00)
            return (
false);

        
$header_flag     = unpack("Cdata", substr($pageData, 5, 1));

        
// Exact granule position
        
$abs_granule_pos = self::_littleEndianBin2Hex( substr($pageData, 6, 8));
        
// Approximate (floating point) granule position
        
$pos = unpack("Va/Vb", substr($pageData, 6, 8));
        
$approx_granule_pos = $pos['a'] + $pos['b'] * pow(2, 32);

        
// Serial number for the current datastream.
        
$stream_serial   = unpack("Vdata", substr($pageData, 14, 4));
        
$page_sequence   = unpack("Vdata", substr($pageData, 18, 4));
        
$checksum        = unpack("Vdata", substr($pageData, 22, 4));
        
$page_segments   = unpack("Cdata", substr($pageData, 26, 1));
        
$segments_total  = 0;
        for (
$i = 0; $i < $page_segments['data']; ++$i) {
            
$segment_length = unpack("Cdata", substr($pageData, 26 + ($i + 1), 1));
            
$segments_total += $segment_length['data'];
        }
        
$pageFinish = $pageOffset + 27 + $page_segments['data'] + $segments_total;
        
$page = array(
            
'stream_version'        => $stream_version['data'],
            
'header_flag'           => $header_flag['data'],
            
'abs_granule_pos'       => $abs_granule_pos,
            
'approx_granule_pos'    => $approx_granule_pos,
            
'checksum'              => sprintf("%u", $checksum['data']),
            
'segments'              => $page_segments['data'],
            
'head_offset'           => $pageOffset,
            
'body_offset'           => $pageOffset + 27 + $page_segments['data'],
            
'body_finish'           => $pageFinish,
            
'data_length'           => $pageFinish - $pageOffset,
            
'group'                 => $groupId,
        );

        
$this->_streamList[$stream_serial['data']]['stream_page'][$page_sequence['data']] = $page;
        return 
$page;
    }
    
    
/**
     *  @access         private
     */
    
function _splitStreams()
    {
        
// Loop through the physical stream until there are no more pages to read.
        
$groupId = 0;
        
$openStreams = 0;
        
$this_page_offset = 0;
        while (!
feof($this->_filePointer)) {
            
$pageData = fread($this->_filePointer, 282);
            if (
strval($pageData) === '') {
                break;
            }
            
$page = $this->_decodePageHeader($pageData, $this_page_offset, $groupId);
            if (
$page === false) {
                throw new 
PEAR_Exception("Cannot decode Ogg file: Invalid page at offset $this_page_offset", OGG_ERROR_UNDECODABLE);
            }

            
// Keep track of multiplexed groups
            
if ($page['header_flag'] & 2/*bos*/) {
                
$openStreams++;
            } elseif (
$page['header_flag'] & 4/*eos*/) {
                
$openStreams--;
                if (!
$openStreams) {
                    
// End of group
                    
$groupId++;
                }
            }
            if (
$openStreams < 0) {
                throw new 
PEAR_Exception("Unexpected end of stream", OGG_ERROR_UNDECODABLE);
            }

            
$this_page_offset = $page['body_finish'];
            
fseek($this->_filePointer, $this_page_offset, SEEK_SET);
        }
        
// Loop through the streams, and find out what type of stream is available.
        
$groupLengths = array();
        foreach (
$this->_streamList as $stream_serial => $pages) {
            
fseek($this->_filePointer, $pages['stream_page'][0]['body_offset'], SEEK_SET);
            
$pattern = fread($this->_filePointer, 8);
            if (
preg_match("/" . OGG_STREAM_CAPTURE_VORBIS . "/", $pattern)) {
                
$this->_streamList[$stream_serial]['stream_type'] = OGG_STREAM_VORBIS;
                
$stream = new File_Ogg_Vorbis($stream_serial, $pages['stream_page'], $this->_filePointer);
            } elseif (
preg_match("/" . OGG_STREAM_CAPTURE_SPEEX . "/", $pattern)) {
                
$this->_streamList[$stream_serial]['stream_type'] = OGG_STREAM_SPEEX;
                
$stream = new File_Ogg_Speex($stream_serial, $pages['stream_page'], $this->_filePointer);
            } elseif (
preg_match("/" . OGG_STREAM_CAPTURE_FLAC . "/", $pattern)) {
                
$this->_streamList[$stream_serial]['stream_type'] = OGG_STREAM_FLAC;
                
$stream = new File_Ogg_Flac($stream_serial, $pages['stream_page'], $this->_filePointer);
            } elseif (
preg_match("/" . OGG_STREAM_CAPTURE_THEORA . "/", $pattern)) {
                
$this->_streamList[$stream_serial]['stream_type'] = OGG_STREAM_THEORA;
                
$stream = new File_Ogg_Theora($stream_serial, $pages['stream_page'], $this->_filePointer);
            } else {
                
$pages['stream_type'] = "unknown";
                
$stream = false;
            }

            if (
$stream) {
                
$this->_streams[$stream_serial] = $stream;
                
$group = $pages['stream_page'][0]['group'];
                if (isset(
$groupLengths[$group])) {
                    
$groupLengths[$group] = max($groupLengths[$group], $stream->getLength());
                } else {
                    
$groupLengths[$group] = $stream->getLength();
                }
            }
        }
        
$this->_groupLengths = $groupLengths;
        
$this->_totalLength = array_sum( $groupLengths );
        unset(
$this->_streamList);
    }
    
    
/**
     * Returns the overead percentage used by the Ogg headers.
     * 
     * This function returns the percentage of the total stream size
     * used for Ogg headers.
     *
     * @return float
     */
    
function getOverhead() {
        
$header_size    = 0;
        
$stream_size    = 0;
        foreach (
$this->_streams as $serial => $stream) {
            foreach (
$stream->_streamList as $offset => $stream_data) {
                
$header_size += $stream_data['body_offset'] - $stream_data['head_offset'];
                
$stream_size  = $stream_data['body_finish'];
            }
        }
        return 
sprintf("%0.2f", ($header_size / $stream_size) * 100);
    }
    
    
/**
     * Returns the appropriate logical bitstream that corresponds to the provided serial.
     *
     * This function returns a logical bitstream contained within the Ogg physical
     * stream, corresponding to the serial used as the offset for that bitstream.
     * The returned stream may be Vorbis, Speex, FLAC or Theora, although the only
     * usable bitstream is Vorbis.
     *
     * @return File_Ogg_Bitstream
     */
    
function &getStream($streamSerial)
    {
        if (! 
array_key_exists($streamSerial, $this->_streams))
                throw new 
PEAR_Exception("The stream number is invalid.", OGG_ERROR_BAD_SERIAL);

        return 
$this->_streams[$streamSerial];
    }
    
    
/**
     * This function returns true if a logical bitstream of the requested type can be found.
     *
     * This function checks the contents of this ogg physical bitstream for of logical
     * bitstream corresponding to the supplied type.  If one is found, the function returns
     * true, otherwise it return false.
     *
     * @param   int     $streamType
     * @return  boolean
     */
    
function hasStream($streamType)
    {
        foreach (
$this->_stream as $stream) {
            if (
$stream['stream_type'] == $streamType)
                return (
true);
        }
        return (
false);
    }
    
    
/**
     * Returns an array of logical streams inside this physical bitstream.
     *
     * This function returns an array of logical streams found within this physical
     * bitstream.  If a filter is provided, only logical streams of the requested type
     * are returned, as an array of serial numbers.  If no filter is provided, this
     * function returns a two-dimensional array, with the stream type as the primary key,
     * and a value consisting of an array of stream serial numbers.
     *
     * @param  int      $filter
     * @return array
     */
    
function listStreams($filter = null)
    {
        
$streams = array();
        
// Loops through the streams and assign them to an appropriate index,
        // ready for filtering the second part of this function.
        
foreach ($this->_streams as $serial => $stream) {
            
$stream_type = 0;
            switch (
get_class($stream)) {
                case 
"file_ogg_flac":
                    
$stream_type = OGG_STREAM_FLAC;
                    break;
                case 
"file_ogg_speex":
                    
$stream_type = OGG_STREAM_SPEEX;
                    break;
                case 
"file_ogg_theora":
                    
$stream_type = OGG_STREAM_THEORA;
                    break;
                case 
"file_ogg_vorbis":
                    
$stream_type = OGG_STREAM_VORBIS;
                    break;
            }
            if (! isset(
$streams[$stream_type]))
                
// Initialise the result list for this stream type.
                
$streams[$stream_type] = array();
                        
            
$streams[$stream_type][] = $serial;
        }

        
// Perform filtering.
        
if (is_null($filter))
            return (
$streams);
        elseif (isset(
$streams[$filter]))
            return (
$streams[$filter]);
        else
            return array();
    }

    
/**
     * Get the total length of the group of streams
     */
    
function getLength() {
        return 
$this->_totalLength;
    }
}
?>
Онлайн: 3
Реклама