Login   Register  
PHP Classes
elePHPant
Icontem

File: src/PHPVideoToolkit/FfmpegProcessProgressable.php

Recommend this page to a friend!
Stumble It! Stumble It! Bookmark in del.icio.us Bookmark in del.icio.us
  Classes of Oliver Lillie  >  PHP Video Toolkit  >  src/PHPVideoToolkit/FfmpegProcessProgressable.php  >  Download  
File: src/PHPVideoToolkit/FfmpegProcessProgressable.php
Role: Class source
Content type: text/plain
Description: Class source
Class: PHP Video Toolkit
Manipulate and convert videos with ffmpeg program
Author: By
Last change: .
Date: 2 months ago
Size: 13,831 bytes
 

Contents

Class file image Download
<?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.1
     * @uses ffmpeg http://ffmpeg.sourceforge.net/
     */
     
    namespace PHPVideoToolkit;
    
    /**
     * undocumented class
     *
     * @access public
     * @author Oliver Lillie
     * @package default
     */
    class FfmpegProcessProgressable extends FfmpegProcess 
    {
        private $_progress_callbacks;
        
        public function __construct($binary_path, Config $config=null)
        {
            parent::__construct($binary_path, $config);
            
            $this->_progress_callbacks = array();
        }
        
        /**
         * Sets a command on ffmpeg that sets a timelimit
         *
         * @access public
         * @author Oliver Lillie
         * @param string $timelimit_in_seconds 
         * @return self
         */
        public function setProcessTimelimit($timelimit_in_seconds)
        {
            $parser = new FfmpegParser($this->_config);
            $commands = $parser->getCommands();
            if(isset($commands['timelimit']) === false)
            {
                throw new Exception('The -timelimit command is not supported by your version of FFmpeg.');
            }
            
            if($timelimit_in_seconds <= 0)
            {
                throw new Exception('The timelimit must be greater than 0 seconds.');
            }
            
            $this->addCommand('-timelimit', $timelimit_in_seconds);
            
            return $this;
        }
        
        /**
         * Attaches a progress handler to the ffmpeg progress. 
         * The progress handler is executed during the ffmpeg process.
         * Attaching a handler causes PHP to block.
         *
         * @access public
         * @author Oliver Lillie
         * @param string $callback 
         * @return self
         */
        public function attachProgressHandler($callback)
        {
            if(is_object($callback) === true)
            {
                if(is_subclass_of($callback, 'PHPVideoToolkit\ProgressHandlerAbstract') === false)
                {
                    throw new Exception('If supplying an object to attach as a progress handler, that object must inherit from ProgressHandlerAbstract.');
                }

                $callback->attachFfmpegProcess($this, $this->_config);
            }
            else if(is_callable($callback) === false)
            {
                throw new Exception('The progress handler must either be a class that extends from ProgressHandlerAbstract or a callable function.');
            }
            
            array_push($this->_progress_callbacks, $callback);
            
            return $this;
        }
        
        /**
         * This function is used to execute the callback handlers when present.
         *
         * IMPORTANT! This is a protected function, however due to the nature of the 
         * callbacks, it must be public in order to be callable. 
         *
         * @access protected
         * @author Oliver Lillie
         * @return void
         */
        public function _executionCallbackRunner()
        {
            foreach($this->_progress_callbacks as $callback)
            {
                if(is_object($callback) === true)
                {
                    $callback->callback();
                }
                else
                {
                    call_user_func($callback, $this);
                }
            }
        }
        
        /**
         * Executes the ffmpeg process and can be supplied with an optional progress callback.
         *
         * @access public
         * @author Oliver Lillie
         * @param mixed $callback If given it must be a valid function that is callable.
         * @return self
         */
        public function execute($callback=null)
        {
            if($callback !== null)
            {
                if(is_callable($callback) === false)
                {
                    throw new Exception('Callback is not callable.');
                }

                $this->attachProgressHandler($callback);
            }
            
            if(empty($this->_progress_callbacks) === false)
            {
                $callback = array($this, '_executionCallbackRunner');
            }

            $this->getExecBuffer()
                 ->setBlocking(false)
                 ->execute($callback);
            
            return $this;
        }
        
        /**
         * Once the process has been completed this function can be called to return the output
         * of the process. Depending on what the process is outputting depends on what is returned.
         * If a single video or audio is being outputted then the related PHPVideoToolkit media object
         * will be returned. However if multiple files are being outputed then an array of the associated
         * objects are returned. Typically speaking an array will be returned when %index or %timecode
         * are within the output path.
         *
         * @access public
         * @author Oliver Lillie
         * @return mixed
         */
        public function getOutput($post_process_callback=null)
        {
            if($this->isCompleted() === false)
            {
                throw new FfmpegProcessOutputException('Encoding has not yet started.');
            }
            
//          check for an error.
            if($this->hasError() === true)
            {
//              check for specific recieved signal errors.
                $last_split = $this->getLastSplit();
                if(preg_match('/Received signal ([0-9]+): terminating\./', $last_split, $matches) > 0)
                {
                    $kill_signals = array(
                        1 => 'Hang up detected on controlling terminal or death of controlling process.',
                        2 => 'User sent an interrupt signal.',
                        3 => 'User sent a quit signal.',
                        4 => 'Illegal instruction.',
                        6 => 'Abort signal from abort(3).',
                        8 => 'Floating point exception.',
                        9 => 'Kill signal sent.',
                        11 => 'Invalid memory reference',
                        13 => 'Broken pipe: write to pipe with no readers',
                        14 => 'Timer signal from alarm(2)',
                        15 => 'Termination signal sent.',
                        24 => 'Imposed time limit ({length} seconds) exceeded.',
                    );
                    // TODO add more signals.
                    $kill_int = (int) $matches[1];
                    if(isset($kill_signals[$kill_int]) === true)
                    {
                        $message = $kill_signals[$kill_int];
                        if($kill_int == 24)
                        {
                            $length = $this->getCommand('-timelimit');
                            $length = !$length ? 'unknown' : $length;
                            $message = str_replace('{length}', $length, $message);
                        }
                        throw new FfmpegProcessOutputException('Process was aborted. '.$message);
                    }
                    else
                    {
                        throw new FfmpegProcessOutputException('Termination signal received and the process aborted. Signal was '.$matches[1]);
                    }
                }
            
                throw new FfmpegProcessOutputException('Encoding failed and an error was returned from ffmpeg. Error code '.$this->getErrorCode().' was returned the message (if any) was: '.$last_split);
            }
            
            if($post_process_callback !== null)
            {
                if(is_callable($post_process_callback) === false)
                {
                    throw new Exception('The supplied post process callback is not callable.');
                }
            }
            
//          get the output of the process
            $output_path = $this->getOutputPath();
            
//          we have the output path but we now need to treat differently dependant on if we have multiple file output.
            if(preg_match('/\.(\%([0-9]*)d)\.([0-9\.]+_[0-9\.]+\.)?_(i|t)\./', $output_path, $matches) > 0)
            {
//              determine what we have to rename all the files to.
                $convert_back_to = $matches[4] === 't' ? 'timecode' : (int) $matches[2];
                
//              get the glob path and then find all the files from this output
                $output_glob_path = str_replace($matches[0], '.*.'.$matches[3].'_'.$matches[4].'.', $output_path);
                $outputted_files = glob($output_glob_path);
                
//              sort the output naturally so that if there is no index padding that we get the frames in the correct order.
                natsort($outputted_files);

//              loop to rename the file and then create each output object.
                $output = array();
                $timecode = null;
                foreach ($outputted_files as $path)
                {
                    $actual_path = preg_replace('/\._u\.[0-9]{5}_[a-z0-9]{5}_[0-9]+\.u_\./', '.', $path);
                    if($convert_back_to === 'timecode')
                    {
//                      if the start timecode has not been generated then find the required from the path string.
                        if($timecode === null)
                        {
                            $matches[3] = rtrim($matches[3], '.');
                            $matches[3] = explode('_', $matches[3]);
                            $timecode = new Timecode($matches[3][1], Timecode::INPUT_FORMAT_SECONDS, $matches[3][0]);
                        }
                        else
                        {
                            $timecode->frame += 1;
                        }
                        $actual_path = preg_replace('/\.[0-9]{12}\.[0-9\.]+_[0-9\.]+\._t\./', $timecode->getTimecode('%hh_%mm_%ss_%ms', false), $actual_path);
                    }
                    else
                    {
                        $actual_path = preg_replace('/\.([0-9]+)\._i\./', '$1', $actual_path);
                    }
                    rename($path, $actual_path);
                    
                    $media_class = $this->_findMediaClass($actual_path);
                    $output_object = new $media_class($actual_path, $this->_config, null, false);
                    
                    array_push($output, $output_object);
                    
                    unset($output_object);
                }
                unset($outputted_files);
                
                // TODO create the multiple image output
            }
            else
            {
//              check for a none multiple file existence
                if(empty($output_path) === true)
                {
                    throw new FfmpegProcessOutputException('Unable to find output for the process as it was not set.');
                }
                else if(is_file($output_path) === false)
                {
                    throw new FfmpegProcessOutputException('The output "'.$output_path.'", of the Ffmpeg process does not exist.');
                }
                else if(filesize($output_path) <= 0)
                {
                    throw new FfmpegProcessOutputException('The output "'.$output_path.'", of the Ffmpeg process is a 0 byte file. Something must have gone wrong however it wasn\'t reported as an error by FFmpeg.');
                }
                
//              get the media class from the output.
//              create the object from the class name and return the new object.
                $media_class = $this->_findMediaClass($output_path);
                $output = new $media_class($output_path, $this->_config, null, false);
            }
                
//          do any post processing callbacks
            if($post_process_callback !== null)
            {
                $output = call_user_func($post_process_callback, $output, $this);
            }
                
//          finally return the output to the user.
            return $output;
        }
        
        /**
         * Attempts to read the data about the file given by $path and then returns the class
         * name of the related media object.
         *
         * @access protected
         * @author Oliver Lillie
         * @param string $path 
         * @return string
         */
        protected function _findMediaClass($path)
        {
//          read the output to determine what it is so it can be post processed.
            $data = getimagesize($path);
            if(!$data)
            {
                $type = 'audio';
            }
            else if(strpos($data['mime'], 'image/') !== false)
            {
                $type = 'image';
            }
            else
            {
                $type = 'video';
            }
            
//          now we have the information switch between the types and create the return object.
            $class = 'Media';
            switch($type)
            {
                case 'audio' :
                case 'video' :
                case 'image' :
                    $class = '\\PHPVideoToolkit\\'.ucfirst(strtolower($type));
                    break;
            }
            
            return $class;
        }

    }