Added runtime check of the ffmpeg version before starting the command in the ComplexMedia. Added the FFMpegDriver::getVersion() method.

This commit is contained in:
CaliforniaMountainSnake 2020-02-26 12:33:45 +03:00
commit d6f95508a9
15 changed files with 212 additions and 25 deletions

View file

@ -16,6 +16,7 @@ use Alchemy\BinaryDriver\Configuration;
use Alchemy\BinaryDriver\ConfigurationInterface; use Alchemy\BinaryDriver\ConfigurationInterface;
use Alchemy\BinaryDriver\Exception\ExecutableNotFoundException as BinaryDriverExecutableNotFound; use Alchemy\BinaryDriver\Exception\ExecutableNotFoundException as BinaryDriverExecutableNotFound;
use FFMpeg\Exception\ExecutableNotFoundException; use FFMpeg\Exception\ExecutableNotFoundException;
use FFMpeg\Exception\RuntimeException;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
class FFMpegDriver extends AbstractBinary class FFMpegDriver extends AbstractBinary
@ -54,4 +55,20 @@ class FFMpegDriver extends AbstractBinary
throw new ExecutableNotFoundException('Unable to load FFMpeg', $e->getCode(), $e); 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];
}
} }

View file

@ -60,6 +60,8 @@ class FFMpeg
/** /**
* Sets the ffmpeg driver. * Sets the ffmpeg driver.
* *
* @param FFMpegDriver $ffmpeg
*
* @return FFMpeg * @return FFMpeg
*/ */
public function setFFMpegDriver(FFMpegDriver $ffmpeg) public function setFFMpegDriver(FFMpegDriver $ffmpeg)

View file

