Added runtime check of the ffmpeg version before starting the command in the ComplexMedia. Added the FFMpegDriver::getVersion() method.
This commit is contained in:
parent
580fb21d5a
commit
d6f95508a9
15 changed files with 212 additions and 25 deletions
|
|
@ -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];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
))
|
))
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue