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

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