<?php
/*
 Copyright 2006 Iván Montes

 This file is part of FLV tools for PHP (FLV4PHP from now on).

 FLV4PHP is free software; you can redistribute it and/or modify it under the 
 terms of the GNU General Public License as published by the Free Software 
 Foundation; either version 2 of the License, or (at your option) any later 
 version.

 FLV4PHP 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 General Public License for more details.

 You should have received a copy of the GNU General Public License along with 
 FLV4PHP; if not, write to the Free Software Foundation, Inc., 51 Franklin 
 Street, Fifth Floor, Boston, MA 02110-1301, USA
*/

define('FLV_INCLUDE_PATH', dirname(__FILE__) . '/');

include_once FLV_INCLUDE_PATH . 'Tag.php';
include_once FLV_INCLUDE_PATH . 'Exceptions.php';

/**
 * Parse a .flv file to extract all the 'tag' information
 *
 */
class FLV {
    /** The FLV header signature */
    const FLV_HEADER_SIGNATURE = 'FLV';
    
    /** The FLV main header size */
    const FLV_HEADER_SIZE = 9;
    
    /** The FLV tag header size */
    const TAG_HEADER_SIZE = 15;
    
    /** 
     *  Maximun number of bytes to process as tag body. This is a safety meassure against
     *  corrupted FLV files.
     */
    const MAX_TAG_BODY_SIZE = 16386;
    
    protected $fp;
    private $lastTagSize = 0;
    
    /**
     * Opens a FLV video file to process its information
     *
     * @throws FLV_FileException, FLV_NotValidFileException
     * 
     * @param string $fname The filename to open
     * @return true on success
     */
    function open( $fname )
    {
        $this->fp = @fopen( $fname, 'r' );
        if (! $this->fp)
            throw( new FLV_FileException('Unable to open the file') );
        
        $hdr = fread( $this->fp, self::FLV_HEADER_SIZE );
        
        //check file header signature
        if ( substr($hdr, 0, 3) !== self::FLV_HEADER_SIGNATURE )
            throw( new FLV_NotValidFileException('The header signature does not match') );
            

        $this->version = ord($hdr[3]);
        $this->hasVideo = (bool)(ord($hdr[4]) & 0x01);
        $this->hasAudio = (bool)(ord($hdr[4]) & 0x04);
        
        $this->bodyOfs =    (ord($hdr[5]) << 24) +
                            (ord($hdr[6]) << 16) +
                            (ord($hdr[7]) << 8) +
                            (ord($hdr[8]));

        fseek( $this->fp, $this->bodyOfs );
        
        $this->eof = false;
        
        return true;
    }
    
    /**
     * Close a previously open FLV file
     *
     */
    function close()
    {
        fclose( $this->fp );    
    }
    
    
    /**
     * Returns the next tag from the open file
     *
     * @throws FLV_CorruptedFileException
     * 
     * @param array $skipTagTypes   The tag types contained in this array won't be examined
     * @return FLV_Tag_Generic or one of its descendants
     */
    function getTag( $skipTagTypes = false )
    {
        static $cnt = 0;
        
        if ($this->eof) return null;

        
        $hdr = fread( $this->fp, self::TAG_HEADER_SIZE );
        if (strlen($hdr) < self::TAG_HEADER_SIZE)
        {
            $this->eof = true;
            return null;
        }
        
        /*
        //DEV: Some files seem to don't store this value!
        // check against corrupted files
        $prevTagSize = unpack( 'Nprev', $hdr );     
        if ($prevTagSize['prev'] != $this->lastTagSize)
        {
            throw( 
                new FLV_CorruptedFileException(  
                    sprintf(    "Previous tag size check failed. Actual size is %d but defined size is %d",
                                $this->lastTagSize,
                                $prevTagSize['prev']
                    )
                )
            );
        }
		*/
        
        // Get the tag object by skiping the first 4 bytes which tell the previous tag size
        $tag = FLV_Tag::getTag( substr( $hdr, 4 ) );

        
        // Read at most MAX_TAG_BODY_SIZE bytes of the body
        $bytesToRead = min( self::MAX_TAG_BODY_SIZE, $tag->size );
        $tag->setBody( fread( $this->fp, $bytesToRead ) );
        
        // Check if the tag body has to be processed
        if ( is_array($skipTagTypes) && !in_array( $tag->type, $skipTagTypes ) )
        {
            $tag->analyze();
        }
            
        // If the tag was skipped or the body size was larger than MAX_TAG_BODY_SIZE
        if ($tag->size > $bytesToRead)
        {
            fseek( $this->fp, $tag->size-$bytesToRead, SEEK_CUR );
        }

        $this->lastTagSize = $tag->size + self::TAG_HEADER_SIZE - 4;
            
        return $tag;
    }
    
    
    /**
     * Returns the offset from the start of the file of the last processed tag
     *
     * @return the offset
     */
    function getTagOffset()
    {
        return ftell($this->fp) - $this->lastTagSize;
    }
}

?>