PHP Classes

File: src/PHPVideoToolkit/FfmpegProcessProgressable.php

Recommend this page to a friend!
  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: Some Fixes
- Corrected some PHPDoc for IDE Recommendation
- added IntelliJ folder to ignore
fixed bug in timecode generation using frame rate

Signed-off-by: Oliver Lillie <buggedcom@gmail.com>
updated documentation and added a new exception. updated exceptions and argument checks where appropriate.
updated php doc bloc @author so not to contain :
fixed issue where the output is generated so quickly that the output requires storing for later reuse.
bug fix with ffmpeg progress able process and multi output completeProcess structure
updated multiple output code to work with new renaming system
updated version in source
fixed an issue where the output if using %timecode or %index would not be automatically renamed unless ->getOutput was called. #24 - Thanks @cassianotartari for aiding in discovering this bug.
Date: 1 year ago
Size: 20,483 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.7-beta * @uses ffmpeg http://ffmpeg.sourceforge.net/ */ namespace PHPVideoToolkit; /** * If tbe ffmpeg process can be used in conjunction with a process handler, then this class is used to extend * the FfmpegProcess object. * * @author Oliver Lillie */ class FfmpegProcessProgressable extends FfmpegProcess { /** * [$_output_renamed description] * @var [type] * @access private */ private $_output_renamed; /** * [$_output_renamed description] * @var [type] * @access private */ private $_final_output; /** * [$_output_renamed description] * @var [type] * @access private */ private $_progress_callbacks; /** * Constructor * * @access public * @author: Oliver Lillie * @param string $program The programme to call. Note this is not the path. If you wish to call ffmpeg/aconv you should jsut * supply 'ffmpeg' and then set the aconv path as the ffmpeg configuration option in Config. * @param Config $config The config object. */ public function __construct($programme, Config $config=null) { parent::__construct($programme, $config); $this->_progress_callbacks = array(); $this->_output_renamed = null; $this->_final_output = null; } /** * Sets a command on ffmpeg that sets a timelimit for the process. If this timelimit is exceeded then ffmpeg bails. * * @access public * @author Oliver Lillie * @param integer $timelimit_in_seconds The timelimit to impose in seconds. * @return FfmpegProcess Returns the current object. * @throws FfmpegProcessCommandUnavailableException If the timelimit command is not available on the configured ffmpeg. * @throws \InvalidArgumentException If the timelimit is not an integer. * @throws \InvalidArgumentException If the timelimit is less than or equal to 0. */ public function setProcessTimelimit($timelimit_in_seconds) { $parser = new FfmpegParser($this->_config); $commands = $parser->getCommands(); if(isset($commands['timelimit']) === false) { throw new FfmpegProcessCommandUnavailableException('The -timelimit command is not supported by your version of FFmpeg.'); } if(is_int($timelimit_in_seconds) === false) { throw new \InvalidArgumentException('The timelimit in seconds argument must be an integer.'); } else if($timelimit_in_seconds <= 0) { throw new \InvalidArgumentException('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 can cause a blocking process depending on the progress handler object or function used. * * @access public * @author Oliver Lillie * @param mixed $callback Can be a callable callback, or an object that extends PHPVideoToolkit\ProgressHandlerAbstract * @return FfmpegProcess Returns the current object. * @throws \InvalidArgumentException If the callback is an object and is not a subclass of PHPVideoToolkit\ProgressHandlerAbstract * @throws \InvalidArgumentException If the callback is not callable if not an object. */ public function attachProgressHandler($callback) { if(is_object($callback) === true) { if(is_subclass_of($callback, 'PHPVideoToolkit\ProgressHandlerAbstract') === false) { throw new \InvalidArgumentException('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 \InvalidArgumentException('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 FfmpegProcess Returns the current object. * @throws \InvalidArgumentException If the callback is not callable. */ public function execute($callback=null) { if($callback !== null) { if(is_callable($callback) === false) { throw new \InvalidArgumentException('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; } /** * Returns the output of the process if the process has completed. * * @access public * @author: Oliver Lillie * @param mixed $post_process_callback * @return array Returns an array of output if more than 1 output file is expected, otherwise returns a string. */ public function getOutput($post_process_callback=null) { // get the output of the process return $this->completeProcess($post_process_callback); } /** * 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 * @param mixed $post_process_callback * @return mixed * @throws \InvalidArgumentException If a callback is supplied but is not callable. * @throws PHPVideoToolkit\FfmpegProcessOutputException If the function is called but the process has not completed * yet. * @throws PHPVideoToolkit\FfmpegProcessOutputException If the process was aborted. * @throws PHPVideoToolkit\FfmpegProcessOutputException If the process completed with a termination signal. * @throws PHPVideoToolkit\FfmpegProcessOutputException If the process completed with an error. * @throws PHPVideoToolkit\FfmpegProcessOutputException If returned output is empty. * @throws PHPVideoToolkit\FfmpegProcessOutputException If returned output files does not exist. * @throws PHPVideoToolkit\FfmpegProcessOutputException If returned output files does exist but is a 0 byte file. */ public function completeProcess($post_process_callback=null) { if($this->_final_output !== null) { return $this->_final_output; } if($post_process_callback !== null) { if(is_callable($post_process_callback) === false) { throw new \InvalidArgumentException('The supplied post process callback is not callable.'); } } 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); } $output_path = $this->_renameMultiOutput(); $output = array(); foreach ($output_path as $key=>$path) { if(is_string($path) === true) { // check for a none multiple file existence if(empty($path) === true) { throw new FfmpegProcessOutputException('Unable to find output for the process as it was not set.'); } else if(is_file($path) === false) { throw new FfmpegProcessOutputException('The output "'.$path.'", of the Ffmpeg process does not exist.'); } else if(filesize($path) <= 0) { throw new FfmpegProcessOutputException('The 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.'); } $output[$key] = $this->_convertPathToMediaObject($path); } else if(is_array($path) === true && empty($path) === false) { $path_output = array(); foreach ($path as $file_path) { array_push($path_output, $this->_convertPathToMediaObject($file_path)); } $output[$key] = $path_output; unset($path_output); } } if(count($output) === 1) { $output = $output[0]; } // do any post processing callbacks if($post_process_callback !== null) { $output = call_user_func($post_process_callback, $output, $this); } return $this->_final_output = $output; } /** * Checks to see if the ffmpeg output is a %d format and if so performs the rename of the output. * * @access protected * @author Oliver Lillie * @return mixed If %d output is expected then an array of path names is returned, otherwise a string path is returned. */ protected function _renameMultiOutput() { // check to see if the output has already been renamed somewhere if($this->_output_renamed !== null) { return $this->_output_renamed; } // get the output of the process $paths = array(); $output_count = $this->getOutputCount(); for($i=0; $i<$output_count; $i++) { $path = $this->getOutputPath($i); // 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)\./', $path) > 0) { $path = $this->_renamePercentDOutput($path); } array_push($paths, $path); } return $paths; } /** * Returns a Media object based class for the given file path. * * @access protected * @author Oliver Lillie * @param string $output_path The file path to convert. * @return object Either "Media", "Video", "Audio" or "Image" PHPVideoToolkit objects. */ protected function _convertPathToMediaObject($output_path) { // 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); return new $media_class($output_path, $this->_config, null, false); } /** * Renames any output from ffmpeg that would have been outputted in a sequence, ie using %d. Typically used with imagery. * * @access public * @author Oliver Lillie * @param string $output_path The string notation for the output path. * @return array Returns an array of modified file paths. */ protected function _renamePercentDOutput($output_path) { $output = array(); // 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. $timecode = null; foreach ($outputted_files as $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((int) $matches[3][1], Timecode::INPUT_FORMAT_SECONDS, (float) $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), $path); } else { $actual_path = preg_replace('/\.([0-9]+)\._i\./', '$1', $path); } $actual_path = preg_replace('/\._u\.[0-9]{5}_[a-z0-9]{5}_[0-9]+\.u_\./', '.', $actual_path); rename($path, $actual_path); array_push($output, $actual_path); } unset($outputted_files); // TODO create the multiple image output } 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 The file pathe of the file to find the media class for. * @return string Returns the class name of the PHPVideoToolkit class related to the given $path argument. */ protected function _findMediaClass($path) { // read the output to determine what it is so it can be post processed. $data = getimagesize($path); if(!$data) { $media_parser = new MediaParser($this->_config); $type = $media_parser->getFileType($path); } 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; } }