From 5814eb3085269b8b039170f52eb320b9c2d47618 Mon Sep 17 00:00:00 2001 From: Romain Neutron Date: Wed, 4 Sep 2013 19:50:38 +0200 Subject: [PATCH 1/5] Inject Video at Frame construction --- src/FFMpeg/Media/Frame.php | 18 ++++++++++++++++-- src/FFMpeg/Media/Video.php | 2 +- tests/FFMpeg/Tests/Media/FrameTest.php | 8 ++++---- tests/FFMpeg/Tests/TestCase.php | 10 ++++++++-- 4 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/FFMpeg/Media/Frame.php b/src/FFMpeg/Media/Frame.php index bfbed8f..55d4311 100644 --- a/src/FFMpeg/Media/Frame.php +++ b/src/FFMpeg/Media/Frame.php @@ -18,16 +18,30 @@ use FFMpeg\Driver\FFMpegDriver; use FFMpeg\FFProbe; use FFMpeg\Exception\RuntimeException; use FFMpeg\Coordinate\TimeCode; +use FFMpeg\Media\Video; class Frame extends AbstractMediaType { /** @var TimeCode */ private $timecode; + /** @var Video */ + private $video; - public function __construct($pathfile, FFMpegDriver $driver, FFProbe $ffprobe, TimeCode $timecode) + public function __construct(Video $video, FFMpegDriver $driver, FFProbe $ffprobe, TimeCode $timecode) { - parent::__construct($pathfile, $driver, $ffprobe); + parent::__construct($video->getPathfile(), $driver, $ffprobe); $this->timecode = $timecode; + $this->video = $video; + } + + /** + * Returns the video related to the frame. + * + * @return Video + */ + public function getVideo() + { + return $this->video; } /** diff --git a/src/FFMpeg/Media/Video.php b/src/FFMpeg/Media/Video.php index 77da940..006e346 100644 --- a/src/FFMpeg/Media/Video.php +++ b/src/FFMpeg/Media/Video.php @@ -159,6 +159,6 @@ class Video extends Audio */ public function frame(TimeCode $at) { - return new Frame($this->pathfile, $this->driver, $this->ffprobe, $at); + return new Frame($this, $this->driver, $this->ffprobe, $at); } } diff --git a/tests/FFMpeg/Tests/Media/FrameTest.php b/tests/FFMpeg/Tests/Media/FrameTest.php index a95df2b..c98cb87 100644 --- a/tests/FFMpeg/Tests/Media/FrameTest.php +++ b/tests/FFMpeg/Tests/Media/FrameTest.php @@ -20,7 +20,7 @@ class FrameTest extends AbstractMediaTestCase $ffprobe = $this->getFFProbeMock(); $timecode = $this->getTimeCodeMock(); - $frame = new Frame(__FILE__, $driver, $ffprobe, $timecode); + $frame = new Frame($this->getVideoMock(__FILE__), $driver, $ffprobe, $timecode); $this->assertSame($timecode, $frame->getTimeCode()); } @@ -30,7 +30,7 @@ class FrameTest extends AbstractMediaTestCase $ffprobe = $this->getFFProbeMock(); $timecode = $this->getTimeCodeMock(); - $frame = new Frame(__FILE__, $driver, $ffprobe, $timecode); + $frame = new Frame($this->getVideoMock(__FILE__), $driver, $ffprobe, $timecode); $this->assertInstanceOf('FFMpeg\Filters\Frame\FrameFilters', $frame->filters()); } @@ -50,7 +50,7 @@ class FrameTest extends AbstractMediaTestCase ->method('add') ->with($filter); - $frame = new Frame(__FILE__, $driver, $ffprobe, $timecode); + $frame = new Frame($this->getVideoMock(__FILE__), $driver, $ffprobe, $timecode); $frame->setFiltersCollection($filters); $frame->addFilter($filter); } @@ -75,7 +75,7 @@ class FrameTest extends AbstractMediaTestCase ->method('command') ->with($commands); - $frame = new Frame(__FILE__, $driver, $ffprobe, $timecode); + $frame = new Frame($this->getVideoMock(__FILE__), $driver, $ffprobe, $timecode); $this->assertSame($frame, $frame->save($pathfile, $accurate)); } diff --git a/tests/FFMpeg/Tests/TestCase.php b/tests/FFMpeg/Tests/TestCase.php index d347acd..0d6f906 100644 --- a/tests/FFMpeg/Tests/TestCase.php +++ b/tests/FFMpeg/Tests/TestCase.php @@ -122,10 +122,16 @@ class TestCase extends \PHPUnit_Framework_TestCase ->getMock(); } - protected function getVideoMock() + protected function getVideoMock($filename = null) { - return $this->getMockBuilder('FFMpeg\Media\Video') + $video = $this->getMockBuilder('FFMpeg\Media\Video') ->disableOriginalConstructor() ->getMock(); + + $video->expects($this->any()) + ->method('getFilename') + ->will($this->returnValue($filename)); + + return $video; } } From bcf3e5f65a07a3d1127eeb7b062fe405521bf435 Mon Sep 17 00:00:00 2001 From: Romain Neutron Date: Wed, 4 Sep 2013 19:52:24 +0200 Subject: [PATCH 2/5] Apply frame filters on Frame::save --- src/FFMpeg/Filters/Frame/FrameFilterInterface.php | 3 +-- src/FFMpeg/Media/Frame.php | 10 ++++++++-- tests/FFMpeg/Tests/Media/FrameTest.php | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/FFMpeg/Filters/Frame/FrameFilterInterface.php b/src/FFMpeg/Filters/Frame/FrameFilterInterface.php index db4fdac..d9df0e4 100644 --- a/src/FFMpeg/Filters/Frame/FrameFilterInterface.php +++ b/src/FFMpeg/Filters/Frame/FrameFilterInterface.php @@ -13,9 +13,8 @@ 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); + public function apply(Frame $frame); } diff --git a/src/FFMpeg/Media/Frame.php b/src/FFMpeg/Media/Frame.php index 55d4311..a48c50e 100644 --- a/src/FFMpeg/Media/Frame.php +++ b/src/FFMpeg/Media/Frame.php @@ -97,16 +97,22 @@ class Frame extends AbstractMediaType '-y', '-ss', (string) $this->timecode, '-i', $this->pathfile, '-vframes', '1', - '-f', 'image2', $pathfile + '-f', 'image2' ); } else { $commands = array( '-y', '-i', $this->pathfile, '-vframes', '1', '-ss', (string) $this->timecode, - '-f', 'image2', $pathfile + '-f', 'image2' ); } + 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) { diff --git a/tests/FFMpeg/Tests/Media/FrameTest.php b/tests/FFMpeg/Tests/Media/FrameTest.php index c98cb87..b64d6e9 100644 --- a/tests/FFMpeg/Tests/Media/FrameTest.php +++ b/tests/FFMpeg/Tests/Media/FrameTest.php @@ -11,7 +11,7 @@ class FrameTest extends AbstractMediaTestCase */ public function testWithInvalidFile() { - new Frame('/No/file', $this->getFFMpegDriverMock(), $this->getFFProbeMock(), $this->getTimeCodeMock()); + new Frame($this->getVideoMock('/No/file'), $this->getFFMpegDriverMock(), $this->getFFProbeMock(), $this->getTimeCodeMock()); } public function testGetTimeCode() From 1de948faba5aba5267f8dec3bbdd7f36e0237698 Mon Sep 17 00:00:00 2001 From: Romain Neutron Date: Thu, 5 Sep 2013 11:05:14 +0200 Subject: [PATCH 3/5] Add DisplayRatioFixer Frame filter --- CHANGELOG.md | 1 + .../Filters/Frame/DisplayRatioFixerFilter.php | 58 +++++++++++++++++++ .../Frame/DisplayRatioFixerFilterTest.php | 28 +++++++++ 3 files changed, 87 insertions(+) create mode 100644 src/FFMpeg/Filters/Frame/DisplayRatioFixerFilter.php create mode 100644 tests/FFMpeg/Tests/Filters/Frame/DisplayRatioFixerFilterTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index e519f4d..95840c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG * 0.3.3 (xx-xx-2013) * Add convenient Stream::getDimensions method to extract video dimension. + * Add DisplayRatioFixer Frame filter. * 0.3.2 (08-08-2013) diff --git a/src/FFMpeg/Filters/Frame/DisplayRatioFixerFilter.php b/src/FFMpeg/Filters/Frame/DisplayRatioFixerFilter.php new file mode 100644 index 0000000..0cc3cea --- /dev/null +++ b/src/FFMpeg/Filters/Frame/DisplayRatioFixerFilter.php @@ -0,0 +1,58 @@ + + * + * 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\Exception\RuntimeException; +use FFMpeg\Media\Frame; + +class DisplayRatioFixerFilter implements FrameFilterInterface +{ + /** @var integer */ + private $priority; + + public function __construct($priority = 0) + { + $this->priority = $priority; + } + + /** + * {@inheritdoc} + */ + public function getPriority() + { + return $this->priority; + } + + /** + * {@inheritdoc} + */ + public function apply(Frame $frame) + { + $dimensions = null; + $commands = array(); + + foreach ($frame->getVideo()->getStreams() as $stream) { + if ($stream->isVideo()) { + try { + $dimensions = $stream->getDimensions(); + $commands[] = '-s'; + $commands[] = $dimensions->getWidth() . 'x' . $dimensions->getHeight(); + break; + } catch (RuntimeException $e) { + + } + } + } + + return $commands; + } +} diff --git a/tests/FFMpeg/Tests/Filters/Frame/DisplayRatioFixerFilterTest.php b/tests/FFMpeg/Tests/Filters/Frame/DisplayRatioFixerFilterTest.php new file mode 100644 index 0000000..bb642b4 --- /dev/null +++ b/tests/FFMpeg/Tests/Filters/Frame/DisplayRatioFixerFilterTest.php @@ -0,0 +1,28 @@ + 'video', 'width' => 960, 'height' => 720)); + $streams = new StreamCollection(array($stream)); + + $video = $this->getVideoMock(__FILE__); + $video->expects($this->once()) + ->method('getStreams') + ->will($this->returnValue($streams)); + + $frame = new Frame($video, $this->getFFMpegDriverMock(), $this->getFFProbeMock(), new TimeCode(0, 0, 0, 0)); + $filter = new DisplayRatioFixerFilter(); + $this->assertEquals(array('-s', '960x720'), $filter->apply($frame)); + } +} From e43da86152e36d78bba3e4e7ddea5a6aad73f8f5 Mon Sep 17 00:00:00 2001 From: Romain Neutron Date: Thu, 5 Sep 2013 11:12:10 +0200 Subject: [PATCH 4/5] Fix FiltersCollection::getIterator in case of empty collection --- src/FFMpeg/Filters/FiltersCollection.php | 8 ++++++-- tests/FFMpeg/Tests/Filters/FiltersCollectionTest.php | 6 ++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/FFMpeg/Filters/FiltersCollection.php b/src/FFMpeg/Filters/FiltersCollection.php index d44a880..c91241a 100644 --- a/src/FFMpeg/Filters/FiltersCollection.php +++ b/src/FFMpeg/Filters/FiltersCollection.php @@ -47,8 +47,12 @@ class FiltersCollection implements \Countable, \IteratorAggregate public function getIterator() { if (null === $this->sorted) { - krsort($this->filters); - $this->sorted = call_user_func_array('array_merge', $this->filters); + if (0 === count($this->filters)) { + $this->sorted = $this->filters; + } else { + krsort($this->filters); + $this->sorted = call_user_func_array('array_merge', $this->filters); + } } return new \ArrayIterator($this->sorted); diff --git a/tests/FFMpeg/Tests/Filters/FiltersCollectionTest.php b/tests/FFMpeg/Tests/Filters/FiltersCollectionTest.php index 52a1395..5a29008 100644 --- a/tests/FFMpeg/Tests/Filters/FiltersCollectionTest.php +++ b/tests/FFMpeg/Tests/Filters/FiltersCollectionTest.php @@ -30,6 +30,12 @@ class FiltersCollectionTest extends TestCase $this->assertCount(2, $coll->getIterator()); } + public function testEmptyIterator() + { + $coll = new FiltersCollection(); + $this->assertInstanceOf('\ArrayIterator', $coll->getIterator()); + } + public function testIteratorSort() { $coll = new FiltersCollection(); From bb3191528552750f5329b89bd23a358526d167a5 Mon Sep 17 00:00:00 2001 From: Romain Neutron Date: Thu, 5 Sep 2013 11:16:26 +0200 Subject: [PATCH 5/5] Add FrameFilters::fixDisplayRatio method --- src/FFMpeg/Filters/Frame/FrameFilters.php | 15 +++++++++++++ .../Tests/Filters/Frame/FrameFiltersTest.php | 21 +++++++++++++++++++ tests/FFMpeg/Tests/TestCase.php | 9 +++++++- 3 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 tests/FFMpeg/Tests/Filters/Frame/FrameFiltersTest.php diff --git a/src/FFMpeg/Filters/Frame/FrameFilters.php b/src/FFMpeg/Filters/Frame/FrameFilters.php index 85bfde9..255da0c 100644 --- a/src/FFMpeg/Filters/Frame/FrameFilters.php +++ b/src/FFMpeg/Filters/Frame/FrameFilters.php @@ -21,4 +21,19 @@ class FrameFilters { $this->frame = $frame; } + + /** + * 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/tests/FFMpeg/Tests/Filters/Frame/FrameFiltersTest.php b/tests/FFMpeg/Tests/Filters/Frame/FrameFiltersTest.php new file mode 100644 index 0000000..a0e6434 --- /dev/null +++ b/tests/FFMpeg/Tests/Filters/Frame/FrameFiltersTest.php @@ -0,0 +1,21 @@ +getFrameMock(); + $filters = new FrameFilters($frame); + + $frame->expects($this->once()) + ->method('addFilter') + ->with($this->isInstanceOf('FFMpeg\Filters\Frame\DisplayRatioFixerFilter')); + + $filters->fixDisplayRatio(); + } +} diff --git a/tests/FFMpeg/Tests/TestCase.php b/tests/FFMpeg/Tests/TestCase.php index 0d6f906..4757f95 100644 --- a/tests/FFMpeg/Tests/TestCase.php +++ b/tests/FFMpeg/Tests/TestCase.php @@ -40,6 +40,13 @@ class TestCase extends \PHPUnit_Framework_TestCase ->getMock(); } + public function getFrameMock() + { + return $this->getMockBuilder('FFMpeg\Media\Frame') + ->disableOriginalConstructor() + ->getMock(); + } + public function getFFMpegDriverMock() { return $this->getMockBuilder('FFMpeg\Driver\FFMpegDriver') @@ -129,7 +136,7 @@ class TestCase extends \PHPUnit_Framework_TestCase ->getMock(); $video->expects($this->any()) - ->method('getFilename') + ->method('getPathfile') ->will($this->returnValue($filename)); return $video;