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:
CaliforniaMountainSnake 2020-02-18 13:49:32 +03:00
commit f20ad8a82e
17 changed files with 1583 additions and 7 deletions

View file

@ -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.
*

View 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,
))
);
}
}

View 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 '';
}
}

View 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);
}

View 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);
}
}

View file

@ -0,0 +1,16 @@
<?php
namespace FFMpeg\Filters\ComplexMedia;
interface ComplexFilterInterface extends ComplexCompatibleFilter
{
/**
* @return string
*/
public function getInLabels();
/**
* @return string
*/
public function getOutLabels();
}

View 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;
}
}

View 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);
}
}

View 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,
))
);
}
}

View 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
))
);
}
}

View 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
))
);
}
}

View file

@ -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;
}

View file

@ -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]',
);
}
}

View 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));
}
}