Added the ComplexMedia type and the possibility to use -filter_complex and -map features. Added some built-in complex filters. Added the unit and functional tests of the new features. README.md has been updated.
This commit is contained in:
parent
89b0c2b4d0
commit
f20ad8a82e
17 changed files with 1583 additions and 7 deletions
|
|
@ -16,6 +16,7 @@ use FFMpeg\Driver\FFMpegDriver;
|
|||
use FFMpeg\Exception\InvalidArgumentException;
|
||||
use FFMpeg\Exception\RuntimeException;
|
||||
use FFMpeg\Media\Audio;
|
||||
use FFMpeg\Media\ComplexMedia;
|
||||
use FFMpeg\Media\Video;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
|
|
@ -102,6 +103,18 @@ class FFMpeg
|
|||
throw new InvalidArgumentException('Unable to detect file format, only audio and video supported');
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens multiple input sources.
|
||||
*
|
||||
* @param string[] $inputs Array of files to be opened.
|
||||
*
|
||||
* @return ComplexMedia
|
||||
*/
|
||||
public function openComplex($inputs)
|
||||
{
|
||||
return new ComplexMedia($inputs, $this->driver, $this->ffprobe);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new FFMpeg instance.
|
||||
*
|
||||
|
|
|
|||
61
src/FFMpeg/Filters/ComplexMedia/ANullSrcFilter.php
Normal file
61
src/FFMpeg/Filters/ComplexMedia/ANullSrcFilter.php
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
namespace FFMpeg\Filters\ComplexMedia;
|
||||
|
||||
use FFMpeg\Media\ComplexMedia;
|
||||
|
||||
/**
|
||||
* @see https://ffmpeg.org/ffmpeg-filters.html#anullsrc
|
||||
*/
|
||||
class ANullSrcFilter extends AbstractComplexFilter
|
||||
{
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $channelLayout;
|
||||
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
private $sampleRate;
|
||||
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
private $nbSamples;
|
||||
|
||||
/**
|
||||
* ANullSrcComplexFilter constructor.
|
||||
*
|
||||
* @param string|null $channelLayout
|
||||
* @param int|null $sampleRate
|
||||
* @param int|null $nbSamples
|
||||
* @param int $priority
|
||||
*/
|
||||
public function __construct(
|
||||
$channelLayout = null,
|
||||
$sampleRate = null,
|
||||
$nbSamples = null,
|
||||
$priority = 0
|
||||
) {
|
||||
parent::__construct($priority);
|
||||
$this->channelLayout = $channelLayout;
|
||||
$this->sampleRate = $sampleRate;
|
||||
$this->nbSamples = $nbSamples;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function applyComplex(ComplexMedia $media)
|
||||
{
|
||||
return array(
|
||||
'-filter_complex',
|
||||
'anullsrc' . $this->buildFilterOptions(array(
|
||||
'channel_layout' => $this->channelLayout,
|
||||
'sample_rate' => $this->sampleRate,
|
||||
'nb_samples' => $this->nbSamples,
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
52
src/FFMpeg/Filters/ComplexMedia/AbstractComplexFilter.php
Normal file
52
src/FFMpeg/Filters/ComplexMedia/AbstractComplexFilter.php
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
namespace FFMpeg\Filters\ComplexMedia;
|
||||
|
||||
abstract class AbstractComplexFilter implements ComplexCompatibleFilter
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $priority;
|
||||
|
||||
/**
|
||||
* AbstractComplexFilter constructor.
|
||||
*
|
||||
* @param int $priority
|
||||
*/
|
||||
public function __construct($priority = 0)
|
||||
{
|
||||
$this->priority = $priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getPriority()
|
||||
{
|
||||
return $this->priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the config of the filter.
|
||||
*
|
||||
* @param array $params Associative array of filter options. The options may be null.
|
||||
*
|
||||
* @return string The string of the form "=name1=value1:name2=value2" or empty string.
|
||||
*/
|
||||
protected function buildFilterOptions(array $params)
|
||||
{
|
||||
$config = array();
|
||||
foreach ($params as $paramName => $paramValue) {
|
||||
if ($paramValue !== null) {
|
||||
$config[] = $paramName . '=' . $paramValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($config)) {
|
||||
return '=' . implode(':', $config);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
18
src/FFMpeg/Filters/ComplexMedia/ComplexCompatibleFilter.php
Normal file
18
src/FFMpeg/Filters/ComplexMedia/ComplexCompatibleFilter.php
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace FFMpeg\Filters\ComplexMedia;
|
||||
|
||||
use FFMpeg\Filters\FilterInterface;
|
||||
use FFMpeg\Media\ComplexMedia;
|
||||
|
||||
interface ComplexCompatibleFilter extends FilterInterface
|
||||
{
|
||||
/**
|
||||
* Apply the complex filter to the given media.
|
||||
*
|
||||
* @param ComplexMedia $media
|
||||
*
|
||||
* @return string[] An array of arguments.
|
||||
*/
|
||||
public function applyComplex(ComplexMedia $media);
|
||||
}
|
||||
80
src/FFMpeg/Filters/ComplexMedia/ComplexFilterContainer.php
Normal file
80
src/FFMpeg/Filters/ComplexMedia/ComplexFilterContainer.php
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
<?php
|
||||
|
||||
namespace FFMpeg\Filters\ComplexMedia;
|
||||
|
||||
use FFMpeg\Media\ComplexMedia;
|
||||
|
||||
/**
|
||||
* Container for the complex filter.
|
||||
*/
|
||||
class ComplexFilterContainer implements ComplexFilterInterface
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $priority;
|
||||
|
||||
/**
|
||||
* @var ComplexCompatibleFilter
|
||||
*/
|
||||
private $baseFilter;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $inLabels;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $outLabels;
|
||||
|
||||
/**
|
||||
* ComplexFilter constructor.
|
||||
*
|
||||
* @param string $inLabels
|
||||
* @param ComplexCompatibleFilter $baseFilter
|
||||
* @param string $outLabels
|
||||
*/
|
||||
public function __construct($inLabels, ComplexCompatibleFilter $baseFilter, $outLabels)
|
||||
{
|
||||
$this->priority = $baseFilter->getPriority();
|
||||
$this->inLabels = $inLabels;
|
||||
$this->baseFilter = $baseFilter;
|
||||
$this->outLabels = $outLabels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the priority of the filter.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getPriority()
|
||||
{
|
||||
return $this->priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getInLabels()
|
||||
{
|
||||
return $this->inLabels;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getOutLabels()
|
||||
{
|
||||
return $this->outLabels;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function applyComplex(ComplexMedia $media)
|
||||
{
|
||||
return $this->baseFilter->applyComplex($media);
|
||||
}
|
||||
}
|
||||
16
src/FFMpeg/Filters/ComplexMedia/ComplexFilterInterface.php
Normal file
16
src/FFMpeg/Filters/ComplexMedia/ComplexFilterInterface.php
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace FFMpeg\Filters\ComplexMedia;
|
||||
|
||||
interface ComplexFilterInterface extends ComplexCompatibleFilter
|
||||
{
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getInLabels();
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getOutLabels();
|
||||
}
|
||||
169
src/FFMpeg/Filters/ComplexMedia/ComplexFilters.php
Normal file
169
src/FFMpeg/Filters/ComplexMedia/ComplexFilters.php
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
<?php
|
||||
|
||||
namespace FFMpeg\Filters\ComplexMedia;
|
||||
|
||||
use FFMpeg\Coordinate\Dimension;
|
||||
use FFMpeg\Filters\Video\PadFilter;
|
||||
use FFMpeg\Filters\Video\WatermarkFilter;
|
||||
use FFMpeg\Media\ComplexMedia;
|
||||
|
||||
class ComplexFilters
|
||||
{
|
||||
/**
|
||||
* @var ComplexMedia
|
||||
*/
|
||||
protected $media;
|
||||
|
||||
/**
|
||||
* ComplexFilters constructor.
|
||||
*
|
||||
* @param ComplexMedia $media
|
||||
*/
|
||||
public function __construct(ComplexMedia $media)
|
||||
{
|
||||
$this->media = $media;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $in
|
||||
* @param string $parameters
|
||||
* @param string $out
|
||||
*
|
||||
* @return ComplexFilters
|
||||
*/
|
||||
public function custom($in, $parameters, $out)
|
||||
{
|
||||
$this->media->addFilter($in, new CustomComplexFilter($parameters), $out);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds padding (black bars) to a video.
|
||||
*
|
||||
* @param string $in
|
||||
* @param Dimension $dimension
|
||||
* @param string $out
|
||||
*
|
||||
* @return ComplexFilters
|
||||
*/
|
||||
public function pad($in, Dimension $dimension, $out)
|
||||
{
|
||||
$this->media->addFilter($in, new PadFilter($dimension), $out);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a watermark image to a video.
|
||||
*
|
||||
* @param string $in
|
||||
* @param string $imagePath
|
||||
* @param string $out
|
||||
* @param array $coordinates
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function watermark($in, $imagePath, $out, array $coordinates = array())
|
||||
{
|
||||
$this->media->addFilter($in, new WatermarkFilter($imagePath, $coordinates), $out);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply "xstack" filter.
|
||||
* Warning: this filter is supported starting from 4.1 ffmpeg version.
|
||||
*
|
||||
* @param string $in
|
||||
* @param string $layout
|
||||
* @param int $inputsCount
|
||||
* @param string $out
|
||||
*
|
||||
* @return ComplexFilters
|
||||
* @see https://ffmpeg.org/ffmpeg-filters.html#xstack
|
||||
*/
|
||||
public function xStack($in, $layout, $inputsCount, $out)
|
||||
{
|
||||
$this->media->addFilter($in, new XStackFilter($layout, $inputsCount), $out);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This filter build various types of computed inputs.
|
||||
*
|
||||
* @param string $out
|
||||
* @param string|null $type
|
||||
* @param string|null $size
|
||||
* @param string|null $duration
|
||||
* @param string|null $sar
|
||||
* @param string|null $rate
|
||||
* @param string|null $level
|
||||
* @param string|null $color
|
||||
* @param int|null $alpha
|
||||
* @param float|null $decimals
|
||||
*
|
||||
* @return ComplexFilters
|
||||
* @see https://ffmpeg.org/ffmpeg-filters.html#allrgb_002c-allyuv_002c-color_002c-haldclutsrc_002c-nullsrc_002c-pal75bars_002c-pal100bars_002c-rgbtestsrc_002c-smptebars_002c-smptehdbars_002c-testsrc_002c-testsrc2_002c-yuvtestsrc
|
||||
*/
|
||||
public function testSrc(
|
||||
$out,
|
||||
$type = TestSrcFilter::TESTSRC,
|
||||
$size = '320x240',
|
||||
$duration = null,
|
||||
$sar = null,
|
||||
$rate = null,
|
||||
$level = null,
|
||||
$color = null,
|
||||
$alpha = null,
|
||||
$decimals = null
|
||||
) {
|
||||
$this->media->addFilter('',
|
||||
new TestSrcFilter($type, $size, $duration, $sar, $rate, $level, $color, $alpha, $decimals), $out);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply "anullsrc" filter.
|
||||
*
|
||||
* @param string $out
|
||||
* @param string|null $channelLayout
|
||||
* @param int|null $sampleRate
|
||||
* @param int|null $nbSamples
|
||||
*
|
||||
* @return ComplexFilters
|
||||
* @see https://ffmpeg.org/ffmpeg-filters.html#anullsrc
|
||||
*/
|
||||
public function aNullSrc(
|
||||
$out,
|
||||
$channelLayout = null,
|
||||
$sampleRate = null,
|
||||
$nbSamples = null
|
||||
) {
|
||||
$this->media->addFilter('', new ANullSrcFilter($channelLayout, $sampleRate, $nbSamples), $out);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply "sine" filter.
|
||||
*
|
||||
* @param $out
|
||||
* @param string $duration
|
||||
* @param int|null $frequency
|
||||
* @param string|null $beep_factor
|
||||
* @param int|null $sample_rate
|
||||
* @param string|null $samples_per_frame
|
||||
*
|
||||
* @return $this
|
||||
* @see https://ffmpeg.org/ffmpeg-filters.html#sine
|
||||
*/
|
||||
public function sine(
|
||||
$out,
|
||||
$duration,
|
||||
$frequency = null,
|
||||
$beep_factor = null,
|
||||
$sample_rate = null,
|
||||
$samples_per_frame = null
|
||||
) {
|
||||
$this->media->addFilter('',
|
||||
new SineFilter($duration, $frequency, $beep_factor, $sample_rate, $samples_per_frame), $out);
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
33
src/FFMpeg/Filters/ComplexMedia/CustomComplexFilter.php
Normal file
33
src/FFMpeg/Filters/ComplexMedia/CustomComplexFilter.php
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
namespace FFMpeg\Filters\ComplexMedia;
|
||||
|
||||
use FFMpeg\Media\ComplexMedia;
|
||||
|
||||
class CustomComplexFilter extends AbstractComplexFilter
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $filter;
|
||||
|
||||
/**
|
||||
* CustomComplexFilter constructor.
|
||||
*
|
||||
* @param string $filter
|
||||
* @param int $priority
|
||||
*/
|
||||
public function __construct($filter, $priority = 0)
|
||||
{
|
||||
parent::__construct($priority);
|
||||
$this->filter = $filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function applyComplex(ComplexMedia $media)
|
||||
{
|
||||
return array('-filter_complex', $this->filter);
|
||||
}
|
||||
}
|
||||
77
src/FFMpeg/Filters/ComplexMedia/SineFilter.php
Normal file
77
src/FFMpeg/Filters/ComplexMedia/SineFilter.php
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
|
||||
namespace FFMpeg\Filters\ComplexMedia;
|
||||
|
||||
use FFMpeg\Media\ComplexMedia;
|
||||
|
||||
/**
|
||||
* @see https://ffmpeg.org/ffmpeg-filters.html#sine
|
||||
*/
|
||||
class SineFilter extends AbstractComplexFilter
|
||||
{
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
private $frequency;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $beep_factor;
|
||||
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
private $sample_rate;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $duration;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $samples_per_frame;
|
||||
|
||||
/**
|
||||
* SineComplexFilter constructor.
|
||||
*
|
||||
* @param string $duration
|
||||
* @param int|null $frequency
|
||||
* @param string|null $beep_factor
|
||||
* @param int|null $sample_rate
|
||||
* @param string|null $samples_per_frame
|
||||
* @param int $priority
|
||||
*/
|
||||
public function __construct($duration, $frequency, $beep_factor, $sample_rate, $samples_per_frame, $priority = 0)
|
||||
{
|
||||
parent::__construct($priority);
|
||||
$this->duration = $duration;
|
||||
$this->frequency = $frequency;
|
||||
$this->beep_factor = $beep_factor;
|
||||
$this->sample_rate = $sample_rate;
|
||||
$this->samples_per_frame = $samples_per_frame;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the complex filter to the given media.
|
||||
*
|
||||
* @param ComplexMedia $media
|
||||
*
|
||||
* @return string[] An array of arguments.
|
||||
*/
|
||||
public function applyComplex(ComplexMedia $media)
|
||||
{
|
||||
return array(
|
||||
'-filter_complex',
|
||||
'sine' . $this->buildFilterOptions(array(
|
||||
'frequency' => $this->frequency,
|
||||
'beep_factor' => $this->beep_factor,
|
||||
'sample_rate' => $this->sample_rate,
|
||||
'duration' => $this->duration,
|
||||
'samples_per_frame' => $this->samples_per_frame,
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
211
src/FFMpeg/Filters/ComplexMedia/TestSrcFilter.php
Normal file
211
src/FFMpeg/Filters/ComplexMedia/TestSrcFilter.php
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
<?php
|
||||
|
||||
namespace FFMpeg\Filters\ComplexMedia;
|
||||
|
||||
use FFMpeg\Media\ComplexMedia;
|
||||
|
||||
/**
|
||||
* This filter builds various types of computed inputs.
|
||||
*
|
||||
* @see https://ffmpeg.org/ffmpeg-filters.html#allrgb_002c-allyuv_002c-color_002c-haldclutsrc_002c-nullsrc_002c-pal75bars_002c-pal100bars_002c-rgbtestsrc_002c-smptebars_002c-smptehdbars_002c-testsrc_002c-testsrc2_002c-yuvtestsrc
|
||||
*/
|
||||
class TestSrcFilter extends AbstractComplexFilter
|
||||
{
|
||||
/**
|
||||
* Source returns frames of size 4096x4096 of all rgb colors.
|
||||
*/
|
||||
const ALLRGB = 'allrgb';
|
||||
|
||||
/**
|
||||
* Source returns frames of size 4096x4096 of all yuv colors.
|
||||
*/
|
||||
const ALLYUV = 'allyuv';
|
||||
|
||||
/**
|
||||
* Source provides an uniformly colored input.
|
||||
*/
|
||||
const COLOR = 'color';
|
||||
|
||||
/**
|
||||
* Source provides an identity Hald CLUT.
|
||||
*/
|
||||
const HALDCLUTSRC = 'haldclutsrc';
|
||||
|
||||
/**
|
||||
* Source returns unprocessed video frames.
|
||||
* It is mainly useful to be employed in analysis / debugging tools,
|
||||
* or as the source for filters which ignore the input data.
|
||||
*/
|
||||
const NULLSRC = 'nullsrc';
|
||||
|
||||
/**
|
||||
* Source generates a color bars pattern, based on EBU PAL recommendations with 75% color levels.
|
||||
*/
|
||||
const PAL75BARS = 'pal75bars';
|
||||
|
||||
/**
|
||||
* Source generates a color bars pattern, based on EBU PAL recommendations with 100% color levels.
|
||||
*/
|
||||
const PAL100BARS = 'pal100bars';
|
||||
|
||||
/**
|
||||
* Source generates an RGB test pattern useful for detecting RGB vs BGR issues.
|
||||
* You should see a red, green and blue stripe from top to bottom.
|
||||
*/
|
||||
const RGBTESTSRC = 'rgbtestsrc';
|
||||
|
||||
/**
|
||||
* Source generates a color bars pattern, based on the SMPTE Engineering Guideline EG 1-1990.
|
||||
*/
|
||||
const SMPTEBARS = 'smptebars';
|
||||
|
||||
/**
|
||||
* Source generates a color bars pattern, based on the SMPTE RP 219-2002.
|
||||
*/
|
||||
const SMPTEHDBARS = 'smptehdbars';
|
||||
|
||||
/**
|
||||
* Source generates a test video pattern, showing a color pattern, a scrolling gradient and a timestamp.
|
||||
* This is mainly intended for testing purposes.
|
||||
*/
|
||||
const TESTSRC = 'testsrc';
|
||||
|
||||
/**
|
||||
* Source is similar to testsrc, but supports more pixel formats instead of just rgb24.
|
||||
* This allows using it as an input for other tests without requiring a format conversion.
|
||||
*/
|
||||
const TESTSRC2 = 'testsrc2';
|
||||
|
||||
/**
|
||||
* Source generates an YUV test pattern. You should see a y, cb and cr stripe from top to bottom.
|
||||
*/
|
||||
const YUVTESTSRC = 'yuvtestsrc';
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $type;
|
||||
|
||||
/**
|
||||
* Specify the level of the Hald CLUT, only available in the haldclutsrc source.
|
||||
* A level of N generates a picture of N*N*N by N*N*N pixels to be used as identity matrix for 3D lookup tables.
|
||||
* Each component is coded on a 1/(N*N) scale.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
private $level;
|
||||
|
||||
/**
|
||||
* Specify the color of the source, only available in the color source.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
private $color;
|
||||
|
||||
/**
|
||||
* Specify the size of the sourced video.
|
||||
* This option is not available with the allrgb, allyuv, and haldclutsrc filters.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
private $size;
|
||||
|
||||
/**
|
||||
* Specify the frame rate of the sourced video, as the number of frames generated per second.
|
||||
* It has to be a string in the format frame_rate_num/frame_rate_den, an integer number,
|
||||
* a floating point number or a valid video frame rate abbreviation. The default value is "25".
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
private $rate;
|
||||
|
||||
/**
|
||||
* Set the duration of the sourced video.
|
||||
* If not specified, or the expressed duration is negative, the video is supposed to be generated forever.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
private $duration;
|
||||
|
||||
/**
|
||||
* Set the sample aspect ratio of the sourced video.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
private $sar;
|
||||
|
||||
/**
|
||||
* Specify the alpha (opacity) of the background, only available in the testsrc2 source.
|
||||
* The value must be between 0 (fully transparent) and 255 (fully opaque, the default).
|
||||
*
|
||||
* @var int|null
|
||||
*/
|
||||
private $alpha;
|
||||
|
||||
/**
|
||||
* Set the number of decimals to show in the timestamp, only available in the testsrc source.
|
||||
* The displayed timestamp value will correspond to the original timestamp value multiplied
|
||||
* by the power of 10 of the specified value. Default value is 0.
|
||||
*
|
||||
* @var float|null
|
||||
*/
|
||||
private $decimals;
|
||||
|
||||
/**
|
||||
* TestSrcComplexFilter constructor.
|
||||
*
|
||||
* @param string|null $type
|
||||
* @param string|null $size
|
||||
* @param string|null $duration
|
||||
* @param string|null $sar
|
||||
* @param string|null $rate
|
||||
* @param string|null $level
|
||||
* @param string|null $color
|
||||
* @param int|null $alpha
|
||||
* @param float|null $decimals
|
||||
* @param int|null $priority
|
||||
*/
|
||||
public function __construct(
|
||||
$type = self::TESTSRC,
|
||||
$size = '320x240',
|
||||
$duration = null,
|
||||
$sar = null,
|
||||
$rate = null,
|
||||
$level = null,
|
||||
$color = null,
|
||||
$alpha = null,
|
||||
$decimals = null,
|
||||
$priority = 0
|
||||
) {
|
||||
parent::__construct($priority);
|
||||
$this->type = $type;
|
||||
$this->level = $level;
|
||||
$this->color = $color;
|
||||
$this->size = $size;
|
||||
$this->rate = $rate;
|
||||
$this->duration = $duration;
|
||||
$this->sar = $sar;
|
||||
$this->alpha = $alpha;
|
||||
$this->decimals = $decimals;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function applyComplex(ComplexMedia $media)
|
||||
{
|
||||
return array(
|
||||
'-filter_complex',
|
||||
$this->type . $this->buildFilterOptions(array(
|
||||
'level' => $this->level,
|
||||
'color' => $this->color,
|
||||
'size' => $this->size,
|
||||
'rate' => $this->rate,
|
||||
'duration' => $this->duration,
|
||||
'sar' => $this->sar,
|
||||
'alpha' => $this->alpha,
|
||||
'decimals' => $this->decimals
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
71
src/FFMpeg/Filters/ComplexMedia/XStackFilter.php
Normal file
71
src/FFMpeg/Filters/ComplexMedia/XStackFilter.php
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
namespace FFMpeg\Filters\ComplexMedia;
|
||||
|
||||
use FFMpeg\Media\ComplexMedia;
|
||||
|
||||
/**
|
||||
* "xstack" filter.
|
||||
* Warning: this filter is supported starting from 4.1 ffmpeg version.
|
||||
*
|
||||
* @see https://ffmpeg.org/ffmpeg-filters.html#xstack
|
||||
*/
|
||||
class XStackFilter extends AbstractComplexFilter
|
||||
{
|
||||
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';
|
||||
const LAYOUT_4X4 = '0_0|0_h0|0_h0+h1|0_h0+h1+h2|w0_0|w0_h0|w0_h0+h1|w0_h0+h1+h2|w0+w4_0|w0+w4_h0|w0+w4_h0+h1|w0+w4_h0+h1+h2|w0+w4+w8_0|w0+w4+w8_h0|w0+w4+w8_h0+h1|w0+w4+w8_h0+h1+h2';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $layout;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $inputsCount;
|
||||
|
||||
/**
|
||||
* CustomComplexFilter constructor.
|
||||
*
|
||||
* @param string $layout
|
||||
* @param int $inputsCount
|
||||
* @param int $priority
|
||||
*/
|
||||
public function __construct($layout, $inputsCount, $priority = 0)
|
||||
{
|
||||
parent::__construct($priority);
|
||||
$this->layout = $layout;
|
||||
$this->inputsCount = $inputsCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $count
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getInputByCount($count)
|
||||
{
|
||||
$result = '';
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$result .= '[' . $i . ':v]';
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function applyComplex(ComplexMedia $media)
|
||||
{
|
||||
return array(
|
||||
'-filter_complex',
|
||||
'xstack' . $this->buildFilterOptions(array(
|
||||
'inputs' => $this->inputsCount,
|
||||
'layout' => $this->layout
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -12,10 +12,12 @@
|
|||
namespace FFMpeg\Filters\Video;
|
||||
|
||||
use FFMpeg\Coordinate\Dimension;
|
||||
use FFMpeg\Media\Video;
|
||||
use FFMpeg\Filters\ComplexMedia\ComplexCompatibleFilter;
|
||||
use FFMpeg\Format\VideoInterface;
|
||||
use FFMpeg\Media\ComplexMedia;
|
||||
use FFMpeg\Media\Video;
|
||||
|
||||
class PadFilter implements VideoFilterInterface
|
||||
class PadFilter implements VideoFilterInterface, ComplexCompatibleFilter
|
||||
{
|
||||
/** @var Dimension */
|
||||
private $dimension;
|
||||
|
|
@ -48,11 +50,30 @@ class PadFilter implements VideoFilterInterface
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function apply(Video $video, VideoInterface $format)
|
||||
{
|
||||
return $this->getCommands();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function applyComplex(ComplexMedia $media)
|
||||
{
|
||||
return $this->getCommands();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function getCommands()
|
||||
{
|
||||
$commands = array();
|
||||
|
||||
$commands[] = '-vf';
|
||||
$commands[] = 'scale=iw*min(' . $this->dimension->getWidth() . '/iw\,' . $this->dimension->getHeight() .'/ih):ih*min(' . $this->dimension->getWidth() . '/iw\,' . $this->dimension->getHeight() .'/ih),pad=' . $this->dimension->getWidth() . ':' . $this->dimension->getHeight() . ':(' . $this->dimension->getWidth() . '-iw)/2:(' . $this->dimension->getHeight() .'-ih)/2';
|
||||
$commands[] = 'scale=iw*min(' . $this->dimension->getWidth() . '/iw\,' . $this->dimension->getHeight()
|
||||
. '/ih):ih*min(' . $this->dimension->getWidth() . '/iw\,' . $this->dimension->getHeight() . '/ih),pad='
|
||||
. $this->dimension->getWidth() . ':' . $this->dimension->getHeight() . ':(' . $this->dimension->getWidth()
|
||||
. '-iw)/2:(' . $this->dimension->getHeight() . '-ih)/2';
|
||||
|
||||
return $commands;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,10 +12,12 @@
|
|||
namespace FFMpeg\Filters\Video;
|
||||
|
||||
use FFMpeg\Exception\InvalidArgumentException;
|
||||
use FFMpeg\Filters\ComplexMedia\ComplexCompatibleFilter;
|
||||
use FFMpeg\Format\VideoInterface;
|
||||
use FFMpeg\Media\ComplexMedia;
|
||||
use FFMpeg\Media\Video;
|
||||
|
||||
class WatermarkFilter implements VideoFilterInterface
|
||||
class WatermarkFilter implements VideoFilterInterface, ComplexCompatibleFilter
|
||||
{
|
||||
/** @var string */
|
||||
private $watermarkPath;
|
||||
|
|
@ -47,6 +49,22 @@ class WatermarkFilter implements VideoFilterInterface
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function apply(Video $video, VideoInterface $format)
|
||||
{
|
||||
return $this->getCommands();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function applyComplex(ComplexMedia $media)
|
||||
{
|
||||
return $this->getCommands();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function getCommands()
|
||||
{
|
||||
$position = isset($this->coordinates['position']) ? $this->coordinates['position'] : 'absolute';
|
||||
|
||||
|
|
@ -55,7 +73,7 @@ class WatermarkFilter implements VideoFilterInterface
|
|||
if (isset($this->coordinates['top'])) {
|
||||
$y = $this->coordinates['top'];
|
||||
} elseif (isset($this->coordinates['bottom'])) {
|
||||
$y = sprintf('main_h - %d - overlay_h', $this->coordinates['bottom']);
|
||||
$y = 'main_h - ' . $this->coordinates['bottom'] . ' - overlay_h';
|
||||
} else {
|
||||
$y = 0;
|
||||
}
|
||||
|
|
@ -63,7 +81,7 @@ class WatermarkFilter implements VideoFilterInterface
|
|||
if (isset($this->coordinates['left'])) {
|
||||
$x = $this->coordinates['left'];
|
||||
} elseif (isset($this->coordinates['right'])) {
|
||||
$x = sprintf('main_w - %d - overlay_w', $this->coordinates['right']);
|
||||
$x = 'main_w - ' . $this->coordinates['right'] . ' - overlay_w';
|
||||
} else {
|
||||
$x = 0;
|
||||
}
|
||||
|
|
@ -75,6 +93,9 @@ class WatermarkFilter implements VideoFilterInterface
|
|||
break;
|
||||
}
|
||||
|
||||
return array('-vf', sprintf('movie=%s [watermark]; [in][watermark] overlay=%s:%s [out]', $this->watermarkPath, $x, $y));
|
||||
return array(
|
||||
'-vf',
|
||||
'movie=' . $this->watermarkPath . ' [watermark]; [in][watermark] overlay=' . $x . ':' . $y . ' [out]',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
381
src/FFMpeg/Media/ComplexMedia.php
Normal file
381
src/FFMpeg/Media/ComplexMedia.php
Normal file
|
|
@ -0,0 +1,381 @@
|
|||
<?php
|
||||
|
||||
namespace FFMpeg\Media;
|
||||
|
||||
use Alchemy\BinaryDriver\Exception\ExecutionFailureException;
|
||||
use FFMpeg\Driver\FFMpegDriver;
|
||||
use FFMpeg\Exception\RuntimeException;
|
||||
use FFMpeg\FFProbe;
|
||||
use FFMpeg\Filters\ComplexMedia\ComplexCompatibleFilter;
|
||||
use FFMpeg\Filters\ComplexMedia\ComplexFilterContainer;
|
||||
use FFMpeg\Filters\ComplexMedia\ComplexFilterInterface;
|
||||
use FFMpeg\Filters\ComplexMedia\ComplexFilters;
|
||||
use FFMpeg\Filters\FiltersCollection;
|
||||
use FFMpeg\Format\AudioInterface;
|
||||
use FFMpeg\Format\FormatInterface;
|
||||
use FFMpeg\Format\ProgressableInterface;
|
||||
use FFMpeg\Format\VideoInterface;
|
||||
|
||||
/**
|
||||
* Complex media may have multiple inputs and multiple outputs.
|
||||
* This class accepts only filters for -filter_complex.
|
||||
* But you can set initial and additional parameters of the ffmpeg command.
|
||||
*
|
||||
* @see http://trac.ffmpeg.org/wiki/Creating%20multiple%20outputs
|
||||
*/
|
||||
class ComplexMedia extends AbstractMediaType
|
||||
{
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $inputs;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $initialParameters;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $additionalParameters;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $mapCommands;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $listeners;
|
||||
|
||||
/**
|
||||
* ComplexMedia constructor.
|
||||
*
|
||||
* @param string[] $inputs Array of files to be opened.
|
||||
* @param FFMpegDriver $driver
|
||||
* @param FFProbe $ffprobe
|
||||
*/
|
||||
public function __construct($inputs, FFMpegDriver $driver, FFProbe $ffprobe)
|
||||
{
|
||||
// In case of error user will see this text in the error log.
|
||||
// But absence of inputs is a correct situation for some cases.
|
||||
// For example, if the user will use filters such as "testsrc".
|
||||
$pathfile = 'you_can_pass_empty_inputs_array_only_if_you_use_computed_inputs';
|
||||
$inputsKeys = array_keys($inputs);
|
||||
if (count($inputsKeys) > 0) {
|
||||
$pathfile = $inputs[$inputsKeys[0]];
|
||||
}
|
||||
|
||||
parent::__construct($pathfile, $driver, $ffprobe);
|
||||
$this->filters = new FiltersCollection();
|
||||
$this->inputs = $inputs;
|
||||
$this->initialParameters = array();
|
||||
$this->additionalParameters = array();
|
||||
$this->mapCommands = array();
|
||||
$this->listeners = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the available filters.
|
||||
*
|
||||
* @return ComplexFilters
|
||||
*/
|
||||
public function filters()
|
||||
{
|
||||
return new ComplexFilters($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $in
|
||||
* @param ComplexCompatibleFilter $filter
|
||||
* @param string $out
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addFilter($in, ComplexCompatibleFilter $filter, $out)
|
||||
{
|
||||
$this->filters->add(new ComplexFilterContainer($in, $filter, $out));
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function setFiltersCollection(FiltersCollection $filters)
|
||||
{
|
||||
foreach ($filters as $filter) {
|
||||
if (!($filter instanceof ComplexFilterInterface)) {
|
||||
throw new RuntimeException ('For ComplexMedia you can set filters collection'
|
||||
. ' contains only objects that implement ComplexFilterInterface!');
|
||||
}
|
||||
}
|
||||
|
||||
return parent::setFiltersCollection($filters);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
* @return void
|
||||
*/
|
||||
public function getInitialParameters()
|
||||
{
|
||||
return $this->initialParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $initialParameters
|
||||
*
|
||||
* @return ComplexMedia
|
||||
*/
|
||||
public function setInitialParameters(array $initialParameters)
|
||||
{
|
||||
$this->initialParameters = $initialParameters;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
* @return void
|
||||
*/
|
||||
public function getAdditionalParameters()
|
||||
{
|
||||
return $this->additionalParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $additionalParameters
|
||||
*
|
||||
* @return ComplexMedia
|
||||
*/
|
||||
public function setAdditionalParameters(array $additionalParameters)
|
||||
{
|
||||
$this->additionalParameters = $additionalParameters;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getInputs()
|
||||
{
|
||||
return $this->inputs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getInputsCount()
|
||||
{
|
||||
return count($this->inputs);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getFinalCommand()
|
||||
{
|
||||
return implode(' ', $this->buildCommand());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $outs Output labels of the -filter_complex part.
|
||||
* @param FormatInterface $format
|
||||
* @param string $outputPathfile
|
||||
* @param bool $forceDisableAudio
|
||||
* @param bool $forceDisableVideo
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function map(
|
||||
array $outs,
|
||||
FormatInterface $format,
|
||||
$outputPathfile,
|
||||
$forceDisableAudio = false,
|
||||
$forceDisableVideo = false
|
||||
) {
|
||||
$commands = array();
|
||||
foreach ($outs as $label) {
|
||||
$commands[] = '-map';
|
||||
$commands[] = $label;
|
||||
}
|
||||
|
||||
// Apply format params.
|
||||
$commands = array_merge($commands, $this->applyFormatParams($format, $forceDisableAudio, $forceDisableVideo));
|
||||
|
||||
// Set output file.
|
||||
$commands[] = $outputPathfile;
|
||||
|
||||
// Create listener.
|
||||
if ($format instanceof ProgressableInterface) {
|
||||
$listener = $format->createProgressListener($this, $this->ffprobe, 1, 1, 0);
|
||||
$this->listeners = array_merge($this->listeners, $listener);
|
||||
}
|
||||
|
||||
$this->mapCommands = array_merge($this->mapCommands, $commands);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply added filters and execute ffmpeg command.
|
||||
*
|
||||
* @return ComplexMedia
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
$command = $this->buildCommand();
|
||||
|
||||
try {
|
||||
$this->driver->command($command, false, $this->listeners);
|
||||
} catch (ExecutionFailureException $e) {
|
||||
throw new RuntimeException('Encoding failed', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param FormatInterface $format
|
||||
* @param bool $forceDisableAudio
|
||||
* @param bool $forceDisableVideo
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function applyFormatParams(
|
||||
FormatInterface $format,
|
||||
$forceDisableAudio = false,
|
||||
$forceDisableVideo = false
|
||||
) {
|
||||
// Set format params.
|
||||
$commands = array();
|
||||
if (!$forceDisableVideo && $format instanceof VideoInterface) {
|
||||
if ($format->getVideoCodec() !== null) {
|
||||
$commands[] = '-vcodec';
|
||||
$commands[] = $format->getVideoCodec();
|
||||
}
|
||||
// If the user passed some additional parameters.
|
||||
if ($format->getAdditionalParameters() !== null) {
|
||||
$commands = array_merge($commands, $format->getAdditionalParameters());
|
||||
}
|
||||
}
|
||||
if (!$forceDisableAudio && $format instanceof AudioInterface) {
|
||||
if ($format->getAudioCodec() !== null) {
|
||||
$commands[] = '-acodec';
|
||||
$commands[] = $format->getAudioCodec();
|
||||
}
|
||||
if ($format->getAudioKiloBitrate() !== null) {
|
||||
$commands[] = '-b:a';
|
||||
$commands[] = $format->getAudioKiloBitrate() . 'k';
|
||||
}
|
||||
if ($format->getAudioChannels() !== null) {
|
||||
$commands[] = '-ac';
|
||||
$commands[] = $format->getAudioChannels();
|
||||
}
|
||||
}
|
||||
|
||||
// If the user passed some extra parameters.
|
||||
if ($format->getExtraParams()) {
|
||||
$commands = array_merge($commands, $format->getExtraParams());
|
||||
}
|
||||
|
||||
return $commands;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ComplexFilterInterface $filter
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function applyComplexFilter(ComplexFilterInterface $filter)
|
||||
{
|
||||
/** @var $format VideoInterface */
|
||||
$filterCommands = $filter->applyComplex($this);
|
||||
foreach ($filterCommands as $index => $command) {
|
||||
if ($command === '-vf' || $command === '-filter:v' || $command === '-filter_complex') {
|
||||
unset ($filterCommands[$index]);
|
||||
}
|
||||
}
|
||||
|
||||
$strCommand = implode(' ', $filterCommands);
|
||||
|
||||
// Compatibility with the some existed filters:
|
||||
// If the command contains [in], just replace it to inLabel. If not - to add it manually.
|
||||
if (stripos($strCommand, '[in]') !== false) {
|
||||
$strCommand = str_replace('[in]', $filter->getInLabels(), $strCommand);
|
||||
$in = '';
|
||||
} else {
|
||||
$in = $filter->getInLabels();
|
||||
}
|
||||
|
||||
// If the command contains [out], just replace it to outLabel. If not - to add it manually.
|
||||
if (stripos($strCommand, '[out]') !== false) {
|
||||
$strCommand = str_replace('[out]', $filter->getOutLabels(), $strCommand);
|
||||
$out = '';
|
||||
} else {
|
||||
$out = $filter->getOutLabels();
|
||||
}
|
||||
|
||||
return $in . $strCommand . $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function buildCommand()
|
||||
{
|
||||
$commands = array('-y');
|
||||
if ($this->driver->getConfiguration()->has('ffmpeg.threads')) {
|
||||
$commands[] = '-threads';
|
||||
$commands[] = $this->driver->getConfiguration()->get('ffmpeg.threads');
|
||||
}
|
||||
|
||||
return array_merge($commands,
|
||||
$this->getInitialParameters(),
|
||||
$this->buildInputsPart($this->inputs),
|
||||
$this->buildComplexFilterPart($this->filters),
|
||||
$this->mapCommands,
|
||||
$this->getAdditionalParameters()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build inputs part of the ffmpeg command.
|
||||
*
|
||||
* @param string[] $inputs
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function buildInputsPart(array $inputs)
|
||||
{
|
||||
$commands = array();
|
||||
foreach ($inputs as $input) {
|
||||
$commands[] = '-i';
|
||||
$commands[] = $input;
|
||||
}
|
||||
|
||||
return $commands;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build "-filter_complex" part of the ffmpeg command.
|
||||
*
|
||||
* @param FiltersCollection $complexFilters
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function buildComplexFilterPart(FiltersCollection $complexFilters)
|
||||
{
|
||||
$commands = array();
|
||||
/** @var ComplexFilterInterface $filter */
|
||||
foreach ($complexFilters as $filter) {
|
||||
$filterCommand = $this->applyComplexFilter($filter);
|
||||
$commands[] = $filterCommand;
|
||||
}
|
||||
|
||||
if (empty($commands)) {
|
||||
return array();
|
||||
}
|
||||
return array('-filter_complex', implode(';', $commands));
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue