added support for retrieving progress information via helpers
This commit is contained in:
		
					parent
					
						
							
								c40348a007
							
						
					
				
			
			
				commit
				
					
						36b0036285
					
				
			
		
					 10 changed files with 463 additions and 7 deletions
				
			
		|  | @ -16,6 +16,7 @@ use FFMpeg\Exception\LogicException; | |||
| use FFMpeg\Exception\RuntimeException; | ||||
| use FFMpeg\Format\Audio; | ||||
| use FFMpeg\Format\Video; | ||||
| use FFMpeg\Helper\HelperInterface; | ||||
| use Symfony\Component\Process\Process; | ||||
| use Symfony\Component\Process\ProcessBuilder; | ||||
| 
 | ||||
|  | @ -35,6 +36,11 @@ class FFMpeg extends Binary | |||
|     protected $prober; | ||||
|     protected $threads = 1; | ||||
| 
 | ||||
|     /** | ||||
|      * @var HelperInterface[] | ||||
|      */ | ||||
|     protected $helpers = array(); | ||||
| 
 | ||||
|     /** | ||||
|      * Destructor | ||||
|      */ | ||||
|  | @ -44,6 +50,24 @@ class FFMpeg extends Binary | |||
|         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); | ||||
|         } | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function setThreads($threads) | ||||
|     { | ||||
|         if ($threads > 64 || $threads < 1) { | ||||
|  | @ -76,9 +100,12 @@ class FFMpeg extends Binary | |||
|         } | ||||
| 
 | ||||
|         $this->logger->addInfo(sprintf('FFmpeg opens %s', $pathfile)); | ||||
| 
 | ||||
|         $this->pathfile = $pathfile; | ||||
| 
 | ||||
|         foreach ($this->helpers as $helper) { | ||||
|             $helper->open($pathfile); | ||||
|         } | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|  | @ -135,7 +162,7 @@ class FFMpeg extends Binary | |||
|         $this->logger->addInfo(sprintf('FFmpeg executes command %s', $process->getCommandline())); | ||||
| 
 | ||||
|         try { | ||||
|             $process->run(); | ||||
|             $process->run(array($this, 'transcodeCallback')); | ||||
|         } catch (\RuntimeException $e) { | ||||
| 
 | ||||
|         } | ||||
|  | @ -218,7 +245,7 @@ class FFMpeg extends Binary | |||
|         $this->logger->addInfo(sprintf('FFmpeg executes command %s', $process->getCommandLine())); | ||||
| 
 | ||||
|         try { | ||||
|             $process->run(); | ||||
|             $process->run(array($this, 'transcodeCallback')); | ||||
|         } catch (\RuntimeException $e) { | ||||
| 
 | ||||
|         } | ||||
|  | @ -344,7 +371,7 @@ class FFMpeg extends Binary | |||
|             $this->logger->addInfo(sprintf('FFmpeg executes command %s', $process->getCommandline())); | ||||
| 
 | ||||
|             try { | ||||
|                 $process->run(); | ||||
|                 $process->run(array($this, 'transcodeCallback')); | ||||
|             } catch (\RuntimeException $e) { | ||||
|                 break; | ||||
|             } | ||||
|  | @ -365,6 +392,19 @@ class FFMpeg extends Binary | |||
|         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 | ||||
|      * | ||||
|  |  | |||
|  | @ -24,6 +24,8 @@ use Symfony\Component\Process\ProcessBuilder; | |||
| class FFProbe extends Binary | ||||
| { | ||||
| 
 | ||||
|     protected $cachedFormats = array(); | ||||
| 
 | ||||
|     /** | ||||
|      * Probe the format of a given file | ||||
|      * | ||||
|  | @ -39,6 +41,10 @@ class FFProbe extends Binary | |||
|             throw new InvalidArgumentException($pathfile); | ||||
|         } | ||||
| 
 | ||||
|         if (isset($this->cachedFormats[$pathfile])) { | ||||
|             return $this->cachedFormats[$pathfile]; | ||||
|         } | ||||
| 
 | ||||
|         $builder = ProcessBuilder::create(array( | ||||
|             $this->binary, $pathfile, '-show_format' | ||||
|         )); | ||||
|  | @ -69,7 +75,7 @@ class FFProbe extends Binary | |||
|             $ret[$key] = $value; | ||||
|         } | ||||
| 
 | ||||
|         return json_encode($ret); | ||||
|         return $this->cachedFormats[$pathfile] = json_encode($ret); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  |  | |||
							
								
								
									
										29
									
								
								src/FFMpeg/Helper/AudioProgressHelper.php
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/FFMpeg/Helper/AudioProgressHelper.php
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,29 @@ | |||
| <?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; | ||||
| 
 | ||||
| /** | ||||
|  * Parses ffmpeg stderr progress information. An example: | ||||
|  * | ||||
|  * <pre> | ||||
|  *       size=    3552kB time=00:03:47.29 bitrate= 128.0kbits/s | ||||
|  * </pre> | ||||
|  * | ||||
|  * @author Robert Gruendler <r.gruendler@gmail.com> | ||||
|  */ | ||||
| class AudioProgressHelper extends ProgressHelper | ||||
| { | ||||
|     public function getPattern() | ||||
|     { | ||||
|         return '/size=(.*?) time=(.*?) /'; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										42
									
								
								src/FFMpeg/Helper/HelperInterface.php
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/FFMpeg/Helper/HelperInterface.php
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,42 @@ | |||
| <?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); | ||||
| } | ||||
							
								
								
									
										223
									
								
								src/FFMpeg/Helper/ProgressHelper.php
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										223
									
								
								src/FFMpeg/Helper/ProgressHelper.php
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,223 @@ | |||
| <?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 = $this->microtimeFloat(); | ||||
|         $currentSize = trim(str_replace('kb', '', strtolower(($matches[1])))); | ||||
|         $percent = $currentDuration/ $this->duration; | ||||
| 
 | ||||
|         if ($this->lastOutput !== null) { | ||||
|             $delta = $currentTime - $this->lastOutput; | ||||
|             $deltaSize = $currentSize - $this->currentSize; | ||||
|             $rate = $deltaSize * $delta; | ||||
|             $totalDuration = $this->totalSize / $rate; | ||||
|             $this->remaining = floor($totalDuration - ($totalDuration * $percent)); | ||||
|             $this->rate = floor($rate); | ||||
|         } | ||||
| 
 | ||||
|         $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( | ||||
|             /* | ||||
|             'currentSize' => $this->currentSize, | ||||
|             'currentTime' => $this->currentTime, | ||||
|             */ | ||||
|             'percent' => $this->percent, | ||||
|             'remaining' => $this->remaining, | ||||
|             'rate' => $this->rate | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return number | ||||
|      */ | ||||
|     protected function microtimeFloat() | ||||
|     { | ||||
|         list($usec, $sec) = explode(" ", microtime()); | ||||
|         return ((float)$usec + (float)$sec); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the regex pattern to match a ffmpeg stderr status line | ||||
|      */ | ||||
|     abstract function getPattern(); | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										29
									
								
								src/FFMpeg/Helper/VideoProgressHelper.php
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/FFMpeg/Helper/VideoProgressHelper.php
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,29 @@ | |||
| <?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; | ||||
| 
 | ||||
| /** | ||||
|  * Parses ffmpeg stderr progress information for video files. An example: | ||||
|  * | ||||
|  * <pre> | ||||
|  *       frame=  171 fps=0.0 q=10.0 size=      18kB time=00:00:05.72 bitrate=  26.4kbits/s dup=8 drop=0 | ||||
|  * </pre> | ||||
|  * | ||||
|  * @author Robert Gruendler <r.gruendler@gmail.com> | ||||
|  */ | ||||
| class VideoProgressHelper extends ProgressHelper | ||||
| { | ||||
|     public function getPattern() | ||||
|     { | ||||
|         return '/size=(.*?) time=(.*?) /'; | ||||
|     } | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue