parent
					
						
							
								233742db0a
							
						
					
				
			
			
				commit
				
					
						0b871e59e7
					
				
			
		
					 5 changed files with 454 additions and 267 deletions
				
			
		
							
								
								
									
										22
									
								
								README.md
									
										
									
									
									
								
							
							
						
						
									
										22
									
								
								README.md
									
										
									
									
									
								
							|  | @ -156,6 +156,28 @@ $video | ||||||
|     ->save(new FFMpeg\Format\Video\X264(), '/path/to/new/file'); |     ->save(new FFMpeg\Format\Video\X264(), '/path/to/new/file'); | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
|  | ##### Clip | ||||||
|  | 
 | ||||||
|  | Cuts the video at a desired point. Use input seeking method. It is faster option than use filter clip. | ||||||
|  | 
 | ||||||
|  | ```php | ||||||
|  | $clip = $video->clip(FFMpeg\Coordinate\TimeCode::fromSeconds(30), FFMpeg\Coordinate\TimeCode::fromSeconds(15)); | ||||||
|  | $clip->save(new FFMpeg\Format\Video\X264(), 'video.avi'); | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | The clip filter takes two parameters: | ||||||
|  | 
 | ||||||
|  | - `$start`, an instance of `FFMpeg\Coordinate\TimeCode`, specifies the start point of the clip | ||||||
|  | - `$duration`, optional, an instance of `FFMpeg\Coordinate\TimeCode`, specifies the duration of the clip | ||||||
|  | 
 | ||||||
|  | On clip you can apply same filters as on video. For example resizing filter. | ||||||
|  | 
 | ||||||
|  | ```php | ||||||
|  | $clip = $video->clip(FFMpeg\Coordinate\TimeCode::fromSeconds(30), FFMpeg\Coordinate\TimeCode::fromSeconds(15)); | ||||||
|  | $clip->filters()->resize(new FFMpeg\Coordinate\Dimension(320, 240), FFMpeg\Filters\Video\ResizeFilter::RESIZEMODE_INSET, true); | ||||||
|  | $clip->save(new FFMpeg\Format\Video\X264(), 'video.avi'); | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
| ##### Generate a waveform | ##### Generate a waveform | ||||||
| 
 | 
 | ||||||
| You can generate a waveform of an audio file using the `FFMpeg\Media\Audio::waveform` | You can generate a waveform of an audio file using the `FFMpeg\Media\Audio::waveform` | ||||||
|  |  | ||||||
							
								
								
									
										292
									
								
								src/FFMpeg/Media/AbstractVideo.php
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										292
									
								
								src/FFMpeg/Media/AbstractVideo.php
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,292 @@ | ||||||
|  | <?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\SimpleFilter; | ||||||
|  | use FFMpeg\Exception\InvalidArgumentException; | ||||||
|  | use FFMpeg\Exception\RuntimeException; | ||||||
|  | use FFMpeg\Filters\Video\VideoFilters; | ||||||
|  | use FFMpeg\Filters\FilterInterface; | ||||||
|  | use FFMpeg\Format\FormatInterface; | ||||||
|  | use FFMpeg\Format\ProgressableInterface; | ||||||
|  | use FFMpeg\Format\AudioInterface; | ||||||
|  | use FFMpeg\Format\VideoInterface; | ||||||
|  | use Neutron\TemporaryFilesystem\Manager as FsManager; | ||||||
|  | use FFMpeg\Filters\Video\ClipFilter; | ||||||
|  | 
 | ||||||
