From 15711a0e50b3486dd73107edb68ee5606922702f Mon Sep 17 00:00:00 2001 From: Romain Neutron Date: Wed, 4 Sep 2013 19:00:43 +0200 Subject: [PATCH 1/2] Add convenient Stream::getDimensions method to extract video dimension. --- CHANGELOG.md | 4 ++ src/FFMpeg/Exception/LogicException.php | 16 +++++ src/FFMpeg/FFProbe/DataMapping/Stream.php | 63 +++++++++++++++++++ src/FFMpeg/Filters/Video/ResizeFilter.php | 19 +++--- .../Tests/FFProbe/DataMapping/StreamTest.php | 42 +++++++++++++ 5 files changed, 134 insertions(+), 10 deletions(-) create mode 100644 src/FFMpeg/Exception/LogicException.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 88525aa..e519f4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ CHANGELOG --------- +* 0.3.3 (xx-xx-2013) + + * Add convenient Stream::getDimensions method to extract video dimension. + * 0.3.2 (08-08-2013) * Fix A/V synchronization over flash and HTML5 players. diff --git a/src/FFMpeg/Exception/LogicException.php b/src/FFMpeg/Exception/LogicException.php new file mode 100644 index 0000000..b73b567 --- /dev/null +++ b/src/FFMpeg/Exception/LogicException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FFMpeg\Exception; + +class LogicException extends \LogicException implements ExceptionInterface +{ +} diff --git a/src/FFMpeg/FFProbe/DataMapping/Stream.php b/src/FFMpeg/FFProbe/DataMapping/Stream.php index 9b0d170..e74503e 100644 --- a/src/FFMpeg/FFProbe/DataMapping/Stream.php +++ b/src/FFMpeg/FFProbe/DataMapping/Stream.php @@ -11,6 +11,10 @@ namespace FFMpeg\FFProbe\DataMapping; +use FFMpeg\Exception\LogicException; +use FFMpeg\Exception\RuntimeException; +use FFMpeg\Coordinate\Dimension; + class Stream extends AbstractData { /** @@ -32,4 +36,63 @@ class Stream extends AbstractData { return $this->has('codec_type') ? 'video' === $this->get('codec_type') : false; } + + /** + * Returns the dimension of the video stream. + * + * @return Dimension + * + * @throws LogicException In case the stream is not a video stream. + * @throws RuntimeException In case the dimensions can not be extracted. + */ + public function getDimensions() + { + if (!$this->isVideo()) { + throw new LogicException('Dimensions can only be retrieved from video streams.'); + } + + $width = $height = $sampleRatio = $displayRatio = null; + + if ($this->has('width')) { + $width = $this->get('width'); + } + if ($this->has('height')) { + $height = $this->get('height'); + } + if (null !== $ratio = $this->extractRatio($this, 'sample_aspect_ratio')) { + $sampleRatio = $ratio; + } + if (null !== $ratio = $this->extractRatio($this, 'display_aspect_ratio')) { + $displayRatio = $ratio; + } + + if (null === $height || null === $width) { + throw new RuntimeException('Unable to extract dimensions.'); + } + + if (null !== $displayRatio && null !== $sampleRatio) { + $width = round($width / $sampleRatio[0] * $sampleRatio[1] * $displayRatio[0] / $displayRatio[1]); + } + + return new Dimension($width, $height); + } + + /** + * Extracts a ratio from a string in a \d+:\d+ format given a key name. + * + * @param Stream $stream The stream where to look for the ratio. + * @param string $name the name of the key. + * @return null|array An array containing the width and the height, null if not found. + */ + private function extractRatio(Stream $stream, $name) + { + if ($stream->has($name)) { + $ratio = $stream->get($name); + if (preg_match('/\d+:\d+/', $ratio)) { + return array_map(function ($int) { return (int) $int; }, explode(':', $ratio)); + } + } + + return null; + } } diff --git a/src/FFMpeg/Filters/Video/ResizeFilter.php b/src/FFMpeg/Filters/Video/ResizeFilter.php index a3e407d..d1ef2b8 100644 --- a/src/FFMpeg/Filters/Video/ResizeFilter.php +++ b/src/FFMpeg/Filters/Video/ResizeFilter.php @@ -12,6 +12,7 @@ namespace FFMpeg\Filters\Video; use FFMpeg\Coordinate\Dimension; +use FFMpeg\Exception\RuntimeException; use FFMpeg\Media\Video; use FFMpeg\Format\VideoInterface; @@ -80,23 +81,21 @@ class ResizeFilter implements VideoFilterInterface */ public function apply(Video $video, VideoInterface $format) { - $originalWidth = $originalHeight = null; + $dimensions = null; + $commands = array(); foreach ($video->getStreams() as $stream) { if ($stream->isVideo()) { - if ($stream->has('width')) { - $originalWidth = $stream->get('width'); - } - if ($stream->has('height')) { - $originalHeight = $stream->get('height'); + try { + $dimensions = $stream->getDimensions(); + } catch (RuntimeException $e) { + } } } - $commands = array(); - - if ($originalHeight !== null && $originalWidth !== null) { - $dimensions = $this->getComputedDimensions(new Dimension($originalWidth, $originalHeight), $format->getModulus()); + if (null !== $dimensions) { + $dimensions = $this->getComputedDimensions($dimensions, $format->getModulus()); $commands[] = '-s'; $commands[] = $dimensions->getWidth() . 'x' . $dimensions->getHeight(); diff --git a/tests/FFMpeg/Tests/FFProbe/DataMapping/StreamTest.php b/tests/FFMpeg/Tests/FFProbe/DataMapping/StreamTest.php index e1ea7b6..c9f9c19 100644 --- a/tests/FFMpeg/Tests/FFProbe/DataMapping/StreamTest.php +++ b/tests/FFMpeg/Tests/FFProbe/DataMapping/StreamTest.php @@ -2,6 +2,7 @@ namespace FFMpeg\Tests\FFProbe\DataMapping; +use FFMpeg\Coordinate\Dimension; use FFMpeg\Tests\TestCase; use FFMpeg\FFProbe\DataMapping\Stream; @@ -40,4 +41,45 @@ class StreamTest extends TestCase array(false, array('codec_type' => 'audio')), ); } + + /** + * @expectedException FFMpeg\Exception\LogicException + * @expectedExceptionMessage Dimensions can only be retrieved from video streams. + */ + public function testGetDimensionsFromAudio() + { + $stream = new Stream(array('codec_type' => 'audio')); + $stream->getDimensions(); + } + + public function testGetDimensionsFromVideo() + { + $stream = new Stream(array('codec_type' => 'video', 'width' => 960, 'height' => 720)); + $this->assertEquals(new Dimension(960, 720), $stream->getDimensions()); + } + + /** + * @dataProvider provideInvalidPropertiesForDimensionsExtraction + * @expectedException FFMpeg\Exception\RuntimeException + * @expectedExceptionMessage Unable to extract dimensions. + */ + public function testUnableToGetDimensionsFromVideo($properties) + { + $stream = new Stream(array('codec_type' => 'video', 'width' => 960)); + $stream->getDimensions(); + } + + public function provideInvalidPropertiesForDimensionsExtraction() + { + return array( + array('codec_type' => 'video', 'width' => 960), + array('codec_type' => 'video', 'height' => 960), + ); + } + + public function testGetDimensionsFromVideoWithDisplayRatio() + { + $stream = new Stream(array('codec_type' => 'video', 'width' => 960, 'height' => 720, 'sample_aspect_ratio' => '4:3', 'display_aspect_ratio' => '16:9')); + $this->assertEquals(new Dimension(1280, 720), $stream->getDimensions()); + } } From f530d60118051af389d9a25342138fe5f5acd55b Mon Sep 17 00:00:00 2001 From: Romain Neutron Date: Wed, 4 Sep 2013 19:44:45 +0200 Subject: [PATCH 2/2] Break if dimension is found --- src/FFMpeg/Filters/Video/ResizeFilter.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/FFMpeg/Filters/Video/ResizeFilter.php b/src/FFMpeg/Filters/Video/ResizeFilter.php index d1ef2b8..c5f96cb 100644 --- a/src/FFMpeg/Filters/Video/ResizeFilter.php +++ b/src/FFMpeg/Filters/Video/ResizeFilter.php @@ -88,6 +88,7 @@ class ResizeFilter implements VideoFilterInterface if ($stream->isVideo()) { try { $dimensions = $stream->getDimensions(); + break; } catch (RuntimeException $e) { }