diff --git a/CHANGELOG.md b/CHANGELOG.md index 26d9950..9d3e74e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,11 @@ CHANGELOG --------- -* 0.4.2 (xx-xx-xx) +* 0.4.3 (11-xx-2013) + + * Fix using rotate and resize filters at the same time (#78) + +* 0.4.2 (11-29-2013) * Add Rotate filter. * Remove time_start metadata when using synchronize filter diff --git a/README.md b/README.md index d67c75f..d0c4864 100644 --- a/README.md +++ b/README.md @@ -10,12 +10,20 @@ Check another amazing repo : [PHP FFMpeg extras](https://github.com/alchemy-fr/P ## Your attention please +### How this library works : + 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, 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/. +### Known issues : + +- Using rotate and resize will produce a corrupted output when using +[libav](http://libav.org/) 0.8. The bug is fixed in version 9. This bug does not +appear in latest ffmpeg version. + ## Installation The recommended way to install PHP-FFMpeg is through [Composer](https://getcomposer.org). diff --git a/src/FFMpeg/FFProbe.php b/src/FFMpeg/FFProbe.php index 7a32105..f4790bd 100644 --- a/src/FFMpeg/FFProbe.php +++ b/src/FFMpeg/FFProbe.php @@ -226,8 +226,13 @@ class FFProbe $parseIsToDo = false; if ($allowJson && $this->optionsTester->has('-print_format')) { + // allowed in latest PHP-FFmpeg version $commands[] = '-print_format'; $commands[] = 'json'; + } elseif ($allowJson && $this->optionsTester->has('-of')) { + // option has changed in avconv 9 + $commands[] = '-of'; + $commands[] = 'json'; } else { $parseIsToDo = true; } diff --git a/src/FFMpeg/FFProbe/DataMapping/AbstractData.php b/src/FFMpeg/FFProbe/DataMapping/AbstractData.php index d35e2b6..63134b2 100644 --- a/src/FFMpeg/FFProbe/DataMapping/AbstractData.php +++ b/src/FFMpeg/FFProbe/DataMapping/AbstractData.php @@ -50,6 +50,21 @@ abstract class AbstractData implements \Countable return $this->properties[$property]; } + /** + * Sets the property value given its name. + * + * @param string $property + * @param mixed $value + * + * @return AbstractData + */ + public function set($property, $value) + { + $this->properties[$property] = $value; + + return $this; + } + /** * Returns all property names. * diff --git a/src/FFMpeg/Filters/Video/RotateFilter.php b/src/FFMpeg/Filters/Video/RotateFilter.php index 95da3ad..5902a54 100644 --- a/src/FFMpeg/Filters/Video/RotateFilter.php +++ b/src/FFMpeg/Filters/Video/RotateFilter.php @@ -55,6 +55,16 @@ class RotateFilter implements VideoFilterInterface */ public function apply(Video $video, VideoInterface $format) { + if (in_array($this->angle, array(self::ROTATE_90, self::ROTATE_270), true)) { + foreach ($video->getStreams()->videos() as $stream) { + if ($stream->has('width') && $stream->has('height')) { + $width = $stream->get('width'); + $stream->set('width', $stream->get('height')); + $stream->set('height', $width); + } + } + } + return array('-vf', $this->angle, '-metadata:s:v:0', 'rotate=0'); } diff --git a/src/FFMpeg/Filters/Video/VideoFilters.php b/src/FFMpeg/Filters/Video/VideoFilters.php index a9faacb..30b4b2a 100644 --- a/src/FFMpeg/Filters/Video/VideoFilters.php +++ b/src/FFMpeg/Filters/Video/VideoFilters.php @@ -96,9 +96,9 @@ class VideoFilters extends AudioFilters return $this; } - public function rotate($angle, $priority = 0) + public function rotate($angle) { - $this->media->addFilter(new RotateFilter($angle, $priority)); + $this->media->addFilter(new RotateFilter($angle, 30)); return $this; } diff --git a/src/FFMpeg/Media/AbstractStreamableMedia.php b/src/FFMpeg/Media/AbstractStreamableMedia.php index 5458015..e09bba0 100644 --- a/src/FFMpeg/Media/AbstractStreamableMedia.php +++ b/src/FFMpeg/Media/AbstractStreamableMedia.php @@ -16,12 +16,18 @@ use FFMpeg\FFProbe\DataMapping\StreamCollection; abstract class AbstractStreamableMedia extends AbstractMediaType { + private $streams; + /** * @return StreamCollection */ public function getStreams() { - return $this->ffprobe->streams($this->pathfile); + if (null === $this->streams) { + $this->streams = $this->ffprobe->streams($this->pathfile); + } + + return $this->streams; } /** diff --git a/tests/FFMpeg/Functional/FunctionalTestCase.php b/tests/FFMpeg/Functional/FunctionalTestCase.php index 062bf68..25e6461 100644 --- a/tests/FFMpeg/Functional/FunctionalTestCase.php +++ b/tests/FFMpeg/Functional/FunctionalTestCase.php @@ -6,6 +6,9 @@ use FFMpeg\FFMpeg; abstract class FunctionalTestCase extends \PHPUnit_Framework_TestCase { + /** + * @return FFMpeg + */ public function getFFMpeg() { return FFMpeg::create(array('timeout' => 300)); diff --git a/tests/FFMpeg/Functional/VideoTranscodeTest.php b/tests/FFMpeg/Functional/VideoTranscodeTest.php index 38e230e..6dde247 100644 --- a/tests/FFMpeg/Functional/VideoTranscodeTest.php +++ b/tests/FFMpeg/Functional/VideoTranscodeTest.php @@ -2,11 +2,14 @@ namespace FFMpeg\Functional; +use FFMpeg\Coordinate\Dimension; +use FFMpeg\Filters\Video\ResizeFilter; +use FFMpeg\Filters\Video\RotateFilter; use FFMpeg\Format\Video\X264; use FFMpeg\Media\Video; class VideoTranscodeTest extends FunctionalTestCase -{ +{ public function testSimpleTranscodeX264() { $filename = __DIR__ . '/output/output-x264.mp4'; @@ -54,4 +57,62 @@ class VideoTranscodeTest extends FunctionalTestCase $this->setExpectedException('FFMpeg\Exception\RuntimeException'); $video->save(new X264('libvo_aacenc'), __DIR__ . '/output/output-x264.mp4'); } + + public function testTranscodePortraitVideo() + { + $info = $this->getNameAndVersion(); + + if ($info['name'] === 'avconv' && version_compare($info['version'], '0.9', '<')) { + $this->markTestSkipped('This version of avconv is buggy and does not support this test.'); + } + + $filename = __DIR__ . '/output/output-x264.mp4'; + if (is_file($filename)) { + unlink(__DIR__ . '/output/output-x264.mp4'); + } + + $ffmpeg = $this->getFFMpeg(); + $video = $ffmpeg->open(__DIR__ . '/../../files/portrait.MOV'); + + $video->filters() + ->resize(new Dimension(320, 240), ResizeFilter::RESIZEMODE_INSET) + ->rotate(RotateFilter::ROTATE_90); + $video->save(new X264('libvo_aacenc'), $filename); + + $dimension = $ffmpeg->getFFProbe() + ->streams($filename) + ->videos() + ->first() + ->getDimensions(); + + $this->assertLessThan(1, $dimension->getRatio(false)->getValue()); + $this->assertEquals(240, $dimension->getHeight()); + + $this->assertFileExists($filename); + unlink($filename); + } + + private function getNameAndVersion() + { + $binary = $this + ->getFFMpeg() + ->getFFMpegDriver() + ->getProcessBuilderFactory() + ->getBinary(); + + $output = $matches = null; + exec($binary . ' -version 2>&1', $output); + + if (!isset($output[0])) { + return array('name' => null, 'version' => null); + } + + preg_match('/^([a-z]+)\s+version\s+([0-9\.]+)/i', $output[0], $matches); + + if (count($matches) > 0) { + return array('name' => $matches[1], 'version' => $matches[2]); + } + + return array('name' => null, 'version' => null); + } } diff --git a/tests/FFMpeg/Tests/Filters/Video/RotateFilterTest.php b/tests/FFMpeg/Tests/Filters/Video/RotateFilterTest.php index 103b313..95c676f 100644 --- a/tests/FFMpeg/Tests/Filters/Video/RotateFilterTest.php +++ b/tests/FFMpeg/Tests/Filters/Video/RotateFilterTest.php @@ -2,18 +2,63 @@ namespace FFMpeg\Tests\Filters\Video; +use FFMpeg\FFProbe\DataMapping\Stream; +use FFMpeg\FFProbe\DataMapping\StreamCollection; use FFMpeg\Filters\Video\RotateFilter; use FFMpeg\Tests\TestCase; class RotateFilterTest extends TestCase { - public function testApply() + /** + * @dataProvider provide90degresTranspositions + */ + public function testApplyWithSizeTransformation($value) { + $stream = new Stream(array('width' => 320, 'height' => 240, 'codec_type' => 'video')); + $streams = new StreamCollection(array($stream)); + $video = $this->getVideoMock(); + $video->expects($this->once()) + ->method('getStreams') + ->will($this->returnValue($streams)); + $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)); + $filter = new RotateFilter($value); + $this->assertEquals(array('-vf', $value, '-metadata:s:v:0', 'rotate=0'), $filter->apply($video, $format)); + + $this->assertEquals(240, $stream->get('width')); + $this->assertEquals(320, $stream->get('height')); + } + + public function provide90degresTranspositions() + { + return array( + array(RotateFilter::ROTATE_90), + array(RotateFilter::ROTATE_270), + ); + } + + /** + * @dataProvider provideDegresWithoutTranspositions + */ + public function testApplyWithoutSizeTransformation($value) + { + $video = $this->getVideoMock(); + $video->expects($this->never()) + ->method('getStreams'); + + $format = $this->getMock('FFMpeg\Format\VideoInterface'); + + $filter = new RotateFilter($value); + $this->assertEquals(array('-vf', $value, '-metadata:s:v:0', 'rotate=0'), $filter->apply($video, $format)); + } + + public function provideDegresWithoutTranspositions() + { + return array( + array(RotateFilter::ROTATE_180), + ); } /** diff --git a/tests/files/portrait.MOV b/tests/files/portrait.MOV new file mode 100644 index 0000000..4d31edc Binary files /dev/null and b/tests/files/portrait.MOV differ