diff --git a/src/FFMpeg/Driver/FFMpegDriver.php b/src/FFMpeg/Driver/FFMpegDriver.php index 62aae29..fdb818c 100644 --- a/src/FFMpeg/Driver/FFMpegDriver.php +++ b/src/FFMpeg/Driver/FFMpegDriver.php @@ -16,6 +16,7 @@ use Alchemy\BinaryDriver\Configuration; use Alchemy\BinaryDriver\ConfigurationInterface; use Alchemy\BinaryDriver\Exception\ExecutableNotFoundException as BinaryDriverExecutableNotFound; use FFMpeg\Exception\ExecutableNotFoundException; +use FFMpeg\Exception\RuntimeException; use Psr\Log\LoggerInterface; class FFMpegDriver extends AbstractBinary @@ -54,4 +55,20 @@ class FFMpegDriver extends AbstractBinary throw new ExecutableNotFoundException('Unable to load FFMpeg', $e->getCode(), $e); } } + + /** + * Get ffmpeg version. + * + * @return string + * @throws RuntimeException + */ + public function getVersion() + { + preg_match('#version\s(\S+)#', $this->command('-version'), $version); + if (!isset($version[1])) { + throw new RuntimeException('Cannot to parse the ffmpeg version!'); + } + + return $version[1]; + } } diff --git a/src/FFMpeg/FFMpeg.php b/src/FFMpeg/FFMpeg.php index cbf59f0..322c6bb 100644 --- a/src/FFMpeg/FFMpeg.php +++ b/src/FFMpeg/FFMpeg.php @@ -60,6 +60,8 @@ class FFMpeg /** * Sets the ffmpeg driver. * + * @param FFMpegDriver $ffmpeg + * * @return FFMpeg */ public function setFFMpegDriver(FFMpegDriver $ffmpeg) diff --git a/src/FFMpeg/Filters/ComplexMedia/ANullSrcFilter.php b/src/FFMpeg/Filters/ComplexMedia/ANullSrcFilter.php index f995bc0..b18a2d3 100644 --- a/src/FFMpeg/Filters/ComplexMedia/ANullSrcFilter.php +++ b/src/FFMpeg/Filters/ComplexMedia/ANullSrcFilter.php @@ -44,6 +44,16 @@ class ANullSrcFilter extends AbstractComplexFilter $this->nbSamples = $nbSamples; } + /** + * Get name of the filter. + * + * @return string + */ + public function getName() + { + return 'anullsrc'; + } + /** * {@inheritdoc} */ @@ -51,7 +61,7 @@ class ANullSrcFilter extends AbstractComplexFilter { return array( '-filter_complex', - 'anullsrc' . $this->buildFilterOptions(array( + $this->getName() . $this->buildFilterOptions(array( 'channel_layout' => $this->channelLayout, 'sample_rate' => $this->sampleRate, 'nb_samples' => $this->nbSamples, diff --git a/src/FFMpeg/Filters/ComplexMedia/AbstractComplexFilter.php b/src/FFMpeg/Filters/ComplexMedia/AbstractComplexFilter.php index 152d672..dab450f 100644 --- a/src/FFMpeg/Filters/ComplexMedia/AbstractComplexFilter.php +++ b/src/FFMpeg/Filters/ComplexMedia/AbstractComplexFilter.php @@ -27,6 +27,16 @@ abstract class AbstractComplexFilter implements ComplexCompatibleFilter return $this->priority; } + /** + * Get minimal version of ffmpeg starting with which this filter is supported. + * + * @return string + */ + public function getMinimalFFMpegVersion() + { + return '0.3'; + } + /** * Generate the config of the filter. * diff --git a/src/FFMpeg/Filters/ComplexMedia/ComplexCompatibleFilter.php b/src/FFMpeg/Filters/ComplexMedia/ComplexCompatibleFilter.php index 38a2bb4..26310da 100644 --- a/src/FFMpeg/Filters/ComplexMedia/ComplexCompatibleFilter.php +++ b/src/FFMpeg/Filters/ComplexMedia/ComplexCompatibleFilter.php @@ -7,6 +7,20 @@ use FFMpeg\Media\ComplexMedia; interface ComplexCompatibleFilter extends FilterInterface { + /** + * Get name of the filter. + * + * @return string + */ + public function getName(); + + /** + * Get minimal version of ffmpeg starting with which this filter is supported. + * + * @return string + */ + public function getMinimalFFMpegVersion(); + /** * Apply the complex filter to the given media. * diff --git a/src/FFMpeg/Filters/ComplexMedia/ComplexFilterContainer.php b/src/FFMpeg/Filters/ComplexMedia/ComplexFilterContainer.php index 3dd0391..cf04cb5 100644 --- a/src/FFMpeg/Filters/ComplexMedia/ComplexFilterContainer.php +++ b/src/FFMpeg/Filters/ComplexMedia/ComplexFilterContainer.php @@ -70,6 +70,26 @@ class ComplexFilterContainer implements ComplexFilterInterface return $this->outLabels; } + /** + * Get name of the filter. + * + * @return string + */ + public function getName() + { + return $this->baseFilter->getName(); + } + + /** + * Get minimal version of ffmpeg starting with which this filter is supported. + * + * @return string + */ + public function getMinimalFFMpegVersion() + { + return $this->baseFilter->getMinimalFFMpegVersion(); + } + /** * {@inheritdoc} */ diff --git a/src/FFMpeg/Filters/ComplexMedia/CustomComplexFilter.php b/src/FFMpeg/Filters/ComplexMedia/CustomComplexFilter.php index 89fe626..1bc93f2 100644 --- a/src/FFMpeg/Filters/ComplexMedia/CustomComplexFilter.php +++ b/src/FFMpeg/Filters/ComplexMedia/CustomComplexFilter.php @@ -23,6 +23,16 @@ class CustomComplexFilter extends AbstractComplexFilter $this->filter = $filter; } + /** + * Get name of the filter. + * + * @return string + */ + public function getName() + { + return 'custom_filter'; + } + /** * {@inheritdoc} */ diff --git a/src/FFMpeg/Filters/ComplexMedia/SineFilter.php b/src/FFMpeg/Filters/ComplexMedia/SineFilter.php index 4726522..066701d 100644 --- a/src/FFMpeg/Filters/ComplexMedia/SineFilter.php +++ b/src/FFMpeg/Filters/ComplexMedia/SineFilter.php @@ -54,6 +54,26 @@ class SineFilter extends AbstractComplexFilter $this->samples_per_frame = $samples_per_frame; } + /** + * Get name of the filter. + * + * @return string + */ + public function getName() + { + return 'sine'; + } + + /** + * Get minimal version of ffmpeg starting with which this filter is supported. + * + * @return string + */ + public function getMinimalFFMpegVersion() + { + return '2.0'; + } + /** * Apply the complex filter to the given media. * @@ -65,7 +85,7 @@ class SineFilter extends AbstractComplexFilter { return array( '-filter_complex', - 'sine' . $this->buildFilterOptions(array( + $this->getName() . $this->buildFilterOptions(array( 'frequency' => $this->frequency, 'beep_factor' => $this->beep_factor, 'sample_rate' => $this->sample_rate, diff --git a/src/FFMpeg/Filters/ComplexMedia/TestSrcFilter.php b/src/FFMpeg/Filters/ComplexMedia/TestSrcFilter.php index 88348e3..139731c 100644 --- a/src/FFMpeg/Filters/ComplexMedia/TestSrcFilter.php +++ b/src/FFMpeg/Filters/ComplexMedia/TestSrcFilter.php @@ -189,6 +189,16 @@ class TestSrcFilter extends AbstractComplexFilter $this->decimals = $decimals; } + /** + * Get name of the filter. + * + * @return string + */ + public function getName() + { + return $this->type; + } + /** * {@inheritdoc} */ diff --git a/src/FFMpeg/Filters/ComplexMedia/XStackFilter.php b/src/FFMpeg/Filters/ComplexMedia/XStackFilter.php index 199e052..f90a02e 100644 --- a/src/FFMpeg/Filters/ComplexMedia/XStackFilter.php +++ b/src/FFMpeg/Filters/ComplexMedia/XStackFilter.php @@ -6,13 +6,14 @@ use FFMpeg\Media\ComplexMedia; /** * "xstack" filter. - * Warning: this filter is supported starting from 4.1 ffmpeg version. + * This filter helps you to create a collage from the given videos. + * This filter is supported starting from 4.1 ffmpeg version. + * (On early versions you can use combinations of hstack and vstack filters). * * @see https://ffmpeg.org/ffmpeg-filters.html#xstack */ class XStackFilter extends AbstractComplexFilter { - const MINIMAL_FFMPEG_VERSION = '4.1'; const LAYOUT_2X2 = '0_0|0_h0|w0_0|w0_h0'; const LAYOUT_1X4 = '0_0|0_h0|0_h0+h1|0_h0+h1+h2'; const LAYOUT_3X3 = '0_0|0_h0|0_h0+h1|w0_0|w0_h0|w0_h0+h1|w0+w3_0|w0+w3_h0|w0+w3_h0+h1'; @@ -56,6 +57,26 @@ class XStackFilter extends AbstractComplexFilter return $result; } + /** + * Get name of the filter. + * + * @return string + */ + public function getName() + { + return 'xstack'; + } + + /** + * Get minimal version of ffmpeg starting with which this filter is supported. + * + * @return string + */ + public function getMinimalFFMpegVersion() + { + return '4.1'; + } + /** * {@inheritdoc} */ @@ -63,7 +84,7 @@ class XStackFilter extends AbstractComplexFilter { return array( '-filter_complex', - 'xstack' . $this->buildFilterOptions(array( + $this->getName() . $this->buildFilterOptions(array( 'inputs' => $this->inputsCount, 'layout' => $this->layout )) diff --git a/src/FFMpeg/Filters/Video/PadFilter.php b/src/FFMpeg/Filters/Video/PadFilter.php index 501c412..edee7b3 100644 --- a/src/FFMpeg/Filters/Video/PadFilter.php +++ b/src/FFMpeg/Filters/Video/PadFilter.php @@ -46,6 +46,26 @@ class PadFilter implements VideoFilterInterface, ComplexCompatibleFilter return $this->dimension; } + /** + * Get name of the filter. + * + * @return string + */ + public function getName() + { + return 'pad'; + } + + /** + * Get minimal version of ffmpeg starting with which this filter is supported. + * + * @return string + */ + public function getMinimalFFMpegVersion() + { + return '0.4.9'; + } + /** * {@inheritdoc} */ diff --git a/src/FFMpeg/Filters/Video/WatermarkFilter.php b/src/FFMpeg/Filters/Video/WatermarkFilter.php index 45e3532..bb4cae8 100644 --- a/src/FFMpeg/Filters/Video/WatermarkFilter.php +++ b/src/FFMpeg/Filters/Video/WatermarkFilter.php @@ -45,6 +45,26 @@ class WatermarkFilter implements VideoFilterInterface, ComplexCompatibleFilter return $this->priority; } + /** + * Get name of the filter. + * + * @return string + */ + public function getName() + { + return 'watermark'; + } + + /** + * Get minimal version of ffmpeg starting with which this filter is supported. + * + * @return string + */ + public function getMinimalFFMpegVersion() + { + return '0.8'; + } + /** * {@inheritdoc} */ diff --git a/src/FFMpeg/Media/ComplexMedia.php b/src/FFMpeg/Media/ComplexMedia.php index bf1db1d..bc92b61 100644 --- a/src/FFMpeg/Media/ComplexMedia.php +++ b/src/FFMpeg/Media/ComplexMedia.php @@ -221,9 +221,11 @@ class ComplexMedia extends AbstractMediaType * Apply added filters and execute ffmpeg command. * * @return ComplexMedia + * @throws RuntimeException */ public function save() { + $this->assertFiltersAreCompatibleToCurrentFFMpegVersion(); $command = $this->buildCommand(); try { @@ -319,6 +321,28 @@ class ComplexMedia extends AbstractMediaType return $in . $strCommand . $out; } + /** + * @return void + * @throws RuntimeException + */ + protected function assertFiltersAreCompatibleToCurrentFFMpegVersion() + { + $messages = array(); + $currentVersion = $this->getFFMpegDriver()->getVersion(); + /** @var ComplexFilterInterface $filter */ + foreach ($this->filters as $filter) { + if (version_compare($currentVersion, $filter->getMinimalFFMpegVersion(), '<')) { + $messages[] = $filter->getName() . ' filter is supported starting from ' + . $filter->getMinimalFFMpegVersion() . ' ffmpeg version'; + } + } + + if (!empty($messages)) { + throw new RuntimeException(implode('; ', $messages) + . '; your ffmpeg version is ' . $currentVersion); + } + } + /** * @return array */ diff --git a/tests/Functional/ComplexMediaTest.php b/tests/Functional/ComplexMediaTest.php index bc8f5a7..b7ad40e 100644 --- a/tests/Functional/ComplexMediaTest.php +++ b/tests/Functional/ComplexMediaTest.php @@ -155,11 +155,12 @@ class ComplexMediaTest extends FunctionalTestCase */ public function testXStackFilter() { + $xStack = new XStackFilter('', 0); $ffmpeg = $this->getFFMpeg(); - $ffmpegVersion = $this->getFFMpegVersion(); - if (version_compare($ffmpegVersion, XStackFilter::MINIMAL_FFMPEG_VERSION) === -1) { - $this->markTestSkipped('XStack filter is supported starting from ' - . XStackFilter::MINIMAL_FFMPEG_VERSION . ' ffmpeg version, your version is ' + $ffmpegVersion = $ffmpeg->getFFMpegDriver()->getVersion(); + if (version_compare($ffmpegVersion, $xStack->getMinimalFFMpegVersion(), '<')) { + $this->markTestSkipped('XStack filter is supported starting from ffmpeg version ' + . $xStack->getMinimalFFMpegVersion() . ', your version is ' . $ffmpegVersion); return; } @@ -223,12 +224,12 @@ class ComplexMediaTest extends FunctionalTestCase $complexMedia1 = $ffmpeg->openComplex(array(__FILE__)); $complexMedia1 ->map(array('test'), $format, 'outputFile.mp4', false); - $this->assertContains('acodec', $complexMedia1->getFinalCommand()); + $this->assertStringContainsString('acodec', $complexMedia1->getFinalCommand()); $complexMedia2 = $ffmpeg->openComplex(array(__FILE__)); $complexMedia2 ->map(array('test'), $format, 'outputFile.mp4', true); - $this->assertNotContains('acodec', $complexMedia2->getFinalCommand()); + $this->assertStringNotContainsString('acodec', $complexMedia2->getFinalCommand()); } public function testForceDisableVideo() @@ -239,11 +240,11 @@ class ComplexMediaTest extends FunctionalTestCase $complexMedia1 = $ffmpeg->openComplex(array(__FILE__)); $complexMedia1->map(array('test'), $format, 'outputFile.mp4', false, false); - $this->assertContains('vcodec', $complexMedia1->getFinalCommand()); + $this->assertStringContainsString('vcodec', $complexMedia1->getFinalCommand()); $complexMedia2 = $ffmpeg->openComplex(array(__FILE__)); $complexMedia2->map(array('test'), $format, 'outputFile.mp4', false, true); - $this->assertNotContains('vcodec', $complexMedia2->getFinalCommand()); + $this->assertStringNotContainsString('vcodec', $complexMedia2->getFinalCommand()); } } diff --git a/tests/Functional/FunctionalTestCase.php b/tests/Functional/FunctionalTestCase.php index dc97876..15fcc26 100644 --- a/tests/Functional/FunctionalTestCase.php +++ b/tests/Functional/FunctionalTestCase.php @@ -14,16 +14,4 @@ abstract class FunctionalTestCase extends BaseTestCase { return FFMpeg::create(array('timeout' => 300)); } - - /** - * Get ffmpeg version. - * - * @return string - */ - public function getFFMpegVersion() - { - preg_match('#version\s(\S+)#', - $this->getFFMpeg()->getFFMpegDriver()->command('-version'), $version); - return $version[1]; - } }