diff --git a/src/FFMpeg/Filters/Waveform/WaveformFilterInterface.php b/src/FFMpeg/Filters/Waveform/WaveformFilterInterface.php new file mode 100644 index 0000000..7cabdd3 --- /dev/null +++ b/src/FFMpeg/Filters/Waveform/WaveformFilterInterface.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FFMpeg\Filters\Waveform; + +use FFMpeg\Filters\FilterInterface; +use FFMpeg\Media\Waveform; + +interface WaveformFilterInterface extends FilterInterface +{ + public function apply(Waveform $waveform); +} diff --git a/src/FFMpeg/Filters/Waveform/WaveformFilters.php b/src/FFMpeg/Filters/Waveform/WaveformFilters.php new file mode 100644 index 0000000..6ef4043 --- /dev/null +++ b/src/FFMpeg/Filters/Waveform/WaveformFilters.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FFMpeg\Filters\Waveform; + +use FFMpeg\Media\Waveform; + +class WaveformFilters +{ + private $waveform; + + public function __construct(Waveform $waveform) + { + $this->waveform = $waveform; + } + + /** + * Fixes the display ratio of the output frame. + * + * In case the sample ratio and display ratio are different, image may be + * anamorphozed. This filter fixes this by specifying the output size. + * + * @return FrameFilters + */ + public function fixDisplayRatio() + { + $this->frame->addFilter(new DisplayRatioFixerFilter()); + + return $this; + } +} diff --git a/src/FFMpeg/Filters/Waveform/WaveformRatioFixerFilter.php b/src/FFMpeg/Filters/Waveform/WaveformRatioFixerFilter.php new file mode 100644 index 0000000..61d558b --- /dev/null +++ b/src/FFMpeg/Filters/Waveform/WaveformRatioFixerFilter.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FFMpeg\Filters\Waveform; + +use FFMpeg\Exception\RuntimeException; +use FFMpeg\Media\Waveform; + +class WaveformRatioFixerFilter implements WaveformFilterInterface +{ + /** @var boolean */ + private $downmix; + + // By default, the downmix value is set to FALSE. + public function __construct($downmix = FALSE) + { + $this->downmix = $downmix; + } + + /** + * {@inheritdoc} + */ + public function getDownmix() + { + return $this->downmix; + } + + /** + * {@inheritdoc} + */ + public function apply(Waveform $waveform) + { + $dimensions = null; + $commands = array(); + + foreach ($waveform->getVideo()->getStreams() as $stream) { + if ($stream->isVideo()) { + try { + + // Get the dimensions of the video + $dimensions = $stream->getDimensions(); + + // If the downmix parameter is set to TRUE, we add an option to the FFMPEG command + if(!$this->downmix) { + $commands[] = '"showwavespic=s=' . $dimensions->getWidth() . 'x' . $dimensions->getHeight().'"'; + } + else { + $commands[] = '"aformat=channel_layouts=mono,showwavespic=s=' . $dimensions->getWidth() . 'x' . $dimensions->getHeight().'"'; + } + + break; + + } catch (RuntimeException $e) { + + } + } + } + + return $commands; + } +} diff --git a/src/FFMpeg/Media/Waveform.php b/src/FFMpeg/Media/Waveform.php new file mode 100644 index 0000000..8adf2be --- /dev/null +++ b/src/FFMpeg/Media/Waveform.php @@ -0,0 +1,101 @@ + + * + * 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\Waveform\WaveformFilterInterface; +use FFMpeg\Filters\Waveform\WaveformFilters; +use FFMpeg\Driver\FFMpegDriver; +use FFMpeg\FFProbe; +use FFMpeg\Exception\RuntimeException; +use FFMpeg\Coordinate\TimeCode; + +class Waveform extends AbstractMediaType +{ + /** @var Video */ + private $video; + + public function __construct(Video $video, FFMpegDriver $driver, FFProbe $ffprobe) + { + parent::__construct($video->getPathfile(), $driver, $ffprobe); + $this->video = $video; + } + + /** + * Returns the video related to the waveform. + * + * @return Video + */ + public function getVideo() + { + return $this->video; + } + + /** + * {@inheritdoc} + * + * @return WaveformFilters + */ + public function filters() + { + return new WaveformFilters($this); + } + + /** + * {@inheritdoc} + * + * @return Waveform + */ + public function addFilter(WaveformFilterInterface $filter) + { + $this->filters->add($filter); + + return $this; + } + + /** + * Saves the waveform in the given filename. + * + * @param string $pathfile + * + * @return Waveform + * + * @throws RuntimeException + */ + public function save($pathfile) + { + /** + * might be optimized with http://ffmpeg.org/trac/ffmpeg/wiki/Seeking%20with%20FFmpeg + * @see http://ffmpeg.org/ffmpeg.html#Main-options + */ + $commands = array( + '-i', 'input', '-filter_complex', + '-frames:v', '1', + $this->pathfile + ); + + foreach ($this->filters as $filter) { + $commands = array_merge($commands, $filter->apply($this)); + } + + $commands = array_merge($commands, array($pathfile)); + + try { + $this->driver->command($commands); + } catch (ExecutionFailureException $e) { + $this->cleanupTemporaryFile($pathfile); + throw new RuntimeException('Unable to save waveform', $e->getCode(), $e); + } + + return $this; + } +} diff --git a/tests/Unit/Media/WaveformTest.php b/tests/Unit/Media/WaveformTest.php new file mode 100644 index 0000000..d88da55 --- /dev/null +++ b/tests/Unit/Media/WaveformTest.php @@ -0,0 +1,71 @@ +getFFMpegDriverMock(); + $ffprobe = $this->getFFProbeMock(); + + $waveform = new Waveform($this->getVideoMock(__FILE__), $driver, $ffprobe); + $this->assertInstanceOf('FFMpeg\Filters\Waveform\WaveformFilters', $waveform->filters()); + } + + public function testAddFiltersAddsAFilter() + { + $driver = $this->getFFMpegDriverMock(); + $ffprobe = $this->getFFProbeMock(); + + $filters = $this->getMockBuilder('FFMpeg\Filters\FiltersCollection') + ->disableOriginalConstructor() + ->getMock(); + + $filter = $this->getMock('FFMpeg\Filters\Waveform\WaveformFilterInterface'); + + $filters->expects($this->once()) + ->method('add') + ->with($filter); + + $waveform = new Waveform($this->getVideoMock(__FILE__), $driver, $ffprobe); + $waveform->setFiltersCollection($filters); + $waveform->addFilter($filter); + } + + /** + * @dataProvider provideSaveOptions + */ + public function testSave($commands) + { + $driver = $this->getFFMpegDriverMock(); + $ffprobe = $this->getFFProbeMock(); + + $pathfile = '/target/destination'; + + array_push($commands, $pathfile); + + $driver->expects($this->once()) + ->method('command') + ->with($commands); + + $waveform = new Waveform($this->getVideoMock(__FILE__), $driver, $ffprobe); + $this->assertSame($waveform, $waveform->save($pathfile)); + } + + public function provideSaveOptions() + { + return array( + array( + array( + '-i', 'input', '-filter_complex', + '-frames:v', '1', + __FILE__ + ), + ), + ); + } +}