diff --git a/CHANGELOG.md b/CHANGELOG.md index 61e683f..26d9950 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ CHANGELOG --------- +* 0.4.2 (xx-xx-xx) + + * Add Rotate filter. + * Remove time_start metadata when using synchronize filter + * Remove restriction on filesystem resources. + * 0.4.1 (11-26-2013) * Add Clip filter (@guimeira) 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. 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/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/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'); } } 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/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/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'); + } +} 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)); } } 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();