|  | abstract class AbstractVideo extends Audio | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * FileSystem Manager instance | ||||||
|  |      * @var Manager | ||||||
|  |      */ | ||||||
|  |     protected $fs; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * FileSystem Manager ID | ||||||
|  |      * @var int | ||||||
|  |      */ | ||||||
|  |     protected $fsId; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * @inheritDoc | ||||||
|  |      * @return VideoFilters | ||||||
|  |      */ | ||||||
|  |     public function filters() | ||||||
|  |     { | ||||||
|  |         return new VideoFilters($this); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * @inheritDoc | ||||||
|  |      * @return Video | ||||||
|  |      */ | ||||||
|  |     public function addFilter(FilterInterface $filter) | ||||||
|  |     { | ||||||
|  |         $this->filters->add($filter); | ||||||
|  | 
 | ||||||
|  |         return $this; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Exports the video in the desired format, applies registered filters. | ||||||
|  |      * | ||||||
|  |      * @param FormatInterface   $format | ||||||
|  |      * @param string            $outputPathfile | ||||||
|  |      * @return Video | ||||||
|  |      * @throws RuntimeException | ||||||
|  |      */ | ||||||
|  |     public function save(FormatInterface $format, $outputPathfile) | ||||||
|  |     { | ||||||
|  |         $passes = $this->buildCommand($format, $outputPathfile); | ||||||
|  | 
 | ||||||
|  |         $failure = null; | ||||||
|  |         $totalPasses = $format->getPasses(); | ||||||
|  | 
 | ||||||
|  |         foreach ($passes as $pass => $passCommands) { | ||||||
|  |             try { | ||||||
|  |                 /** add listeners here */ | ||||||
|  |                 $listeners = null; | ||||||
|  | 
 | ||||||
|  |                 if ($format instanceof ProgressableInterface) { | ||||||
|  |                     $filters = clone $this->filters; | ||||||
|  |                     $duration = 0; | ||||||
|  | 
 | ||||||
|  |                     // check the filters of the video, and if the video has the ClipFilter then
 | ||||||
|  |                     // take the new video duration and send to the
 | ||||||
|  |                     // FFMpeg\Format\ProgressListener\AbstractProgressListener class
 | ||||||
|  |                     foreach ($filters as $filter) { | ||||||
|  |                         if ($filter instanceof ClipFilter) { | ||||||
|  |                             $duration = $filter->getDuration()->toSeconds(); | ||||||
|  |                             break; | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                     $listeners = $format->createProgressListener($this, $this->ffprobe, $pass + 1, $totalPasses, $duration); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 $this->driver->command($passCommands, false, $listeners); | ||||||
|  |             } catch (ExecutionFailureException $e) { | ||||||
|  |                 $failure = $e; | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         $this->fs->clean($this->fsId); | ||||||
|  | 
 | ||||||
|  |         if (null !== $failure) { | ||||||
|  |             throw new RuntimeException('Encoding failed', $failure->getCode(), $failure); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return $this; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * NOTE: This method is different to the Audio's one, because Video is using passes. | ||||||
|  |      * @inheritDoc | ||||||
|  |      */ | ||||||
|  |     public function getFinalCommand(FormatInterface $format, $outputPathfile) | ||||||
|  |     { | ||||||
|  |         $finalCommands = array(); | ||||||
|  | 
 | ||||||
|  |         foreach ($this->buildCommand($format, $outputPathfile) as $pass => $passCommands) { | ||||||
|  |             $finalCommands[] = implode(' ', $passCommands); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         $this->fs->clean($this->fsId); | ||||||
|  | 
 | ||||||
|  |         return $finalCommands; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * **NOTE:** This creates passes instead of a single command! | ||||||
|  |      * | ||||||
|  |      * @inheritDoc | ||||||
|  |      * @return string[][] | ||||||
|  |      */ | ||||||
|  |     protected function buildCommand(FormatInterface $format, $outputPathfile) | ||||||
|  |     { | ||||||
|  |         $commands = $this->basePartOfCommand(); | ||||||
|  | 
 | ||||||
|  |         $filters = clone $this->filters; | ||||||
|  |         $filters->add(new SimpleFilter($format->getExtraParams(), 10)); | ||||||
|  | 
 | ||||||
|  |         if ($this->driver->getConfiguration()->has('ffmpeg.threads')) { | ||||||
|  |             $filters->add(new SimpleFilter(array('-threads', $this->driver->getConfiguration()->get('ffmpeg.threads')))); | ||||||
|  |         } | ||||||
|  |         if ($format instanceof VideoInterface) { | ||||||
|  |             if (null !== $format->getVideoCodec()) { | ||||||
|  |                 $filters->add(new SimpleFilter(array('-vcodec', $format->getVideoCodec()))); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         if ($format instanceof AudioInterface) { | ||||||
|  |             if (null !== $format->getAudioCodec()) { | ||||||
|  |                 $filters->add(new SimpleFilter(array('-acodec', $format->getAudioCodec()))); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         foreach ($filters as $filter) { | ||||||
|  |             $commands = array_merge($commands, $filter->apply($this, $format)); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if ($format instanceof VideoInterface) { | ||||||
|  |             $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'; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if ($format instanceof AudioInterface) { | ||||||
|  |             if (null !== $format->getAudioKiloBitrate()) { | ||||||
|  |                 $commands[] = '-b:a'; | ||||||
|  |                 $commands[] = $format->getAudioKiloBitrate() . 'k'; | ||||||
|  |             } | ||||||
|  |             if (null !== $format->getAudioChannels()) { | ||||||
|  |                 $commands[] = '-ac'; | ||||||
|  |                 $commands[] = $format->getAudioChannels(); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // If the user passed some additional parameters
 | ||||||
|  |         if ($format instanceof VideoInterface) { | ||||||
|  |             if (null !== $format->getAdditionalParameters()) { | ||||||
|  |                 foreach ($format->getAdditionalParameters() as $additionalParameter) { | ||||||
|  |                     $commands[] = $additionalParameter; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // Merge Filters into one command
 | ||||||
|  |         $videoFilterVars = $videoFilterProcesses = array(); | ||||||
|  |         for ($i = 0; $i < count($commands); $i++) { | ||||||
|  |             $command = $commands[$i]; | ||||||
|  |             if ($command == '-vf') { | ||||||
|  |                 $commandSplits = explode(";", $commands[$i + 1]); | ||||||
|  |                 if (count($commandSplits) == 1) { | ||||||
|  |                     $commandSplit = $commandSplits[0]; | ||||||
|  |                     $command = trim($commandSplit); | ||||||
|  |                     if (preg_match("/^\[in\](.*?)\[out\]$/is", $command, $match)) { | ||||||
|  |                         $videoFilterProcesses[] = $match[1]; | ||||||
|  |                     } else { | ||||||
|  |                         $videoFilterProcesses[] = $command; | ||||||
|  |                     } | ||||||
|  |                 } else { | ||||||
|  |                     foreach ($commandSplits as $commandSplit) { | ||||||
|  |                         $command = trim($commandSplit); | ||||||
|  |                         if (preg_match("/^\[[^\]]+\](.*?)\[[^\]]+\]$/is", $command, $match)) { | ||||||
|  |                             $videoFilterProcesses[] = $match[1]; | ||||||
|  |                         } else { | ||||||
|  |                             $videoFilterVars[] = $command; | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 unset($commands[$i]); | ||||||
|  |                 unset($commands[$i + 1]); | ||||||
|  |                 $i++; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         $videoFilterCommands = $videoFilterVars; | ||||||
|  |         $lastInput = 'in'; | ||||||
|  |         foreach ($videoFilterProcesses as $i => $process) { | ||||||
|  |             $command = '[' . $lastInput . ']'; | ||||||
|  |             $command .= $process; | ||||||
|  |             $lastInput = 'p' . $i; | ||||||
|  |             if ($i === (count($videoFilterProcesses) - 1)) { | ||||||
|  |                 $command .= '[out]'; | ||||||
|  |             } else { | ||||||
|  |                 $command .= '[' . $lastInput . ']'; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             $videoFilterCommands[] = $command; | ||||||
|  |         } | ||||||
|  |         $videoFilterCommand = implode(';', $videoFilterCommands); | ||||||
|  | 
 | ||||||
|  |         if ($videoFilterCommand) { | ||||||
|  |             $commands[] = '-vf'; | ||||||
|  |             $commands[] = $videoFilterCommand; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         $this->fs = FsManager::create(); | ||||||
|  |         $this->fsId = uniqid('ffmpeg-passes'); | ||||||
|  |         $passPrefix = $this->fs->createTemporaryDirectory(0777, 50, $this->fsId) . '/' . uniqid('pass-'); | ||||||
|  |         $passes = array(); | ||||||
|  |         $totalPasses = $format->getPasses(); | ||||||
|  | 
 | ||||||
|  |         if (!$totalPasses) { | ||||||
|  |             throw new InvalidArgumentException('Pass number should be a positive value.'); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         for ($i = 1; $i <= $totalPasses; $i++) { | ||||||
|  |             $pass = $commands; | ||||||
|  | 
 | ||||||
|  |             if ($totalPasses > 1) { | ||||||
|  |                 $pass[] = '-pass'; | ||||||
|  |                 $pass[] = $i; | ||||||
|  |                 $pass[] = '-passlogfile'; | ||||||
|  |                 $pass[] = $passPrefix; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             $pass[] = $outputPathfile; | ||||||
|  | 
 | ||||||
|  |             $passes[] = $pass; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return $passes; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Return base part of command. | ||||||
|  |      * | ||||||
|  |      * @return array | ||||||
|  |      */ | ||||||
|  |     protected function basePartOfCommand() | ||||||
|  |     { | ||||||
|  |         return array('-y', '-i', $this->pathfile); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										60
									
								
								src/FFMpeg/Media/Clip.php
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								src/FFMpeg/Media/Clip.php
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,60 @@ | ||||||
|  | <?php | ||||||
|  | namespace FFMpeg\Media; | ||||||
|  | 
 | ||||||
|  | use FFMpeg\Driver\FFMpegDriver; | ||||||
|  | use FFMpeg\FFProbe; | ||||||
|  | use FFMpeg\Coordinate\TimeCode; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Video clip. | ||||||
|  |  * | ||||||
|  |  * Use input seeking, see http://trac.ffmpeg.org/wiki/Seeking | ||||||
|  |  */ | ||||||
|  | class Clip extends Video | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  |     /** @var TimeCode Start time */ | ||||||
|  |     private $start; | ||||||
|  | 
 | ||||||
|  |     /** @var TimeCode Duration */ | ||||||
|  |     private $duration; | ||||||
|  | 
 | ||||||
|  |     /** @var Video Parrent video */ | ||||||
|  |     private $video; | ||||||
|  | 
 | ||||||
|  |     public function __construct(Video $video, FFMpegDriver $driver, FFProbe $ffprobe, TimeCode $start, TimeCode $duration = null) | ||||||
|  |     { | ||||||
|  |         $this->start = $start; | ||||||
|  |         $this->duration = $duration; | ||||||
|  |         $this->video = $video; | ||||||
|  | 
 | ||||||
|  |         parent::__construct($video->getPathfile(), $driver, $ffprobe); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Returns the video related to the frame. | ||||||
|  |      * | ||||||
|  |      * @return Video | ||||||
|  |      */ | ||||||
|  |     public function getVideo() | ||||||
|  |     { | ||||||
|  |         return $this->video; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Return base part of command. | ||||||
|  |      * | ||||||
|  |      * @return array | ||||||
|  |      */ | ||||||
|  |     protected function basePartOfCommand() | ||||||
|  |     { | ||||||
|  |         $arr = array('-y', '-ss', (string) $this->start, '-i', $this->pathfile); | ||||||
|  | 
 | ||||||
|  |         if (is_null($this->duration) === false) { | ||||||
|  |             $arr[] = '-t'; | ||||||
|  |             $arr[] = (string) $this->duration; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return $arr; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -1,5 +1,4 @@ | ||||||
| <?php | <?php | ||||||
| 
 |  | ||||||
| /* | /* | ||||||
|  * This file is part of PHP-FFmpeg. |  * This file is part of PHP-FFmpeg. | ||||||
|  * |  * | ||||||
|  | @ -8,278 +7,13 @@ | ||||||
|  * For the full copyright and license information, please view the LICENSE |  * For the full copyright and license information, please view the LICENSE | ||||||
|  * file that was distributed with this source code. |  * file that was distributed with this source code. | ||||||
|  */ |  */ | ||||||
| 
 |  | ||||||
| namespace FFMpeg\Media; | namespace FFMpeg\Media; | ||||||
| 
 | 
 | ||||||
| use Alchemy\BinaryDriver\Exception\ExecutionFailureException; |  | ||||||
| use FFMpeg\Coordinate\TimeCode; | use FFMpeg\Coordinate\TimeCode; | ||||||
| use FFMpeg\Coordinate\Dimension; | use FFMpeg\Coordinate\Dimension; | ||||||
| use FFMpeg\Filters\Audio\SimpleFilter; |  | ||||||
| use FFMpeg\Exception\InvalidArgumentException; |  | ||||||
| use FFMpeg\Exception\RuntimeException; |  | ||||||
| use FFMpeg\Filters\Video\VideoFilters; |  | ||||||
| use FFMpeg\Filters\FilterInterface; |  | ||||||
| use FFMpeg\Format\FormatInterface; |  | ||||||
| use FFMpeg\Format\ProgressableInterface; |  | ||||||
| use FFMpeg\Format\AudioInterface; |  | ||||||
| use FFMpeg\Format\VideoInterface; |  | ||||||
| use Neutron\TemporaryFilesystem\Manager as FsManager; |  | ||||||
| use FFMpeg\Filters\Video\ClipFilter; |  | ||||||
| 
 | 
 | ||||||
| class Video extends Audio | class Video extends AbstractVideo | ||||||
| { | { | ||||||
|     /** |  | ||||||
|      * FileSystem Manager instance |  | ||||||
|      * @var Manager |  | ||||||
|      */ |  | ||||||
|     protected $fs; |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * FileSystem Manager ID |  | ||||||
|      * @var int |  | ||||||
|      */ |  | ||||||
|     protected $fsId; |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * @inheritDoc |  | ||||||
|      * @return VideoFilters |  | ||||||
|      */ |  | ||||||
|     public function filters() |  | ||||||
|     { |  | ||||||
|         return new VideoFilters($this); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * @inheritDoc |  | ||||||
|      * @return Video |  | ||||||
|      */ |  | ||||||
|     public function addFilter(FilterInterface $filter) |  | ||||||
|     { |  | ||||||
|         $this->filters->add($filter); |  | ||||||
| 
 |  | ||||||
|         return $this; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Exports the video in the desired format, applies registered filters. |  | ||||||
|      * |  | ||||||
|      * @param FormatInterface   $format |  | ||||||
|      * @param string            $outputPathfile |  | ||||||
|      * @return Video |  | ||||||
|      * @throws RuntimeException |  | ||||||
|      */ |  | ||||||
|     public function save(FormatInterface $format, $outputPathfile) |  | ||||||
|     { |  | ||||||
|         $passes = $this->buildCommand($format, $outputPathfile); |  | ||||||
| 
 |  | ||||||
|         $failure = null; |  | ||||||
|         $totalPasses = $format->getPasses(); |  | ||||||
| 
 |  | ||||||
|         foreach ($passes as $pass => $passCommands) { |  | ||||||
|             try { |  | ||||||
|                 /** add listeners here */ |  | ||||||
|                 $listeners = null; |  | ||||||
|          |  | ||||||
|                 if ($format instanceof ProgressableInterface) { |  | ||||||
|                     $filters = clone $this->filters; |  | ||||||
|                     $duration = 0; |  | ||||||
|                      |  | ||||||
|                     // check the filters of the video, and if the video has the ClipFilter then
 |  | ||||||
|                     // take the new video duration and send to the
 |  | ||||||
|                     // FFMpeg\Format\ProgressListener\AbstractProgressListener class
 |  | ||||||
|                     foreach ($filters as $filter) { |  | ||||||
|                         if($filter instanceof ClipFilter){ |  | ||||||
|                             $duration = $filter->getDuration()->toSeconds(); |  | ||||||
|                             break; |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                     $listeners = $format->createProgressListener($this, $this->ffprobe, $pass + 1, $totalPasses, $duration); |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 $this->driver->command($passCommands, false, $listeners); |  | ||||||
|             } catch (ExecutionFailureException $e) { |  | ||||||
|                 $failure = $e; |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         $this->fs->clean($this->fsId); |  | ||||||
| 
 |  | ||||||
|         if (null !== $failure) { |  | ||||||
|             throw new RuntimeException('Encoding failed', $failure->getCode(), $failure); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return $this; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * NOTE: This method is different to the Audio's one, because Video is using passes. |  | ||||||
|      * @inheritDoc |  | ||||||
|      */ |  | ||||||
|     public function getFinalCommand(FormatInterface $format, $outputPathfile) { |  | ||||||
|         $finalCommands = array(); |  | ||||||
| 
 |  | ||||||
|         foreach($this->buildCommand($format, $outputPathfile) as $pass => $passCommands) { |  | ||||||
|             $finalCommands[] = implode(' ', $passCommands); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         $this->fs->clean($this->fsId); |  | ||||||
| 
 |  | ||||||
|         return $finalCommands; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * **NOTE:** This creates passes instead of a single command! |  | ||||||
|      * |  | ||||||
|      * @inheritDoc |  | ||||||
|      * @return string[][] |  | ||||||
|      */ |  | ||||||
|     protected function buildCommand(FormatInterface $format, $outputPathfile) { |  | ||||||
|         $commands = array('-y', '-i', $this->pathfile); |  | ||||||
| 
 |  | ||||||
|         $filters = clone $this->filters; |  | ||||||
|         $filters->add(new SimpleFilter($format->getExtraParams(), 10)); |  | ||||||
| 
 |  | ||||||
|         if ($this->driver->getConfiguration()->has('ffmpeg.threads')) { |  | ||||||
|             $filters->add(new SimpleFilter(array('-threads', $this->driver->getConfiguration()->get('ffmpeg.threads')))); |  | ||||||
|         } |  | ||||||
|         if ($format instanceof VideoInterface) { |  | ||||||
|             if (null !== $format->getVideoCodec()) { |  | ||||||
|                 $filters->add(new SimpleFilter(array('-vcodec', $format->getVideoCodec()))); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         if ($format instanceof AudioInterface) { |  | ||||||
|             if (null !== $format->getAudioCodec()) { |  | ||||||
|                 $filters->add(new SimpleFilter(array('-acodec', $format->getAudioCodec()))); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         foreach ($filters as $filter) { |  | ||||||
|             $commands = array_merge($commands, $filter->apply($this, $format)); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if ($format instanceof VideoInterface) { |  | ||||||
|             $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'; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if ($format instanceof AudioInterface) { |  | ||||||
|             if (null !== $format->getAudioKiloBitrate()) { |  | ||||||
|                 $commands[] = '-b:a'; |  | ||||||
|                 $commands[] = $format->getAudioKiloBitrate() . 'k'; |  | ||||||
|             } |  | ||||||
|             if (null !== $format->getAudioChannels()) { |  | ||||||
|                 $commands[] = '-ac'; |  | ||||||
|                 $commands[] = $format->getAudioChannels(); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         // If the user passed some additional parameters
 |  | ||||||
|         if ($format instanceof VideoInterface) { |  | ||||||
|             if (null !== $format->getAdditionalParameters()) { |  | ||||||
|                 foreach ($format->getAdditionalParameters() as $additionalParameter) { |  | ||||||
|                     $commands[] = $additionalParameter; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         // Merge Filters into one command
 |  | ||||||
|         $videoFilterVars = $videoFilterProcesses = array(); |  | ||||||
|         for($i=0;$i<count($commands);$i++) { |  | ||||||
|             $command = $commands[$i]; |  | ||||||
|             if ( $command == '-vf' ) { |  | ||||||
|                 $commandSplits = explode(";", $commands[$i + 1]); |  | ||||||
|                 if ( count($commandSplits) == 1 ) { |  | ||||||
|                     $commandSplit = $commandSplits[0]; |  | ||||||
|                     $command = trim($commandSplit); |  | ||||||
|                     if ( preg_match("/^\[in\](.*?)\[out\]$/is", $command, $match) ) { |  | ||||||
|                         $videoFilterProcesses[] = $match[1]; |  | ||||||
|                     } else { |  | ||||||
|                         $videoFilterProcesses[] = $command; |  | ||||||
|                     } |  | ||||||
|                 } else { |  | ||||||
|                     foreach($commandSplits as $commandSplit) { |  | ||||||
|                         $command = trim($commandSplit); |  | ||||||
|                         if ( preg_match("/^\[[^\]]+\](.*?)\[[^\]]+\]$/is", $command, $match) ) { |  | ||||||
|                             $videoFilterProcesses[] = $match[1]; |  | ||||||
|                         } else { |  | ||||||
|                             $videoFilterVars[] = $command; |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 unset($commands[$i]); |  | ||||||
|                 unset($commands[$i + 1]); |  | ||||||
|                 $i++; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         $videoFilterCommands = $videoFilterVars; |  | ||||||
|         $lastInput = 'in'; |  | ||||||
|         foreach($videoFilterProcesses as $i => $process) { |  | ||||||
|             $command = '[' . $lastInput .']'; |  | ||||||
|             $command .= $process; |  | ||||||
|             $lastInput = 'p' . $i; |  | ||||||
|             if($i === (count($videoFilterProcesses) - 1)) { |  | ||||||
|                 $command .= '[out]'; |  | ||||||
|             } else { |  | ||||||
|                 $command .= '[' . $lastInput . ']'; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             $videoFilterCommands[] = $command; |  | ||||||
|         } |  | ||||||
|         $videoFilterCommand = implode(';', $videoFilterCommands); |  | ||||||
| 
 |  | ||||||
|         if($videoFilterCommand) { |  | ||||||
|             $commands[] = '-vf'; |  | ||||||
|             $commands[] = $videoFilterCommand; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         $this->fs = FsManager::create(); |  | ||||||
|         $this->fsId = uniqid('ffmpeg-passes'); |  | ||||||
|         $passPrefix = $this->fs->createTemporaryDirectory(0777, 50, $this->fsId) . '/' . uniqid('pass-'); |  | ||||||
|         $passes = array(); |  | ||||||
|         $totalPasses = $format->getPasses(); |  | ||||||
| 
 |  | ||||||
|         if(!$totalPasses) { |  | ||||||
|             throw new InvalidArgumentException('Pass number should be a positive value.'); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         for($i = 1; $i <= $totalPasses; $i++) { |  | ||||||
|             $pass = $commands; |  | ||||||
| 
 |  | ||||||
|             if ($totalPasses > 1) { |  | ||||||
|                 $pass[] = '-pass'; |  | ||||||
|                 $pass[] = $i; |  | ||||||
|                 $pass[] = '-passlogfile'; |  | ||||||
|                 $pass[] = $passPrefix; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             $pass[] = $outputPathfile; |  | ||||||
| 
 |  | ||||||
|             $passes[] = $pass; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return $passes; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Gets the frame at timecode. |      * Gets the frame at timecode. | ||||||
|  | @ -315,4 +49,16 @@ class Video extends Audio | ||||||
|     { |     { | ||||||
|         return new Concat($sources, $this->driver, $this->ffprobe); |         return new Concat($sources, $this->driver, $this->ffprobe); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Clips the video at the given time(s). | ||||||
|  |      * | ||||||
|  |      * @param TimeCode $start Start time | ||||||
|  |      * @param TimeCode $duration Duration | ||||||
|  |      * @return \FFMpeg\Media\Clip | ||||||
|  |      */ | ||||||
|  |     public function clip(TimeCode $start, TimeCode $duration = null) | ||||||
|  |     { | ||||||
|  |         return new Clip($this, $this->driver, $this->ffprobe, $start, $duration); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										67
									
								
								tests/Unit/Media/ClipTest.php
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								tests/Unit/Media/ClipTest.php
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,67 @@ | ||||||
|  | <?php | ||||||
|  | namespace Tests\FFMpeg\Unit\Media; | ||||||
|  | 
 | ||||||
|  | use FFMpeg\Media\Clip; | ||||||
|  | 
 | ||||||
|  | class ClipTest extends AbstractMediaTestCase | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * @dataProvider provideBuildOptions | ||||||
|  |      */ | ||||||
|  |     public function testBuildCommand($startValue, $durationValue, $commands) | ||||||
|  |     { | ||||||
|  |         $configuration = $this->getConfigurationMock(); | ||||||
|  | 
 | ||||||
|  |         $driver = $this->getFFMpegDriverMock(); | ||||||
|  |         $driver->expects($this->any()) | ||||||
|  |             ->method('getConfiguration') | ||||||
|  |             ->will($this->returnValue($configuration)); | ||||||
|  | 
 | ||||||
|  |         $ffprobe = $this->getFFProbeMock(); | ||||||
|  | 
 | ||||||
|  |         $start = $this->getTimeCodeMock(); | ||||||
|  |         $start->expects($this->once()) | ||||||
|  |             ->method('__toString') | ||||||
|  |             ->will($this->returnValue($startValue)); | ||||||
|  | 
 | ||||||
|  |         $duration = null; | ||||||
|  |         if (null !== $durationValue) { | ||||||
|  |             $duration = $this->getTimeCodeMock(); | ||||||
|  |             $duration->expects($this->once()) | ||||||
|  |                 ->method('__toString') | ||||||
|  |                 ->will($this->returnValue($durationValue)); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         $outputPathfile = '/target/file'; | ||||||
|  | 
 | ||||||
|  |         $format = $this->getMock('FFMpeg\Format\VideoInterface'); | ||||||
|  |         $format->expects($this->any()) | ||||||
|  |             ->method('getPasses') | ||||||
|  |             ->will($this->returnValue(1)); | ||||||
|  |         $format->expects($this->any()) | ||||||
|  |             ->method('getExtraParams') | ||||||
|  |             ->will($this->returnValue(array())); | ||||||
|  | 
 | ||||||
|  |         $clip = new Clip($this->getVideoMock(__FILE__), $driver, $ffprobe, $start, $duration); | ||||||
|  |         $fc = $clip->getFinalCommand($format, $outputPathfile); | ||||||
|  | 
 | ||||||
|  |         $this->assertCount(1, $fc); | ||||||
|  |         $this->assertStringStartsWith(implode(' ', $commands), $fc[0]); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function provideBuildOptions() | ||||||
|  |     { | ||||||
|  |         return array( | ||||||
|  |             array('SS01', null, array( | ||||||
|  |                     '-y', '-ss', 'SS01', | ||||||
|  |                     '-i', __FILE__) | ||||||
|  |             ), | ||||||
|  |             array('SS02', 'D02', array( | ||||||
|  |                     '-y', '-ss', 'SS02', | ||||||
|  |                     '-i', __FILE__, | ||||||
|  |                     '-t', 'D02') | ||||||
|  |             ) | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | } | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue