diff --git a/.travis.yml b/.travis.yml index 3d30c1e..93f32e8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,3 +14,4 @@ php: script: - vendor/bin/phpunit + - vendor/bin/phpunit -c phpunit-functional.xml.dist diff --git a/CHANGELOG.md b/CHANGELOG.md index 512d779..3e6796d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ CHANGELOG * 0.3.5 (xx-xx-2013) * Add vorbis audio format (@jacobbudin). + * Fix #66 : Allow single pass encodings. * 0.3.4 (05-09-2013) diff --git a/composer.json b/composer.json index 1a680d5..4020aa1 100644 --- a/composer.json +++ b/composer.json @@ -17,10 +17,11 @@ } ], "require": { - "php" : ">=5.3.3", - "alchemy/binary-driver" : "~1.5", - "doctrine/cache" : "~1.0", - "evenement/evenement" : "~1.0" + "php" : ">=5.3.3", + "alchemy/binary-driver" : "~1.5", + "doctrine/cache" : "~1.0", + "evenement/evenement" : "~1.0", + "neutron/temporary-filesystem" : "~2.1, >=2.1.1" }, "suggest": { "php-ffmpeg/extras" : "A compilation of common audio & video drivers for PHP-FFMpeg" diff --git a/src/FFMpeg/Media/Video.php b/src/FFMpeg/Media/Video.php index 006e346..b51549a 100644 --- a/src/FFMpeg/Media/Video.php +++ b/src/FFMpeg/Media/Video.php @@ -14,12 +14,14 @@ namespace FFMpeg\Media; use Alchemy\BinaryDriver\Exception\ExecutionFailureException; use FFMpeg\Coordinate\TimeCode; use FFMpeg\Filters\Audio\SimpleFilter; +use FFMpeg\Exception\InvalidArgumentException; use FFMpeg\Exception\RuntimeException; use FFMpeg\Filters\Video\VideoFilters; use FFMpeg\Filters\FilterInterface; use FFMpeg\Format\FormatInterface; use FFMpeg\Format\ProgressableInterface; use FFMpeg\Media\Frame; +use Neutron\TemporaryFilesystem\Manager as FsManager; class Video extends Audio { @@ -104,32 +106,40 @@ class Video extends Audio $commands[] = $format->getAudioKiloBitrate() . 'k'; } - $passPrefix = uniqid('pass-'); + $fs = FsManager::create(); + $fsId = uniqid('ffmpeg-passes'); + $passPrefix = $fs->createTemporaryDirectory(0777, 50, $fsId) . '/' . uniqid('pass-'); + $passes = array(); + $totalPasses = $format->getPasses(); - $pass1 = $commands; - $pass2 = $commands; + if (1 > $totalPasses) { + throw new InvalidArgumentException('Pass number should be a positive value.'); + } - $pass1[] = '-pass'; - $pass1[] = '1'; - $pass1[] = '-passlogfile'; - $pass1[] = $passPrefix; - $pass1[] = $outputPathfile; + for ($i = 1; $i <= $totalPasses; $i++) { + $pass = $commands; - $pass2[] = '-pass'; - $pass2[] = '2'; - $pass2[] = '-passlogfile'; - $pass2[] = $passPrefix; - $pass2[] = $outputPathfile; + if ($totalPasses > 1) { + $pass[] = '-pass'; + $pass[] = $i; + $pass[] = '-passlogfile'; + $pass[] = $passPrefix; + } + + $pass[] = $outputPathfile; + + $passes[] = $pass; + } $failure = null; - foreach (array($pass1, $pass2) as $pass => $passCommands) { + foreach ($passes as $pass => $passCommands) { try { /** add listeners here */ $listeners = null; if ($format instanceof ProgressableInterface) { - $listeners = $format->createProgressListener($this, $this->ffprobe, $pass + 1, 2); + $listeners = $format->createProgressListener($this, $this->ffprobe, $pass + 1, $totalPasses); } $this->driver->command($passCommands, false, $listeners); @@ -139,10 +149,7 @@ class Video extends Audio } } - $this - ->cleanupTemporaryFile(getcwd() . '/' . $passPrefix . '-0.log') - ->cleanupTemporaryFile(getcwd() . '/' . $passPrefix . '-0.log') - ->cleanupTemporaryFile(getcwd() . '/' . $passPrefix . '-0.log.mbtree'); + $fs->clean($fsId); if (null !== $failure) { throw new RuntimeException('Encoding failed', $failure->getCode(), $failure); diff --git a/tests/FFMpeg/Tests/Media/VideoTest.php b/tests/FFMpeg/Tests/Media/VideoTest.php index 3cc5771..e0f0ee9 100644 --- a/tests/FFMpeg/Tests/Media/VideoTest.php +++ b/tests/FFMpeg/Tests/Media/VideoTest.php @@ -92,6 +92,9 @@ class VideoTest extends AbstractStreamableTestCase $ffprobe = $this->getFFProbeMock(); $outputPathfile = '/target/file'; $format = $this->getMock('FFMpeg\Format\VideoInterface'); + $format->expects($this->any()) + ->method('getPasses') + ->will($this->returnValue(1)); $format->expects($this->any()) ->method('getExtraParams') ->will($this->returnValue(array())); @@ -121,6 +124,9 @@ class VideoTest extends AbstractStreamableTestCase $format->expects($this->any()) ->method('getExtraParams') ->will($this->returnValue(array())); + $format->expects($this->any()) + ->method('getPasses') + ->will($this->returnValue(2)); $configuration = $this->getMock('Alchemy\BinaryDriver\ConfigurationInterface'); @@ -188,7 +194,7 @@ class VideoTest extends AbstractStreamableTestCase $capturedCommands = array(); $capturedListeners = null; - $driver->expects($this->exactly(2)) + $driver->expects($this->exactly(count($expectedCommands))) ->method('command') ->with($this->isType('array'), false, $this->anything()) ->will($this->returnCallback(function ($commands, $errors, $listeners) use (&$capturedCommands, &$capturedListeners) { @@ -201,21 +207,24 @@ class VideoTest extends AbstractStreamableTestCase $video = new Video(__FILE__, $driver, $ffprobe); $video->save($format, $outputPathfile); - $prefix = null; - foreach ($capturedCommands as $passKey => $pass) { - foreach ($pass as $command) { - if (0 === strpos($command, 'pass-')) { - $prefix = $command; - break; + $prefix = null; + if (count($expectedCommands) > 1) { + // look for pass commands only in multipass cases + foreach ($pass as $command) { + $prefix = null; + if (false !== strpos($command, '/pass-')) { + $prefix = $command; + break; + } + } + + if (null === $prefix) { + $this->fail('Unable to find pass prefix command.'); } } - if (null === $prefix) { - $this->fail('Unable to find pass prefix command.'); - } - - $found = false; + $found = false || (null === $prefix); foreach ($pass as $key => $command) { if ($command === $prefix) { $found = true; @@ -246,6 +255,9 @@ class VideoTest extends AbstractStreamableTestCase $format->expects($this->any()) ->method('getAudioKiloBitrate') ->will($this->returnValue(92)); + $format->expects($this->any()) + ->method('getPasses') + ->will($this->returnValue(2)); $audioVideoFormat = $this->getMock('FFMpeg\Format\VideoInterface'); $audioVideoFormat->expects($this->any()) @@ -263,6 +275,29 @@ class VideoTest extends AbstractStreamableTestCase $audioVideoFormat->expects($this->any()) ->method('getAudioKiloBitrate') ->will($this->returnValue(92)); + $audioVideoFormat->expects($this->any()) + ->method('getPasses') + ->will($this->returnValue(2)); + + $audioVideoFormatSinglePass = $this->getMock('FFMpeg\Format\VideoInterface'); + $audioVideoFormatSinglePass->expects($this->any()) + ->method('getExtraParams') + ->will($this->returnValue(array())); + $audioVideoFormatSinglePass->expects($this->any()) + ->method('getVideoCodec') + ->will($this->returnValue('gloubi-boulga-video')); + $audioVideoFormatSinglePass->expects($this->any()) + ->method('getAudioCodec') + ->will($this->returnValue('patati-patata-audio')); + $audioVideoFormatSinglePass->expects($this->any()) + ->method('getKiloBitrate') + ->will($this->returnValue(664)); + $audioVideoFormatSinglePass->expects($this->any()) + ->method('getAudioKiloBitrate') + ->will($this->returnValue(92)); + $audioVideoFormatSinglePass->expects($this->any()) + ->method('getPasses') + ->will($this->returnValue(1)); $formatExtra = $this->getMock('FFMpeg\Format\VideoInterface'); $formatExtra->expects($this->any()) @@ -274,6 +309,9 @@ class VideoTest extends AbstractStreamableTestCase $formatExtra->expects($this->any()) ->method('getAudioKiloBitrate') ->will($this->returnValue(92)); + $formatExtra->expects($this->any()) + ->method('getPasses') + ->will($this->returnValue(2)); $listeners = array($this->getMock('Alchemy\BinaryDriver\Listeners\ListenerInterface')); @@ -291,6 +329,9 @@ class VideoTest extends AbstractStreamableTestCase $progressableFormat->expects($this->any()) ->method('getAudioKiloBitrate') ->will($this->returnValue(92)); + $progressableFormat->expects($this->any()) + ->method('getPasses') + ->will($this->returnValue(2)); return array( array(false, array(array( @@ -325,6 +366,15 @@ class VideoTest extends AbstractStreamableTestCase '-qdiff', '4', '-trellis', '1', '-b:a', '92k', '-pass', '2', '-passlogfile', '/target/file', )), null, $audioVideoFormat), + array(false, array(array( + '-y', '-i', __FILE__, + '-vcodec', 'gloubi-boulga-video', + '-acodec', 'patati-patata-audio', '-b:v', '664k', + '-refs', '6', '-coder', '1', '-sc_threshold', '40', '-flags', '+loop', + '-me_range', '16', '-subq', '7', '-i_qfactor', '0.71', '-qcomp', '0.6', + '-qdiff', '4', '-trellis', '1', '-b:a', '92k', + '/target/file', + )), null, $audioVideoFormatSinglePass), array(false, array(array( '-y', '-i', __FILE__, 'extra', 'param','-b:v', '665k', @@ -440,6 +490,9 @@ class VideoTest extends AbstractStreamableTestCase $format->expects($this->any()) ->method('getExtraParams') ->will($this->returnValue(array('param'))); + $format->expects($this->any()) + ->method('getPasses') + ->will($this->returnValue(2)); $video = new Video(__FILE__, $driver, $ffprobe); $video->save($format, $outputPathfile); @@ -462,8 +515,9 @@ class VideoTest extends AbstractStreamableTestCase $n = 1; foreach ($capturedCommands as $capturedCommand) { + $prefix = null; foreach ($capturedCommand as $command) { - if (0 === strpos($command, 'pass-')) { + if (false !== strpos($command, '/pass-')) { $prefix = $command; break; }