Version 0.3
This commit is contained in:
		
					parent
					
						
							
								0d69145ec3
							
						
					
				
			
			
				commit
				
					
						ad3a5af623
					
				
			
		
					 130 changed files with 7283 additions and 2627 deletions
				
			
		|  | @ -1,128 +0,0 @@ | |||
| <?php | ||||
| 
 | ||||
| /* | ||||
|  * This file is part of PHP-FFmpeg. | ||||
|  * | ||||
|  * (c) Alchemy <info@alchemy.fr> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
| 
 | ||||
| namespace FFMpeg; | ||||
| 
 | ||||
| use FFMpeg\Exception\BinaryNotFoundException; | ||||
| use FFMpeg\Exception\InvalidArgumentException; | ||||
| use Monolog\Logger; | ||||
| use Symfony\Component\Process\ExecutableFinder; | ||||
| 
 | ||||
| /** | ||||
|  * Binary abstract class | ||||
|  * | ||||
|  * @author Romain Neutron imprec@gmail.com | ||||
|  */ | ||||
| abstract class Binary implements AdapterInterface | ||||
| { | ||||
|     protected $binary; | ||||
| 
 | ||||
|     /** | ||||
|      * | ||||
|      * @var Logger | ||||
|      */ | ||||
|     protected $logger; | ||||
| 
 | ||||
|     /** | ||||
|      * @var Integer | ||||
|      */ | ||||
|     protected $timeout; | ||||
| 
 | ||||
|     /** | ||||
|      * Binary constructor | ||||
|      * | ||||
|      * @param type   $binary The path file to the binary | ||||
|      * @param Logger $logger A logger | ||||
|      * @param Integer $timeout The timout for the underlying process, 0 means no timeout | ||||
|      */ | ||||
|     public function __construct($binary, Logger $logger, $timeout = 60) | ||||
|     { | ||||
|         if (!is_executable($binary)) { | ||||
|             throw new \FFMpeg\Exception\BinaryNotFoundException(sprintf('`%s` is not a valid binary', $binary)); | ||||
|         } | ||||
| 
 | ||||
|         $this->binary = $binary; | ||||
|         $this->logger = $logger; | ||||
|         $this->setTimeout($timeout); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the current timeout for underlying processes. | ||||
|      * | ||||
|      * @return integer|float | ||||
|      */ | ||||
|     public function getTimeout() | ||||
|     { | ||||
|         return $this->timeout; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Sets the timeout for the underlying processes, use 0 to disable timeout. | ||||
|      * | ||||
|      * @param integer|float $timeout | ||||
|      * | ||||
|      * @return Binary | ||||
|      */ | ||||
|     public function setTimeout($timeout) | ||||
|     { | ||||
|         if (0 > $timeout) { | ||||
|             throw new InvalidArgumentException('Timeout must be a non-negative value'); | ||||
|         } | ||||
| 
 | ||||
|         $this->timeout = $timeout; | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Destructor | ||||
|      */ | ||||
|     public function __destruct() | ||||
|     { | ||||
|         $this->binary = $binary = $this->logger = null; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      * | ||||
|      * @param  Logger $logger A logger | ||||
|      * @param  Integer $timeout The timout for the underlying process, 0 means no timeout | ||||
|      * | ||||
|      * @return Binary The binary | ||||
|      * | ||||
|      * @throws Exception\BinaryNotFoundException | ||||
|      */ | ||||
|     public static function load(Logger $logger, $timeout = 60) | ||||
|     { | ||||
|         $finder = new ExecutableFinder(); | ||||
|         $binary = null; | ||||
| 
 | ||||
|         foreach (static::getBinaryName() as $candidate) { | ||||
|             if (null !== $binary = $finder->find($candidate)) { | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if (null === $binary) { | ||||
|             throw new BinaryNotFoundException('Binary not found'); | ||||
|         } | ||||
| 
 | ||||
|         return new static($binary, $logger, $timeout); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Return the binary name | ||||
|      */ | ||||
|     protected static function getBinaryName() | ||||
|     { | ||||
|         throw new \Exception('Should be implemented'); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										248
									
								
								src/FFMpeg/Coordinate/AspectRatio.php
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										248
									
								
								src/FFMpeg/Coordinate/AspectRatio.php
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,248 @@ | |||
| <?php | ||||
| 
 | ||||
| /* | ||||
|  * This file is part of PHP-FFmpeg. | ||||
|  * | ||||
|  * (c) Alchemy <dev.team@alchemy.fr> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
| 
 | ||||
| namespace FFMpeg\Coordinate; | ||||
| 
 | ||||
| use FFMpeg\Exception\InvalidArgumentException; | ||||
| 
 | ||||
| // see http://en.wikipedia.org/wiki/List_of_common_resolutions
 | ||||
| class AspectRatio | ||||
| { | ||||
|     // named 4:3 or 1.33:1 Traditional TV
 | ||||
|     const AR_4_3 = '4/3'; | ||||
|     // named 16:9 or 1.77:1 HD video standard
 | ||||
|     const AR_16_9 = '16/9'; | ||||
| 
 | ||||
|     // named 3:2 or 1.5:1 see http://en.wikipedia.org/wiki/135_film
 | ||||
|     const AR_3_2 = '3/2'; | ||||
|     // named 5:3 or 1.66:1 see http://en.wikipedia.org/wiki/Super_16_mm
 | ||||
|     const AR_5_3 = '5/3'; | ||||
| 
 | ||||
|     // mostly used in Photography
 | ||||
|     const AR_5_4 = '5/4'; | ||||
|     const AR_1_1 = '1/1'; | ||||
| 
 | ||||
|     // 1.85:1 US widescreen cinema standard see http://en.wikipedia.org/wiki/Widescreen#Film
 | ||||
|     const AR_1_DOT_85_1 = '1.85:1'; | ||||
|     // 2.39:1 or 2.40:1 Current widescreen cinema standard see http://en.wikipedia.org/wiki/Anamorphic_format
 | ||||
|     const AR_2_DOT_39_1 = '2.39:1'; | ||||
| 
 | ||||
|     // Rotated constants
 | ||||
| 
 | ||||
|     // Rotated 4:3
 | ||||
|     const AR_ROTATED_3_4 = '3/4'; | ||||
|     // Rotated 16:9
 | ||||
|     const AR_ROTATED_9_16 = '9/16'; | ||||
| 
 | ||||
|     // Rotated 3:2
 | ||||
|     const AR_ROTATED_2_3 = '2/3'; | ||||
|     // Rotated 5:3
 | ||||
|     const AR_ROTATED_3_5 = '3/5'; | ||||
| 
 | ||||
|     // Rotated 5:4
 | ||||
|     const AR_ROTATED_4_5 = '4/5'; | ||||
| 
 | ||||
|     // Rotated 1.85
 | ||||
|     const AR_ROTATED_1_DOT_85 = '1/1.85'; | ||||
|     // Rotated 2.39
 | ||||
|     const AR_ROTATED_2_DOT_39 = '1/2.39'; | ||||
| 
 | ||||
|     /** @var float */ | ||||
|     private $ratio; | ||||
| 
 | ||||
|     public function __construct($ratio) | ||||
|     { | ||||
|         $this->ratio = $ratio; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the value of the ratio. | ||||
|      * | ||||
|      * @return float | ||||
|      */ | ||||
|     public function getValue() | ||||
|     { | ||||
|         return $this->ratio; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Compute the best width for given height and modulus. | ||||
|      * | ||||
|      * @param Integer $height | ||||
|      * @param Integer $modulus | ||||
|      * | ||||
|      * @return Integer | ||||
|      */ | ||||
|     public function calculateWidth($height, $modulus = 1) | ||||
|     { | ||||
|         $maxPossibleWidth = $this->getMultipleUp(ceil($this->ratio * $height), $modulus); | ||||
|         $minPossibleWidth = $this->getMultipleDown(floor($this->ratio * $height), $modulus); | ||||
| 
 | ||||
|         $maxRatioDiff = abs($this->ratio - ($maxPossibleWidth / $height)); | ||||
|         $minRatioDiff = abs($this->ratio - ($minPossibleWidth / $height)); | ||||
| 
 | ||||
|         return $maxRatioDiff < $minRatioDiff ? $maxPossibleWidth : $minPossibleWidth; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Compute the best height for given width and modulus. | ||||
|      * | ||||
|      * @param Integer $width | ||||
|      * @param Integer $modulus | ||||
|      * | ||||
|      * @return Integer | ||||
|      */ | ||||
|     public function calculateHeight($width, $modulus = 1) | ||||
|     { | ||||
|         $maxPossibleHeight = $this->getMultipleUp(ceil($width / $this->ratio), $modulus); | ||||
|         $minPossibleHeight = $this->getMultipleDown(floor($width / $this->ratio), $modulus); | ||||
| 
 | ||||
|         $maxRatioDiff = abs($this->ratio - ($width / $maxPossibleHeight)); | ||||
|         $minRatioDiff = abs($this->ratio - ($width / $minPossibleHeight)); | ||||
| 
 | ||||
|         return $maxRatioDiff < $minRatioDiff ? $maxPossibleHeight : $minPossibleHeight; | ||||
|     } | ||||
| 
 | ||||
|     private function getMultipleUp($value, $multiple) | ||||
|     { | ||||
|         while (0 !== $value % $multiple) { | ||||
|             $value++; | ||||
|         } | ||||
| 
 | ||||
|         return $value; | ||||
|     } | ||||
| 
 | ||||
|     private function getMultipleDown($value, $multiple) | ||||
|     { | ||||
|         while (0 !== $value % $multiple) { | ||||
|             $value--; | ||||
|         } | ||||
| 
 | ||||
|         return $value; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Creates a ratio based on Dimension. | ||||
|      * | ||||
|      * The strategy parameter forces by default to use standardized ratios. If | ||||
|      * custom ratio need to be used, disable it. | ||||
|      * | ||||
|      * @param Dimension $dimension | ||||
|      * @param Boolean   $forceStandards Whether to force or not standard ratios | ||||
|      * | ||||
|      * @return AspectRatio | ||||
|      * | ||||
|      * @throws InvalidArgumentException | ||||
|      */ | ||||
|     public static function create(Dimension $dimension, $forceStandards = true) | ||||
|     { | ||||
|         $incoming = $dimension->getWidth() / $dimension->getHeight(); | ||||
| 
 | ||||
|         if ($forceStandards) { | ||||
|             return new static(static::nearestStrategy($incoming)); | ||||
|         } else { | ||||
|             return new static(static::customStrategy($incoming)); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private static function valueFromName($name) | ||||
|     { | ||||
|         switch ($name) { | ||||
|             case static::AR_4_3: | ||||
|                 return 4 / 3; | ||||
|             case static::AR_16_9: | ||||
|                 return 16 / 9; | ||||
|             case static::AR_1_1: | ||||
|                 return 1 / 1; | ||||
|             case static::AR_1_DOT_85_1: | ||||
|                 return 1.85; | ||||
|             case static::AR_2_DOT_39_1: | ||||
|                 return 2.39; | ||||
|             case static::AR_3_2: | ||||
|                 return 3 / 2; | ||||
|             case static::AR_5_3: | ||||
|                 return 5 / 3; | ||||
|             case static::AR_5_4: | ||||
|                 return 5 / 4; | ||||
|             case static::AR_ROTATED_3_4: | ||||
|                 return 3 / 4; | ||||
|             case static::AR_ROTATED_9_16: | ||||
|                 return 9 / 16; | ||||
|             case static::AR_ROTATED_2_3: | ||||
|                 return 2 / 3; | ||||
|             case static::AR_ROTATED_3_5: | ||||
|                 return 3 / 5; | ||||
|             case static::AR_ROTATED_4_5: | ||||
|                 return 4 / 5; | ||||
|             case static::AR_ROTATED_1_DOT_85: | ||||
|                 return 1 / 1.85; | ||||
|             case static::AR_ROTATED_2_DOT_39: | ||||
|                 return 1 / 2.39; | ||||
|             default: | ||||
|                 throw new InvalidArgumentException(sprintf('Unable to find value for %s', $name)); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private static function customStrategy($incoming) | ||||
|     { | ||||
|         $try = static::nearestStrategy($incoming); | ||||
| 
 | ||||
|         if (abs($try - $incoming) < $try * 0.05) { | ||||
|             return $try; | ||||
|         } | ||||
| 
 | ||||
|         return $incoming; | ||||
|     } | ||||
| 
 | ||||
|     private static function nearestStrategy($incoming) | ||||
|     { | ||||
|         $availables = array( | ||||
|             static::AR_4_3 => static::valueFromName(static::AR_4_3), | ||||
|             static::AR_16_9 => static::valueFromName(static::AR_16_9), | ||||
|             static::AR_1_1 => static::valueFromName(static::AR_1_1), | ||||
|             static::AR_1_DOT_85_1 => static::valueFromName(static::AR_1_DOT_85_1), | ||||
|             static::AR_2_DOT_39_1 => static::valueFromName(static::AR_2_DOT_39_1), | ||||
|             static::AR_3_2 => static::valueFromName(static::AR_3_2), | ||||
|             static::AR_5_3 => static::valueFromName(static::AR_5_3), | ||||
|             static::AR_5_4 => static::valueFromName(static::AR_5_4), | ||||
| 
 | ||||
|             // Rotated
 | ||||
|             static::AR_ROTATED_4_5 => static::valueFromName(static::AR_ROTATED_4_5), | ||||
|             static::AR_ROTATED_9_16 => static::valueFromName(static::AR_ROTATED_9_16), | ||||
|             static::AR_ROTATED_2_3 => static::valueFromName(static::AR_ROTATED_2_3), | ||||
|             static::AR_ROTATED_3_5 => static::valueFromName(static::AR_ROTATED_3_5), | ||||
|             static::AR_ROTATED_3_4 => static::valueFromName(static::AR_ROTATED_3_4), | ||||
|             static::AR_ROTATED_1_DOT_85 => static::valueFromName(static::AR_ROTATED_1_DOT_85), | ||||
|             static::AR_ROTATED_2_DOT_39 => static::valueFromName(static::AR_ROTATED_2_DOT_39), | ||||
|         ); | ||||
|         asort($availables); | ||||
| 
 | ||||
|         $previous = $current = null; | ||||
| 
 | ||||
|         foreach ($availables as $name => $value) { | ||||
|             $current = $value; | ||||
|             if ($incoming <= $value) { | ||||
|                 break; | ||||
|             } | ||||
|             $previous = $value; | ||||
|         } | ||||
| 
 | ||||
|         if (null === $previous) { | ||||
|             return $current; | ||||
|         } | ||||
| 
 | ||||
|         if (($current - $incoming) < ($incoming - $previous)) { | ||||
|             return $current; | ||||
|         } | ||||
| 
 | ||||
|         return $previous; | ||||
|     } | ||||
| } | ||||
|  | @ -9,19 +9,17 @@ | |||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
| 
 | ||||
| namespace FFMpeg\Format; | ||||
| namespace FFMpeg\Coordinate; | ||||
| 
 | ||||
| use FFMpeg\Exception\InvalidArgumentException; | ||||
| 
 | ||||
| /** | ||||
|  * Dimension object, used for manipulating width and height couples | ||||
|  * | ||||
|  * @author Romain Neutron imprec@gmail.com | ||||
|  */ | ||||
| class Dimension | ||||
| { | ||||
|     protected $width; | ||||
|     protected $height; | ||||
|     private $width; | ||||
|     private $height; | ||||
| 
 | ||||
|     /** | ||||
|      * Constructor | ||||
|  | @ -59,4 +57,16 @@ class Dimension | |||
|     { | ||||
|         return $this->height; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the ratio | ||||
|      * | ||||
|      * @param type $forceStandards Whether or not force the use of standards ratios; | ||||
|      * | ||||
|      * @return AspectRatio | ||||
|      */ | ||||
|     public function getRatio($forceStandards = true) | ||||
|     { | ||||
|         return AspectRatio::create($this, $forceStandards); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										36
									
								
								src/FFMpeg/Coordinate/FrameRate.php
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/FFMpeg/Coordinate/FrameRate.php
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,36 @@ | |||
| <?php | ||||
| 
 | ||||
| /* | ||||
|  * This file is part of PHP-FFmpeg. | ||||
|  * | ||||
|  * (c) Alchemy <info@alchemy.fr> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
| 
 | ||||
| namespace FFMpeg\Coordinate; | ||||
| 
 | ||||
| use FFMpeg\Exception\InvalidArgumentException; | ||||
| 
 | ||||
| class FrameRate | ||||
| { | ||||
|     private $value; | ||||
| 
 | ||||
|     public function __construct($value) | ||||
|     { | ||||
|         if ($value <= 0) { | ||||
|             throw new InvalidArgumentException('Invalid frame rate, must be positive value.'); | ||||
|         } | ||||
| 
 | ||||
|         $this->value = $value; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return float | ||||
|      */ | ||||
|     public function getValue() | ||||
|     { | ||||
|         return $this->value; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										40
									
								
								src/FFMpeg/Coordinate/Point.php
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/FFMpeg/Coordinate/Point.php
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,40 @@ | |||
| <?php | ||||
| 
 | ||||
| /* | ||||
|  * This file is part of PHP-FFmpeg. | ||||
|  * | ||||
|  * (c) Alchemy <info@alchemy.fr> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
| 
 | ||||
| namespace FFMpeg\Coordinate; | ||||
| 
 | ||||
| class Point | ||||
| { | ||||
|     private $x; | ||||
|     private $Y; | ||||
| 
 | ||||
|     public function __construct($x, $y) | ||||
|     { | ||||
|         $this->x = (int) $x; | ||||
|         $this->y = (int) $y; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return integer | ||||
|      */ | ||||
|     public function getX() | ||||
|     { | ||||
|         return $this->x; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return integer | ||||
|      */ | ||||
|     public function getY() | ||||
|     { | ||||
|         return $this->y; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										66
									
								
								src/FFMpeg/Coordinate/TimeCode.php
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								src/FFMpeg/Coordinate/TimeCode.php
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,66 @@ | |||
| <?php | ||||
| 
 | ||||
| /* | ||||
|  * This file is part of PHP-FFmpeg. | ||||
|  * | ||||
|  * (c) Alchemy <info@alchemy.fr> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
| 
 | ||||
| namespace FFMpeg\Coordinate; | ||||
| 
 | ||||
| use FFMpeg\Exception\InvalidArgumentException; | ||||
| 
 | ||||
| class TimeCode | ||||
| { | ||||
|     //see http://www.dropframetimecode.org/
 | ||||
|     private $hours; | ||||
|     private $minutes; | ||||
|     private $seconds; | ||||
|     private $frames; | ||||
| 
 | ||||
|     public function __construct($hours, $minutes, $seconds, $frames) | ||||
|     { | ||||
|         $this->hours = $hours; | ||||
|         $this->minutes = $minutes; | ||||
|         $this->seconds = $seconds; | ||||
|         $this->frames = $frames; | ||||
|     } | ||||
| 
 | ||||
|     public function __toString() | ||||
|     { | ||||
|         return sprintf('%02d:%02d:%02d.%02d', $this->hours, $this->minutes, $this->seconds, $this->frames); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Creates timecode from string | ||||
|      * | ||||
|      * @param string $timecode | ||||
|      * | ||||
|      * @return TimeCode | ||||
|      * | ||||
|      * @throws InvalidArgumentException In case an invalid timecode is supplied | ||||
|      */ | ||||
|     public static function fromString($timecode) | ||||
|     { | ||||
|         $days = 0; | ||||
| 
 | ||||
|         if (preg_match('/^[0-9]+:[0-9]+:[0-9]+:[0-9]+\.[0-9]+$/', $timecode)) { | ||||
|             list($days, $hours, $minutes, $seconds, $frames) = sscanf($timecode, '%d:%d:%d:%d.%d'); | ||||
|         } elseif (preg_match('/^[0-9]+:[0-9]+:[0-9]+:[0-9]+:[0-9]+$/', $timecode)) { | ||||
|             list($days, $hours, $minutes, $seconds, $frames) = sscanf($timecode, '%d:%d:%d:%d:%d'); | ||||
|         } elseif (preg_match('/^[0-9]+:[0-9]+:[0-9]+\.[0-9]+$/', $timecode)) { | ||||
|             list($hours, $minutes, $seconds, $frames) = sscanf($timecode, '%d:%d:%d.%s'); | ||||
|         } elseif (preg_match('/^[0-9]+:[0-9]+:[0-9]+:[0-9]+$/', $timecode)) { | ||||
|             list($hours, $minutes, $seconds, $frames) = sscanf($timecode, '%d:%d:%d:%s'); | ||||
|         } else { | ||||
|             throw new InvalidArgumentException(sprintf('Unable to parse timecode %s', $timecode)); | ||||
|         } | ||||
| 
 | ||||
|         $hours += $days * 24; | ||||
| 
 | ||||
|         return new static($hours, $minutes, $seconds, $frames); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										40
									
								
								src/FFMpeg/Driver/FFMpegDriver.php
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/FFMpeg/Driver/FFMpegDriver.php
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,40 @@ | |||
| <?php | ||||
| 
 | ||||
| /* | ||||
|  * This file is part of PHP-FFmpeg. | ||||
|  * | ||||
|  * (c) Alchemy <info@alchemy.fr> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
| 
 | ||||
| namespace FFMpeg\Driver; | ||||
| 
 | ||||
| use Alchemy\BinaryDriver\AbstractBinary; | ||||
| use Alchemy\BinaryDriver\Configuration; | ||||
| use Psr\Log\LoggerInterface; | ||||
| 
 | ||||
| class FFMpegDriver extends AbstractBinary | ||||
| { | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function getName() | ||||
|     { | ||||
|         return 'ffmpeg'; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Creates an FFMpegDriver. | ||||
|      * | ||||
|      * @param LoggerInterface     $logger | ||||
|      * @param array|Configuration $configuration | ||||
|      * | ||||
|      * @return FFMpegDriver | ||||
|      */ | ||||
|     public static function create(LoggerInterface $logger, $configuration) | ||||
|     { | ||||
|         return static::load(array('avconv', 'ffmpeg'), $logger, $configuration); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										47
									
								
								src/FFMpeg/Driver/FFProbeDriver.php
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								src/FFMpeg/Driver/FFProbeDriver.php
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,47 @@ | |||
| <?php | ||||
| 
 | ||||
| /* | ||||
|  * This file is part of PHP-FFmpeg. | ||||
|  * | ||||
|  * (c) Alchemy <info@alchemy.fr> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
| 
 | ||||
| namespace FFMpeg\Driver; | ||||
| 
 | ||||
| use Alchemy\BinaryDriver\AbstractBinary; | ||||
| use Alchemy\BinaryDriver\Configuration; | ||||
| use Alchemy\BinaryDriver\ConfigurationInterface; | ||||
| use Psr\Log\LoggerInterface; | ||||
| 
 | ||||
| class FFProbeDriver extends AbstractBinary | ||||
| { | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function getName() | ||||
|     { | ||||
|         return 'ffprobe'; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Creates an FFProbeDriver | ||||
|      * | ||||
|      * @param array|ConfigurationInterface $configuration | ||||
|      * @param LoggerInterface              $logger | ||||
|      * | ||||
|      * @return FFProbeDriver | ||||
|      */ | ||||
|     public static function create($configuration, LoggerInterface $logger = null) | ||||
|     { | ||||
|         if (!$configuration instanceof ConfigurationInterface) { | ||||
|             $configuration = new Configuration($configuration); | ||||
|         } | ||||
| 
 | ||||
|         $binaries = $configuration->get('ffprobe.binaries', array('avprobe', 'ffprobe')); | ||||
| 
 | ||||
|         return static::load($binaries, $logger, $configuration); | ||||
|     } | ||||
| } | ||||
|  | @ -13,5 +13,4 @@ namespace FFMpeg\Exception; | |||
| 
 | ||||
| interface ExceptionInterface | ||||
| { | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -13,5 +13,4 @@ namespace FFMpeg\Exception; | |||
| 
 | ||||
| class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface | ||||
| { | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -13,5 +13,4 @@ namespace FFMpeg\Exception; | |||
| 
 | ||||
| class RuntimeException extends \RuntimeException implements ExceptionInterface | ||||
| { | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -11,466 +11,126 @@ | |||
| 
 | ||||
| namespace FFMpeg; | ||||
| 
 | ||||
| use Alchemy\BinaryDriver\ConfigurationInterface; | ||||
| use FFMpeg\Driver\FFMpegDriver; | ||||
| use FFMpeg\Exception\InvalidArgumentException; | ||||
| use FFMpeg\Exception\LogicException; | ||||
| use FFMpeg\Exception\RuntimeException; | ||||
| use FFMpeg\Format\AudioInterface; | ||||
| use FFMpeg\Format\VideoInterface; | ||||
| use FFMpeg\Format\Video\Resamplable as VideoResamplable; | ||||
| use FFMpeg\Format\Video\Resizable as VideoResizable; | ||||
| use FFMpeg\Format\Video\Transcodable as VideoTranscodable; | ||||
| use FFMpeg\Format\Audio\Resamplable as AudioResamplable; | ||||
| use FFMpeg\Format\Audio\Transcodable as AudioTranscodable; | ||||
| use FFMpeg\Helper\HelperInterface; | ||||
| use Symfony\Component\Process\Process; | ||||
| use Symfony\Component\Process\ProcessBuilder; | ||||
| use FFMpeg\Media\Audio; | ||||
| use FFMpeg\Media\Video; | ||||
| use Alchemy\BinaryDriver\Configuration; | ||||
| use Psr\Log\LoggerInterface; | ||||
| 
 | ||||
| /** | ||||
|  * FFMpeg driver | ||||
|  * | ||||
|  * @author Romain Neutron imprec@gmail.com | ||||
|  */ | ||||
| class FFMpeg extends Binary | ||||
| class FFMpeg | ||||
| { | ||||
|     protected $pathfile; | ||||
|     /** @var FFMpegDriver */ | ||||
|     private $driver; | ||||
|     /** @var FFProbe */ | ||||
|     private $ffprobe; | ||||
| 
 | ||||
|     public function __construct(FFMpegDriver $ffmpeg, FFProbe $ffprobe) | ||||
|     { | ||||
|         $this->driver = $ffmpeg; | ||||
|         $this->ffprobe = $ffprobe; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Sets ffprobe | ||||
|      * | ||||
|      * @var FFProbe | ||||
|      * @param FFProbe | ||||
|      * | ||||
|      * @return FFMpeg | ||||
|      */ | ||||
|     protected $prober; | ||||
|     protected $threads = 1; | ||||
| 
 | ||||
|     /** | ||||
|      * @var HelperInterface[] | ||||
|      */ | ||||
|     protected $helpers = array(); | ||||
| 
 | ||||
|     /** | ||||
|      * Destructor | ||||
|      */ | ||||
|     public function __destruct() | ||||
|     public function setFFProbe(FFProbe $ffprobe) | ||||
|     { | ||||
|         $this->prober = null; | ||||
|         parent::__destruct(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param HelperInterface $helper | ||||
|      * @return \FFMpeg\FFMpeg | ||||
|      */ | ||||
|     public function attachHelper(HelperInterface $helper) | ||||
|     { | ||||
|         $this->helpers[] = $helper; | ||||
|         $helper->setProber($this->prober); | ||||
| 
 | ||||
|         // ensure the helpers have the path to the file in case
 | ||||
|         // they need to probe for format information
 | ||||
|         if ($this->pathfile !== null) { | ||||
|             $helper->open($this->pathfile); | ||||
|         } | ||||
|         $this->ffprobe = $ffprobe; | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function setThreads($threads) | ||||
|     /** | ||||
|      * Gets FFProbe | ||||
|      * | ||||
|      * @return FFProbe | ||||
|      */ | ||||
|     public function getFFProbe() | ||||
|     { | ||||
|         if ($threads > 64 || $threads < 1) { | ||||
|             throw new InvalidArgumentException('Invalid `threads` value ; threads must fit in range 1 - 64'); | ||||
|         } | ||||
|         return $this->ffprobe; | ||||
|     } | ||||
| 
 | ||||
|         $this->threads = (int) $threads; | ||||
|     /** | ||||
|      * Sets ffmpeg driver | ||||
|      * | ||||
|      * @return FFMpeg | ||||
|      */ | ||||
|     public function setFFMpegDriver(FFMpegDriver $ffmpeg) | ||||
|     { | ||||
|         $this->driver = $ffmpeg; | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function getThreads() | ||||
|     /** | ||||
|      * Gets the ffmpeg driver | ||||
|      * | ||||
|      * @return FFMpegDriver | ||||
|      */ | ||||
|     public function getFFMpegDriver() | ||||
|     { | ||||
|         return $this->threads; | ||||
|         return $this->driver; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Opens a file in order to be processed | ||||
|      * | ||||
|      * @param  string                   $pathfile A pathfile | ||||
|      * @return \FFMpeg\FFMpeg | ||||
|      * @param string $pathfile A pathfile | ||||
|      * | ||||
|      * @return Audio|Video | ||||
|      * | ||||
|      * @throws InvalidArgumentException | ||||
|      */ | ||||
|     public function open($pathfile) | ||||
|     { | ||||
|         if (!file_exists($pathfile)) { | ||||
|             $this->logger->addError(sprintf('FFmpeg failed to open %s', $pathfile)); | ||||
| 
 | ||||
|             throw new InvalidArgumentException(sprintf('File %s does not exist', $pathfile)); | ||||
|             throw new InvalidArgumentException(sprintf('File %s does not exists', $pathfile)); | ||||
|         } | ||||
| 
 | ||||
|         $this->logger->addInfo(sprintf('FFmpeg opens %s', $pathfile)); | ||||
|         $this->pathfile = $pathfile; | ||||
|         $streams = $this->ffprobe->streams($pathfile); | ||||
| 
 | ||||
|         foreach ($this->helpers as $helper) { | ||||
|             $helper->open($pathfile); | ||||
|         if (0 < count($streams->videos())) { | ||||
|             return new Video($pathfile, $this->driver, $this->ffprobe); | ||||
|         } elseif (0 < count($streams->audios())) { | ||||
|             return new Audio($pathfile, $this->driver, $this->ffprobe); | ||||
|         } | ||||
| 
 | ||||
|         return $this; | ||||
|         throw new InvalidArgumentException('Unable to detect file format, only audio and video supported'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Set a prober | ||||
|      * Creates a new FFMpeg instance | ||||
|      * | ||||
|      * @return \FFMpeg\FFMpeg | ||||
|      * @param array|ConfigurationInterface $configuration | ||||
|      * @param LoggerInterface              $logger | ||||
|      * @param FFProbe                      $probe | ||||
|      * | ||||
|      * @return FFMpeg | ||||
|      */ | ||||
|     public function setProber(FFProbe $prober) | ||||
|     public static function create($configuration = array(), LoggerInterface $logger = null, FFProbe $probe = null) | ||||
|     { | ||||
|         $this->prober = $prober; | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Close a file | ||||
|      * | ||||
|      * @return \FFMpeg\FFMpeg | ||||
|      */ | ||||
|     public function close() | ||||
|     { | ||||
|         $this->logger->addInfo(sprintf('FFmpeg closes %s', $this->pathfile)); | ||||
| 
 | ||||
|         $this->pathfile = null; | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Extract an image from a media file | ||||
|      * | ||||
|      * @param  integer|string   $time     The time where to take the snapshot, time could either be in second or in hh:mm:ss[.xxx] form. | ||||
|      * @param  string           $output   The pathfile where to write | ||||
|      * @param  Boolean          $accurate Whether to decode the whole video until position or seek and extract. See -ss option in FFMpeg manual (http://ffmpeg.org/ffmpeg.html#Main-options)
 | ||||
|      * | ||||
|      * @return \FFMpeg\FFMpeg | ||||
|      * | ||||
|      * @throws RuntimeException | ||||
|      * @throws LogicException | ||||
|      */ | ||||
|     public function extractImage($time, $output, $accurate = false) | ||||
|     { | ||||
|         if (!$this->pathfile) { | ||||
|             throw new LogicException('No file open'); | ||||
|         if (!$configuration instanceof ConfigurationInterface) { | ||||
|             $configuration = new Configuration($configuration); | ||||
|         } | ||||
| 
 | ||||
|         /** | ||||
|          * @see http://ffmpeg.org/ffmpeg.html#Main-options
 | ||||
|          */ | ||||
|         if (!$accurate) { | ||||
|             $options = array( | ||||
|                 $this->binary, '-ss', $time, | ||||
|                 '-i', $this->pathfile, | ||||
|                 '-vframes', '1', | ||||
|                 '-f', 'image2', $output | ||||
|             ); | ||||
|         } else { | ||||
|             $options = array( | ||||
|                 $this->binary, | ||||
|                 '-i', $this->pathfile, | ||||
|                 '-vframes', '1', '-ss', $time, | ||||
|                 '-f', 'image2', $output | ||||
|             ); | ||||
|         $binaries = $configuration->get('ffmpeg.binaries', array('avconv', 'ffmpeg')); | ||||
| 
 | ||||
|         if (!$configuration->has('timeout')) { | ||||
|             $configuration->set('timeout', 300); | ||||
|         } | ||||
| 
 | ||||
|         $builder = ProcessBuilder::create($options); | ||||
|         $process = $builder->getProcess(); | ||||
|         $process->setTimeout($this->timeout); | ||||
| 
 | ||||
|         $this->logger->addInfo(sprintf('FFmpeg executes command %s', $process->getCommandline())); | ||||
| 
 | ||||
|         try { | ||||
|             $process->run(array($this, 'transcodeCallback')); | ||||
|         } catch (\RuntimeException $e) { | ||||
|         $driver = FFMpegDriver::load($binaries, $logger, $configuration); | ||||
| 
 | ||||
|         if (null === $probe) { | ||||
|             $probe = FFProbe::create($configuration, $logger, null); | ||||
|         } | ||||
| 
 | ||||
|         if (!$process->isSuccessful()) { | ||||
|             $this->logger->addError(sprintf('FFmpeg command failed: %s', $process->getErrorOutput())); | ||||
| 
 | ||||
|             $this->cleanupTemporaryFile($output); | ||||
| 
 | ||||
|             throw new RuntimeException('Failed to extract image'); | ||||
|         } | ||||
| 
 | ||||
|         $this->logger->addInfo(sprintf('FFmpeg command successful')); | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Encode the file to the specified format | ||||
|      * | ||||
|      * @param  AudioInterface   $format         The output format | ||||
|      * @param  string           $outputPathfile The pathfile where to write | ||||
|      * @return \FFMpeg\FFMpeg | ||||
|      * @throws RuntimeException | ||||
|      * @throws LogicException | ||||
|      */ | ||||
|     public function encode(AudioInterface $format, $outputPathfile) | ||||
|     { | ||||
|         if (!$this->pathfile) { | ||||
|             throw new LogicException('No file open'); | ||||
|         } | ||||
| 
 | ||||
|         switch (true) { | ||||
|             case $format instanceof VideoInterface: | ||||
|                 $this->encodeVideo($format, $outputPathfile); | ||||
|                 break; | ||||
|             default: | ||||
|             case $format instanceof AudioInterface: | ||||
|                 $this->encodeAudio($format, $outputPathfile); | ||||
|                 break; | ||||
|         } | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Encode to audio | ||||
|      * | ||||
|      * @param  Audio            $format         The output format | ||||
|      * @param  string           $outputPathfile The pathfile where to write | ||||
|      * @return \FFMpeg\FFMpeg | ||||
|      * @throws RuntimeException | ||||
|      */ | ||||
|     protected function encodeAudio(AudioInterface $format, $outputPathfile) | ||||
|     { | ||||
|         $builder = ProcessBuilder::create(array( | ||||
|                 $this->binary, | ||||
|                 '-y', '-i', | ||||
|                 $this->pathfile, | ||||
|                 '-threads', $this->threads, | ||||
|                 '-ab', $format->getKiloBitrate() . 'k ', | ||||
|             )); | ||||
| 
 | ||||
|         foreach ($format->getExtraParams() as $parameter) { | ||||
|             $builder->add($parameter); | ||||
|         } | ||||
| 
 | ||||
|         if ($format instanceof AudioTranscodable) { | ||||
|             $builder->add('-acodec')->add($format->getAudioCodec()); | ||||
|         } | ||||
| 
 | ||||
|         if ($format instanceof AudioResamplable) { | ||||
|             $builder->add('-ac')->add(2)->add('-ar')->add($format->getAudioSampleRate()); | ||||
|         } | ||||
| 
 | ||||
|         $builder->add($outputPathfile); | ||||
| 
 | ||||
|         $process = $builder->getProcess(); | ||||
| 
 | ||||
|         $process->setTimeout($this->timeout); | ||||
| 
 | ||||
|         $this->logger->addInfo(sprintf('FFmpeg executes command %s', $process->getCommandLine())); | ||||
| 
 | ||||
|         try { | ||||
|             $process->run(array($this, 'transcodeCallback')); | ||||
|         } catch (\RuntimeException $e) { | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         if (!$process->isSuccessful()) { | ||||
|             $this->logger->addInfo(sprintf('FFmpeg command failed')); | ||||
|             throw new RuntimeException(sprintf('Encoding failed: %s', $process->getErrorOutput())); | ||||
|         } | ||||
| 
 | ||||
|         $this->logger->addInfo(sprintf('FFmpeg command successful')); | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Encode to video | ||||
|      * | ||||
|      * @param  VideoInterface   $format         The output format | ||||
|      * @param  string           $outputPathfile The pathfile where to write | ||||
|      * @return \FFMpeg\FFMpeg | ||||
|      * @throws RuntimeException | ||||
|      */ | ||||
|     protected function encodeVideo(VideoInterface $format, $outputPathfile) | ||||
|     { | ||||
|         $builder = ProcessBuilder::create(array( | ||||
|                 $this->binary, '-y', '-i', | ||||
|                 $this->pathfile | ||||
|             )); | ||||
| 
 | ||||
|         foreach ($format->getExtraParams() as $parameter) { | ||||
|             $builder->add($parameter); | ||||
|         } | ||||
| 
 | ||||
|         if ($format instanceof VideoResizable) { | ||||
|             if (!$this->prober) { | ||||
|                 throw new LogicException('You must set a valid prober if you use a resizable format'); | ||||
|             } | ||||
| 
 | ||||
|             $result = json_decode($this->prober->probeStreams($this->pathfile), true); | ||||
| 
 | ||||
|             $originalWidth = $originalHeight = null; | ||||
| 
 | ||||
|             foreach ($result as $stream) { | ||||
|                 foreach ($stream as $name => $value) { | ||||
|                     if ($name == 'width') { | ||||
|                         $originalWidth = $value; | ||||
|                         continue; | ||||
|                     } | ||||
|                     if ($name == 'height') { | ||||
|                         $originalHeight = $value; | ||||
|                         continue; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             if ($originalHeight !== null && $originalWidth !== null) { | ||||
|                 $this->logger->addInfo(sprintf('Read dimension for resizing succesful : %s x %s', $originalWidth, $originalHeight)); | ||||
|             } else { | ||||
|                 $this->logger->addInfo(sprintf('Read dimension for resizing failed !')); | ||||
|             } | ||||
| 
 | ||||
|             if ($originalHeight !== null && $originalWidth !== null) { | ||||
|                 $dimensions = $format->getComputedDimensions($originalWidth, $originalHeight); | ||||
|                 $width = $this->getMultiple($dimensions->getWidth(), $format->getModulus()); | ||||
|                 $height = $this->getMultiple($dimensions->getHeight(), $format->getModulus()); | ||||
| 
 | ||||
|                 $builder->add('-s')->add($width . 'x' . $height); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         if ($format instanceof VideoResamplable) { | ||||
|             $builder->add('-r')->add($format->getFrameRate()); | ||||
| 
 | ||||
|             /** | ||||
|              * @see http://sites.google.com/site/linuxencoding/x264-ffmpeg-mapping | ||||
|              */ | ||||
|             if ($format->supportBFrames()) { | ||||
|                 $builder->add('-b_strategy') | ||||
|                     ->add('1') | ||||
|                     ->add('-bf') | ||||
|                     ->add('3') | ||||
|                     ->add('-g') | ||||
|                     ->add($format->getGOPSize()); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if ($format instanceof VideoTranscodable) { | ||||
|             $builder->add('-vcodec')->add($format->getVideoCodec()); | ||||
|         } | ||||
| 
 | ||||
|         $builder->add('-b:v')->add($format->getKiloBitrate() . 'k') | ||||
|             ->add('-threads')->add($this->threads) | ||||
|             ->add('-refs')->add('6') | ||||
|             ->add('-coder')->add('1') | ||||
|             ->add('-sc_threshold')->add('40') | ||||
|             ->add('-flags')->add('+loop') | ||||
|             ->add('-me_range')->add('16') | ||||
|             ->add('-subq')->add('7') | ||||
|             ->add('-i_qfactor')->add('0.71') | ||||
|             ->add('-qcomp')->add('0.6') | ||||
|             ->add('-qdiff')->add('4') | ||||
|             ->add('-trellis')->add('1') | ||||
|             ->add('-b:a')->add('92k'); | ||||
| 
 | ||||
|         if ($format instanceof AudioTranscodable) { | ||||
|             $builder->add('-acodec')->add($format->getAudioCodec()); | ||||
|         } | ||||
| 
 | ||||
|         $passPrefix = uniqid('pass-'); | ||||
| 
 | ||||
|         $pass1 = $builder; | ||||
|         $pass2 = clone $builder; | ||||
| 
 | ||||
|         $passes[] = $pass1 | ||||
|             ->add('-pass')->add('1') | ||||
|             ->add('-passlogfile')->add($passPrefix) | ||||
|             ->add('-an')->add($outputPathfile) | ||||
|             ->getProcess(); | ||||
|         $passes[] = $pass2 | ||||
|             ->add('-pass')->add('2') | ||||
|             ->add('-passlogfile')->add($passPrefix) | ||||
|             ->add('-ac')->add('2') | ||||
|             ->add('-ar')->add('44100')->add($outputPathfile) | ||||
|             ->getProcess(); | ||||
| 
 | ||||
|         foreach ($passes as $process) { | ||||
| 
 | ||||
|             $process->setTimeout($this->timeout); | ||||
| 
 | ||||
|             $this->logger->addInfo(sprintf('FFmpeg executes command %s', $process->getCommandline())); | ||||
| 
 | ||||
|             try { | ||||
|                 $process->run(array($this, 'transcodeCallback')); | ||||
|             } catch (\RuntimeException $e) { | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         $this->cleanupTemporaryFile(getcwd() . '/' . $passPrefix . '-0.log'); | ||||
|         $this->cleanupTemporaryFile(getcwd() . '/' . $passPrefix . '-0.log'); | ||||
|         $this->cleanupTemporaryFile(getcwd() . '/' . $passPrefix . '-0.log.mbtree'); | ||||
| 
 | ||||
|         if (!$process->isSuccessful()) { | ||||
|             $this->logger->addInfo(sprintf('FFmpeg command failed')); | ||||
|             throw new RuntimeException(sprintf('Encoding failed : %s', $process->getErrorOutput())); | ||||
|         } | ||||
| 
 | ||||
|         $this->logger->addInfo(sprintf('FFmpeg command successful')); | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * The main transcoding callback, delegates the content to the helpers. | ||||
|      * | ||||
|      * @param string $channel (stdio|stderr) | ||||
|      * @param string $content the current line of the ffmpeg output | ||||
|      */ | ||||
|     public function transcodeCallback($channel, $content) | ||||
|     { | ||||
|         foreach ($this->helpers as $helper) { | ||||
|             $helper->transcodeCallback($channel, $content); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Removes unnecessary file | ||||
|      * | ||||
|      * @param string $pathfile | ||||
|      */ | ||||
|     protected function cleanupTemporaryFile($pathfile) | ||||
|     { | ||||
|         if (file_exists($pathfile) && is_writable($pathfile)) { | ||||
|             unlink($pathfile); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the nearest multiple for a value | ||||
|      * | ||||
|      * @param  integer $value | ||||
|      * @param  integer $multiple | ||||
|      * @return integer | ||||
|      */ | ||||
|     protected function getMultiple($value, $multiple) | ||||
|     { | ||||
|         while (0 !== $value % $multiple) { | ||||
|             $value++; | ||||
|         } | ||||
| 
 | ||||
|         return $value; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      * | ||||
|      * @return string | ||||
|      */ | ||||
|     protected static function getBinaryName() | ||||
|     { | ||||
|         return array('avconv', 'ffmpeg'); | ||||
|         return new static($driver, $probe); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -11,49 +11,52 @@ | |||
| 
 | ||||
| namespace FFMpeg; | ||||
| 
 | ||||
| use Doctrine\Common\Cache\ArrayCache; | ||||
| use FFMpeg\FFMpeg; | ||||
| use FFMpeg\FFProbe; | ||||
| use Monolog\Logger; | ||||
| use Monolog\Handler\NullHandler; | ||||
| use Silex\Application; | ||||
| use Silex\ServiceProviderInterface; | ||||
| 
 | ||||
| class FFMpegServiceProvider implements ServiceProviderInterface | ||||
| { | ||||
| 
 | ||||
|     public function register(Application $app) | ||||
|     { | ||||
|         if (isset($app['monolog'])) { | ||||
|             $app['ffmpeg.logger'] = function() use ($app) { | ||||
|                 return $app['monolog']; | ||||
|             }; | ||||
|         } else { | ||||
|             $app['ffmpeg.logger'] = $app->share(function(Application $app) { | ||||
|                 $logger = new Logger('FFMpeg logger'); | ||||
|                 $logger->pushHandler(new NullHandler()); | ||||
|         $app['ffmpeg.configuration'] = array(); | ||||
|         $app['ffmpeg.default.configuration'] = array( | ||||
|             'ffmpeg.threads'   => 4, | ||||
|             'ffmpeg.timeout'   => 300, | ||||
|             'ffmpeg.binaries'  => array('avconv', 'ffmpeg'), | ||||
|             'ffprobe.timeout'  => 30, | ||||
|             'ffprobe.binaries' => array('avprobe', 'ffprobe'), | ||||
|         ); | ||||
|         $app['ffmpeg.logger'] = null; | ||||
| 
 | ||||
|                 return $logger; | ||||
|             }); | ||||
|         } | ||||
|         $app['ffmpeg.configuration.build'] = $app->share(function (Application $app) { | ||||
|             return array_replace($app['ffmpeg.default.configuration'], $app['ffmpeg.configuration']); | ||||
|         }); | ||||
| 
 | ||||
|         $app['ffmpeg.ffmpeg'] = $app->share(function(Application $app) { | ||||
|             if (isset($app['ffmpeg.ffmpeg.binary'])) { | ||||
|                 $ffmpeg = new FFMpeg($app['ffmpeg.ffmpeg.binary'], $app['ffmpeg.logger']); | ||||
|             } else { | ||||
|                 $ffmpeg = FFMpeg::load($app['ffmpeg.logger']); | ||||
|         $app['ffmpeg'] = $app['ffmpeg.ffmpeg'] = $app->share(function(Application $app) { | ||||
|             $configuration = $app['ffmpeg.configuration.build']; | ||||
| 
 | ||||
|             if (isset($configuration['ffmpeg.timeout'])) { | ||||
|                 $configuration['timeout'] = $configuration['ffmpeg.timeout']; | ||||
|             } | ||||
| 
 | ||||
|             return $ffmpeg | ||||
|                     ->setProber($app['ffmpeg.ffprobe']) | ||||
|                     ->setThreads(isset($app['ffmpeg.threads']) ? $app['ffmpeg.threads'] : 1); | ||||
|             return FFMpeg::create($configuration, $app['ffmpeg.logger'], $app['ffmpeg.ffprobe']); | ||||
|         }); | ||||
| 
 | ||||
|         $app['ffprobe.cache'] = $app->share(function () { | ||||
|             return new ArrayCache(); | ||||
|         }); | ||||
| 
 | ||||
|         $app['ffmpeg.ffprobe'] = $app->share(function(Application $app) { | ||||
|             if (isset($app['ffmpeg.ffprobe.binary'])) { | ||||
|                 return new FFProbe($app['ffmpeg.ffprobe.binary'], $app['ffmpeg.logger']); | ||||
|             } else { | ||||
|                 return FFProbe::load($app['ffmpeg.logger']); | ||||
|             $configuration = $app['ffmpeg.configuration.build']; | ||||
| 
 | ||||
|             if (isset($configuration['ffmpeg.timeout'])) { | ||||
|                 $configuration['timeout'] = $configuration['ffprobe.timeout']; | ||||
|             } | ||||
| 
 | ||||
|             return FFProbe::create($configuration, $app['ffmpeg.logger'], $app['ffprobe.cache']); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -11,163 +11,258 @@ | |||
| 
 | ||||
| namespace FFMpeg; | ||||
| 
 | ||||
| use Alchemy\BinaryDriver\ConfigurationInterface; | ||||
| use Doctrine\Common\Cache\ArrayCache; | ||||
| use Doctrine\Common\Cache\Cache; | ||||
| use FFMpeg\Driver\FFProbeDriver; | ||||
| use FFMpeg\FFProbe\DataMapping\Format; | ||||
| use FFMpeg\FFProbe\Mapper; | ||||
| use FFMpeg\FFProbe\MapperInterface; | ||||
| use FFMpeg\FFProbe\OptionsTester; | ||||
| use FFMpeg\FFProbe\OptionsTesterInterface; | ||||
| use FFMpeg\FFProbe\OutputParser; | ||||
| use FFMpeg\FFProbe\OutputParserInterface; | ||||
| use FFMpeg\Exception\InvalidArgumentException; | ||||
| use FFMpeg\Exception\RuntimeException; | ||||
| use Symfony\Component\Process\Process; | ||||
| use Symfony\Component\Process\ProcessBuilder; | ||||
| use Psr\Log\LoggerInterface; | ||||
| 
 | ||||
| /** | ||||
|  * FFProbe driver | ||||
|  * | ||||
|  * @author Romain Neutron imprec@gmail.com | ||||
|  */ | ||||
| class FFProbe extends Binary | ||||
| class FFProbe | ||||
| { | ||||
|     const TYPE_STREAMS = 'streams'; | ||||
|     const TYPE_FORMAT = 'format'; | ||||
| 
 | ||||
|     protected $cachedFormats = array(); | ||||
|     /** @var Cache */ | ||||
|     private $cache; | ||||
|     /** @var OptionsTesterInterface */ | ||||
|     private $optionsTester; | ||||
|     /** @var OutputParserInterface */ | ||||
|     private $parser; | ||||
|     /** @var FFProbeDriver */ | ||||
|     private $ffprobe; | ||||
|     /** @var MapperInterface */ | ||||
|     private $mapper; | ||||
| 
 | ||||
|     public function __construct(FFProbeDriver $ffprobe, Cache $cache) | ||||
|     { | ||||
|         $this->ffprobe = $ffprobe; | ||||
|         $this->optionsTester = new OptionsTester($ffprobe, $cache); | ||||
|         $this->parser = new OutputParser(); | ||||
|         $this->mapper = new Mapper(); | ||||
|         $this->cache = $cache; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return OutputParserInterface | ||||
|      */ | ||||
|     public function getParser() | ||||
|     { | ||||
|         return $this->parser; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param OutputParserInterface $parser | ||||
|      * | ||||
|      * @return FFProbe | ||||
|      */ | ||||
|     public function setParser(OutputParserInterface $parser) | ||||
|     { | ||||
|         $this->parser = $parser; | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return FFProbeDriver | ||||
|      */ | ||||
|     public function getFFProbeDriver() | ||||
|     { | ||||
|         return $this->ffprobe; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param FFProbeDriver $ffprobe | ||||
|      * | ||||
|      * @return FFProbe | ||||
|      */ | ||||
|     public function setFFProbeDriver(FFProbeDriver $ffprobe) | ||||
|     { | ||||
|         $this->ffprobe = $ffprobe; | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param OptionsTesterInterface $tester | ||||
|      * | ||||
|      * @return FFProbe | ||||
|      */ | ||||
|     public function setOptionsTester(OptionsTesterInterface $tester) | ||||
|     { | ||||
|         $this->optionsTester = $tester; | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return OptionsTesterInterface | ||||
|      */ | ||||
|     public function getOptionsTester() | ||||
|     { | ||||
|         return $this->optionsTester; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param Cache $cache | ||||
|      * | ||||
|      * @return FFProbe | ||||
|      */ | ||||
|     public function setCache(Cache $cache) | ||||
|     { | ||||
|         $this->cache = $cache; | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return Cache | ||||
|      */ | ||||
|     public function getCache() | ||||
|     { | ||||
|         return $this->cache; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return MapperInterface | ||||
|      */ | ||||
|     public function getMapper() | ||||
|     { | ||||
|         return $this->mapper; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param MapperInterface $mapper | ||||
|      * | ||||
|      * @return FFProbe | ||||
|      */ | ||||
|     public function setMapper(MapperInterface $mapper) | ||||
|     { | ||||
|         $this->mapper = $mapper; | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @api | ||||
|      * | ||||
|      * Probe the format of a given file | ||||
|      * | ||||
|      * @param  string $pathfile | ||||
|      * @return string A Json object containing the key/values of the probe output | ||||
|      * @param string $pathfile | ||||
|      * | ||||
|      * @return Format A Format object | ||||
|      * | ||||
|      * @throws InvalidArgumentException | ||||
|      * @throws RuntimeException | ||||
|      */ | ||||
|     public function probeFormat($pathfile) | ||||
|     public function format($pathfile) | ||||
|     { | ||||
|         if ( ! is_file($pathfile)) { | ||||
|             throw new InvalidArgumentException($pathfile); | ||||
|         } | ||||
| 
 | ||||
|         if (isset($this->cachedFormats[$pathfile])) { | ||||
|             return $this->cachedFormats[$pathfile]; | ||||
|         } | ||||
| 
 | ||||
|         $builder = ProcessBuilder::create(array( | ||||
|             $this->binary, $pathfile, '-show_format' | ||||
|         )); | ||||
| 
 | ||||
|         $output = $this->executeProbe($builder->getProcess()); | ||||
| 
 | ||||
|         $ret = array(); | ||||
| 
 | ||||
|         foreach (explode(PHP_EOL, $output) as $line) { | ||||
| 
 | ||||
|             if (in_array($line, array('[FORMAT]', '[/FORMAT]'))) { | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             $chunks = explode('=', $line); | ||||
|             $key = array_shift($chunks); | ||||
| 
 | ||||
|             if ('' === trim($key)) { | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             $value = trim(implode('=', $chunks)); | ||||
| 
 | ||||
|             if (ctype_digit($value)) { | ||||
|                 $value = (int) $value; | ||||
|             } | ||||
| 
 | ||||
|             $ret[$key] = $value; | ||||
|         } | ||||
| 
 | ||||
|         return $this->cachedFormats[$pathfile] = json_encode($ret); | ||||
|         return $this->probe($pathfile, '-show_format', static::TYPE_FORMAT); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @api | ||||
|      * | ||||
|      * Probe the streams contained in a given file | ||||
|      * | ||||
|      * @param  string $pathfile | ||||
|      * @return array  An array of streams array | ||||
|      * @param string $pathfile | ||||
|      * | ||||
|      * @return StreamCollection A collection of streams | ||||
|      * | ||||
|      * @throws InvalidArgumentException | ||||
|      * @throws RuntimeException | ||||
|      */ | ||||
|     public function probeStreams($pathfile) | ||||
|     public function streams($pathfile) | ||||
|     { | ||||
|         if ( ! is_file($pathfile)) { | ||||
|             throw new InvalidArgumentException($pathfile); | ||||
|         } | ||||
| 
 | ||||
|         $builder = ProcessBuilder::create(array( | ||||
|             $this->binary, $pathfile, '-show_streams' | ||||
|         )); | ||||
| 
 | ||||
|         $output = explode(PHP_EOL, $this->executeProbe($builder->getProcess())); | ||||
| 
 | ||||
|         $ret = array(); | ||||
|         $n = 0; | ||||
| 
 | ||||
|         foreach ($output as $line) { | ||||
| 
 | ||||
|             if ($line == '[STREAM]') { | ||||
|                 $n ++; | ||||
|                 $ret[$n] = array(); | ||||
|                 continue; | ||||
|             } | ||||
|             if ($line == '[/STREAM]') { | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             $chunks = explode('=', $line); | ||||
|             $key = array_shift($chunks); | ||||
| 
 | ||||
|             if ('' === trim($key)) { | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             $value = trim(implode('=', $chunks)); | ||||
| 
 | ||||
|             if (ctype_digit($value)) { | ||||
|                 $value = (int) $value; | ||||
|             } | ||||
| 
 | ||||
|             $ret[$n][$key] = $value; | ||||
|         } | ||||
| 
 | ||||
|         return json_encode(array_values($ret)); | ||||
|         return $this->probe($pathfile, '-show_streams', static::TYPE_STREAMS); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @api | ||||
|      * | ||||
|      * @param  Process          $process | ||||
|      * @return string | ||||
|      * @throws RuntimeException | ||||
|      * @param array|ConfigurationInterface $configuration | ||||
|      * @param LoggerInterface              $logger | ||||
|      * @param Cache                        $cache | ||||
|      * | ||||
|      * @return FFProbe | ||||
|      */ | ||||
|     protected function executeProbe(Process $process) | ||||
|     public static function create($configuration = array(), LoggerInterface $logger = null, Cache $cache = null) | ||||
|     { | ||||
|         $this->logger->addInfo(sprintf('FFprobe executes command %s', $process->getCommandline())); | ||||
| 
 | ||||
|         try { | ||||
|             $process->run(); | ||||
|         } catch (\RuntimeException $e) { | ||||
|             $this->logger->addInfo('FFprobe command failed'); | ||||
| 
 | ||||
|             throw new RuntimeException(sprintf('Failed to run the given command %s', $process->getCommandline())); | ||||
|         if (null === $cache) { | ||||
|             $cache = new ArrayCache(); | ||||
|         } | ||||
| 
 | ||||
|         if ( ! $process->isSuccessful()) { | ||||
|             $this->logger->addInfo('FFprobe command failed'); | ||||
| 
 | ||||
|             throw new RuntimeException(sprintf('Failed to probe %s', $process->getCommandline())); | ||||
|         } | ||||
| 
 | ||||
|         $this->logger->addInfo('FFprobe command successful'); | ||||
| 
 | ||||
|         return $process->getOutput(); | ||||
|         return new static(FFProbeDriver::create($configuration, $logger), $cache); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      * | ||||
|      * @return string | ||||
|      */ | ||||
|     protected static function getBinaryName() | ||||
|     private function probe($pathfile, $command, $type, $allowJson = true) | ||||
|     { | ||||
|         return array('avprobe', 'ffprobe'); | ||||
|         if (!is_file($pathfile)) { | ||||
|             throw new InvalidArgumentException(sprintf( | ||||
|                 'Invalid filepath %s, unable to read.', $pathfile | ||||
|             )); | ||||
|         } | ||||
| 
 | ||||
|         $id = sprintf('%s-%s', $command, $pathfile); | ||||
| 
 | ||||
|         if ($this->cache->contains($id)) { | ||||
|             return $this->cache->fetch($id); | ||||
|         } | ||||
| 
 | ||||
|         if (!$this->optionsTester->has($command)) { | ||||
|             throw new RuntimeException(sprintf( | ||||
|                 'This version of ffprobe is too old and ' | ||||
|                 . 'does not support `%s` option, please upgrade', $command | ||||
|             )); | ||||
|         } | ||||
| 
 | ||||
|         $commands = array($pathfile, $command); | ||||
| 
 | ||||
|         $parseIsToDo = false; | ||||
| 
 | ||||
|         if ($allowJson && $this->optionsTester->has('-print_format')) { | ||||
|             $commands[] = '-print_format'; | ||||
|             $commands[] = 'json'; | ||||
|         } else { | ||||
|             $parseIsToDo = true; | ||||
|         } | ||||
| 
 | ||||
|         $output = $this->ffprobe->command($commands); | ||||
| 
 | ||||
|         if ($parseIsToDo) { | ||||
|             $data = $this->parser->parse($type, $output); | ||||
|         } else { | ||||
|             try { | ||||
|                 // Malformed json may be retrieved
 | ||||
|                 $data = $this->parseJson($output); | ||||
|             } catch (RuntimeException $e) { | ||||
|                 return $this->probe($pathfile, $command, $type, false); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         $ret = $this->mapper->map($type, $data); | ||||
| 
 | ||||
|         $this->cache->save($id, $ret); | ||||
| 
 | ||||
|         return $ret; | ||||
|     } | ||||
| 
 | ||||
|     private function parseJson($data) | ||||
|     { | ||||
|         $ret = @json_decode($data, true); | ||||
| 
 | ||||
|         if (JSON_ERROR_NONE !== json_last_error()) { | ||||
|             throw new RuntimeException(sprintf('Unable to parse json %s', $ret)); | ||||
|         } | ||||
| 
 | ||||
|         return $ret; | ||||
|     } | ||||
| } | ||||
|  |  | |||
							
								
								
									
										80
									
								
								src/FFMpeg/FFProbe/DataMapping/AbstractData.php
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								src/FFMpeg/FFProbe/DataMapping/AbstractData.php
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,80 @@ | |||
| <?php | ||||
| 
 | ||||
| /* | ||||
|  * This file is part of PHP-FFmpeg. | ||||
|  * | ||||
|  * (c) Alchemy <info@alchemy.fr> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
| 
 | ||||
| namespace FFMpeg\FFProbe\DataMapping; | ||||
| 
 | ||||
| use FFMpeg\Exception\InvalidArgumentException; | ||||
| 
 | ||||
| abstract class AbstractData implements \Countable | ||||
| { | ||||
|     private $properties; | ||||
| 
 | ||||
|     public function __construct(array $properties) | ||||
|     { | ||||
|         $this->properties = $properties; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns true if data has property | ||||
|      * | ||||
|      * @param  string  $property | ||||
|      * @return Boolean | ||||
|      */ | ||||
|     public function has($property) | ||||
|     { | ||||
|         return isset($this->properties[$property]); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the property value given its name | ||||
|      * | ||||
|      * @param  string $property | ||||
|      * @return mixed | ||||
|      * | ||||
|      * @throws InvalidArgumentException In case the data does not have the property | ||||
|      */ | ||||
|     public function get($property) | ||||
|     { | ||||
|         if (!isset($this->properties[$property])) { | ||||
|             throw new InvalidArgumentException(sprintf('Invalid property `%s`.', $property)); | ||||
|         } | ||||
| 
 | ||||
|         return $this->properties[$property]; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns all property names | ||||
|      * | ||||
|      * @return array | ||||
|      */ | ||||
|     public function keys() | ||||
|     { | ||||
|         return array_keys($this->properties); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns all properties and their values | ||||
|      * | ||||
|      * @return array | ||||
|      */ | ||||
|     public function all() | ||||
|     { | ||||
|         return $this->properties; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function count() | ||||
|     { | ||||
|         return count($this->properties); | ||||
|     } | ||||
| } | ||||
|  | @ -9,9 +9,8 @@ | |||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
| 
 | ||||
| namespace FFMpeg\Exception; | ||||
| namespace FFMpeg\FFProbe\DataMapping; | ||||
| 
 | ||||
| class LogicException extends \LogicException implements ExceptionInterface | ||||
| class Format extends AbstractData | ||||
| { | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										35
									
								
								src/FFMpeg/FFProbe/DataMapping/Stream.php
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/FFMpeg/FFProbe/DataMapping/Stream.php
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,35 @@ | |||
| <?php | ||||
| 
 | ||||
| /* | ||||
|  * This file is part of PHP-FFmpeg. | ||||
|  * | ||||
|  * (c) Alchemy <info@alchemy.fr> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
| 
 | ||||
| namespace FFMpeg\FFProbe\DataMapping; | ||||
| 
 | ||||
| class Stream extends AbstractData | ||||
| { | ||||
|     /** | ||||
|      * Returns true if the stream is an audio stream | ||||
|      * | ||||
|      * @return Boolean | ||||
|      */ | ||||
|     public function isAudio() | ||||
|     { | ||||
|         return $this->has('codec_type') ? 'audio' === $this->get('codec_type') : false; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns true if the stream is a video stream | ||||
|      * | ||||
|      * @return Boolean | ||||
|      */ | ||||
|     public function isVideo() | ||||
|     { | ||||
|         return $this->has('codec_type') ? 'video' === $this->get('codec_type') : false; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										99
									
								
								src/FFMpeg/FFProbe/DataMapping/StreamCollection.php
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								src/FFMpeg/FFProbe/DataMapping/StreamCollection.php
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,99 @@ | |||
| <?php | ||||
| 
 | ||||
| /* | ||||
|  * This file is part of PHP-FFmpeg. | ||||
|  * | ||||
|  * (c) Alchemy <info@alchemy.fr> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
| 
 | ||||
| namespace FFMpeg\FFProbe\DataMapping; | ||||
| 
 | ||||
| class StreamCollection implements \Countable, \IteratorAggregate | ||||
| { | ||||
|     private $streams; | ||||
| 
 | ||||
|     public function __construct(array $streams = array()) | ||||
|     { | ||||
|         $this->streams = array_values($streams); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the first stream of the collection, null if the collection is | ||||
|      * empty. | ||||
|      * | ||||
|      * @return null|Stream | ||||
|      */ | ||||
|     public function first() | ||||
|     { | ||||
|         $stream = reset($this->streams); | ||||
| 
 | ||||
|         return $stream ?: null; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Adds a stream to the collection | ||||
|      * | ||||
|      * @param Stream $stream | ||||
|      * | ||||
|      * @return StreamCollection | ||||
|      */ | ||||
|     public function add(Stream $stream) | ||||
|     { | ||||
|         $this->streams[] = $stream; | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns a new StreamCollection with only video streams | ||||
|      * | ||||
|      * @return StreamCollection | ||||
|      */ | ||||
|     public function videos() | ||||
|     { | ||||
|         return new static(array_filter($this->streams, function (Stream $stream) { | ||||
|             return $stream->isVideo(); | ||||
|         })); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns a new StreamCollection with only audio streams | ||||
|      * | ||||
|      * @return StreamCollection | ||||
|      */ | ||||
|     public function audios() | ||||
|     { | ||||
|         return new static(array_filter($this->streams, function (Stream $stream) { | ||||
|             return $stream->isAudio(); | ||||
|         })); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function count() | ||||
|     { | ||||
|         return count($this->streams); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the array of contained streams | ||||
|      * | ||||
|      * @return array | ||||
|      */ | ||||
|     public function all() | ||||
|     { | ||||
|         return $this->streams; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function getIterator() | ||||
|     { | ||||
|         return new \ArrayIterator($this->streams); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										54
									
								
								src/FFMpeg/FFProbe/Mapper.php
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/FFMpeg/FFProbe/Mapper.php
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,54 @@ | |||
| <?php | ||||
| 
 | ||||
| /* | ||||
|  * This file is part of PHP-FFmpeg. | ||||
|  * | ||||
|  * (c) Alchemy <info@alchemy.fr> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
| 
 | ||||
| namespace FFMpeg\FFProbe; | ||||
| 
 | ||||
| use FFMpeg\FFProbe; | ||||
| use FFMpeg\FFProbe\DataMapping\Format; | ||||
| use FFMpeg\FFProbe\DataMapping\StreamCollection; | ||||
| use FFMpeg\FFProbe\DataMapping\Stream; | ||||
| use FFMpeg\Exception\InvalidArgumentException; | ||||
| 
 | ||||
| class Mapper implements MapperInterface | ||||
| { | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function map($type, $data) | ||||
|     { | ||||
|         switch ($type) { | ||||
|             case FFProbe::TYPE_FORMAT: | ||||
|                 return $this->mapFormat($data); | ||||
|             case FFProbe::TYPE_STREAMS: | ||||
|                 return $this->mapStreams($data); | ||||
|             default: | ||||
|                 throw new InvalidArgumentException(sprintf( | ||||
|                     'Invalid type `%s`.', $type | ||||
|                 )); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private function mapFormat($data) | ||||
|     { | ||||
|         return new Format($data['format']); | ||||
|     } | ||||
| 
 | ||||
|     private function mapStreams($data) | ||||
|     { | ||||
|         $streams = new StreamCollection(); | ||||
| 
 | ||||
|         foreach ($data['streams'] as $properties) { | ||||
|             $streams->add(new Stream($properties)); | ||||
|         } | ||||
| 
 | ||||
|         return $streams; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										27
									
								
								src/FFMpeg/FFProbe/MapperInterface.php
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/FFMpeg/FFProbe/MapperInterface.php
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,27 @@ | |||
| <?php | ||||
| 
 | ||||
| /* | ||||
|  * This file is part of PHP-FFmpeg. | ||||
|  * | ||||
|  * (c) Alchemy <info@alchemy.fr> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
| 
 | ||||
| namespace FFMpeg\FFProbe; | ||||
| 
 | ||||
| interface MapperInterface | ||||
| { | ||||
|     /** | ||||
|      * Maps data given its type | ||||
|      * | ||||
|      * @param string $type One of FFProbe::TYPE_* constant | ||||
|      * @param string $data The data | ||||
|      * | ||||
|      * @return Format|Stream | ||||
|      * | ||||
|      * @throws InvalidArgumentException In case the type is not supported | ||||
|      */ | ||||
|     public function map($type, $data); | ||||
| } | ||||
							
								
								
									
										64
									
								
								src/FFMpeg/FFProbe/OptionsTester.php
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								src/FFMpeg/FFProbe/OptionsTester.php
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,64 @@ | |||
| <?php | ||||
| 
 | ||||
| /* | ||||
|  * This file is part of PHP-FFmpeg. | ||||
|  * | ||||
|  * (c) Alchemy <info@alchemy.fr> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
| 
 | ||||
| namespace FFMpeg\FFProbe; | ||||
| 
 | ||||
| use Doctrine\Common\Cache\Cache; | ||||
| use FFMpeg\Driver\FFProbeDriver; | ||||
| 
 | ||||
| class OptionsTester implements OptionsTesterInterface | ||||
| { | ||||
|     /** @var FFProbeDriver */ | ||||
|     private $ffprobe; | ||||
|     /** @var Cache */ | ||||
|     private $cache; | ||||
| 
 | ||||
|     public function __construct(FFProbeDriver $ffprobe, Cache $cache) | ||||
|     { | ||||
|         $this->ffprobe = $ffprobe; | ||||
|         $this->cache = $cache; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function has($name) | ||||
|     { | ||||
|         $id = sprintf('option-%s', $name); | ||||
| 
 | ||||
|         if ($this->cache->contains($id)) { | ||||
|             return $this->cache->fetch($id); | ||||
|         } | ||||
| 
 | ||||
|         $output = $this->retrieveHelpOutput(); | ||||
| 
 | ||||
|         $ret = (Boolean) preg_match('/^'.$name.'/m', $output); | ||||
| 
 | ||||
|         $this->cache->save($id, $ret); | ||||
| 
 | ||||
|         return $ret; | ||||
|     } | ||||
| 
 | ||||
|     private function retrieveHelpOutput() | ||||
|     { | ||||
|         $id = 'help'; | ||||
| 
 | ||||
|         if ($this->cache->contains($id)) { | ||||
|             return $this->cache->fetch($id); | ||||
|         } | ||||
| 
 | ||||
|         $output = $this->ffprobe->command(array('-help', '-loglevel', 'quiet')); | ||||
| 
 | ||||
|         $this->cache->save($id, $output); | ||||
| 
 | ||||
|         return $output; | ||||
|     } | ||||
| } | ||||
|  | @ -9,20 +9,16 @@ | |||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
| 
 | ||||
| namespace FFMpeg; | ||||
| namespace FFMpeg\FFProbe; | ||||
| 
 | ||||
| use Monolog\Logger; | ||||
| 
 | ||||
| /** | ||||
|  * FFMpeg Adapter interface | ||||
|  * | ||||
|  * @author Romain Neutron imprec@gmail.com | ||||
|  */ | ||||
| interface AdapterInterface | ||||
| interface OptionsTesterInterface | ||||
| { | ||||
| 
 | ||||
|     /** | ||||
|      * Loads the adapter | ||||
|      * Tells if the given option is supported by ffprobe | ||||
|      * | ||||
|      * @param string $name | ||||
|      * | ||||
|      * @return Boolean | ||||
|      */ | ||||
|     public static function load(Logger $logger); | ||||
|     public function has($name); | ||||
| } | ||||
							
								
								
									
										125
									
								
								src/FFMpeg/FFProbe/OutputParser.php
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								src/FFMpeg/FFProbe/OutputParser.php
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,125 @@ | |||
| <?php | ||||
| 
 | ||||
| /* | ||||
|  * This file is part of PHP-FFmpeg. | ||||
|  * | ||||
|  * (c) Alchemy <info@alchemy.fr> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
| 
 | ||||
| namespace FFMpeg\FFProbe; | ||||
| 
 | ||||
| use FFMpeg\FFProbe; | ||||
| use FFMpeg\Exception\InvalidArgumentException; | ||||
| 
 | ||||
| class OutputParser implements OutputParserInterface | ||||
| { | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function parse($type, $data) | ||||
|     { | ||||
|         switch ($type) { | ||||
|             case FFProbe::TYPE_FORMAT: | ||||
|                 return $this->parseFormat($data); | ||||
|                 break; | ||||
|             case FFProbe::TYPE_STREAMS: | ||||
|                 return $this->parseStreams($data); | ||||
|                 break; | ||||
|             default: | ||||
|                 throw new InvalidArgumentException(sprintf('Unknown data type %s', $type)); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private function parseFormat($data) | ||||
|     { | ||||
|         $ret = array(); | ||||
| 
 | ||||
|         foreach (explode(PHP_EOL, $data) as $line) { | ||||
| 
 | ||||
|             if (in_array($line, array('[FORMAT]', '[/FORMAT]'))) { | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             $chunks = explode('=', $line); | ||||
|             $key = array_shift($chunks); | ||||
| 
 | ||||
|             if ('' === trim($key)) { | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             $value = trim(implode('=', $chunks)); | ||||
| 
 | ||||
|             if ('nb_streams' === $key) { | ||||
|                 $value = (int) $value; | ||||
|             } | ||||
| 
 | ||||
|             if (0 === strpos($key, 'TAG:')) { | ||||
|                 if (!isset($ret['tags'])) { | ||||
|                     $ret['tags'] = array(); | ||||
|                 } | ||||
|                 $ret['tags'][substr($key, 4)] = $value; | ||||
|             } else { | ||||
|                 $ret[$key] = $value; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return array('format' => $ret); | ||||
|     } | ||||
| 
 | ||||
|     private function parseStreams($data) | ||||
|     { | ||||
|         $ret = array(); | ||||
|         $n = -1; | ||||
| 
 | ||||
|         foreach (explode(PHP_EOL, $data) as $line) { | ||||
| 
 | ||||
|             if ($line == '[STREAM]') { | ||||
|                 $n ++; | ||||
|                 $ret[$n] = array(); | ||||
|                 continue; | ||||
|             } | ||||
|             if ($line == '[/STREAM]') { | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             $chunks = explode('=', $line); | ||||
|             $key = array_shift($chunks); | ||||
| 
 | ||||
|             if ('' === trim($key)) { | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             $value = trim(implode('=', $chunks)); | ||||
| 
 | ||||
|             if ('N/A' === $value) { | ||||
|                 continue; | ||||
|             } | ||||
|             if ('profile' === $key && 'unknown' === $value) { | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             if (in_array($key, array('index', 'width', 'height', 'channels', 'bits_per_sample', 'has_b_frames', 'level', 'start_pts', 'duration_ts'))) { | ||||
|                 $value = (int) $value; | ||||
|             } | ||||
| 
 | ||||
|             if (0 === strpos($key, 'TAG:')) { | ||||
|                 if (!isset($ret[$n]['tags'])) { | ||||
|                     $ret[$n]['tags'] = array(); | ||||
|                 } | ||||
|                 $ret[$n]['tags'][substr($key, 4)] = $value; | ||||
|             } elseif (0 === strpos($key, 'DISPOSITION:')) { | ||||
|                 if (!isset($ret[$n]['disposition'])) { | ||||
|                     $ret[$n]['disposition'] = array(); | ||||
|                 } | ||||
|                 $ret[$n]['disposition'][substr($key, 12)] = $value; | ||||
|             } else { | ||||
|                 $ret[$n][$key] = $value; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return array('streams' => $ret); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										27
									
								
								src/FFMpeg/FFProbe/OutputParserInterface.php
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/FFMpeg/FFProbe/OutputParserInterface.php
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,27 @@ | |||
| <?php | ||||
| 
 | ||||
| /* | ||||
|  * This file is part of PHP-FFmpeg. | ||||
|  * | ||||
|  * (c) Alchemy <info@alchemy.fr> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
| 
 | ||||
| namespace FFMpeg\FFProbe; | ||||
| 
 | ||||
| interface OutputParserInterface | ||||
| { | ||||
|     /** | ||||
|      * Parses ffprobe raw output | ||||
|      * | ||||
|      * @param string $type One of FFProbe::TYPE_* constant | ||||
|      * @param string $data The data | ||||
|      * | ||||
|      * @return array | ||||
|      * | ||||
|      * @throws InvalidArgumentException In case the type is not supported | ||||
|      */ | ||||
|     public function parse($type, $data); | ||||
| } | ||||
							
								
								
									
										29
									
								
								src/FFMpeg/Filters/Audio/AudioFilterInterface.php
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/FFMpeg/Filters/Audio/AudioFilterInterface.php
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,29 @@ | |||
| <?php | ||||
| 
 | ||||
| /* | ||||
|  * This file is part of PHP-FFmpeg. | ||||
|  * | ||||
|  * (c) Alchemy <dev.team@alchemy.fr> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
| 
 | ||||
| namespace FFMpeg\Filters\Audio; | ||||
| 
 | ||||
| use FFMpeg\Filters\FilterInterface; | ||||
| use FFMpeg\Format\AudioInterface; | ||||
| use FFMpeg\Media\Audio; | ||||
| 
 | ||||
| interface AudioFilterInterface extends FilterInterface | ||||
| { | ||||
|     /** | ||||
|      * Applies the filter on the the Audio media given an format. | ||||
|      * | ||||
|      * @param Audio          $audio | ||||
|      * @param AudioInterface $format | ||||
|      * | ||||
|      * @return array An array of arguments | ||||
|      */ | ||||
|     public function apply(Audio $audio, AudioInterface $format); | ||||
| } | ||||
							
								
								
									
										30
									
								
								src/FFMpeg/Filters/Audio/AudioFilters.php
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/FFMpeg/Filters/Audio/AudioFilters.php
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,30 @@ | |||
| <?php | ||||
| 
 | ||||
| namespace FFMpeg\Filters\Audio; | ||||
| 
 | ||||
| use FFMpeg\Media\Audio; | ||||
| use FFMpeg\Filters\Audio\AudioResamplableFilter; | ||||
| 
 | ||||
| class AudioFilters | ||||
| { | ||||
|     private $audio; | ||||
| 
 | ||||
|     public function __construct(Audio $audio) | ||||
|     { | ||||
|         $this->audio = $audio; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Resamples the audio file. | ||||
|      * | ||||
|      * @param Integer $rate | ||||
|      * | ||||
|      * @return AudioFilters | ||||
|      */ | ||||
|     public function resample($rate) | ||||
|     { | ||||
|         $this->audio->addFilter(new AudioResamplableFilter($rate)); | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										43
									
								
								src/FFMpeg/Filters/Audio/AudioResamplableFilter.php
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/FFMpeg/Filters/Audio/AudioResamplableFilter.php
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,43 @@ | |||
| <?php | ||||
| 
 | ||||
| /* | ||||
|  * This file is part of PHP-FFmpeg. | ||||
|  * | ||||
|  * (c) Alchemy <dev.team@alchemy.fr> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
| 
 | ||||
| namespace FFMpeg\Filters\Audio; | ||||
| 
 | ||||
| use FFMpeg\Format\AudioInterface; | ||||
| use FFMpeg\Media\Audio; | ||||
| 
 | ||||
| class AudioResamplableFilter implements AudioFilterInterface | ||||
| { | ||||
|     /** @var string */ | ||||
|     private $rate; | ||||
| 
 | ||||
|     public function __construct($rate) | ||||
|     { | ||||
|         $this->rate = $rate; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * | ||||
|      * @return Integer | ||||
|      */ | ||||
|     public function getRate() | ||||
|     { | ||||
|         return $this->rate; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function apply(Audio $audio, AudioInterface $format) | ||||
|     { | ||||
|         return array('-ac', 2, '-ar', $this->rate); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										16
									
								
								src/FFMpeg/Filters/FilterInterface.php
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/FFMpeg/Filters/FilterInterface.php
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,16 @@ | |||
| <?php | ||||
| 
 | ||||
| /* | ||||
|  * This file is part of PHP-FFmpeg. | ||||
|  * | ||||
|  * (c) Alchemy <dev.team@alchemy.fr> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
| 
 | ||||
| namespace FFMpeg\Filters; | ||||
| 
 | ||||
| interface FilterInterface | ||||
| { | ||||
| } | ||||
							
								
								
									
										45
									
								
								src/FFMpeg/Filters/FiltersCollection.php
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								src/FFMpeg/Filters/FiltersCollection.php
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,45 @@ | |||
| <?php | ||||
| 
 | ||||
| /* | ||||
|  * This file is part of PHP-FFmpeg. | ||||
|  * | ||||
|  * (c) Alchemy <dev.team@alchemy.fr> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
| 
 | ||||
| namespace FFMpeg\Filters; | ||||
| 
 | ||||
| class FiltersCollection implements \Countable, \IteratorAggregate | ||||
| { | ||||
|     private $filters = array(); | ||||
| 
 | ||||
|     /** | ||||
|      * @param FilterInterface $filter | ||||
|      * | ||||
|      * @return FiltersCollection | ||||
|      */ | ||||
|     public function add(FilterInterface $filter) | ||||
|     { | ||||
|         $this->filters[] = $filter; | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function count() | ||||
|     { | ||||
|         return count($this->filters); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function getIterator() | ||||
|     { | ||||
|         return new \ArrayIterator($this->filters); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										21
									
								
								src/FFMpeg/Filters/Frame/FrameFilterInterface.php
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/FFMpeg/Filters/Frame/FrameFilterInterface.php
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,21 @@ | |||
| <?php | ||||
| 
 | ||||
| /* | ||||
|  * This file is part of PHP-FFmpeg. | ||||
|  * | ||||
|  * (c) Alchemy <dev.team@alchemy.fr> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
| 
 | ||||
| namespace FFMpeg\Filters\Frame; | ||||
| 
 | ||||
| use FFMpeg\Filters\FilterInterface; | ||||
| use FFMpeg\Media\Frame; | ||||
| use FFMpeg\Format\FrameInterface; | ||||
| 
 | ||||
| interface FrameFilterInterface extends FilterInterface | ||||
| { | ||||
|     public function apply(Frame $frame, FrameInterface $format); | ||||
| } | ||||
							
								
								
									
										24
									
								
								src/FFMpeg/Filters/Frame/FrameFilters.php
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/FFMpeg/Filters/Frame/FrameFilters.php
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,24 @@ | |||
| <?php | ||||
| 
 | ||||
| /* | ||||
|  * This file is part of PHP-FFmpeg. | ||||
|  * | ||||
|  * (c) Alchemy <dev.team@alchemy.fr> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
| 
 | ||||
| namespace FFMpeg\Filters\Frame; | ||||
| 
 | ||||
| use FFMpeg\Media\Frame; | ||||
| 
 | ||||
| class FrameFilters | ||||
| { | ||||
|     private $frame; | ||||
| 
 | ||||
|     public function __construct(Frame $frame) | ||||
|     { | ||||
|         $this->frame = $frame; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										126
									
								
								src/FFMpeg/Filters/Video/ResizeFilter.php
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								src/FFMpeg/Filters/Video/ResizeFilter.php
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,126 @@ | |||
| <?php | ||||
| 
 | ||||
| /* | ||||
|  * This file is part of PHP-FFmpeg. | ||||
|  * | ||||
|  * (c) Alchemy <dev.team@alchemy.fr> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
| 
 | ||||
| namespace FFMpeg\Filters\Video; | ||||
| 
 | ||||
| use FFMpeg\Coordinate\Dimension; | ||||
| use FFMpeg\Media\Video; | ||||
| use FFMpeg\Format\VideoInterface; | ||||
| 
 | ||||
| class ResizeFilter implements VideoFilterInterface | ||||
| { | ||||
|     const RESIZEMODE_FIT = 'fit'; | ||||
|     const RESIZEMODE_INSET = 'inset'; | ||||
|     const RESIZEMODE_SCALE_WIDTH = 'width'; | ||||
|     const RESIZEMODE_SCALE_HEIGHT = 'height'; | ||||
| 
 | ||||
|     /** @var Dimension */ | ||||
|     private $dimension; | ||||
|     /** @var string */ | ||||
|     private $mode; | ||||
|     /** @var Boolean */ | ||||
|     private $forceStandards; | ||||
| 
 | ||||
|     public function __construct(Dimension $dimension, $mode = self::RESIZEMODE_FIT, $forceStandards = true) | ||||
|     { | ||||
|         $this->dimension = $dimension; | ||||
|         $this->mode = $mode; | ||||
|         $this->forceStandards = $forceStandards; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return Dimension | ||||
|      */ | ||||
|     public function getDimension() | ||||
|     { | ||||
|         return $this->dimension; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return string | ||||
|      */ | ||||
|     public function getMode() | ||||
|     { | ||||
|         return $this->mode; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return Boolean | ||||
|      */ | ||||
|     public function areStandardsForced() | ||||
|     { | ||||
|         return $this->forceStandards; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function apply(Video $video, VideoInterface $format) | ||||
|     { | ||||
|         $originalWidth = $originalHeight = null; | ||||
| 
 | ||||
|         foreach ($video->getStreams() as $stream) { | ||||
|             if ($stream->isVideo()) { | ||||
|                 if ($stream->has('width')) { | ||||
|                     $originalWidth = $stream->get('width'); | ||||
|                 } | ||||
|                 if ($stream->has('height')) { | ||||
|                     $originalHeight = $stream->get('height'); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         $commands = array(); | ||||
| 
 | ||||
|         if ($originalHeight !== null && $originalWidth !== null) { | ||||
|             $dimensions = $this->getComputedDimensions(new Dimension($originalWidth, $originalHeight), $format->getModulus()); | ||||
| 
 | ||||
|             $commands[] = '-s'; | ||||
|             $commands[] = $dimensions->getWidth() . 'x' . $dimensions->getHeight(); | ||||
|         } | ||||
| 
 | ||||
|         return $commands; | ||||
|     } | ||||
| 
 | ||||
|     private function getComputedDimensions(Dimension $dimension, $modulus) | ||||
|     { | ||||
|         $originalRatio = $dimension->getRatio($this->forceStandards); | ||||
| 
 | ||||
|         switch ($this->mode) { | ||||
|             case self::RESIZEMODE_SCALE_WIDTH: | ||||
|                 $height = $this->dimension->getHeight(); | ||||
|                 $width = $originalRatio->calculateWidth($height, $modulus); | ||||
|                 break; | ||||
|             case self::RESIZEMODE_SCALE_HEIGHT: | ||||
|                 $width = $this->dimension->getWidth(); | ||||
|                 $height = $originalRatio->calculateHeight($width, $modulus); | ||||
|                 break; | ||||
|             case self::RESIZEMODE_INSET: | ||||
|                 $targetRatio = $this->dimension->getRatio($this->forceStandards); | ||||
| 
 | ||||
|                 if ($targetRatio->getValue() > $originalRatio->getValue()) { | ||||
|                     $height = $this->dimension->getHeight(); | ||||
|                     $width = $originalRatio->calculateWidth($height, $modulus); | ||||
|                 } else { | ||||
|                     $width = $this->dimension->getWidth(); | ||||
|                     $height = $originalRatio->calculateHeight($width, $modulus); | ||||
|                 } | ||||
|                 break; | ||||
|             case self::RESIZEMODE_FIT: | ||||
|             default: | ||||
|                 $width = $this->dimension->getWidth(); | ||||
|                 $height = $this->dimension->getHeight(); | ||||
|                 break; | ||||
|         } | ||||
| 
 | ||||
|         return new Dimension($width, $height); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										49
									
								
								src/FFMpeg/Filters/Video/SynchronizeFilter.php
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								src/FFMpeg/Filters/Video/SynchronizeFilter.php
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,49 @@ | |||
| <?php | ||||
| 
 | ||||
| /* | ||||
|  * This file is part of PHP-FFmpeg. | ||||
|  * | ||||
|  * (c) Alchemy <dev.team@alchemy.fr> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
| 
 | ||||
| namespace FFMpeg\Filters\Video; | ||||
| 
 | ||||
| use FFMpeg\Format\VideoInterface; | ||||
| use FFMpeg\Media\Video; | ||||
| 
 | ||||
| class SynchronizeFilter implements VideoFilterInterface | ||||
| { | ||||
|     public function apply(Video $video, VideoInterface $format) | ||||
|     { | ||||
|         $streams = $video->getStreams(); | ||||
| 
 | ||||
|         if (null === $videoStream = $streams->videos()->first()) { | ||||
|             return array(); | ||||
|         } | ||||
|         if (!$videoStream->has('start_time')) { | ||||
|             return array(); | ||||
|         } | ||||
| 
 | ||||
|         $params = array( | ||||
|             '-itsoffset', | ||||
|             $videoStream->get('start_time'), | ||||
|             '-i', | ||||
|             $video->getPathfile(), | ||||
|         ); | ||||
| 
 | ||||
|         foreach ($streams as $stream) { | ||||
|             if ($videoStream === $stream) { | ||||
|                 $params[] = '-map'; | ||||
|                 $params[] = '1:' . $stream->get('index'); | ||||
|             } else { | ||||
|                 $params[] = '-map'; | ||||
|                 $params[] = '0:' . $stream->get('index'); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return $params; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										29
									
								
								src/FFMpeg/Filters/Video/VideoFilterInterface.php
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/FFMpeg/Filters/Video/VideoFilterInterface.php
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,29 @@ | |||
| <?php | ||||
| 
 | ||||
| /* | ||||
|  * This file is part of PHP-FFmpeg. | ||||
|  * | ||||
|  * (c) Alchemy <dev.team@alchemy.fr> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
| 
 | ||||
| namespace FFMpeg\Filters\Video; | ||||
| 
 | ||||
| use FFMpeg\Filters\FilterInterface; | ||||
| use FFMpeg\Format\VideoInterface; | ||||
| use FFMpeg\Media\Video; | ||||
| 
 | ||||
| interface VideoFilterInterface extends FilterInterface | ||||
| { | ||||
|     /** | ||||
|      * Applies the filter on the the Video media given an format. | ||||
|      * | ||||
|      * @param Video          $video | ||||
|      * @param VideoInterface $format | ||||
|      * | ||||
|      * @return array An array of arguments | ||||
|      */ | ||||
|     public function apply(Video $video, VideoInterface $format); | ||||
| } | ||||
							
								
								
									
										69
									
								
								src/FFMpeg/Filters/Video/VideoFilters.php
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								src/FFMpeg/Filters/Video/VideoFilters.php
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,69 @@ | |||
| <?php | ||||
| 
 | ||||
| /* | ||||
|  * This file is part of PHP-FFmpeg. | ||||
|  * | ||||
|  * (c) Alchemy <dev.team@alchemy.fr> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
| 
 | ||||
| namespace FFMpeg\Filters\Video; | ||||
| 
 | ||||
| use FFMpeg\Media\Video; | ||||
| use FFMpeg\Coordinate\Dimension; | ||||
| use FFMpeg\Coordinate\FrameRate; | ||||
| 
 | ||||
| class VideoFilters | ||||
| { | ||||
|     private $video; | ||||
| 
 | ||||
|     public function __construct(Video $video) | ||||
|     { | ||||
|         $this->video = $video; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Resizes a video to a given dimension | ||||
|      * | ||||
|      * @param Dimension $dimension | ||||
|      * @param string    $mode | ||||
|      * @param Boolean   $forceStandards | ||||
|      * | ||||
|      * @return VideoFilters | ||||
|      */ | ||||
|     public function resize(Dimension $dimension, $mode = ResizeFilter::RESIZEMODE_FIT, $forceStandards = true) | ||||
|     { | ||||
|         $this->video->addFilter(new ResizeFilter($dimension, $mode, $forceStandards)); | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Resamples the video to the given framerate. | ||||
|      * | ||||
|      * @param FrameRate $framerate | ||||
|      * @param type      $gop | ||||
|      * | ||||
|      * @return VideoFilters | ||||
|      */ | ||||
|     public function resample(FrameRate $framerate, $gop) | ||||
|     { | ||||
|         $this->video->addFilter(new VideoResampleFilter($framerate, $gop)); | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Synchronizes audio and video. | ||||
|      * | ||||
|      * @return VideoFilters | ||||
|      */ | ||||
|     public function synchronize() | ||||
|     { | ||||
|         $this->video->addFilter(new SynchronizeFilter()); | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										72
									
								
								src/FFMpeg/Filters/Video/VideoResampleFilter.php
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								src/FFMpeg/Filters/Video/VideoResampleFilter.php
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,72 @@ | |||
| <?php | ||||
| 
 | ||||
| /* | ||||
|  * This file is part of PHP-FFmpeg. | ||||
|  * | ||||
|  * (c) Alchemy <dev.team@alchemy.fr> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
| 
 | ||||
| namespace FFMpeg\Filters\Video; | ||||
| 
 | ||||
| use FFMpeg\Coordinate\FrameRate; | ||||
| use FFMpeg\Media\Video; | ||||
| use FFMpeg\Format\VideoInterface; | ||||
| 
 | ||||
| class VideoResampleFilter implements VideoFilterInterface | ||||
| { | ||||
|     private $rate; | ||||
|     private $gop; | ||||
| 
 | ||||
|     public function __construct(FrameRate $rate, $gop) | ||||
|     { | ||||
|         $this->rate = $rate; | ||||
|         $this->gop = $gop; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the frame rate | ||||
|      * | ||||
|      * @return FrameRate | ||||
|      */ | ||||
|     public function getFrameRate() | ||||
|     { | ||||
|         return $this->rate; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the GOP size | ||||
|      * | ||||
|      * @see https://wikipedia.org/wiki/Group_of_pictures | ||||
|      * | ||||
|      * @return Integer | ||||
|      */ | ||||
|     public function getGOP() | ||||
|     { | ||||
|         return $this->gop; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function apply(Video $video, VideoInterface $format) | ||||
|     { | ||||
|         $commands = array('-r', $this->rate->getValue()); | ||||
| 
 | ||||
|         /** | ||||
|          * @see http://sites.google.com/site/linuxencoding/x264-ffmpeg-mapping | ||||
|          */ | ||||
|         if ($format->supportBFrames()) { | ||||
|             $commands[] = '-b_strategy'; | ||||
|             $commands[] = '1'; | ||||
|             $commands[] = '-bf'; | ||||
|             $commands[] = '3'; | ||||
|             $commands[] = '-g'; | ||||
|             $commands[] = $this->gop; | ||||
|         } | ||||
| 
 | ||||
|         return $commands; | ||||
|     } | ||||
| } | ||||
|  | @ -11,23 +11,24 @@ | |||
| 
 | ||||
| namespace FFMpeg\Format\Audio; | ||||
| 
 | ||||
| use Evenement\EventEmitter; | ||||
| use FFMpeg\Exception\InvalidArgumentException; | ||||
| use FFMpeg\Format\AudioInterface; | ||||
| use FFMpeg\Media\MediaTypeInterface; | ||||
| use FFMpeg\Format\ProgressableInterface; | ||||
| use FFMpeg\Format\ProgressListener\AudioProgressListener; | ||||
| use FFMpeg\FFProbe; | ||||
| 
 | ||||
| /** | ||||
|  * The abstract default Audio format | ||||
|  * | ||||
|  * @author Romain Neutron imprec@gmail.com | ||||
|  */ | ||||
| abstract class DefaultAudio implements Resamplable, Interactive | ||||
| abstract class DefaultAudio extends EventEmitter implements AudioInterface, ProgressableInterface | ||||
| { | ||||
|     /** @var string */ | ||||
|     protected $audioCodec; | ||||
|     protected $audioSampleRate = 44100; | ||||
|     protected $kiloBitrate = 128; | ||||
| 
 | ||||
|     /** @var integer */ | ||||
|     protected $audioKiloBitrate = 128; | ||||
| 
 | ||||
|     /** | ||||
|      * Returns extra parameters for the encoding | ||||
|      * | ||||
|      * @return string | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function getExtraParams() | ||||
|     { | ||||
|  | @ -43,11 +44,12 @@ abstract class DefaultAudio implements Resamplable, Interactive | |||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Set the audio codec, Should be in the available ones, otherwise an | ||||
|      * Sets the audio codec, Should be in the available ones, otherwise an | ||||
|      * exception is thrown | ||||
|      * | ||||
|      * @param  string                    $audioCodec | ||||
|      * @throws \InvalidArgumentException | ||||
|      * @param string $audioCodec | ||||
|      * | ||||
|      * @throws InvalidArgumentException | ||||
|      */ | ||||
|     public function setAudioCodec($audioCodec) | ||||
|     { | ||||
|  | @ -66,24 +68,24 @@ abstract class DefaultAudio implements Resamplable, Interactive | |||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function getAudioSampleRate() | ||||
|     public function getAudioKiloBitrate() | ||||
|     { | ||||
|         return $this->audioSampleRate; | ||||
|         return $this->audioKiloBitrate; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Set the audio sample rate | ||||
|      * Sets the kiloBitrate value | ||||
|      * | ||||
|      * @param  integer                   $audioSampleRate | ||||
|      * @throws \InvalidArgumentException | ||||
|      * @param  integer                  $kiloBitrate | ||||
|      * @throws InvalidArgumentException | ||||
|      */ | ||||
|     public function setAudioSampleRate($audioSampleRate) | ||||
|     public function setAudioKiloBitrate($kiloBitrate) | ||||
|     { | ||||
|         if ($audioSampleRate < 1) { | ||||
|             throw new InvalidArgumentException('Wrong audio sample rate value'); | ||||
|         if ($kiloBitrate < 1) { | ||||
|             throw new InvalidArgumentException('Wrong kiloBitrate value'); | ||||
|         } | ||||
| 
 | ||||
|         $this->audioSampleRate = (int) $audioSampleRate; | ||||
|         $this->audioKiloBitrate = (int) $kiloBitrate; | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
|  | @ -91,25 +93,14 @@ abstract class DefaultAudio implements Resamplable, Interactive | |||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function getKiloBitrate() | ||||
|     public function createProgressListener(MediaTypeInterface $media, FFProbe $ffprobe, $pass, $total) | ||||
|     { | ||||
|         return $this->kiloBitrate; | ||||
|     } | ||||
|         $format = $this; | ||||
|         $listener = new AudioProgressListener($ffprobe, $media->getPathfile(), $pass, $total); | ||||
|         $listener->on('progress', function () use ($media, $format) { | ||||
|            $format->emit('progress', array_merge(array($media, $format), func_get_args())); | ||||
|         }); | ||||
| 
 | ||||
|     /** | ||||
|      * Set the kiloBitrate value | ||||
|      * | ||||
|      * @param  int integer               $kiloBitrate | ||||
|      * @throws \InvalidArgumentException | ||||
|      */ | ||||
|     public function setKiloBitrate($kiloBitrate) | ||||
|     { | ||||
|         if ($kiloBitrate < 1) { | ||||
|             throw new InvalidArgumentException('Wrong kiloBitrate value'); | ||||
|         } | ||||
| 
 | ||||
|         $this->kiloBitrate = (int) $kiloBitrate; | ||||
| 
 | ||||
|         return $this; | ||||
|         return array($listener); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -13,12 +13,13 @@ namespace FFMpeg\Format\Audio; | |||
| 
 | ||||
| /** | ||||
|  * The Flac audio format | ||||
|  * | ||||
|  * @author Romain Neutron imprec@gmail.com | ||||
|  */ | ||||
| class Flac extends DefaultAudio | ||||
| { | ||||
|     protected $audioCodec = 'flac'; | ||||
|     public function __construct() | ||||
|     { | ||||
|         $this->audioCodec = 'flac'; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritDoc} | ||||
|  |  | |||
|  | @ -1,30 +0,0 @@ | |||
| <?php | ||||
| 
 | ||||
| /* | ||||
|  * This file is part of PHP-FFmpeg. | ||||
|  * | ||||
|  * (c) Alchemy <info@alchemy.fr> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
| 
 | ||||
| namespace FFMpeg\Format\Audio; | ||||
| 
 | ||||
| /** | ||||
|  * The interactive audio interface. This provide a method to list available | ||||
|  * codecs. This is usefull to build interactive development and switch between | ||||
|  * different codecs | ||||
|  * | ||||
|  * @author Romain Neutron imprec@gmail.com | ||||
|  */ | ||||
| interface Interactive extends Transcodable | ||||
| { | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the list of available audio codecs for this format | ||||
|      * | ||||
|      * @return array | ||||
|      */ | ||||
|     public function getAvailableAudioCodecs(); | ||||
| } | ||||
|  | @ -13,12 +13,13 @@ namespace FFMpeg\Format\Audio; | |||
| 
 | ||||
| /** | ||||
|  * The MP3 audio format | ||||
|  * | ||||
|  * @author Romain Neutron imprec@gmail.com | ||||
|  */ | ||||
| class Mp3 extends DefaultAudio | ||||
| { | ||||
|     protected $audioCodec = 'libmp3lame'; | ||||
|     public function __construct() | ||||
|     { | ||||
|         $this->audioCodec = 'libmp3lame'; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritDoc} | ||||
|  |  | |||
|  | @ -1,32 +0,0 @@ | |||
| <?php | ||||
| 
 | ||||
| /* | ||||
|  * This file is part of PHP-FFmpeg. | ||||
|  * | ||||
|  * (c) Alchemy <info@alchemy.fr> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
| 
 | ||||
| namespace FFMpeg\Format\Audio; | ||||
| 
 | ||||
| use FFMpeg\Format\AudioInterface; | ||||
| 
 | ||||
| /** | ||||
|  * The resamplable audio interface | ||||
|  * | ||||
|  * This provide a method to define the AudiosampleRate | ||||
|  * | ||||
|  * @author Romain Neutron imprec@gmail.com | ||||
|  */ | ||||
| interface Resamplable extends AudioInterface | ||||
| { | ||||
| 
 | ||||
|     /** | ||||
|      * Get the audio sample rate | ||||
|      * | ||||
|      * @return integer | ||||
|      */ | ||||
|     public function getAudioSampleRate(); | ||||
| } | ||||
|  | @ -8,23 +8,16 @@ | |||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
| 
 | ||||
| namespace FFMpeg\Format; | ||||
| 
 | ||||
| /** | ||||
|  * The base audio interface | ||||
|  * | ||||
|  * @author Romain Neutron imprec@gmail.com | ||||
|  */ | ||||
| interface AudioInterface | ||||
| interface AudioInterface extends FormatInterface | ||||
| { | ||||
| 
 | ||||
|     /** | ||||
|      * Get the kiloBitrate value | ||||
|      * Get the audio kiloBitrate value | ||||
|      * | ||||
|      * @return integer | ||||
|      */ | ||||
|     public function getKiloBitrate(); | ||||
|     public function getAudioKiloBitrate(); | ||||
| 
 | ||||
|     /** | ||||
|      * Return an array of extra parameters to add to ffmpeg commandline | ||||
|  | @ -33,4 +26,17 @@ interface AudioInterface | |||
|      */ | ||||
|     public function getExtraParams(); | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the audio codec | ||||
|      * | ||||
|      * @return string | ||||
|      */ | ||||
|     public function getAudioCodec(); | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the list of available audio codecs for this format | ||||
|      * | ||||
|      * @return array | ||||
|      */ | ||||
|     public function getAvailableAudioCodecs(); | ||||
| } | ||||
|  |  | |||
|  | @ -8,10 +8,8 @@ | |||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
| namespace FFMpeg\Format; | ||||
| 
 | ||||
| namespace FFMpeg\Exception; | ||||
| 
 | ||||
| class BinaryNotFoundException extends \Exception implements ExceptionInterface | ||||
| interface FormatInterface | ||||
| { | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										16
									
								
								src/FFMpeg/Format/FrameInterface.php
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/FFMpeg/Format/FrameInterface.php
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,16 @@ | |||
| <?php | ||||
| 
 | ||||
| /* | ||||
|  * This file is part of PHP-FFmpeg. | ||||
|  * | ||||
|  * (c) Alchemy <dev.team@alchemy.fr> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
| 
 | ||||
| namespace FFMpeg\Format; | ||||
| 
 | ||||
| interface FrameInterface extends FormatInterface | ||||
| { | ||||
| } | ||||
							
								
								
									
										238
									
								
								src/FFMpeg/Format/ProgressListener/AbstractProgressListener.php
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										238
									
								
								src/FFMpeg/Format/ProgressListener/AbstractProgressListener.php
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,238 @@ | |||
| <?php | ||||
| 
 | ||||
| /* | ||||
|  * This file is part of PHP-FFmpeg. | ||||
|  * | ||||
|  * (c) Alchemy <info@alchemy.fr> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
| 
 | ||||
| namespace FFMpeg\Format\ProgressListener; | ||||
| 
 | ||||
| use Alchemy\BinaryDriver\Listeners\ListenerInterface; | ||||
| use Evenement\EventEmitter; | ||||
| use FFMpeg\FFProbe; | ||||
| use FFMpeg\Exception\RuntimeException; | ||||
| 
 | ||||
| /** | ||||
|  * @author Robert Gruendler <r.gruendler@gmail.com> | ||||
|  */ | ||||
| abstract class AbstractProgressListener extends EventEmitter implements ListenerInterface | ||||
| { | ||||
|     /** @var integer */ | ||||
|     private $duration; | ||||
| 
 | ||||
|     /** @var integer */ | ||||
|     private $totalSize; | ||||
| 
 | ||||
|     /** @var integer */ | ||||
|     private $currentSize; | ||||
| 
 | ||||
|     /** @var integer */ | ||||
|     private $currentTime; | ||||
| 
 | ||||
|     /** @var double */ | ||||
|     private $lastOutput = null; | ||||
| 
 | ||||
|     /** @var FFProbe */ | ||||
|     private $ffprobe; | ||||
| 
 | ||||
|     /** @var string */ | ||||
|     private $pathfile; | ||||
| 
 | ||||
|     /** @var Boolean */ | ||||
|     private $initialized = false; | ||||
| 
 | ||||
|     /** @var integer */ | ||||
|     private $currentPass; | ||||
| 
 | ||||
|     /** @var integer */ | ||||
|     private $totalPass; | ||||
| 
 | ||||
|     /** | ||||
|      * Transcoding rate in kb/s | ||||
|      * | ||||
|      * @var integer | ||||
|      */ | ||||
|     private $rate; | ||||
| 
 | ||||
|     /** | ||||
|      * Percentage of transcoding progress (0 - 100) | ||||
|      * | ||||
|      * @var integer | ||||
|      */ | ||||
|     private $percent = 0; | ||||
| 
 | ||||
|     /** | ||||
|      * Time remaining (seconds) | ||||
|      * | ||||
|      * @var integer | ||||
|      */ | ||||
|     private $remaining = null; | ||||
| 
 | ||||
|     /** | ||||
|      * @param FFProbe $ffprobe | ||||
|      * @param string  $pathfile | ||||
|      * | ||||
|      * @throws RuntimeException | ||||
|      */ | ||||
|     public function __construct(FFProbe $ffprobe, $pathfile, $currentPass, $totalPass) | ||||
|     { | ||||
|         $this->ffprobe = $ffprobe; | ||||
|         $this->pathfile = $pathfile; | ||||
|         $this->currentPass = $currentPass; | ||||
|         $this->totalPass = $totalPass; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return FFProbe | ||||
|      */ | ||||
|     public function getFFProbe() | ||||
|     { | ||||
|         return $this->ffprobe; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return string | ||||
|      */ | ||||
|     public function getPathfile() | ||||
|     { | ||||
|         return $this->pathfile; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return integer | ||||
|      */ | ||||
|     public function getCurrentPass() | ||||
|     { | ||||
|         return $this->currentPass; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return integer | ||||
|      */ | ||||
|     public function getTotalPass() | ||||
|     { | ||||
|         return $this->totalPass; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function handle($type, $data) | ||||
|     { | ||||
|         if (null !== $progress = $this->parseProgress($data)) { | ||||
|             $this->emit('progress', array_values($progress)); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function forwardedEvents() | ||||
|     { | ||||
|         return array(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the regex pattern to match a ffmpeg stderr status line | ||||
|      */ | ||||
|     abstract protected function getPattern(); | ||||
| 
 | ||||
|     /** | ||||
|      * @param string $progress A ffmpeg stderr progress output | ||||
|      * | ||||
|      * @return array the progressinfo array or null if there's no progress available yet. | ||||
|      */ | ||||
|     private function parseProgress($progress) | ||||
|     { | ||||
|         if (!$this->initialized) { | ||||
|             $this->initialize(); | ||||
|         } | ||||
| 
 | ||||
|         $matches = array(); | ||||
| 
 | ||||
|         if (preg_match($this->getPattern(), $progress, $matches) !== 1) { | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         $currentDuration = $this->convertDuration($matches[2]); | ||||
|         $currentTime = microtime(true); | ||||
|         $currentSize = trim(str_replace('kb', '', strtolower(($matches[1])))); | ||||
|         $percent = max(0, min(1, $currentDuration / $this->duration)); | ||||
| 
 | ||||
|         if ($this->lastOutput !== null) { | ||||
|             $delta = $currentTime - $this->lastOutput; | ||||
|             $deltaSize = $currentSize - $this->currentSize; | ||||
|             $rate = $deltaSize * $delta; | ||||
|             if ($rate > 0) { | ||||
|                 $totalDuration = $this->totalSize / $rate; | ||||
|                 $this->remaining = floor($totalDuration - ($totalDuration * $percent)); | ||||
|                 $this->rate = floor($rate); | ||||
|             } else { | ||||
|                 $this->remaining = 0; | ||||
|                 $this->rate = 0; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         $percent = $percent / $this->totalPass + ($this->currentPass - 1) / $this->totalPass; | ||||
| 
 | ||||
|         $this->percent = floor($percent * 100); | ||||
|         $this->lastOutput = $currentTime; | ||||
|         $this->currentSize = (int) $currentSize; | ||||
|         $this->currentTime = $currentDuration; | ||||
| 
 | ||||
|         return $this->getProgressInfo(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * | ||||
|      * @param  string $rawDuration in the format 00:00:00.00 | ||||
|      * @return number | ||||
|      */ | ||||
|     private function convertDuration($rawDuration) | ||||
|     { | ||||
|         $ar = array_reverse(explode(":", $rawDuration)); | ||||
|         $duration = floatval($ar[0]); | ||||
|         if (!empty($ar[1])) { | ||||
|             $duration += intval($ar[1]) * 60; | ||||
|         } | ||||
|         if (!empty($ar[2])) { | ||||
|             $duration += intval($ar[2]) * 60 * 60; | ||||
|         } | ||||
| 
 | ||||
|         return $duration; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return array | ||||
|      */ | ||||
|     private function getProgressInfo() | ||||
|     { | ||||
|         if ($this->remaining === null) { | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         return array( | ||||
|             'percent'   => $this->percent, | ||||
|             'remaining' => $this->remaining, | ||||
|             'rate'      => $this->rate | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     private function initialize() | ||||
|     { | ||||
|         $format = $this->ffprobe->format($this->pathfile); | ||||
| 
 | ||||
|         if (false === $format->has('size') || false === $format->has('duration')) { | ||||
|             throw new RuntimeException(sprintf('Unable to probe format for %s', $this->pathfile)); | ||||
|         } | ||||
| 
 | ||||
|         $this->totalSize = $format->get('size') / 1024; | ||||
|         $this->duration = $format->get('duration'); | ||||
| 
 | ||||
|         $this->initialized = true; | ||||
|     } | ||||
| } | ||||
|  | @ -9,7 +9,7 @@ | |||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
| 
 | ||||
| namespace FFMpeg\Helper; | ||||
| namespace FFMpeg\Format\ProgressListener; | ||||
| 
 | ||||
| /** | ||||
|  * Parses ffmpeg stderr progress information. An example: | ||||
|  | @ -20,7 +20,7 @@ namespace FFMpeg\Helper; | |||
|  * | ||||
|  * @author Robert Gruendler <r.gruendler@gmail.com> | ||||
|  */ | ||||
| class AudioProgressHelper extends ProgressHelper | ||||
| class AudioProgressListener extends AbstractProgressListener | ||||
| { | ||||
|     public function getPattern() | ||||
|     { | ||||
|  | @ -9,7 +9,7 @@ | |||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
| 
 | ||||
| namespace FFMpeg\Helper; | ||||
| namespace FFMpeg\Format\ProgressListener; | ||||
| 
 | ||||
| /** | ||||
|  * Parses ffmpeg stderr progress information for video files. An example: | ||||
|  | @ -20,7 +20,7 @@ namespace FFMpeg\Helper; | |||
|  * | ||||
|  * @author Robert Gruendler <r.gruendler@gmail.com> | ||||
|  */ | ||||
| class VideoProgressHelper extends ProgressHelper | ||||
| class VideoProgressListener extends AbstractProgressListener | ||||
| { | ||||
|     public function getPattern() | ||||
|     { | ||||
							
								
								
									
										31
									
								
								src/FFMpeg/Format/ProgressableInterface.php
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/FFMpeg/Format/ProgressableInterface.php
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,31 @@ | |||
| <?php | ||||
| 
 | ||||
| /* | ||||
|  * This file is part of PHP-FFmpeg. | ||||
|  * | ||||
|  * (c) Alchemy <dev.team@alchemy.fr> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
| 
 | ||||
| namespace FFMpeg\Format; | ||||
| 
 | ||||
| use Evenement\EventEmitterInterface; | ||||
| use FFMpeg\FFProbe; | ||||
| use FFMpeg\Media\MediaTypeInterface; | ||||
| 
 | ||||
| interface ProgressableInterface extends EventEmitterInterface | ||||
| { | ||||
|     /** | ||||
|      * Creates the progress listener | ||||
|      * | ||||
|      * @param MediaTypeInterface $media | ||||
|      * @param FFProbe            $ffprobe | ||||
|      * @param Integer            $pass    The current pas snumber | ||||
|      * @param Integer            $total   The total pass number | ||||
|      * | ||||
|      * @return array An array of listeners | ||||
|      */ | ||||
|     public function createProgressListener(MediaTypeInterface $media, FFProbe $ffprobe, $pass, $total); | ||||
| } | ||||
|  | @ -11,171 +11,48 @@ | |||
| 
 | ||||
| namespace FFMpeg\Format\Video; | ||||
| 
 | ||||
| use FFMpeg\Format\Audio\DefaultAudio; | ||||
| use FFMpeg\Format\Dimension; | ||||
| use FFMpeg\FFProbe; | ||||
| use FFMpeg\Exception\InvalidArgumentException; | ||||
| use FFMpeg\Format\Audio\DefaultAudio; | ||||
| use FFMpeg\Format\VideoInterface; | ||||
| use FFMpeg\Media\MediaTypeInterface; | ||||
| use FFMpeg\Format\ProgressListener\VideoProgressListener; | ||||
| 
 | ||||
| /** | ||||
|  * The abstract default Video format | ||||
|  * | ||||
|  * @author Romain Neutron imprec@gmail.com | ||||
|  */ | ||||
| abstract class DefaultVideo extends DefaultAudio implements Interactive, Resamplable, Resizable | ||||
| abstract class DefaultVideo extends DefaultAudio implements VideoInterface | ||||
| { | ||||
|     const RESIZEMODE_FIT = 'fit'; | ||||
|     const RESIZEMODE_INSET = 'inset'; | ||||
|     const RESIZEMODE_SCALE_WIDTH = 'width'; | ||||
|     const RESIZEMODE_SCALE_HEIGHT = 'height'; | ||||
| 
 | ||||
|     protected $width; | ||||
|     protected $height; | ||||
|     protected $frameRate = 25; | ||||
|     protected $resizeMode = self::RESIZEMODE_FIT; | ||||
|     /** @var string */ | ||||
|     protected $videoCodec; | ||||
|     protected $GOPsize = 25; | ||||
| 
 | ||||
|     /** @var Integer */ | ||||
|     protected $kiloBitrate = 1000; | ||||
| 
 | ||||
|     /** @var Integer */ | ||||
|     protected $modulus = 16; | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the width setting. | ||||
|      * The return of this method should not depend on a media file size | ||||
|      * | ||||
|      * @return integer | ||||
|      */ | ||||
|     public function getWidth() | ||||
|     { | ||||
|         return $this->width; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the height setting | ||||
|      * The return of this method should not depend on a media file size | ||||
|      * | ||||
|      * @return integer | ||||
|      */ | ||||
|     public function getHeight() | ||||
|     { | ||||
|         return $this->height; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Set the dimensions | ||||
|      * | ||||
|      * @param  integer                   $width  The heigth | ||||
|      * @param  integer                   $height The width | ||||
|      * @throws \InvalidArgumentException | ||||
|      */ | ||||
|     public function setDimensions($width, $height) | ||||
|     { | ||||
|         if ($width < 1) { | ||||
|             throw new InvalidArgumentException('Wrong width value'); | ||||
|         } | ||||
|         if ($height < 1) { | ||||
|             throw new InvalidArgumentException('Wrong height value'); | ||||
|         } | ||||
| 
 | ||||
|         $this->width = $width; | ||||
|         $this->height = $height; | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc) | ||||
|      */ | ||||
|     public function getComputedDimensions($originalWidth, $originalHeight) | ||||
|     { | ||||
|         $originalRatio = $originalWidth / $originalHeight; | ||||
| 
 | ||||
|         switch ($this->getResizeMode()) { | ||||
|             case self::RESIZEMODE_SCALE_WIDTH: | ||||
|                 $height = $this->height; | ||||
|                 $width = round($originalRatio * $this->height); | ||||
|                 break; | ||||
|             case self::RESIZEMODE_SCALE_HEIGHT: | ||||
|                 $width = $this->width; | ||||
|                 $height = round($this->width / $originalRatio); | ||||
|                 break; | ||||
|             case self::RESIZEMODE_INSET: | ||||
|                 $targetRatio = $this->width / $this->height; | ||||
| 
 | ||||
|                 if ($targetRatio > $originalRatio) { | ||||
|                     $height = $this->height; | ||||
|                     $width = round($originalRatio * $this->height); | ||||
|                 } else { | ||||
|                     $width = $this->width; | ||||
|                     $height = round($this->width / $originalRatio); | ||||
|                 } | ||||
|                 break; | ||||
|             case self::RESIZEMODE_FIT: | ||||
|             default: | ||||
|                 if (null !== $this->width && null !== $this->height) { | ||||
|                     $width = $this->width; | ||||
|                     $height = $this->height; | ||||
|                 } else { | ||||
|                     $width = $originalWidth; | ||||
|                     $height = $originalHeight; | ||||
|                 } | ||||
|                 break; | ||||
|         } | ||||
| 
 | ||||
|         return new Dimension($width, $height); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Set the resize mode | ||||
|      * | ||||
|      * @param string $mode The mode, one of the self::RESIZEMODE_* constants | ||||
|      * | ||||
|      * @throws InvalidArgumentException | ||||
|      */ | ||||
|     public function setResizeMode($mode) | ||||
|     { | ||||
|         if ( ! in_array($mode, array(self::RESIZEMODE_FIT, self::RESIZEMODE_INSET, self::RESIZEMODE_SCALE_WIDTH, self::RESIZEMODE_SCALE_HEIGHT))) { | ||||
|             throw new InvalidArgumentException( | ||||
|                 'Resize mode `%s` is not valid , avalaible values are %s', | ||||
|                 $mode, | ||||
|                 implode(', ', array(self::RESIZEMODE_FIT, self::RESIZEMODE_INSET, self::RESIZEMODE_SCALE_WIDTH, self::RESIZEMODE_SCALE_HEIGHT)) | ||||
|             ); | ||||
|         } | ||||
| 
 | ||||
|         $this->resizeMode = $mode; | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the current resize mode name | ||||
|      * | ||||
|      * @return string | ||||
|      */ | ||||
|     public function getResizeMode() | ||||
|     { | ||||
|         return $this->resizeMode; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function getFrameRate() | ||||
|     public function getKiloBitrate() | ||||
|     { | ||||
|         return $this->frameRate; | ||||
|         return $this->kiloBitrate; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Set the framerate | ||||
|      * Sets the kiloBitrate value | ||||
|      * | ||||
|      * @param integer $frameRate | ||||
|      * | ||||
|      * @throws \InvalidArgumentException | ||||
|      * @param  integer                  $kiloBitrate | ||||
|      * @throws InvalidArgumentException | ||||
|      */ | ||||
|     public function setFrameRate($frameRate) | ||||
|     public function setKiloBitrate($kiloBitrate) | ||||
|     { | ||||
|         if ($frameRate < 1) { | ||||
|             throw new InvalidArgumentException('Wrong framerate value'); | ||||
|         if ($kiloBitrate < 1) { | ||||
|             throw new InvalidArgumentException('Wrong kiloBitrate value'); | ||||
|         } | ||||
| 
 | ||||
|         $this->frameRate = (int) $frameRate; | ||||
|         $this->kiloBitrate = (int) $kiloBitrate; | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
|  | @ -189,11 +66,11 @@ abstract class DefaultVideo extends DefaultAudio implements Interactive, Resampl | |||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Set the video codec, Should be in the available ones, otherwise an | ||||
|      * Sets the video codec, Should be in the available ones, otherwise an | ||||
|      * exception is thrown | ||||
|      * | ||||
|      * @param  string                    $videoCodec | ||||
|      * @throws \InvalidArgumentException | ||||
|      * @param  string                   $videoCodec | ||||
|      * @throws InvalidArgumentException | ||||
|      */ | ||||
|     public function setVideoCodec($videoCodec) | ||||
|     { | ||||
|  | @ -209,32 +86,6 @@ abstract class DefaultVideo extends DefaultAudio implements Interactive, Resampl | |||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function getGOPsize() | ||||
|     { | ||||
|         return $this->GOPsize; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Set the GOP size | ||||
|      * | ||||
|      * @param integer $GOPsize | ||||
|      * | ||||
|      * @throws \InvalidArgumentException | ||||
|      */ | ||||
|     public function setGOPsize($GOPsize) | ||||
|     { | ||||
|         if ($GOPsize < 1) { | ||||
|             throw new InvalidArgumentException('Wrong GOP size value'); | ||||
|         } | ||||
| 
 | ||||
|         $this->GOPsize = (int) $GOPsize; | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritDoc} | ||||
|      */ | ||||
|  | @ -244,24 +95,27 @@ abstract class DefaultVideo extends DefaultAudio implements Interactive, Resampl | |||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Used to determine what resolutions sizes are valid. | ||||
|      * | ||||
|      * @param int $value | ||||
|      */ | ||||
|     public function setModulus($value) | ||||
|     { | ||||
|         if(!in_array($value, array(2, 4, 8, 16))){ | ||||
|             throw new InvalidArgumentException('Wrong modulus division value. Valid values are 2, 4, 8 or 16'); | ||||
|         } | ||||
| 
 | ||||
|         $this->modulus = $value; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return int | ||||
|      * @return integer | ||||
|      */ | ||||
|     public function getModulus() | ||||
|     { | ||||
|         return $this->modulus; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function createProgressListener(MediaTypeInterface $media, FFProbe $ffprobe, $pass, $total) | ||||
|     { | ||||
|         $format = $this; | ||||
|         $listeners = array(new VideoProgressListener($ffprobe, $media->getPathfile(), $pass, $total)); | ||||
| 
 | ||||
|         foreach ($listeners as $listener) { | ||||
|             $listener->on('progress', function () use ($format, $media) { | ||||
|                $format->emit('progress', array_merge(array($media, $format), func_get_args())); | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         return $listeners; | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -1,30 +0,0 @@ | |||
| <?php | ||||
| 
 | ||||
| /* | ||||
|  * This file is part of PHP-FFmpeg. | ||||
|  * | ||||
|  * (c) Alchemy <info@alchemy.fr> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
| 
 | ||||
| namespace FFMpeg\Format\Video; | ||||
| 
 | ||||
| /** | ||||
|  * The interactive video interface. This provide a method to list available | ||||
|  * codecs. This is usefull to build interactive development and switch between | ||||
|  * different codecs | ||||
|  * | ||||
|  * @author Romain Neutron imprec@gmail.com | ||||
|  */ | ||||
| interface Interactive extends Transcodable | ||||
| { | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the list of available video codecs for this format | ||||
|      * | ||||
|      * @return array | ||||
|      */ | ||||
|     public function getAvailableVideoCodecs(); | ||||
| } | ||||
|  | @ -13,13 +13,15 @@ namespace FFMpeg\Format\Video; | |||
| 
 | ||||
| /** | ||||
|  * The Ogg video format | ||||
|  * | ||||
|  * @author Romain Neutron imprec@gmail.com | ||||
|  */ | ||||
| class Ogg extends DefaultVideo | ||||
| { | ||||
|     protected $audioCodec = 'libvorbis'; | ||||
|     protected $videoCodec = 'libtheora'; | ||||
|     public function __construct($audioCodec = 'libvorbis', $videoCodec = 'libtheora') | ||||
|     { | ||||
|         $this | ||||
|             ->setAudioCodec($audioCodec) | ||||
|             ->setVideoCodec($videoCodec); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritDoc} | ||||
|  |  | |||
|  | @ -1,50 +0,0 @@ | |||
| <?php | ||||
| 
 | ||||
| /* | ||||
|  * This file is part of PHP-FFmpeg. | ||||
|  * | ||||
|  * (c) Alchemy <info@alchemy.fr> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
| 
 | ||||
| namespace FFMpeg\Format\Video; | ||||
| 
 | ||||
| use FFMpeg\Format\VideoInterface; | ||||
| 
 | ||||
| /** | ||||
|  * The resamplable video interface | ||||
|  * | ||||
|  * This interface provides frame rate and GOP size settings for video encoding | ||||
|  * | ||||
|  * @author Romain Neutron imprec@gmail.com | ||||
|  */ | ||||
| interface Resamplable extends VideoInterface | ||||
| { | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the frame rate | ||||
|      * | ||||
|      * @return integer | ||||
|      */ | ||||
|     public function getFrameRate(); | ||||
| 
 | ||||
|     /** | ||||
|      * Returns true if the current format supports B-Frames | ||||
|      * | ||||
|      * @see https://wikipedia.org/wiki/Video_compression_picture_types | ||||
|      * | ||||
|      * @return Boolean | ||||
|      */ | ||||
|     public function supportBFrames(); | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the GOP size | ||||
|      * | ||||
|      * @see https://wikipedia.org/wiki/Group_of_pictures | ||||
|      * | ||||
|      * @return integer | ||||
|      */ | ||||
|     public function getGOPSize(); | ||||
| } | ||||
|  | @ -1,48 +0,0 @@ | |||
| <?php | ||||
| 
 | ||||
| /* | ||||
|  * This file is part of PHP-FFmpeg. | ||||
|  * | ||||
|  * (c) Alchemy <info@alchemy.fr> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
| 
 | ||||
| namespace FFMpeg\Format\Video; | ||||
| 
 | ||||
| use FFMpeg\Format\VideoInterface; | ||||
| use FFMpeg\Format\Dimension; | ||||
| 
 | ||||
| /** | ||||
|  * The resizable video interface | ||||
|  * | ||||
|  * This interface provides methods for video resizing. | ||||
|  * | ||||
|  * @author Romain Neutron imprec@gmail.com | ||||
|  */ | ||||
| interface Resizable extends VideoInterface | ||||
| { | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the computed dimensions for the resize, after operation. | ||||
|      * This method return the actual dimensions that FFmpeg will use. | ||||
|      * | ||||
|      * @param  integer   $originalWidth | ||||
|      * @param  integer   $originalHeight | ||||
|      * @return Dimension A dimension | ||||
|      */ | ||||
|     public function getComputedDimensions($originalWidth, $originalHeight); | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the modulus used by the Resizable video. | ||||
|      * | ||||
|      * This used to calculate the target dimensions while maintaining the best | ||||
|      * aspect ratio. | ||||
|      * | ||||
|      * @see http://www.undeadborn.net/tools/rescalculator.php | ||||
|      * | ||||
|      * @return integer | ||||
|      */ | ||||
|     public function getModulus(); | ||||
| } | ||||
|  | @ -1,28 +0,0 @@ | |||
| <?php | ||||
| 
 | ||||
| /* | ||||
|  * This file is part of PHP-FFmpeg. | ||||
|  * | ||||
|  * (c) Alchemy <info@alchemy.fr> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
| 
 | ||||
| namespace FFMpeg\Format\Video; | ||||
| 
 | ||||
| use FFMpeg\Format\VideoInterface; | ||||
| 
 | ||||
| /** | ||||
|  * @author Romain Neutron imprec@gmail.com | ||||
|  */ | ||||
| interface Transcodable extends VideoInterface | ||||
| { | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the video codec | ||||
|      * | ||||
|      * @return string | ||||
|      */ | ||||
|     public function getVideoCodec(); | ||||
| } | ||||
|  | @ -13,13 +13,15 @@ namespace FFMpeg\Format\Video; | |||
| 
 | ||||
| /** | ||||
|  * The WMV video format | ||||
|  * | ||||
|  * @author Romain Neutron imprec@gmail.com | ||||
|  */ | ||||
| class WMV extends DefaultVideo | ||||
| { | ||||
|     protected $audioCodec = 'wmav2'; | ||||
|     protected $videoCodec = 'wmv2'; | ||||
|     public function __construct($audioCodec = 'wmav2', $videoCodec = 'wmv2') | ||||
|     { | ||||
|         $this | ||||
|             ->setAudioCodec($audioCodec) | ||||
|             ->setVideoCodec($videoCodec); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritDoc} | ||||
|  |  | |||
|  | @ -13,13 +13,15 @@ namespace FFMpeg\Format\Video; | |||
| 
 | ||||
| /** | ||||
|  * The WMV video format | ||||
|  * | ||||
|  * @author Romain Neutron imprec@gmail.com | ||||
|  */ | ||||
| class WMV3 extends DefaultVideo | ||||
| { | ||||
|     protected $audioCodec = 'wmav3'; | ||||
|     protected $videoCodec = 'wmv3'; | ||||
|     public function __construct($audioCodec = 'wmav3', $videoCodec = 'wmv3') | ||||
|     { | ||||
|         $this | ||||
|             ->setAudioCodec($audioCodec) | ||||
|             ->setVideoCodec($videoCodec); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritDoc} | ||||
|  |  | |||
|  | @ -13,13 +13,15 @@ namespace FFMpeg\Format\Video; | |||
| 
 | ||||
| /** | ||||
|  * The WebM video format | ||||
|  * | ||||
|  * @author Romain Neutron imprec@gmail.com | ||||
|  */ | ||||
| class WebM extends DefaultVideo | ||||
| { | ||||
|     protected $audioCodec = 'libvorbis'; | ||||
|     protected $videoCodec = 'libvpx'; | ||||
|     public function __construct($audioCodec = 'libvorbis', $videoCodec = 'libvpx') | ||||
|     { | ||||
|         $this | ||||
|             ->setAudioCodec($audioCodec) | ||||
|             ->setVideoCodec($videoCodec); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritDoc} | ||||
|  |  | |||
|  | @ -13,13 +13,15 @@ namespace FFMpeg\Format\Video; | |||
| 
 | ||||
| /** | ||||
|  * The X264 video format | ||||
|  * | ||||
|  * @author Romain Neutron imprec@gmail.com | ||||
|  */ | ||||
| class X264 extends DefaultVideo | ||||
| { | ||||
|     protected $audioCodec = 'libmp3lame'; | ||||
|     protected $videoCodec = 'libx264'; | ||||
|     public function __construct($audioCodec = 'libfaac', $videoCodec = 'libx264') | ||||
|     { | ||||
|         $this | ||||
|             ->setAudioCodec($audioCodec) | ||||
|             ->setVideoCodec($videoCodec); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritDoc} | ||||
|  |  | |||
|  | @ -11,17 +11,54 @@ | |||
| 
 | ||||
| namespace FFMpeg\Format; | ||||
| 
 | ||||
| /** | ||||
|  * The base video interface | ||||
|  * | ||||
|  * @author Romain Neutron imprec@gmail.com | ||||
|  */ | ||||
| interface VideoInterface extends AudioInterface | ||||
| { | ||||
|     /** | ||||
|      * Get the kiloBitrate value | ||||
|      * | ||||
|      * @return integer | ||||
|      */ | ||||
|     public function getKiloBitrate(); | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the number of passes | ||||
|      * | ||||
|      * @return string | ||||
|      */ | ||||
|     public function getPasses(); | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the modulus used by the Resizable video. | ||||
|      * | ||||
|      * This used to calculate the target dimensions while maintaining the best | ||||
|      * aspect ratio. | ||||
|      * | ||||
|      * @see http://www.undeadborn.net/tools/rescalculator.php | ||||
|      * | ||||
|      * @return integer | ||||
|      */ | ||||
|     public function getModulus(); | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the video codec | ||||
|      * | ||||
|      * @return string | ||||
|      */ | ||||
|     public function getVideoCodec(); | ||||
| 
 | ||||
|     /** | ||||
|      * Returns true if the current format supports B-Frames | ||||
|      * | ||||
|      * @see https://wikipedia.org/wiki/Video_compression_picture_types | ||||
|      * | ||||
|      * @return Boolean | ||||
|      */ | ||||
|     public function supportBFrames(); | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the list of available video codecs for this format | ||||
|      * | ||||
|      * @return array | ||||
|      */ | ||||
|     public function getAvailableVideoCodecs(); | ||||
| } | ||||
|  |  | |||
|  | @ -1,42 +0,0 @@ | |||
| <?php | ||||
| 
 | ||||
| /* | ||||
|  * This file is part of PHP-FFmpeg. | ||||
|  * | ||||
|  * (c) Alchemy <info@alchemy.fr> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
| 
 | ||||
| namespace FFMpeg\Helper; | ||||
| 
 | ||||
| use FFMpeg\FFProbe; | ||||
| 
 | ||||
| /** | ||||
|  * @author Robert Gruendler <r.gruendler@gmail.com> | ||||
|  */ | ||||
| interface HelperInterface | ||||
| { | ||||
|     /** | ||||
|      * The callback from the ffmpeg process. | ||||
|      * | ||||
|      * @param string $channel (stdio|stderr) | ||||
|      * @param string $content the current line of the ffmpeg output | ||||
|      */ | ||||
|     function transcodeCallback($channel, $content); | ||||
| 
 | ||||
|     /** | ||||
|      * The helper has access to a prober instance if available. | ||||
|      * | ||||
|      * @param FFProbe $prober | ||||
|      */ | ||||
|     function setProber(FFProbe $prober); | ||||
| 
 | ||||
|     /** | ||||
|      * Called when the input file is opened. | ||||
|      * | ||||
|      * @param string $pathfile | ||||
|      */ | ||||
|     function open($pathfile); | ||||
| } | ||||
|  | @ -1,218 +0,0 @@ | |||
| <?php | ||||
| 
 | ||||
| /* | ||||
|  * This file is part of PHP-FFmpeg. | ||||
|  * | ||||
|  * (c) Alchemy <info@alchemy.fr> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
| 
 | ||||
| namespace FFMpeg\Helper; | ||||
| 
 | ||||
| use FFMpeg\FFProbe; | ||||
| 
 | ||||
| /** | ||||
|  * @author Robert Gruendler <r.gruendler@gmail.com> | ||||
|  */ | ||||
| abstract class ProgressHelper implements HelperInterface | ||||
| { | ||||
|     /** | ||||
|      * @var number | ||||
|      */ | ||||
|     protected $duration = null; | ||||
| 
 | ||||
|     /** | ||||
|      * transcoding rate in kb/s | ||||
|      * | ||||
|      * @var number | ||||
|      */ | ||||
|     protected $rate; | ||||
| 
 | ||||
|     /** | ||||
|      * @var array | ||||
|      */ | ||||
|     protected $format; | ||||
| 
 | ||||
|     /** | ||||
|      * @var number | ||||
|      */ | ||||
|     protected $totalSize; | ||||
| 
 | ||||
|     /** | ||||
|      * @var number | ||||
|      */ | ||||
|     protected $currentSize; | ||||
| 
 | ||||
|     /** | ||||
|      * @var number | ||||
|      */ | ||||
|     protected $currentTime; | ||||
| 
 | ||||
|     /** | ||||
|      * @var double | ||||
|      */ | ||||
|     protected $lastOutput = null; | ||||
| 
 | ||||
|     /** | ||||
|      * Percentage of transcoding progress (0 - 100) | ||||
|      * | ||||
|      * @var number | ||||
|      */ | ||||
|     protected $percent = 0; | ||||
| 
 | ||||
|     /** | ||||
|      * Time remaining (seconds) | ||||
|      * | ||||
|      * @var number | ||||
|      */ | ||||
|     protected $remaining = null; | ||||
| 
 | ||||
|     /** | ||||
|      * @var FFProbe | ||||
|      */ | ||||
|     protected $prober; | ||||
| 
 | ||||
|     /** | ||||
|      * @var Closure|string|array | ||||
|      */ | ||||
|     protected $callback; | ||||
| 
 | ||||
|     /** | ||||
|      * @param mixed $callback | ||||
|      */ | ||||
|     public function __construct($callback) | ||||
|     { | ||||
|         $this->callback = $callback; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Used to ease testing. | ||||
|      * | ||||
|      * @param number $duration | ||||
|      */ | ||||
|     public function setDuration($duration) | ||||
|     { | ||||
|         $this->duration = $duration; | ||||
|     } | ||||
| 
 | ||||
|     /* | ||||
|      *  {@inheritdoc} | ||||
|      */ | ||||
|     public function transcodeCallback($channel, $content) | ||||
|     { | ||||
|         $progress = $this->parseProgress($content); | ||||
| 
 | ||||
|         if (is_array($progress)) { | ||||
|             call_user_func_array($this->callback, $progress); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /* | ||||
|      *  {@inheritdoc} | ||||
|      */ | ||||
|     public function setProber(FFProbe $prober) | ||||
|     { | ||||
|         $this->prober = $prober; | ||||
|     } | ||||
| 
 | ||||
|     /* | ||||
|      *  {@inheritdoc} | ||||
|      */ | ||||
|     public function open($pathfile) | ||||
|     { | ||||
|         if ($this->prober === null) { | ||||
|             throw new \RuntimeException('Unable to report audio progress without a prober'); | ||||
|         } | ||||
| 
 | ||||
|         $format = json_decode($this->prober->probeFormat($pathfile), true); | ||||
| 
 | ||||
|         if ($format === null || count($format) === 0 || isset($format['size']) === false) { | ||||
|             throw new \RuntimeException('Unable to probe format for ' . $pathfile); | ||||
|         } | ||||
| 
 | ||||
|         $this->format = $format; | ||||
|         $this->totalSize = $format['size'] / 1024; | ||||
|         $this->duration = $format['duration']; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param string $progress A ffmpeg stderr progress output | ||||
|      * @return array the progressinfo array or null if there's no progress available yet. | ||||
|      */ | ||||
|     public function parseProgress($progress) | ||||
|     { | ||||
|         $matches = array(); | ||||
| 
 | ||||
|         if (preg_match($this->getPattern(), $progress, $matches) !== 1) { | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         $currentDuration = $this->convertDuration($matches[2]); | ||||
|         $currentTime = microtime(true); | ||||
|         $currentSize = trim(str_replace('kb', '', strtolower(($matches[1])))); | ||||
|         $percent = max(0, min(1, $currentDuration / $this->duration)); | ||||
| 
 | ||||
|         if ($this->lastOutput !== null) { | ||||
|             $delta = $currentTime - $this->lastOutput; | ||||
|             $deltaSize = $currentSize - $this->currentSize; | ||||
|             $rate = $deltaSize * $delta; | ||||
|             if ($rate > 0) { | ||||
|                 $totalDuration = $this->totalSize / $rate; | ||||
|                 $this->remaining = floor($totalDuration - ($totalDuration * $percent)); | ||||
|                 $this->rate = floor($rate); | ||||
|             } else { | ||||
|                 $this->remaining = 0; | ||||
|                 $this->rate = 0; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         $this->percent = floor($percent * 100); | ||||
|         $this->lastOutput = $currentTime; | ||||
|         $this->currentSize = (int) $currentSize; | ||||
|         $this->currentTime = $currentDuration; | ||||
| 
 | ||||
|         return $this->getProgressInfo(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * | ||||
|      * @param string $rawDuration in the format 00:00:00.00 | ||||
|      * @return number | ||||
|      */ | ||||
|     protected function convertDuration($rawDuration) | ||||
|     { | ||||
|         $ar = array_reverse(explode(":", $rawDuration)); | ||||
|         $duration = floatval($ar[0]); | ||||
|         if (!empty($ar[1])) { | ||||
|             $duration += intval($ar[1]) * 60; | ||||
|         } | ||||
|         if (!empty($ar[2])) { | ||||
|             $duration += intval($ar[2]) * 60 * 60; | ||||
|         } | ||||
| 
 | ||||
|         return $duration; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return array | ||||
|      */ | ||||
|     public function getProgressInfo() | ||||
|     { | ||||
|         if ($this->remaining === null) { | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         return array( | ||||
|             'percent' => $this->percent, | ||||
|             'remaining' => $this->remaining, | ||||
|             'rate' => $this->rate | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the regex pattern to match a ffmpeg stderr status line | ||||
|      */ | ||||
|     abstract function getPattern(); | ||||
| } | ||||
							
								
								
									
										126
									
								
								src/FFMpeg/Media/AbstractMediaType.php
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								src/FFMpeg/Media/AbstractMediaType.php
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,126 @@ | |||
| <?php | ||||
| 
 | ||||
| /* | ||||
|  * This file is part of PHP-FFmpeg. | ||||
|  * | ||||
|  * (c) Alchemy <dev.team@alchemy.fr> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
| 
 | ||||
| namespace FFMpeg\Media; | ||||
| 
 | ||||
| use FFMpeg\Driver\FFMpegDriver; | ||||
| use FFMpeg\Exception\InvalidArgumentException; | ||||
| use FFMpeg\FFProbe; | ||||
| use FFMpeg\Filters\FiltersCollection; | ||||
| use FFMpeg\Media\MediaTypeInterface; | ||||
| 
 | ||||
| abstract class AbstractMediaType implements MediaTypeInterface | ||||
| { | ||||
|     /** @var string */ | ||||
|     protected $pathfile; | ||||
|     /** @var FFMpegDriver */ | ||||
|     protected $driver; | ||||
|     /** @var FFProbe */ | ||||
|     protected $ffprobe; | ||||
|     /** @var FiltersCollection */ | ||||
|     protected $filters; | ||||
| 
 | ||||
|     public function __construct($pathfile, FFMpegDriver $driver, FFProbe $ffprobe) | ||||
|     { | ||||
|         $this->ensureFileIsPresent($pathfile); | ||||
| 
 | ||||
|         $this->pathfile = $pathfile; | ||||
|         $this->driver = $driver; | ||||
|         $this->ffprobe = $ffprobe; | ||||
|         $this->filters = new FiltersCollection(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return FFMpegDriver | ||||
|      */ | ||||
|     public function getFFMpegDriver() | ||||
|     { | ||||
|         return $this->driver; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param FFMpegDriver $driver | ||||
|      * | ||||
|      * @return MediaTypeInterface | ||||
|      */ | ||||
|     public function setFFMpegDriver(FFMpegDriver $driver) | ||||
|     { | ||||
|         $this->driver = $driver; | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return FFProbe | ||||
|      */ | ||||
|     public function getFFProbe() | ||||
|     { | ||||
|         return $this->ffprobe; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param FFProbe $ffprobe | ||||
|      * | ||||
|      * @return MediaTypeInterface | ||||
|      */ | ||||
|     public function setFFProbe(FFProbe $ffprobe) | ||||
|     { | ||||
|         $this->ffprobe = $ffprobe; | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return string | ||||
|      */ | ||||
|     public function getPathfile() | ||||
|     { | ||||
|         return $this->pathfile; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param FiltersCollection $filters | ||||
|      * | ||||
|      * @return MediaTypeInterface | ||||
|      */ | ||||
|     public function setFiltersCollection(FiltersCollection $filters) | ||||
|     { | ||||
|         $this->filters = $filters; | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return MediaTypeInterface | ||||
|      */ | ||||
|     public function getFiltersCollection() | ||||
|     { | ||||
|         return $this->filters; | ||||
|     } | ||||
| 
 | ||||
|     protected function ensureFileIsPresent($filename) | ||||
|     { | ||||
|         if (!is_file($filename) || !is_readable($filename)) { | ||||
|             throw new InvalidArgumentException(sprintf( | ||||
|                 '%s is not present or not readable', $filename | ||||
|             )); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     protected function cleanupTemporaryFile($filename) | ||||
|     { | ||||
|         if (file_exists($filename) && is_writable($filename)) { | ||||
|             unlink($filename); | ||||
|         } | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										34
									
								
								src/FFMpeg/Media/AbstractStreamableMedia.php
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/FFMpeg/Media/AbstractStreamableMedia.php
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,34 @@ | |||
| <?php | ||||
| 
 | ||||
| /* | ||||
|  * This file is part of PHP-FFmpeg. | ||||
|  * | ||||
|  * (c) Alchemy <info@alchemy.fr> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
| 
 | ||||
| namespace FFMpeg\Media; | ||||
| 
 | ||||
| use FFMpeg\FFProbe\DataMapping\Stream; | ||||
| use FFMpeg\FFProbe\DataMapping\StreamCollection; | ||||
| 
 | ||||
| abstract class AbstractStreamableMedia extends AbstractMediaType | ||||
| { | ||||
|     /** | ||||
|      * @return StreamCollection | ||||
|      */ | ||||
|     public function getStreams() | ||||
|     { | ||||
|         return $this->ffprobe->streams($this->pathfile); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return Stream | ||||
|      */ | ||||
|     public function getFormat() | ||||
|     { | ||||
|         return $this->ffprobe->format($this->pathfile); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										92
									
								
								src/FFMpeg/Media/Audio.php
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								src/FFMpeg/Media/Audio.php
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,92 @@ | |||
| <?php | ||||
| 
 | ||||
| /* | ||||
|  * This file is part of PHP-FFmpeg. | ||||
|  * | ||||
|  * (c) Alchemy <info@alchemy.fr> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
| 
 | ||||
| namespace FFMpeg\Media; | ||||
| 
 | ||||
| use Alchemy\BinaryDriver\Exception\ExecutionFailureException; | ||||
| use FFMpeg\Filters\Audio\AudioFilters; | ||||
| use FFMpeg\Format\FormatInterface; | ||||
| use FFMpeg\Exception\RuntimeException; | ||||
| use FFMpeg\Filters\Audio\AudioFilterInterface; | ||||
| use FFMpeg\Format\ProgressableInterface; | ||||
| 
 | ||||
| class Audio extends AbstractStreamableMedia | ||||
| { | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      * | ||||
|      * @return AudioFilters | ||||
|      */ | ||||
|     public function filters() | ||||
|     { | ||||
|         return new AudioFilters($this); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      * | ||||
|      * @return Audio | ||||
|      */ | ||||
|     public function addFilter(AudioFilterInterface $filter) | ||||
|     { | ||||
|         $this->filters->add($filter); | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Export the audio in the desired format, applies registered filters | ||||
|      * | ||||
|      * @param FormatInterface $format | ||||
|      * @param string          $outputPathfile | ||||
|      * | ||||
|      * @return Audio | ||||
|      * | ||||
|      * @throws RuntimeException | ||||
|      */ | ||||
|     public function save(FormatInterface $format, $outputPathfile) | ||||
|     { | ||||
|         $listeners = null; | ||||
| 
 | ||||
|         if ($format instanceof ProgressableInterface) { | ||||
|             $listeners = $format->createProgressListener($this, $this->ffprobe, 1, 1); | ||||
|         } | ||||
| 
 | ||||
|         $commands = array_merge(array('-y', '-i', $this->pathfile), $format->getExtraParams()); | ||||
| 
 | ||||
|         foreach ($this->filters as $filter) { | ||||
|             $commands = array_merge($commands, $filter->apply($this, $format)); | ||||
|         } | ||||
| 
 | ||||
|         if ($this->driver->getConfiguration()->has('ffmpeg.threads')) { | ||||
|             $commands[] = '-threads'; | ||||
|             $commands[] = $this->driver->getConfiguration()->get('ffmpeg.threads'); | ||||
|         } | ||||
| 
 | ||||
|         if (null !== $format->getAudioCodec()) { | ||||
|             $commands[] = '-acodec'; | ||||
|             $commands[] = $format->getAudioCodec(); | ||||
|         } | ||||
| 
 | ||||
|         $commands[] = '-b:a'; | ||||
|         $commands[] = $format->getAudioKiloBitrate() . 'k'; | ||||
|         $commands[] = $outputPathfile; | ||||
| 
 | ||||
|         try { | ||||
|             $this->driver->command($commands, false, $listeners); | ||||
|         } catch (ExecutionFailureException $e) { | ||||
|             $this->cleanupTemporaryFile($outputPathfile); | ||||
|             throw new RuntimeException('Encoding failed', $e->getCode(), $e); | ||||
|         } | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										104
									
								
								src/FFMpeg/Media/Frame.php
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								src/FFMpeg/Media/Frame.php
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,104 @@ | |||
| <?php | ||||
| 
 | ||||
| /* | ||||
|  * This file is part of PHP-FFmpeg. | ||||
|  * | ||||
|  * (c) Alchemy <info@alchemy.fr> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
| 
 | ||||
| namespace FFMpeg\Media; | ||||
| 
 | ||||
| use Alchemy\BinaryDriver\Exception\ExecutionFailureException; | ||||
| use FFMpeg\Filters\Frame\FrameFilterInterface; | ||||
| use FFMpeg\Filters\Frame\FrameFilters; | ||||
| use FFMpeg\Driver\FFMpegDriver; | ||||
| use FFMpeg\FFProbe; | ||||
| use FFMpeg\Exception\RuntimeException; | ||||
| use FFMpeg\Coordinate\TimeCode; | ||||
| 
 | ||||
| class Frame extends AbstractMediaType | ||||
| { | ||||
|     /** @var TimeCode */ | ||||
|     private $timecode; | ||||
| 
 | ||||
|     public function __construct($pathfile, FFMpegDriver $driver, FFProbe $ffprobe, TimeCode $timecode) | ||||
|     { | ||||
|         parent::__construct($pathfile, $driver, $ffprobe); | ||||
|         $this->timecode = $timecode; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      * | ||||
|      * @return FrameFilters | ||||
|      */ | ||||
|     public function filters() | ||||
|     { | ||||
|         return new FrameFilters($this); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      * | ||||
|      * @return Frame | ||||
|      */ | ||||
|     public function addFilter(FrameFilterInterface $filter) | ||||
|     { | ||||
|         $this->filters->add($filter); | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return TimeCode | ||||
|      */ | ||||
|     public function getTimeCode() | ||||
|     { | ||||
|         return $this->timecode; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Saves the frame in the given filename. | ||||
|      * | ||||
|      * Uses the `unaccurate method by default.` | ||||
|      * | ||||
|      * @param string $pathfile | ||||
|      * @param Boolean $accurate | ||||
|      * | ||||
|      * @return Frame | ||||
|      * | ||||
|      * @throws RuntimeException | ||||
|      */ | ||||
|     public function saveAs($pathfile, $accurate = false) | ||||
|     { | ||||
|         /** | ||||
|          * @see http://ffmpeg.org/ffmpeg.html#Main-options
 | ||||
|          */ | ||||
|         if (!$accurate) { | ||||
|             $commands = array( | ||||
|                 '-ss', (string) $this->timecode, | ||||
|                 '-i', $this->pathfile, | ||||
|                 '-vframes', '1', | ||||
|                 '-f', 'image2', $pathfile | ||||
|             ); | ||||
|         } else { | ||||
|             $commands = array( | ||||
|                 '-i', $this->pathfile, | ||||
|                 '-vframes', '1', '-ss', (string) $this->timecode, | ||||
|                 '-f', 'image2', $pathfile | ||||
|             ); | ||||
|         } | ||||
| 
 | ||||
|         try { | ||||
|             $this->driver->command($commands); | ||||
|         } catch (ExecutionFailureException $e) { | ||||
|             $this->cleanupTemporaryFile($pathfile); | ||||
|             throw new RuntimeException('Unable to save frame', $e->getCode(), $e); | ||||
|         } | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| } | ||||
|  | @ -9,20 +9,17 @@ | |||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
| 
 | ||||
| namespace FFMpeg\Format\Audio; | ||||
| namespace FFMpeg\Media; | ||||
| 
 | ||||
| use FFMpeg\Format\AudioInterface; | ||||
| 
 | ||||
| /** | ||||
|  * @author Romain Neutron imprec@gmail.com | ||||
|  */ | ||||
| interface Transcodable extends AudioInterface | ||||
| interface MediaTypeInterface | ||||
| { | ||||
|     /** | ||||
|      * Returns the available filters | ||||
|      */ | ||||
|     public function filters(); | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the audio codec | ||||
|      * | ||||
|      * @return string | ||||
|      */ | ||||
|     public function getAudioCodec(); | ||||
|     public function getPathfile(); | ||||
| } | ||||
							
								
								
									
										166
									
								
								src/FFMpeg/Media/Video.php
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										166
									
								
								src/FFMpeg/Media/Video.php
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,166 @@ | |||
| <?php | ||||
| 
 | ||||
| /* | ||||
|  * This file is part of PHP-FFmpeg. | ||||
|  * | ||||
|  * (c) Alchemy <info@alchemy.fr> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
| 
 | ||||
| namespace FFMpeg\Media; | ||||
| 
 | ||||
| use Alchemy\BinaryDriver\Exception\ExecutionFailureException; | ||||
| use FFMpeg\Coordinate\TimeCode; | ||||
| use FFMpeg\Exception\RuntimeException; | ||||
| use FFMpeg\Filters\Video\VideoFilters; | ||||
| use FFMpeg\Filters\Video\VideoFilterInterface; | ||||
| use FFMpeg\Format\VideoInterface; | ||||
| use FFMpeg\Format\ProgressableInterface; | ||||
| use FFMpeg\Media\Frame; | ||||
| 
 | ||||
| class Video extends AbstractStreamableMedia | ||||
| { | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      * | ||||
|      * @return VideoFilters | ||||
|      */ | ||||
|     public function filters() | ||||
|     { | ||||
|         return new VideoFilters($this); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      * | ||||
|      * @return Video | ||||
|      */ | ||||
|     public function addFilter(VideoFilterInterface $filter) | ||||
|     { | ||||
|         $this->filters->add($filter); | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Export the video in the desired format, applies registered filters | ||||
|      * | ||||
|      * @param FormatInterface $format | ||||
|      * @param string          $outputPathfile | ||||
|      * | ||||
|      * @return Video | ||||
|      * | ||||
|      * @throws RuntimeException | ||||
|      */ | ||||
|     public function save(VideoInterface $format, $outputPathfile) | ||||
|     { | ||||
|         $commands = array_merge(array('-y', '-i', $this->pathfile), $format->getExtraParams()); | ||||
| 
 | ||||
|         foreach ($this->filters as $filter) { | ||||
|             $commands = array_merge($commands, $filter->apply($this, $format)); | ||||
|         } | ||||
| 
 | ||||
|         if ($this->driver->getConfiguration()->has('ffmpeg.threads')) { | ||||
|             $commands[] = '-threads'; | ||||
|             $commands[] = $this->driver->getConfiguration()->get('ffmpeg.threads'); | ||||
|         } | ||||
| 
 | ||||
|         if (null !== $format->getVideoCodec()) { | ||||
|             $commands[] = '-vcodec'; | ||||
|             $commands[] = $format->getVideoCodec(); | ||||
|         } | ||||
|         if (null !== $format->getAudioCodec()) { | ||||
|             $commands[] = '-acodec'; | ||||
|             $commands[] = $format->getAudioCodec(); | ||||
|         } | ||||
| 
 | ||||
|         $commands[] = '-b:v'; | ||||
|         $commands[] = $format->getKiloBitrate() . 'k'; | ||||
|         $commands[] = '-refs'; | ||||
|         $commands[] = '6'; | ||||
|         $commands[] = '-coder'; | ||||
|         $commands[] = '1'; | ||||
|         $commands[] = '-sc_threshold'; | ||||
|         $commands[] = '40'; | ||||
|         $commands[] = '-flags'; | ||||
|         $commands[] = '+loop'; | ||||
|         $commands[] = '-me_range'; | ||||
|         $commands[] = '16'; | ||||
|         $commands[] = '-subq'; | ||||
|         $commands[] = '7'; | ||||
|         $commands[] = '-i_qfactor'; | ||||
|         $commands[] = '0.71'; | ||||
|         $commands[] = '-qcomp'; | ||||
|         $commands[] = '0.6'; | ||||
|         $commands[] = '-qdiff'; | ||||
|         $commands[] = '4'; | ||||
|         $commands[] = '-trellis'; | ||||
|         $commands[] = '1'; | ||||
|         $commands[] = '-b:a'; | ||||
|         $commands[] = $format->getAudioKiloBitrate() . 'k'; | ||||
| 
 | ||||
|         $passPrefix = uniqid('pass-'); | ||||
| 
 | ||||
|         $pass1 = $commands; | ||||
|         $pass2 = $commands; | ||||
| 
 | ||||
|         $pass1[] = '-pass'; | ||||
|         $pass1[] = '1'; | ||||
|         $pass1[] = '-passlogfile'; | ||||
|         $pass1[] = $passPrefix; | ||||
|         $pass1[] = '-an'; | ||||
|         $pass1[] = $outputPathfile; | ||||
| 
 | ||||
|         $pass2[] = '-pass'; | ||||
|         $pass2[] = '2'; | ||||
|         $pass2[] = '-passlogfile'; | ||||
|         $pass2[] = $passPrefix; | ||||
|         $pass2[] = '-ac'; | ||||
|         $pass2[] = '2'; | ||||
|         $pass2[] = '-ar'; | ||||
|         $pass2[] = '44100'; | ||||
|         $pass2[] = $outputPathfile; | ||||
| 
 | ||||
|         $failure = null; | ||||
| 
 | ||||
|         foreach (array($pass1, $pass2) as $pass => $passCommands) { | ||||
|             try { | ||||
|                 /** add listeners here */ | ||||
|                 $listeners = null; | ||||
| 
 | ||||
|                 if ($format instanceof ProgressableInterface) { | ||||
|                     $listeners = $format->createProgressListener($this, $this->ffprobe, $pass + 1, 2); | ||||
|                 } | ||||
| 
 | ||||
|                 $this->driver->command($passCommands, false, $listeners); | ||||
|             } catch (ExecutionFailureException $e) { | ||||
|                 $failure = $e; | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         $this | ||||
|             ->cleanupTemporaryFile(getcwd() . '/' . $passPrefix . '-0.log') | ||||
|             ->cleanupTemporaryFile(getcwd() . '/' . $passPrefix . '-0.log') | ||||
|             ->cleanupTemporaryFile(getcwd() . '/' . $passPrefix . '-0.log.mbtree'); | ||||
| 
 | ||||
|         if (null !== $failure) { | ||||
|             throw new RuntimeException('Encoding failed', $failure->getCode(), $failure); | ||||
|         } | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the frame at timecode | ||||
|      * | ||||
|      * @param  Timecode $at | ||||
|      * @return Frame | ||||
|      */ | ||||
|     public function frame(Timecode $at) | ||||
|     { | ||||
|         return new Frame($this->pathfile, $this->driver, $this->ffprobe, $at); | ||||
|     } | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue