<?php
    
    /**
     * This file is part of the PHP Video Toolkit v2 package.
     *
     * @author Oliver Lillie (aka buggedcom) <publicmail@buggedcom.co.uk>
     * @license Dual licensed under MIT and GPLv2
     * @copyright Copyright (c) 2008-2014 Oliver Lillie <http://www.buggedcom.co.uk>
     * @package PHPVideoToolkit V2
     * @version 2.1.7-beta
     * @uses ffmpeg http://ffmpeg.sourceforge.net/
     */
     
    namespace PHPVideoToolkit;
     
    /**
     * Abstract class based upon Parser to server as a base class for creating FFmpeg parser classes from differing sources.
     *
     * @author Oliver Lillie
     */
    abstract class FfmpegParserAbstract extends Parser
    {
        /**
         * Returns the raw codec data.
         *
         * @access public
         * @author Oliver Lillie
         * @param boolean $read_from_cache If true and the data exists within a cache then that data is used. If false
         *  then the data is re-read from ffmpeg.
         * @return string Returns the raw buffer data from ffmpeg.
         */
        abstract function getRawCodecData($read_from_cache=true);
        
        /**
         * Returns the raw filters data.
         *
         * @access public
         * @author Oliver Lillie
         * @param boolean $read_from_cache If true and the data exists within a cache then that data is used. If false
         *  then the data is re-read from ffmpeg.
         * @return string Returns the raw buffer data from ffmpeg.
         */
        abstract function getRawBitstreamFiltersData($read_from_cache=true);
        
        /**
         * Returns the raw filters data.
         *
         * @access public
         * @author Oliver Lillie
         * @param boolean $read_from_cache If true and the data exists within a cache then that data is used. If false
         *  then the data is re-read from ffmpeg.
         * @return string Returns the raw buffer data from ffmpeg.
         */
        abstract function getRawFiltersData($read_from_cache=true);
        
        /**
         * Returns the raw protocols data.
         *
         * @access public
         * @author Oliver Lillie
         * @param boolean $read_from_cache If true and the data exists within a cache then that data is used. If false
         *  then the data is re-read from ffmpeg.
         * @return string Returns the raw buffer data from ffmpeg.
         */
        abstract function getRawProtocolsData($read_from_cache=true);
        
        /**
         * Returns the raw data returned from ffmpeg about the available supported pixel formats.
         *
         * @access public
         * @author Oliver Lillie
         * @param boolean $read_from_cache If true and the data exists within a cache then that data is used. If false
         *  then the data is re-read from ffmpeg.
         * @return string Returns the raw buffer data from ffmpeg.
         * @throws PHPVideoToolkit\FfmpegProcessException If the call to ffmpeg encounters an error.
         */
        public function getRawPixelFormatsData($read_from_cache=true)
        {
            $cache_key = 'ffmpeg_parser/raw_pixel_formats_data';
            if($read_from_cache === true && ($data = $this->_cacheGet($cache_key)))
            {
                return $data;
            }
            
            $exec = new FfmpegProcess('ffmpeg', $this->_config);
            $data = $exec->addCommand('-pix_fmt', 'list')
                         ->addCommand('-pix_fmts')
                         ->execute()
                         ->getBuffer();
            
//          check the process for any errors.
            if($exec->hasError() === true)
            {
                throw new FfmpegProcessException('An error was encountered when attempting to read the pixel format data. FFmpeg reported: '.$exec->getLastLine(), null, $exec);
            }
            
            $this->_cacheSet($cache_key, $data);
            return $data;
        }
        
        /**
         * Returns the raw data returned from ffmpeg help command.
         *
         * @access public
         * @author Oliver Lillie
         * @param boolean $read_from_cache If true and the data exists within a cache then that data is used. If false
         *  then the data is re-read from ffmpeg.
         * @return string Returns the raw buffer data from ffmpeg.
         * @throws PHPVideoToolkit\FfmpegProcessException If the call to ffmpeg encounters an error.
         */
        public function getRawCommandsData($read_from_cache=true)
        {
            $cache_key = 'ffmpeg_parser/raw_commands_data';
            if($read_from_cache === true && ($data = $this->_cacheGet($cache_key)))
            {
                return $data;
            }
            
            $exec = new FfmpegProcess('ffmpeg', $this->_config);
            $data = $exec->addCommand('-h', 'long')
                         ->execute()
                         ->getBuffer();
            
//          check the process for any errors.
            if($exec->hasError() === true)
            {
                throw new FfmpegProcessException('An error was encountered when attempting to read FFmpegs\' available commands. FFmpeg reported: '.$exec->getLastLine(), null, $exec);
            }
            
            $this->_cacheSet($cache_key, $data);
            return $data;
        }

        /**
         * Returns the available filters in their processed easy to use format.
         *
         * @access public
         * @author Oliver Lillie
         * @param boolean $read_from_cache If true and the data exists within a cache then that data is used. If false
         *  then the data is re-read from ffmpeg.
         * @return array Returns an array of data about the current ffmpeg binary.
         */
        public function getFfmpegData($read_from_cache=true)
        {
            $cache_key = 'ffmpeg_parser/ffmpeg_data';
            if($read_from_cache === true && ($data = $this->_cacheGet($cache_key)))
            {
                return $data;
            }
            
//          get the raw format information
            $raw_data = $this->getRawFfmpegData($read_from_cache);
            
//          then match out the relevant data, clean and process.
            $data = array(
                'from-cache' => true,
                'read-at' => time(),
                'binary' => array(
                    'configuration' => array(),
                    'vhook-support' => false,
                ),
                'compiler' => array(
                    'gcc' => null,
                    'build_date' => null,
                ),
            );
            if(preg_match_all('/--[a-zA-Z0-9\-]+/', $raw_data, $config_flags) > 0)
            {
                $data['binary']['configuration'] = $config_flags[0];
                $data['binary']['vhook-support'] = in_array('--enable-vhook', $config_flags[0]) === true || in_array('--disable-vhook', $config_flags[0]) === false;

                if(preg_match('/built on (.*)(?:, gcc:| with) (.*)/', $raw_data, $conf) > 0) // 
                {
                    $data['compiler']['gcc'] = $conf[2];
                    //$data['compiler']['build_date'] = $conf[1];
                    $data['compiler']['build_date'] = strtotime($conf[1]);
                }
            }
            
            $this->_cacheSet($cache_key, $data);

            $data['from-cache'] = false;
            return $data;
        }
        
        /**
         * Returns the version of ffmpeg if it is found and available.
         *
         * @access public
         * @author Jorrit Schippers
         * @param boolean $read_from_cache If true and the data exists within a cache then that data is used. If false
         *  then the data is re-read from ffmpeg.
         * @return array Returns an array with the following keys; build, version. Note, depending on your version of ffmpeg
         *  either one or both of these keys will container values.
         * @throws PHPVideoToolkit\FfmpegProcessException If a version is not found in the basic information then we call ffmpeg -version
         *  to get the version information. If that call encounters an error this exception is thrown.
         */
        public function getVersion($read_from_cache=true)
        {
            $cache_key = 'ffmpeg_parser/ffmpeg_version';
            if($read_from_cache === true && ($data = $this->_cacheGet($cache_key)))
            {
                return $data;
            }
            
            $version = null;
            $build = null;
            
            $raw_data = $this->getRawFfmpegData($read_from_cache);
            
//          Search for SVN string
//          FFmpeg version SVN-r20438, Copyright (c) 2000-2009 Fabrice Bellard, et al.
            if($build === null && preg_match('/(?:ffmpeg|avconv) version SVN-r([0-9.]*)/i', $raw_data, $matches) > 0)
            {
                $build = $matches[1];
            }

// //           Some OSX versions are built from a very early CVS
// //           I do not know what to do with this version- using 1 for now
//          if(preg_match('/(?:ffmpeg|avconv) version(.*)CVS.*Mac OSX universal/msUi', $raw_data, $matches) > 0)
//          {
//              $build = $matches[1];
//          }

//          Search for git string
//          FFmpeg version git-N-29240-gefb5fa7, Copyright (c) 2000-2011 the FFmpeg developers.
//          ffmpeg version N-31145-g59bd0fe, Copyright (c) 2000-2011 the FFmpeg developers
            if($build === null && preg_match('/(?:ffmpeg|avconv) version.*N-([0-9.]*)/i', $raw_data, $matches) > 0)
            {
//              Versions above this seem to be ok
                if($matches[1] >= 29240)
                {
                    $build = $matches[1];
                }
            }

//          Do we have a release?
//          ffmpeg version 0.4.9-pre1, build 4736, Copyright (c) 2000-2004 Fabrice Bellard
            if($build === null && preg_match('/(?:ffmpeg|avconv) version ([^,]+) build ([0-9]+),/i', $raw_data, $matches) > 0)
            {
                $version = $matches[1];
                $build = $matches[2];
            }

//          Do we have a build version?
//          ffmpeg version 0.4.9-pre1, build 4736, Copyright (c) 2000-2004 Fabrice Bellard
            if($build === null && preg_match('/(?:ffmpeg|avconv) version.*, build ([0-9]*)/i', $raw_data, $matches) > 0)
            {
                $build = $matches[1];
            }
            
//          ffmpeg version 1.1.2 Copyright (c) 2000-2013 the FFmpeg developers
            if($version === null && preg_match('/ffmpeg version ([^\s]+) Copyright/i', $raw_data, $matches) > 0)
            {
                $version = $matches[1];
            }
            
//          avconv version 0.8.9-4:0.8.9-0ubuntu0.12.04.1, Copyright (c) 2000-2013 the Libav developers built on Nov 9 2013 19:08:00 with gcc 4.6.3
            if($version === null && preg_match('/avconv version\s+([^:]+):/msUi', $raw_data, $matches) > 0)
            {
                $version = $matches[1];
            }

//          get the version from -version
            if($version === null)
            {
                $exec = new FfmpegProcess('ffmpeg', $this->_config);
                $data = $exec->addCommand('-version')
                             ->execute()
                             ->getBuffer();
            
//              check the process for any errors.
                if($exec->hasError() === true)
                {
                    throw new FfmpegProcessException('An error was encountered when attempting to read FFmpegs\' version. FFmpeg reported: '.$exec->getLastLine(), null, $exec);
                }
                
                if(preg_match('/FFmpeg version ([0-9\.]+)/i', $data, $matches) > 0)
                {
                    $version = $matches[1];
                }
                else if(preg_match('/FFmpeg ([0-9]+\.[0-9]+\.[0-9]+)/i', $data, $matches) > 0)
                {
                    $version = $matches[1];
                }
            }
            
//          if both version and build are not available throw a new exception to get the user to provide their ffmpeg data to github so we can start building up different formats of ffmpeg output.
            if($version === null && $build === null)
            {
                throw new FfmpegProcessException('Unable to determine your FFmpeg version or build. Please create an issue at the github repository for PHPVideoToolkit 2; https://github.com/buggedcom/phpvideotoolkit-v2/issues. Please add the following data to the ticket:<br />
<br />              
<code>'.$this->getRawFfmpegData($read_from_cache).'</code>');
            }
            
            $data = array(
                'build' => $build,
                'version' => $version, 
                'from-cache' => true,
                'read-at' => time(),
            );

            $this->_cacheSet($cache_key, $data);

            $data['from-cache'] = false;
            return $data;
        }
        
        /**
         * Returns the information about ffmpeg itself.
         *
         * @access public
         * @author Oliver Lillie
         * @param boolean $read_from_cache If true and the data exists within a cache then that data is used. If false
         *  then the data is re-read from ffmpeg.
         * @return array Returns all the information available about ffmpeg in a single array.
         */
        public function getInformation($read_from_cache=true)
        {
            $cache_key = 'ffmpeg_parser/parsed_information';
            if($read_from_cache === true && ($data = $this->_cacheGet($cache_key)))
            {
                return $data;
            }
            
            $data                           = $this->getFfmpegData($read_from_cache);
            $data['version']                = $this->getVersion($read_from_cache);
            $data['has-ffmpeg-php-support'] = $this->hasFfmpegPhpSupport($read_from_cache);
            $data['formats']                = $this->getFormats($read_from_cache);
            $data['codecs']                 = $this->getCodecs(null, $read_from_cache);
            $data['protocols']              = $this->getProtocols($read_from_cache);
            $data['pixel_formats']          = $this->getPixelFormats($read_from_cache);
            //$data['commands']                 = $this->getCommands();
            
            $this->_cacheSet($cache_key, $data);
            return $data;
        }
        
        /**
         * Determines if we are able to utilise FFmpeg-PHP either through a loaded
         * module or one of our emulators.
         *
         * @access public
         * @author Oliver Lillie
         * @param boolean $read_from_cache 
         * @return mixed Returns false (boolean) on failure otherwise returns module or emulated.
         */
        public function hasFfmpegPhpSupport($read_from_cache=true)
        {
            $cache_key = 'ffmpeg_parser/ffmpeg_php_available';
            if($read_from_cache === true && ($data = $this->_cacheGet($cache_key, -1)) !== -1)
            {
                return $data;
            }
            
//          check to see if the module is loaded.
            if(extension_loaded('ffmpeg') === true)
            {
                $data = 'module';
                $this->_cacheSet($cache_key, $data);
                return $data;
            }
            
//          check to see if an adapter exists
            $base_dir = dirname(dirname(__FILE__));
            if(   is_file($base_dir.'/emulators/ffmpeg-php/ffmpeg_movie.php') === true
               && is_file($base_dir.'/emulators/ffmpeg-php/ffmpeg_frame.php') === true
               && is_file($base_dir.'/emulators/ffmpeg-php/ffmpeg_animated_gif.php') === true)
            {
                $data = 'emulated';
                $this->_cacheSet($cache_key, $data);
                return $data;
            }
            
            $data = false;
            $this->_cacheSet($cache_key, $data);
            return $data;
        }
        
        /**
         * Returns the available formats in a processed easy to use format.
         *
         * @access public
         * @author Oliver Lillie
         * @param boolean $read_from_cache If true and the data exists within a cache then that data is used. If false
         *  then the data is re-read from ffmpeg.
         * @return array Returns an array of formats and related information about each format.
         */
        public function getFormats($read_from_cache=true)
        {
            $cache_key = 'ffmpeg_parser/parsed_formats';
            if($read_from_cache === true && ($data = $this->_cacheGet($cache_key)))
            {
                return $data;
            }
            
//          get the raw format information
            $raw_data = $this->getRawFormatData();

//          then match out the relevant data, clean and process.
            $data = array();
            if(preg_match_all('/ (DE|D|E) (.*) {1,} (.*)/', $raw_data, $format_matches) > 0)
            {
                foreach($format_matches[0] as $key=>$match)
                {
                    $format_code = strtolower(trim($format_matches[2][$key]));
                    
                    $mux_and_demux = $format_matches[1][$key] === 'DE';
                    $data[$format_code] = array(
                        'mux'       => $mux_and_demux === true || $format_matches[1][$key] === 'E',
                        'demux'     => $mux_and_demux === true || $format_matches[1][$key] === 'D',
                        'fullname'  => $format_matches[3][$key],
                        'extensions'=> Formats::toExtensions($format_code),
                    );
                }
            }
            
            $this->_cacheSet($cache_key, $data);
            return $data;
        }
        
        /**
         * Returns the available codecs in their processed easy to use format.
         *
         * @access public
         * @author Oliver Lillie
         * @param mixed $component If not set or set to null then all the audio, video and subtitle codecs
         *  are returned, otherwise if set to 'audio', 'video', or 'subtitle' then just the related data
         *  is returned.
         * @param boolean $read_from_cache If true and the data exists within a cache then that data is used. If false
         *  then the data is re-read from ffmpeg.
         * @return array Returns an array of formats and related information about each codec.
         * @throws \InvalidArgumentException If the $component argument is invalid.
         */
        public function getCodecs($component=null, $read_from_cache=true)
        {
            if(in_array($component, array('video', 'audio', 'subtitle', null)) === false)
            {
                throw new \InvalidArgumentException('Unrecognised codec component specified.');
            }
            
            $cache_key = 'ffmpeg_parser/parsed_codecs';
            if($read_from_cache === true && ($data = $this->_cacheGet($cache_key)))
            {
                return $component === null ? $data : (isset($data[$component]) === true ? $data[$component] : false);
            }
            
//          get the raw format information
            $raw_data = $this->getRawCodecData();
            
//          then match out the relevant data, clean and process.
            $data = array(
                'video'     => array(), 
                'audio'     => array(), 
                'subtitle'  => array()
            );
            if(preg_match_all('/ ((?:[DEVAST ]{6})|(?:[DEVASTFB ]{8})|(?:[DEVASIL\.]{6})) ([A-Za-z0-9\_]+) (.+)/', $raw_data, $codec_matches) > 0)
            {
//              FFMPEG 0.12+
//               D..... = Decoding supported
//               .E.... = Encoding supported
//               ..V... = Video codec
//               ..A... = Audio codec
//               ..S... = Subtitle codec
//               ...I.. = Intra frame-only codec
//               ....L. = Lossy compression
//               .....S = Lossless compression
//              FFMPEG OTHER
//               D..... = Decoding supported
//               .E.... = Encoding supported
//               ..V... = Video codec
//               ..A... = Audio codec
//               ..S... = Subtitle codec
//               ...S.. = Supports draw_horiz_band
//               ....D. = Supports direct rendering method 1
//               .....T = Supports weird frame truncation
                // TODO, seperate out into distinct functions
                foreach ($codec_matches[3] as $key => $fullname)
                {
                    $options = preg_split('//', $codec_matches[1][$key], -1, PREG_SPLIT_NO_EMPTY);
                    if(empty($options) === false)
                    {
                        $id = trim($codec_matches[2][$key]);
                        $type = $options[2] === 'V' ? 'video' : ($options[2] === 'A' ? 'audio' : 'subtitle');
                        switch ($options[2])
                        {
//                          video
                            case 'V' :
                                $data[$type][$id] = array(
                                    'encode'                    => isset($options[1]) === true && $options[1] === 'E',
                                    'decode'                    => isset($options[0]) === true && $options[0] === 'D',
                                    'draw_horizontal_band'      => isset($options[3]) === true && $options[3] === 'S',
                                    'direct_rendering_method_1' => isset($options[4]) === true && $options[4] === 'D',
                                    'weird_frame_truncation'    => isset($options[5]) === true && $options[5] === 'T',
                                    'fullname'                  => trim($fullname),
                                );
                                break;
//                          audio and subtitles.
                            case 'A' :
                            case 'S' :
                                $data[$type][$id] = array(
                                    'encode'    => isset($options[1]) === true && $options[1] === 'E',
                                    'decode'    => isset($options[0]) === true && $options[0] === 'D',
                                    'fullname'  => trim($fullname),
                                );
                            break;
                        }
                    }
                }
            }
            
            $this->_cacheSet($cache_key, $data);
            
//          are we to only return a specific component of the data?
            if($component !== null)
            {
                return $data[$component];
            }
            
            return $data;
        }
        
        /**
         * Returns the available bitstream filters in their processed easy to use format.
         *
         * @access public
         * @author Oliver Lillie
         * @param boolean $read_from_cache If true and the data exists within a cache then that data is used. If false
         *  then the data is re-read from ffmpeg.
         * @return array Returns an array of bitstream filter strings.
         */
        public function getBitstreamFilters($read_from_cache=true)
        {
            $cache_key = 'ffmpeg_parser/parsed_bitstream_filters';
            if($read_from_cache === true && ($data = $this->_cacheGet($cache_key)))
            {
                return $data;
            }
            
//          get the raw format information
            $raw_data = $this->getRawBitstreamFiltersData();
            
//          then match out the relevant data, clean and process.
            $data = array();
            $locate = 'Bitstream filters:';
            if(empty($raw_data) === false && ($pos = strpos($raw_data, $locate)) !== false)
            {
                $raw_data = trim(substr($raw_data, $pos + strlen($locate)));
                $data = explode("\n", $raw_data);
                array_walk($data, function(&$filter, $key)
                {
                    $filter = trim($filter);
                });
            }
            
            $this->_cacheSet($cache_key, $data);
            return $data;
        }
        
        /**
         * Returns the available filters in their processed easy to use format.
         *
         * @access public
         * @author Oliver Lillie
         * @param boolean $just_filter_names If true then just the list of available filters will be returned.
         *  otherwise all the available data will be returned.
         * @param boolean $read_from_cache If true and the data exists within a cache then that data is used. If false
         *  then the data is re-read from ffmpeg.
         * @return array Returns an array of available filters. If $just_filter_names is true, then just the filter names
         *  are returned, otherwise the array is returned with the filter names as the key and the value as another array containing
         *  information about the filter.
         */
        public function getFilters($just_filter_names=true, $read_from_cache=true)
        {
            $component = $just_filter_names === true ? 'filters' : 'verbose';
            
            $cache_key = 'ffmpeg_parser/parsed_filters';
            if($read_from_cache === true && ($data = $this->_cacheGet($cache_key)))
            {
                return $component === 'verbose' ? $data : array_keys($data);
            }
            
//          get the raw format information
            $raw_data = $this->getRawFiltersData();

//          then match out the relevant data, clean and process.
            $data = array();
            $locate = 'Filters:';
            if(empty($raw_data) === false && ($pos = strpos($raw_data, $locate)) !== false)
            {
                $raw_data = trim(substr($raw_data, $pos + strlen($locate)));
                if(preg_match_all('/([a-z]+)\s+([\|VA]{1,2})\->([\|VA]{1,2})\s+(.*)/i', $raw_data, $matches) > 0)
                {
                    foreach ($matches[1] as $key => $filter)
                    {
                        $from = $matches[2][$key];
                        $from_name = $from === 'V' ? 'video1' : ($from === 'VV' ? 'video2' : ($from === 'A' ? 'audio1' : ($from === 'AA' ? 'audio2' : ($from === '|' ? 'pipe' : null))));
                            
                        $to = $matches[2][$key];
                        $to_name = $to === 'V' ? 'video1' : ($to === 'VV' ? 'video2' : ($to === 'A' ? 'audio1' : ($to === 'AA' ? 'audio2' : ($to === '|' ? 'pipe' : null))));
                        
                        $data[$filter] = array(
                            'filter' => $filter,
                            'description' => $matches[4][$key],
                            'from' => $from_name,
                            'to' => $to_name,
                        );
                    }
                }
            }
            
            $this->_cacheSet($cache_key, $data);

            return $component === 'verbose' ? $data : array_keys($data);
        }
        
        /**
         * Returns the available filters in their processed easy to use format.
         *
         * @access public
         * @author Oliver Lillie
         * @param boolean $read_from_cache If true and the data exists within a cache then that data is used. If false
         *  then the data is re-read from ffmpeg.
         * @return array Returns an array of available protocols and whether or not they are input or ouput protocols.
         */
        public function getProtocols($read_from_cache=true)
        {
            $cache_key = 'ffmpeg_parser/parsed_protocols';
            if($read_from_cache === true && ($data = $this->_cacheGet($cache_key)))
            {
                return $data;
            }
            
//          get the raw format information
            $raw_data = $this->getRawProtocolsData();
            
//          then match out the relevant data, clean and process.
            $data = array();
            if(empty($raw_data) === false)
            {
//              check to see if we have input and output settings.
                $input_locate = 'Input:';
                if(($input_pos = strpos($raw_data, $input_locate)) !== false)
                {
//                  seperate out input and output protocols
                    $input_locate_length = strlen($input_locate);
                    $raw_data = substr($raw_data, $input_pos+$input_locate_length);
                    $raw_data = explode('Output:', $raw_data);
                    
//                  process the input
                    $input = trim($raw_data[0]);
                    $input = explode("\n", $input);
                    if(empty($input) === false)
                    {
                        foreach($input as $protocol)
                        {
                            $data[$protocol] = array(
                                'input' => true,
                                'output' => false,
                            );
                        }
                    }
                    
//                  if the output protocols are found, process them by augmenting the existing data
//                  or creating a new record in the data.
                    if(isset($raw_data[1]) === true && empty($raw_data[1]) === false)
                    {
                        $output = trim($raw_data[1]);
                        $output = explode("\n", $output);
                        foreach($output as $protocol)
                        {
                            if(isset($data[$protocol]) === true)
                            {
                                $data[$protocol]['output'] = true;
                            }
                            else
                            {
                                $data[$protocol] = array(
                                    'input' => false,
                                    'output' => true,
                                );
                            }
                        }
                    }
                }
                else 
                {
//                  this is the older version of the protocol format.
                    $locate = 'Supported file protocols:';
                    if(($pos = strpos($raw_data, $locate)) !== false)
                    {
                        $raw_data = trim(substr($raw_data, $pos + strlen($locate)));
                        $raw_data = explode("\n", $raw_data);
                        array_walk($raw_data, function(&$protocol, $key)
                        {
//                          both input and output is assumed to be true
                            $data[$protocol] = array(
                                'input' => true,
                                'output' => true,
                            );
                        });
                    }
                }
            }
            
            $this->_cacheSet($cache_key, $data);
            return $data;
        }
        
        /**
         * Returns the available filters in their processed easy to use format.
         *
         * @access public
         * @author Oliver Lillie
         * @param boolean $read_from_cache If true and the data exists within a cache then that data is used. If false
         *  then the data is re-read from ffmpeg.
         * @return array Returns an array of information about the available pixel formats.
         */
        public function getPixelFormats($read_from_cache=true)
        {
            $cache_key = 'ffmpeg_parser/parsed_pixel_formats';
            if($read_from_cache === true && ($data = $this->_cacheGet($cache_key)))
            {
                return $data;
            }
            
//          get the raw format information
            $raw_data = $this->getRawPixelFormatsData();
            
//          then match out the relevant data, clean and process.
            $data = array();
            if(strpos($raw_data, 'Pixel formats') === false)
            {
//              Format:
//              name       nb_channels depth is_alpha
//              yuv420p         3         8      n
//              yuyv422         1         8      n
                if(preg_match_all('/(\w+)\s+(\d+)\s+(\d+)\s+(y|n)/', $raw_data, $matches, PREG_SET_ORDER) > 0)
                {
                    foreach($matches as $match)
                    {
//                      we have to assume both encode and decode are true.
                        $data[$match[1]] = array(
                            'encode'               => true, 
                            'decode'               => true,
                            'components'           => (int) $match[2],
                            'bpp'                  => (int) $match[3],
                            'hardware_accelerated' => null,
                            'paletted_format'      => null,
                            'bitstream_format'     => null,
                            'alpha'                => $match[4] === 'y',
                        );
                    }
                }
            }
            else
            {
//              Format:
//              Pixel formats:
//              I.... = Supported Input  format for conversion
//              .O... = Supported Output format for conversion
//              ..H.. = Hardware accelerated format
//              ...P. = Paletted format
//              ....B = Bitstream format
//              FLAGS NAME            NB_COMPONENTS BITS_PER_PIXEL
//              -----
//              IO... yuv420p                3            12
                if(preg_match_all('/(I|\.)(O|\.)(H|\.)(P|\.)(B|\.)\s+(\w+)\s+(\d+)\s+(\d+)/', $raw_data, $matches, PREG_SET_ORDER) > 0)
                {
                    foreach ($matches as $match)
                    {
                        $data[$match[6]] = array(
                            'encode'               => $match[1] === 'I',
                            'decode'               => $match[2] === 'O',
                            'components'           => (int) $match[7],
                            'bpp'                  => (int) $match[8],
                            'hardware_accelerated' => $match[3] === 'H',
                            'paletted_format'      => $match[4] === 'P',
                            'bitstream_format'     => $match[5] === 'B',
                            'alpha'                => null,
                        );
                    }
                }
            }
            
            $this->_cacheSet($cache_key, $data);
            return $data;
        }
        
        /**
         * Returns the available filters in their processed easy to use format.
         *
         * @access public
         * @author Oliver Lillie
         * @param boolean $read_from_cache If true and the data exists within a cache then that data is used. If false
         *  then the data is re-read from ffmpeg.
         * @return array Returns the list of commands the ffmpeg can support.
         */
        public function getCommands($read_from_cache=true)
        {
            $cache_key = 'ffmpeg_parser/parsed_commands';
            if($read_from_cache === true && ($data = $this->_cacheGet($cache_key)))
            {
                return $data;
            }
            
//          get the raw format information
            $raw_data = $this->getRawCommandsData();
            
//          then match out the relevant data, clean and process.
            $data = array();
            if(preg_match_all('/\n-(\w+)(.*)  (.*)/', $raw_data, $matches, PREG_SET_ORDER) > 0)
            {
                foreach ($matches as $key=>$match)
                {
                    $data_type = null;
                    if(preg_match_all('/(?:\s+<(int|string|binary|flags|int64|float|rational)>)/', $match[2], $data_type_matches) > 0)
                    {
                        $data_type = trim($data_type_matches[1][0]);
                    }
                    
                    $args = trim($match[2]);
                    $args = empty($args) === true ? array() : explode(' ', $args);
                    
                    $deprecated = strpos($match[3], 'deprecated') !== false;
                    $removed = strpos($match[3], 'Removed') !== false;
                    
                    $data[$match[1]] = array(
                        'datatype' => $data_type,
                        'description' => $match[3],
                        'arguments' => $args,
                        'status' => $deprecated === true ? 'deprecated' : ($removed === true ? 'removed' : null),
                    );
                }
            }
            
            $this->_cacheSet($cache_key, $data);
            return $data;
        }
        
    }
