From 6948e263ff76ea42007fd261771ce69e941e262b Mon Sep 17 00:00:00 2001 From: Romain Neutron Date: Thu, 28 Nov 2013 15:33:57 +0100 Subject: [PATCH 1/5] Add a rotate filter (fix #15) --- CHANGELOG.md | 4 + src/FFMpeg/Filters/Video/RotateFilter.php | 73 +++++++++++++++++++ src/FFMpeg/Filters/Video/VideoFilters.php | 7 ++ .../Tests/Filters/Video/RotateFilterTest.php | 27 +++++++ 4 files changed, 111 insertions(+) create mode 100644 src/FFMpeg/Filters/Video/RotateFilter.php create mode 100644 tests/FFMpeg/Tests/Filters/Video/RotateFilterTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 61e683f..5059adf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ CHANGELOG --------- +* 0.4.2 (xx-xx-xx) + + * Add Rotate filter. + * 0.4.1 (11-26-2013) * Add Clip filter (@guimeira) diff --git a/src/FFMpeg/Filters/Video/RotateFilter.php b/src/FFMpeg/Filters/Video/RotateFilter.php new file mode 100644 index 0000000..95da3ad --- /dev/null +++ b/src/FFMpeg/Filters/Video/RotateFilter.php @@ -0,0 +1,73 @@ + + * + * 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\Exception\InvalidArgumentException; +use FFMpeg\Exception\RuntimeException; +use FFMpeg\Media\Video; +use FFMpeg\Format\VideoInterface; + +class RotateFilter implements VideoFilterInterface +{ + const ROTATE_90 = 'transpose=1'; + const ROTATE_180 = 'hflip,vflip'; + const ROTATE_270 = 'transpose=2'; + + /** @var string */ + private $angle; + /** @var integer */ + private $priority; + + public function __construct($angle, $priority = 0) + { + $this->setAngle($angle); + $this->priority = (int) $priority; + } + + /** + * {@inheritdoc} + */ + public function getPriority() + { + return $this->priority; + } + + /** + * @return Dimension + */ + public function getAngle() + { + return $this->angle; + } + + /** + * {@inheritdoc} + */ + public function apply(Video $video, VideoInterface $format) + { + return array('-vf', $this->angle, '-metadata:s:v:0', 'rotate=0'); + } + + private function setAngle($angle) + { + switch ($angle) { + case self::ROTATE_90: + case self::ROTATE_180: + case self::ROTATE_270: + $this->angle = $angle; + break; + default: + throw new InvalidArgumentException('Invalid angle value.'); + } + } +} diff --git a/src/FFMpeg/Filters/Video/VideoFilters.php b/src/FFMpeg/Filters/Video/VideoFilters.php index c8a31d8..a9faacb 100644 --- a/src/FFMpeg/Filters/Video/VideoFilters.php +++ b/src/FFMpeg/Filters/Video/VideoFilters.php @@ -95,4 +95,11 @@ class VideoFilters extends AudioFilters return $this; } + + public function rotate($angle, $priority = 0) + { + $this->media->addFilter(new RotateFilter($angle, $priority)); + + return $this; + } } diff --git a/tests/FFMpeg/Tests/Filters/Video/RotateFilterTest.php b/tests/FFMpeg/Tests/Filters/Video/RotateFilterTest.php new file mode 100644 index 0000000..103b313 --- /dev/null +++ b/tests/FFMpeg/Tests/Filters/Video/RotateFilterTest.php @@ -0,0 +1,27 @@ +getVideoMock(); + $format = $this->getMock('FFMpeg\Format\VideoInterface'); + + $filter = new RotateFilter(RotateFilter::ROTATE_90); + $this->assertEquals(array('-vf', RotateFilter::ROTATE_90, '-metadata:s:v:0', 'rotate=0'), $filter->apply($video, $format)); + } + + /** + * @expectedException \FFMpeg\Exception\InvalidArgumentException + * @expectedExceptionMessage Invalid angle value. + */ + public function testApplyInvalidAngle() + { + new RotateFilter('90'); + } +} From 7ede8579c612dd1aa6e85f0a48e9ca71111de377 Mon Sep 17 00:00:00 2001 From: Romain Neutron Date: Thu, 28 Nov 2013 18:39:28 +0100 Subject: [PATCH 2/5] Remove time_start metadata when using synchronize filter --- CHANGELOG.md | 1 + src/FFMpeg/Filters/Video/SynchronizeFilter.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5059adf..7535cde 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG * 0.4.2 (xx-xx-xx) * Add Rotate filter. + * Remove time_start metadata when using synchronize filter * 0.4.1 (11-26-2013) diff --git a/src/FFMpeg/Filters/Video/SynchronizeFilter.php b/src/FFMpeg/Filters/Video/SynchronizeFilter.php index 8ca72e0..97a4043 100644 --- a/src/FFMpeg/Filters/Video/SynchronizeFilter.php +++ b/src/FFMpeg/Filters/Video/SynchronizeFilter.php @@ -39,6 +39,6 @@ class SynchronizeFilter implements VideoFilterInterface */ public function apply(Video $video, VideoInterface $format) { - return array('-async', '1'); + return array('-async', '1', '-metadata:s:v:0', 'start_time=0'); } } From a87522759143aba354fe16d9e4dfdcf097b54613 Mon Sep 17 00:00:00 2001 From: Romain Neutron Date: Thu, 28 Nov 2013 18:49:13 +0100 Subject: [PATCH 3/5] Fix tests --- tests/FFMpeg/Tests/Filters/Video/SynchronizeFilterTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/FFMpeg/Tests/Filters/Video/SynchronizeFilterTest.php b/tests/FFMpeg/Tests/Filters/Video/SynchronizeFilterTest.php index 69fb681..7bf2dc2 100644 --- a/tests/FFMpeg/Tests/Filters/Video/SynchronizeFilterTest.php +++ b/tests/FFMpeg/Tests/Filters/Video/SynchronizeFilterTest.php @@ -13,6 +13,6 @@ class SynchronizeFilterTest extends TestCase $format = $this->getMock('FFMpeg\Format\VideoInterface'); $filter = new SynchronizeFilter(); - $this->assertEquals(array('-async', '1'), $filter->apply($video, $format)); + $this->assertEquals(array('-async', '1', '-metadata:s:v:0', 'start_time=0'), $filter->apply($video, $format)); } } From ad2b9e75a5c2bd57043f1c5ef5d08ad13ec2adb5 Mon Sep 17 00:00:00 2001 From: Romain Neutron Date: Fri, 29 Nov 2013 11:14:49 +0100 Subject: [PATCH 4/5] Fix #76 : Remove restriction on filesystem resources --- CHANGELOG.md | 1 + src/FFMpeg/FFMpeg.php | 7 +++---- .../AbstractProgressListener.php | 11 ++++++++++- src/FFMpeg/Media/AbstractMediaType.php | 11 ----------- .../FFMpeg/Functional/VideoTranscodeTest.php | 19 +++++++++++++++++++ tests/FFMpeg/Tests/FFMpegTest.php | 5 +++-- .../Tests/Media/AbstractMediaTestCase.php | 1 - tests/FFMpeg/Tests/Media/AudioTest.php | 11 ----------- tests/FFMpeg/Tests/Media/FrameTest.php | 8 -------- tests/FFMpeg/Tests/Media/VideoTest.php | 11 ----------- 10 files changed, 36 insertions(+), 49 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7535cde..26d9950 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG * Add Rotate filter. * Remove time_start metadata when using synchronize filter + * Remove restriction on filesystem resources. * 0.4.1 (11-26-2013) diff --git a/src/FFMpeg/FFMpeg.php b/src/FFMpeg/FFMpeg.php index 1abc05a..91ce361 100644 --- a/src/FFMpeg/FFMpeg.php +++ b/src/FFMpeg/FFMpeg.php @@ -14,6 +14,7 @@ namespace FFMpeg; use Alchemy\BinaryDriver\ConfigurationInterface; use FFMpeg\Driver\FFMpegDriver; use FFMpeg\Exception\InvalidArgumentException; +use FFMpeg\Exception\RuntimeException; use FFMpeg\Media\Audio; use FFMpeg\Media\Video; use Psr\Log\LoggerInterface; @@ -88,12 +89,10 @@ class FFMpeg */ public function open($pathfile) { - if (!file_exists($pathfile)) { - throw new InvalidArgumentException(sprintf('File %s does not exists', $pathfile)); + if (null === $streams = $this->ffprobe->streams($pathfile)) { + throw new RuntimeException(sprintf('Unable to probe "%s".', $pathfile)); } - $streams = $this->ffprobe->streams($pathfile); - if (0 < count($streams->videos())) { return new Video($pathfile, $this->driver, $this->ffprobe); } elseif (0 < count($streams->audios())) { diff --git a/src/FFMpeg/Format/ProgressListener/AbstractProgressListener.php b/src/FFMpeg/Format/ProgressListener/AbstractProgressListener.php index 8636825..cda6815 100644 --- a/src/FFMpeg/Format/ProgressListener/AbstractProgressListener.php +++ b/src/FFMpeg/Format/ProgressListener/AbstractProgressListener.php @@ -154,6 +154,10 @@ abstract class AbstractProgressListener extends EventEmitter implements Listener $this->initialize(); } + if (null === $this->totalSize || null === $this->duration) { + return; + } + $matches = array(); if (preg_match($this->getPattern(), $progress, $matches) !== 1) { @@ -226,9 +230,14 @@ abstract class AbstractProgressListener extends EventEmitter implements Listener private function initialize() { - $format = $this->ffprobe->format($this->pathfile); + try { + $format = $this->ffprobe->format($this->pathfile); + } catch (RuntimeException $e) { + return; + } if (false === $format->has('size') || false === $format->has('duration')) { + return; throw new RuntimeException(sprintf('Unable to probe format for %s', $this->pathfile)); } diff --git a/src/FFMpeg/Media/AbstractMediaType.php b/src/FFMpeg/Media/AbstractMediaType.php index 4cb3345..879265b 100644 --- a/src/FFMpeg/Media/AbstractMediaType.php +++ b/src/FFMpeg/Media/AbstractMediaType.php @@ -30,8 +30,6 @@ abstract class AbstractMediaType implements MediaTypeInterface public function __construct($pathfile, FFMpegDriver $driver, FFProbe $ffprobe) { - $this->ensureFileIsPresent($pathfile); - $this->pathfile = $pathfile; $this->driver = $driver; $this->ffprobe = $ffprobe; @@ -106,15 +104,6 @@ abstract class AbstractMediaType implements MediaTypeInterface 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)) { diff --git a/tests/FFMpeg/Functional/VideoTranscodeTest.php b/tests/FFMpeg/Functional/VideoTranscodeTest.php index 2b9ff24..38e230e 100644 --- a/tests/FFMpeg/Functional/VideoTranscodeTest.php +++ b/tests/FFMpeg/Functional/VideoTranscodeTest.php @@ -3,6 +3,7 @@ namespace FFMpeg\Functional; use FFMpeg\Format\Video\X264; +use FFMpeg\Media\Video; class VideoTranscodeTest extends FunctionalTestCase { @@ -35,4 +36,22 @@ class VideoTranscodeTest extends FunctionalTestCase $this->assertFileExists($filename); unlink($filename); } + + /** + * @expectedException \FFMpeg\Exception\RuntimeException + */ + public function testTranscodeInvalidFile() + { + $ffmpeg = $this->getFFMpeg(); + $ffmpeg->open(__DIR__ . '/../../files/UnknownFileTest.ogv'); + } + + public function testSaveInvalidForgedVideo() + { + $ffmpeg = $this->getFFMpeg(); + $video = new Video(__DIR__ . '/../../files/UnknownFileTest.ogv', $ffmpeg->getFFMpegDriver(), $ffmpeg->getFFProbe()); + + $this->setExpectedException('FFMpeg\Exception\RuntimeException'); + $video->save(new X264('libvo_aacenc'), __DIR__ . '/output/output-x264.mp4'); + } } diff --git a/tests/FFMpeg/Tests/FFMpegTest.php b/tests/FFMpeg/Tests/FFMpegTest.php index 6695df0..f9e8779 100644 --- a/tests/FFMpeg/Tests/FFMpegTest.php +++ b/tests/FFMpeg/Tests/FFMpegTest.php @@ -10,7 +10,8 @@ use FFMpeg\FFProbe\DataMapping\Stream; class FFMpegTest Extends TestCase { /** - * @expectedException FFMpeg\Exception\InvalidArgumentException + * @expectedException \FFMpeg\Exception\RuntimeException + * @expectedExceptionMessage Unable to probe "/path/to/unknown/file". */ public function testOpenInvalid() { @@ -58,7 +59,7 @@ class FFMpegTest Extends TestCase } /** - * @expectedException FFMpeg\Exception\InvalidArgumentException + * @expectedException \FFMpeg\Exception\InvalidArgumentException */ public function testOpenUnknown() { diff --git a/tests/FFMpeg/Tests/Media/AbstractMediaTestCase.php b/tests/FFMpeg/Tests/Media/AbstractMediaTestCase.php index 1af48a5..c30f198 100644 --- a/tests/FFMpeg/Tests/Media/AbstractMediaTestCase.php +++ b/tests/FFMpeg/Tests/Media/AbstractMediaTestCase.php @@ -6,5 +6,4 @@ use FFMpeg\Tests\TestCase; abstract class AbstractMediaTestCase extends TestCase { - abstract public function testWithInvalidFile(); } diff --git a/tests/FFMpeg/Tests/Media/AudioTest.php b/tests/FFMpeg/Tests/Media/AudioTest.php index 81b956a..2f37942 100644 --- a/tests/FFMpeg/Tests/Media/AudioTest.php +++ b/tests/FFMpeg/Tests/Media/AudioTest.php @@ -9,17 +9,6 @@ use FFMpeg\Format\AudioInterface; class AudioTest extends AbstractStreamableTestCase { - /** - * @expectedException FFMpeg\Exception\InvalidArgumentException - */ - public function testWithInvalidFile() - { - $driver = $this->getFFMpegDriverMock(); - $ffprobe = $this->getFFProbeMock(); - - new Audio('/no/file', $driver, $ffprobe); - } - public function testFiltersReturnsAudioFilters() { $driver = $this->getFFMpegDriverMock(); diff --git a/tests/FFMpeg/Tests/Media/FrameTest.php b/tests/FFMpeg/Tests/Media/FrameTest.php index b64d6e9..8f9af04 100644 --- a/tests/FFMpeg/Tests/Media/FrameTest.php +++ b/tests/FFMpeg/Tests/Media/FrameTest.php @@ -6,14 +6,6 @@ use FFMpeg\Media\Frame; class FrameTest extends AbstractMediaTestCase { - /** - * @expectedException FFMpeg\Exception\InvalidArgumentException - */ - public function testWithInvalidFile() - { - new Frame($this->getVideoMock('/No/file'), $this->getFFMpegDriverMock(), $this->getFFProbeMock(), $this->getTimeCodeMock()); - } - public function testGetTimeCode() { $driver = $this->getFFMpegDriverMock(); diff --git a/tests/FFMpeg/Tests/Media/VideoTest.php b/tests/FFMpeg/Tests/Media/VideoTest.php index a2c776e..9f5e84c 100644 --- a/tests/FFMpeg/Tests/Media/VideoTest.php +++ b/tests/FFMpeg/Tests/Media/VideoTest.php @@ -9,17 +9,6 @@ use FFMpeg\Format\VideoInterface; class VideoTest extends AbstractStreamableTestCase { - /** - * @expectedException FFMpeg\Exception\InvalidArgumentException - */ - public function testWithInvalidFile() - { - $driver = $this->getFFMpegDriverMock(); - $ffprobe = $this->getFFProbeMock(); - - new Video('/no/file', $driver, $ffprobe); - } - public function testFiltersReturnsVideoFilters() { $driver = $this->getFFMpegDriverMock(); From 5a9d4a0f04b49afe48f81c797be6fe223bc97b00 Mon Sep 17 00:00:00 2001 From: Romain Neutron Date: Fri, 29 Nov 2013 11:38:16 +0100 Subject: [PATCH 5/5] Update README --- README.md | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5c3938b..d67c75f 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Check another amazing repo : [PHP FFMpeg extras](https://github.com/alchemy-fr/P ## Your attention please This library requires a working FFMpeg install. You will need both FFMpeg and FFProbe binaries to use it. -Be sure that these binaries can be located with system PATH to get the benefit of the binary detection, +Be sure that these binaries can be located with system PATH to get the benefit of the binary detection, otherwise you should have to explicitely give the binaries path on load. For Windows users : Please find the binaries at http://ffmpeg.zeranoe.com/builds/. @@ -75,8 +75,17 @@ $ffmpeg = FFMpeg\FFMpeg::create(array( ### Manipulate media -`FFMpeg\FFMpeg` creates media based on file paths. To open a file path, use the -`FFMpeg\FFMpeg::open` method. +`FFMpeg\FFMpeg` creates media based on URIs. URIs could be either a pointer to a +local filesystem resource, an HTTP resource or any resource supported by FFmpeg. + +**Note** : To list all supported resource type of your FFmpeg build, use the +`-protocols` command : + +``` +ffmpeg -protocols +``` + +To open a resource, use the `FFMpeg\FFMpeg::open` method. ```php $ffmpeg->open('video.mpeg'); @@ -145,6 +154,20 @@ $video ->synchronize(); ``` +###### Rotate + +Rotates a video to a given angle. + +```php +$video->filters()->rotate($angle); +``` + +The `$angle` parameter must be one of the following constants : + +- `FFMpeg\Filters\Video\RotateFilter::ROTATE_90` : 90° clockwise +- `FFMpeg\Filters\Video\RotateFilter::ROTATE_180` : 180° +- `FFMpeg\Filters\Video\RotateFilter::ROTATE_270` : 90° counterclockwise + ###### Resize Resizes a video to a given size.