@ -44,6 +44,16 @@ class ANullSrcFilter extends AbstractComplexFilter
$this->nbSamples = $nbSamples; $this->nbSamples = $nbSamples;
} }
/**
* Get name of the filter.
*
* @return string
*/
public function getName()
{
return 'anullsrc';
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
@ -51,7 +61,7 @@ class ANullSrcFilter extends AbstractComplexFilter
{ {
return array( return array(
'-filter_complex', '-filter_complex',
'anullsrc' . $this->buildFilterOptions(array( $this->getName() . $this->buildFilterOptions(array(
'channel_layout' => $this->channelLayout, 'channel_layout' => $this->channelLayout,
'sample_rate' => $this->sampleRate, 'sample_rate' => $this->sampleRate,
'nb_samples' => $this->nbSamples, 'nb_samples' => $this->nbSamples,

View file

@ -27,6 +27,16 @@ abstract class AbstractComplexFilter implements ComplexCompatibleFilter
return $this->priority; 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. * Generate the config of the filter.
* *

View file

@ -7,6 +7,20 @@ use FFMpeg\Media\ComplexMedia;
interface ComplexCompatibleFilter extends FilterInterface 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. * Apply the complex filter to the given media.
* *

View file

@ -70,6 +70,26 @@ class ComplexFilterContainer implements ComplexFilterInterface
return $this->outLabels; 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} * {@inheritdoc}
*/ */

View file

@ -23,6 +23,16 @@ class CustomComplexFilter extends AbstractComplexFilter
$this->filter = $filter; $this->filter = $filter;
} }
/**
* Get name of the filter.
*
* @return string
*/
public function getName()
{
return 'custom_filter';
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */

View file

@ -54,6 +54,26 @@ class SineFilter extends AbstractComplexFilter
$this->samples_per_frame = $samples_per_frame; $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. * Apply the complex filter to the given media.
* *
@ -65,7 +85,7 @@ class SineFilter extends AbstractComplexFilter
{ {
return array( return array(
'-filter_complex', '-filter_complex',
'sine' . $this->buildFilterOptions(array( $this->getName() . $this->buildFilterOptions(array(
'frequency' => $this->frequency, 'frequency' => $this->frequency,
'beep_factor' => $this->beep_factor, 'beep_factor' => $this->beep_factor,
'sample_rate' => $this->sample_rate, 'sample_rate' => $this->sample_rate,

View file

@ -189,6 +189,16 @@ class TestSrcFilter extends AbstractComplexFilter
$this->decimals = $decimals; $this->decimals = $decimals;
} }
/**
* Get name of the filter.
*
* @return string
*/
public function getName()
{
return $this->type;
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */

View file

@ -6,13 +6,14 @@ use FFMpeg\Media\ComplexMedia;
/** /**
* "xstack" filter. * "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 * @see https://ffmpeg.org/ffmpeg-filters.html#xstack
*/ */
class XStackFilter extends AbstractComplexFilter class XStackFilter extends AbstractComplexFilter
{ {
const MINIMAL_FFMPEG_VERSION = '4.1';
const LAYOUT_2X2 = '0_0|0_h0|w0_0|w0_h0'; 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_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'; 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; 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} * {@inheritdoc}
*/ */
@ -63,7 +84,7 @@ class XStackFilter extends AbstractComplexFilter
{ {
return array( return array(
'-filter_complex', '-filter_complex',
'xstack' . $this->buildFilterOptions(array( $this->getName() . $this->buildFilterOptions(array(
'inputs' => $this->inputsCount, 'inputs' => $this->inputsCount,
'layout' => $this->layout 'layout' => $this->layout
)) ))

View file

@ -46,6 +46,26 @@ class PadFilter implements VideoFilterInterface, ComplexCompatibleFilter
return $this->dimension; 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} * {@inheritdoc}
*/ */

View file

@ -45,6 +45,26 @@ class WatermarkFilter implements VideoFilterInterface, ComplexCompatibleFilter
return $this->priority; 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} * {@inheritdoc}
*/ */

View file

@ -221,9 +221,11 @@ class ComplexMedia extends AbstractMediaType
* Apply added filters and execute ffmpeg command. * Apply added filters and execute ffmpeg command.
* *
* @return ComplexMedia * @return ComplexMedia
* @throws RuntimeException
*/ */
public function save() public function save()
{ {
$this->assertFiltersAreCompatibleToCurrentFFMpegVersion();
$command = $this->buildCommand(); $command = $this->buildCommand();
try { try {
@ -319,6 +321,28 @@ class ComplexMedia extends AbstractMediaType
return $in . $strCommand . $out; 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 * @return array
*/ */

View file

@ -155,11 +155,12 @@ class ComplexMediaTest extends FunctionalTestCase
*/ */
public function testXStackFilter() public function testXStackFilter()
{ {
$xStack = new XStackFilter('', 0);
$ffmpeg = $this->getFFMpeg(); $ffmpeg = $this->getFFMpeg();
$ffmpegVersion = $this->getFFMpegVersion(); $ffmpegVersion = $ffmpeg->getFFMpegDriver()->getVersion();
if (version_compare($ffmpegVersion, XStackFilter::MINIMAL_FFMPEG_VERSION) === -1) { if (version_compare($ffmpegVersion, $xStack->getMinimalFFMpegVersion(), '<')) {
$this->markTestSkipped('XStack filter is supported starting from ' $this->markTestSkipped('XStack filter is supported starting from ffmpeg version '
. XStackFilter::MINIMAL_FFMPEG_VERSION . ' ffmpeg version, your version is ' . $xStack->getMinimalFFMpegVersion() . ', your version is '
. $ffmpegVersion); . $ffmpegVersion);
return; return;
} }
@ -223,12 +224,12 @@ class ComplexMediaTest extends FunctionalTestCase
$complexMedia1 = $ffmpeg->openComplex(array(__FILE__)); $complexMedia1 = $ffmpeg->openComplex(array(__FILE__));
$complexMedia1 $complexMedia1
->map(array('test'), $format, 'outputFile.mp4', false); ->map(array('test'), $format, 'outputFile.mp4', false);
$this->assertContains('acodec', $complexMedia1->getFinalCommand()); $this->assertStringContainsString('acodec', $complexMedia1->getFinalCommand());
$complexMedia2 = $ffmpeg->openComplex(array(__FILE__)); $complexMedia2 = $ffmpeg->openComplex(array(__FILE__));
$complexMedia2 $complexMedia2
->map(array('test'), $format, 'outputFile.mp4', true); ->map(array('test'), $format, 'outputFile.mp4', true);
$this->assertNotContains('acodec', $complexMedia2->getFinalCommand()); $this->assertStringNotContainsString('acodec', $complexMedia2->getFinalCommand());
} }
public function testForceDisableVideo() public function testForceDisableVideo()
@ -239,11 +240,11 @@ class ComplexMediaTest extends FunctionalTestCase
$complexMedia1 = $ffmpeg->openComplex(array(__FILE__)); $complexMedia1 = $ffmpeg->openComplex(array(__FILE__));
$complexMedia1->map(array('test'), $format, $complexMedia1->map(array('test'), $format,
'outputFile.mp4', false, false); 'outputFile.mp4', false, false);
$this->assertContains('vcodec', $complexMedia1->getFinalCommand()); $this->assertStringContainsString('vcodec', $complexMedia1->getFinalCommand());
$complexMedia2 = $ffmpeg->openComplex(array(__FILE__)); $complexMedia2 = $ffmpeg->openComplex(array(__FILE__));
$complexMedia2->map(array('test'), $format, $complexMedia2->map(array('test'), $format,
'outputFile.mp4', false, true); 'outputFile.mp4', false, true);
$this->assertNotContains('vcodec', $complexMedia2->getFinalCommand()); $this->assertStringNotContainsString('vcodec', $complexMedia2->getFinalCommand());
} }
} }

View file

@ -14,16 +14,4 @@ abstract class FunctionalTestCase extends BaseTestCase
{ {
return FFMpeg::create(array('timeout' => 300)); 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];
}
} }