From f20ad8a82e9cdeb503299cb1b6ab5bd968e29f67 Mon Sep 17 00:00:00 2001 From: CaliforniaMountainSnake Date: Tue, 18 Feb 2020 13:49:32 +0300 Subject: [PATCH 1/6] 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. --- README.md | 76 ++++ src/FFMpeg/FFMpeg.php | 13 + .../Filters/ComplexMedia/ANullSrcFilter.php | 61 +++ .../ComplexMedia/AbstractComplexFilter.php | 52 +++ .../ComplexMedia/ComplexCompatibleFilter.php | 18 + .../ComplexMedia/ComplexFilterContainer.php | 80 ++++ .../ComplexMedia/ComplexFilterInterface.php | 16 + .../Filters/ComplexMedia/ComplexFilters.php | 169 ++++++++ .../ComplexMedia/CustomComplexFilter.php | 33 ++ .../Filters/ComplexMedia/SineFilter.php | 77 ++++ .../Filters/ComplexMedia/TestSrcFilter.php | 211 ++++++++++ .../Filters/ComplexMedia/XStackFilter.php | 71 ++++ src/FFMpeg/Filters/Video/PadFilter.php | 27 +- src/FFMpeg/Filters/Video/WatermarkFilter.php | 29 +- src/FFMpeg/Media/ComplexMedia.php | 381 ++++++++++++++++++ tests/Functional/ComplexMediaTest.php | 241 +++++++++++ tests/Unit/Media/ComplexMediaTest.php | 35 ++ 17 files changed, 1583 insertions(+), 7 deletions(-) create mode 100644 src/FFMpeg/Filters/ComplexMedia/ANullSrcFilter.php create mode 100644 src/FFMpeg/Filters/ComplexMedia/AbstractComplexFilter.php create mode 100644 src/FFMpeg/Filters/ComplexMedia/ComplexCompatibleFilter.php create mode 100644 src/FFMpeg/Filters/ComplexMedia/ComplexFilterContainer.php create mode 100644 src/FFMpeg/Filters/ComplexMedia/ComplexFilterInterface.php create mode 100644 src/FFMpeg/Filters/ComplexMedia/ComplexFilters.php create mode 100644 src/FFMpeg/Filters/ComplexMedia/CustomComplexFilter.php create mode 100644 src/FFMpeg/Filters/ComplexMedia/SineFilter.php create mode 100644 src/FFMpeg/Filters/ComplexMedia/TestSrcFilter.php create mode 100644 src/FFMpeg/Filters/ComplexMedia/XStackFilter.php create mode 100644 src/FFMpeg/Media/ComplexMedia.php create mode 100644 tests/Functional/ComplexMediaTest.php create mode 100644 tests/Unit/Media/ComplexMediaTest.php diff --git a/README.md b/README.md index 6d2beb8..ae908ec 100644 --- a/README.md +++ b/README.md @@ -520,6 +520,82 @@ $video More details about concatenation in FFMPEG can be found [here](https://trac.ffmpeg.org/wiki/Concatenate), [here](https://ffmpeg.org/ffmpeg-formats.html#concat-1) and [here](https://ffmpeg.org/ffmpeg.html#Stream-copy). +### ComplexMedia +ComplexMedia may have multiple inputs and multiple outputs. + +This class has been developed primarily to use with `-filter_complex`. + +So, its `filters()` method accepts only filters that can be used inside `-filter_complex` command. +ComplexMedia already contains some built-in filters. + +#### Base usage +For example: + +```php +$complexMedia = $ffmpeg->openComplex(array ('video_1.mp4', 'video_2.mp4')); +$complexMedia->filters() + ->custom('[0:v][1:v]', 'hstack', '[v]'); +$complexMedia + ->map(array('0:a', '[v]'), new X264('aac', 'libx264'), 'output.mp4') + ->save(); +``` + +This code takes 2 input videos, stacks they horizontally in 1 output video and adds to this new video the audio from the first video. +(It is impossible with simple filtergraph that has only 1 input and only 1 output). + + +#### Complicated example +A more difficult example of possibilities of the ComplexMedia. Consider all input videos already have the same resolution and duration. ("xstack" filter has been added in the 4.1 version of the ffmpeg). + +```php +$inputs = array( + 'video_1.mp4', + 'video_2.mp4', + 'video_3.mp4', + 'video_4.mp4', +); + +$complexMedia = $ffmpeg->openComplex($inputs); +$complexMedia->filters() + ->custom('[0:v]', 'negate', '[v0negate]') + ->custom('[1:v]', 'edgedetect', '[v1edgedetect]') + ->custom('[2:v]', 'hflip', '[v2hflip]') + ->custom('[3:v]', 'vflip', '[v3vflip]') + ->xStack('[v0negate][v1edgedetect][v2hflip][v3vflip]', XStackFilter::LAYOUT_2X2, 4, '[resultv]'); +$complexMedia + ->map(array('0:a'), new Mp3(), 'video_1.mp3') + ->map(array('1:a'), new Flac(), 'video_2.flac') + ->map(array('2:a'), new Wav(), 'video_3.wav') + ->map(array('3:a'), new Aac(), 'video_4.aac') + ->map(array('[resultv]'), new X264('aac', 'libx264'), 'output.mp4') + ->save(); +``` + +This code takes 4 input videos, then the negates the first video, stores result in `[v0negate]` stream, detects edges in the second video, stores result in `[v1edgedetect]` stream, horizontally flips the third video, stores result in `[v2hflip]` stream, vertically flips the fourth video, stores result in `[v3vflip]` stream, then takes this 4 generated streams ans combine them in one 2x2 collage video. +Then saves audios from the original videos into the 4 different formats and saves the generated collage video into the separate file. + +As you can see, you can take multiple input sources, perform the complicated processing for them and produce multiple output files in the same time, in the one ffmpeg command. + +#### Just give me map! +You do not have to use `-filter_complex`. For example, just extract the audio from the video: + +```php +$complexMedia = $ffmpeg->openComplex(array ('video.mp4')); +$complexMedia + ->map(array('0:a'), new Mp3(), 'output.mp3') + ->save(); +``` + +#### Customisation +If you need you can extra customize the result ffmpeg command of the ComplexMedia: + +```php +$complexMedia = $ffmpeg->openComplex($inputs); +$complexMedia + ->setInitialParameters(array('the', 'params', 'that', 'will', 'be', 'added', 'before', '-i', 'part', 'of', 'the', 'command')) + ->setAdditionalParameters(array('the', 'params', 'that', 'will', 'be', 'added', 'at', 'the', 'end', 'of', 'the', 'command')); +``` + #### Formats A format implements `FFMpeg\Format\FormatInterface`. To save to a video file, diff --git a/src/FFMpeg/FFMpeg.php b/src/FFMpeg/FFMpeg.php index 91ce361..cbf59f0 100644 --- a/src/FFMpeg/FFMpeg.php +++ b/src/FFMpeg/FFMpeg.php @@ -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. * diff --git a/src/FFMpeg/Filters/ComplexMedia/ANullSrcFilter.php b/src/FFMpeg/Filters/ComplexMedia/ANullSrcFilter.php new file mode 100644 index 0000000..f995bc0 --- /dev/null +++ b/src/FFMpeg/Filters/ComplexMedia/ANullSrcFilter.php @@ -0,0 +1,61 @@ +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, + )) + ); + } +} diff --git a/src/FFMpeg/Filters/ComplexMedia/AbstractComplexFilter.php b/src/FFMpeg/Filters/ComplexMedia/AbstractComplexFilter.php new file mode 100644 index 0000000..152d672 --- /dev/null +++ b/src/FFMpeg/Filters/ComplexMedia/AbstractComplexFilter.php @@ -0,0 +1,52 @@ +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 ''; + } +} diff --git a/src/FFMpeg/Filters/ComplexMedia/ComplexCompatibleFilter.php b/src/FFMpeg/Filters/ComplexMedia/ComplexCompatibleFilter.php new file mode 100644 index 0000000..38a2bb4 --- /dev/null +++ b/src/FFMpeg/Filters/ComplexMedia/ComplexCompatibleFilter.php @@ -0,0 +1,18 @@ +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); + } +} diff --git a/src/FFMpeg/Filters/ComplexMedia/ComplexFilterInterface.php b/src/FFMpeg/Filters/ComplexMedia/ComplexFilterInterface.php new file mode 100644 index 0000000..6276f90 --- /dev/null +++ b/src/FFMpeg/Filters/ComplexMedia/ComplexFilterInterface.php @@ -0,0 +1,16 @@ +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; + } +} diff --git a/src/FFMpeg/Filters/ComplexMedia/CustomComplexFilter.php b/src/FFMpeg/Filters/ComplexMedia/CustomComplexFilter.php new file mode 100644 index 0000000..89fe626 --- /dev/null +++ b/src/FFMpeg/Filters/ComplexMedia/CustomComplexFilter.php @@ -0,0 +1,33 @@ +filter = $filter; + } + + /** + * {@inheritdoc} + */ + public function applyComplex(ComplexMedia $media) + { + return array('-filter_complex', $this->filter); + } +} diff --git a/src/FFMpeg/Filters/ComplexMedia/SineFilter.php b/src/FFMpeg/Filters/ComplexMedia/SineFilter.php new file mode 100644 index 0000000..4726522 --- /dev/null +++ b/src/FFMpeg/Filters/ComplexMedia/SineFilter.php @@ -0,0 +1,77 @@ +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, + )) + ); + } +} diff --git a/src/FFMpeg/Filters/ComplexMedia/TestSrcFilter.php b/src/FFMpeg/Filters/ComplexMedia/TestSrcFilter.php new file mode 100644 index 0000000..88348e3 --- /dev/null +++ b/src/FFMpeg/Filters/ComplexMedia/TestSrcFilter.php @@ -0,0 +1,211 @@ +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 + )) + ); + } +} diff --git a/src/FFMpeg/Filters/ComplexMedia/XStackFilter.php b/src/FFMpeg/Filters/ComplexMedia/XStackFilter.php new file mode 100644 index 0000000..a17b285 --- /dev/null +++ b/src/FFMpeg/Filters/ComplexMedia/XStackFilter.php @@ -0,0 +1,71 @@ +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 + )) + ); + } +} diff --git a/src/FFMpeg/Filters/Video/PadFilter.php b/src/FFMpeg/Filters/Video/PadFilter.php index a6dcf2d..501c412 100644 --- a/src/FFMpeg/Filters/Video/PadFilter.php +++ b/src/FFMpeg/Filters/Video/PadFilter.php @@ -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; } diff --git a/src/FFMpeg/Filters/Video/WatermarkFilter.php b/src/FFMpeg/Filters/Video/WatermarkFilter.php index 38183ad..45e3532 100644 --- a/src/FFMpeg/Filters/Video/WatermarkFilter.php +++ b/src/FFMpeg/Filters/Video/WatermarkFilter.php @@ -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]', + ); } } diff --git a/src/FFMpeg/Media/ComplexMedia.php b/src/FFMpeg/Media/ComplexMedia.php new file mode 100644 index 0000000..bf1db1d --- /dev/null +++ b/src/FFMpeg/Media/ComplexMedia.php @@ -0,0 +1,381 @@ + 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)); + } +} diff --git a/tests/Functional/ComplexMediaTest.php b/tests/Functional/ComplexMediaTest.php new file mode 100644 index 0000000..78cf080 --- /dev/null +++ b/tests/Functional/ComplexMediaTest.php @@ -0,0 +1,241 @@ +getFFMpeg(); + $inputs = array(realpath(__DIR__ . '/../files/Test.ogv')); + $format = new Mp3(); + $output = __DIR__ . '/output/' . self::PATH_PREFIX . 'extracted_with_map.mp3'; + + // You can run it without -filter_complex, just using -map. + $complexMedia = $ffmpeg->openComplex($inputs); + $complexMedia + ->map(array('0:a'), $format, $output) + ->save(); + + $this->assertFileExists($output); + $this->assertEquals('MP2/3 (MPEG audio layer 2/3)', + $ffmpeg->open($output)->getFormat()->get('format_long_name')); + unlink($output); + } + + public function testAudio() + { + $ffmpeg = $this->getFFMpeg(); + $inputs = array(realpath(__DIR__ . '/../files/Audio.mp3')); + $format = new Mp3(); + $format->setAudioKiloBitrate(30); + $output = __DIR__ . '/output/' . self::PATH_PREFIX . 'audio_test.mp3'; + + $complexMedia = $ffmpeg->openComplex($inputs); + $complexMedia + ->map(array('0:a'), $format, $output) + ->save(); + + $this->assertFileExists($output); + $this->assertEquals('MP2/3 (MPEG audio layer 2/3)', + $ffmpeg->open($output)->getFormat()->get('format_long_name')); + unlink($output); + } + + public function testMultipleInputs() + { + $ffmpeg = $this->getFFMpeg(); + $inputs = array( + realpath(__DIR__ . '/../files/portrait.MOV'), + realpath(__DIR__ . '/../files/portrait.MOV') + ); + $format = new X264('aac', 'libx264'); + $output = __DIR__ . '/output/' . self::PATH_PREFIX . 'multiple_inputs_test.mp4'; + + $complexMedia = $ffmpeg->openComplex($inputs); + $complexMedia->filters() + ->custom('[0:v][1:v]', 'hstack', '[v]'); + $complexMedia + ->map(array('0:a', '[v]'), $format, $output) + ->save(); + + $this->assertFileExists($output); + $this->assertEquals('QuickTime / MOV', + $ffmpeg->open($output)->getFormat()->get('format_long_name')); + unlink($output); + } + + /** + * @covers \FFMpeg\Media\ComplexMedia::map + */ + public function testMultipleOutputsTestAbsenceOfInputs() + { + $ffmpeg = $this->getFFMpeg(); + // in this test we use only computed inputs + // and can ignore -i part of the command, pass empty inputs array. + $inputs = array(); + $formatX264 = new X264('aac', 'libx264'); + $formatMp3 = new Mp3(); + + $outputMp3 = __DIR__ . '/output/' . self::PATH_PREFIX . 'test_multiple_outputs.mp3'; + $outputVideo1 = __DIR__ . '/output/' . self::PATH_PREFIX . 'test_multiple_outputs_v1.mp4'; + $outputVideo2 = __DIR__ . '/output/' . self::PATH_PREFIX . 'test_multiple_outputs_v2.mp4'; + + $complexMedia = $ffmpeg->openComplex($inputs); + $complexMedia->filters() + ->sine('[a]', 5) + ->testSrc('[v1]', TestSrcFilter::TESTSRC, '160x120', 5) + ->testSrc('[v2]', TestSrcFilter::TESTSRC, '160x120', 5) + ->custom('[v1]', 'negate', '[v1negate]') + ->custom('[v2]', 'edgedetect', '[v2edgedetect]'); + $complexMedia + ->map(array('[a]'), $formatMp3, $outputMp3) + ->map(array('[v1negate]'), $formatX264, $outputVideo1) + ->map(array('[v2edgedetect]'), $formatX264, $outputVideo2) + ->save(); + + + $this->assertFileExists($outputMp3); + $this->assertEquals('MP2/3 (MPEG audio layer 2/3)', + $ffmpeg->open($outputMp3)->getFormat()->get('format_long_name')); + unlink($outputMp3); + + $this->assertFileExists($outputVideo1); + $this->assertEquals('QuickTime / MOV', + $ffmpeg->open($outputVideo1)->getFormat()->get('format_long_name')); + unlink($outputVideo1); + + $this->assertFileExists($outputVideo2); + $this->assertEquals('QuickTime / MOV', + $ffmpeg->open($outputVideo2)->getFormat()->get('format_long_name')); + unlink($outputVideo2); + } + + /** + * @covers \FFMpeg\Filters\ComplexMedia\TestSrcFilter + * @covers \FFMpeg\Filters\ComplexMedia\SineFilter + */ + public function testTestSrcFilterTestSineFilter() + { + $ffmpeg = $this->getFFMpeg(); + $inputs = array(realpath(__DIR__ . '/../files/Test.ogv')); + $format = new X264('aac', 'libx264'); + $output = __DIR__ . '/output/' . self::PATH_PREFIX . 'testsrc.mp4'; + + $complexMedia = $ffmpeg->openComplex($inputs); + $complexMedia->filters() + ->sine('[a]', 10) + ->testSrc('[v]', TestSrcFilter::TESTSRC, '160x120', 10); + $complexMedia + ->map(array('[a]', '[v]'), $format, $output) + ->save(); + + $this->assertFileExists($output); + $this->assertEquals('QuickTime / MOV', + $ffmpeg->open($output)->getFormat()->get('format_long_name')); + unlink($output); + } + + /** + * XStack filter is supported starting from 4.1 ffmpeg version. + * + * @covers \FFMpeg\Filters\ComplexMedia\XStackFilter + * @covers \FFMpeg\Filters\ComplexMedia\SineFilter + */ + public function testXStackFilter() + { + $ffmpeg = $this->getFFMpeg(); + $inputs = array(realpath(__DIR__ . '/../files/Test.ogv')); + $format = new X264('aac', 'libx264'); + $output = __DIR__ . '/output/' . self::PATH_PREFIX . 'xstack_test.mp4'; + + $complexMedia = $ffmpeg->openComplex($inputs); + $complexMedia->filters() + ->sine('[a]', 5) + ->testSrc('[v1]', TestSrcFilter::TESTSRC, '160x120', 5) + ->testSrc('[v2]', TestSrcFilter::TESTSRC, '160x120', 5) + ->testSrc('[v3]', TestSrcFilter::TESTSRC, '160x120', 5) + ->testSrc('[v4]', TestSrcFilter::TESTSRC, '160x120', 5) + ->xStack('[v1][v2][v3][v4]', + XStackFilter::LAYOUT_2X2, 4, '[v]'); + $complexMedia + ->map(array('[a]', '[v]'), $format, $output) + ->save(); + + $this->assertFileExists($output); + $this->assertEquals('QuickTime / MOV', + $ffmpeg->open($output)->getFormat()->get('format_long_name')); + unlink($output); + } + + public function testOfCompatibilityWithExistedFilters() + { + $ffmpeg = $this->getFFMpeg(); + $inputs = array(realpath(__DIR__ . '/../files/Test.ogv')); + $watermark = realpath(__DIR__ . '/../files/watermark.png'); + $format = new X264('aac', 'libx264'); + $output = __DIR__ . '/output/' . self::PATH_PREFIX . 'test_of_compatibility_with_existed_filters.mp4'; + + $complexMedia = $ffmpeg->openComplex($inputs); + $complexMedia->filters() + // For unknown reasons WatermarkFilter produce an error on Windows, + // because the path to the watermark becomes corrupted. + // This behaviour related with Alchemy\BinaryDriver\AbstractBinary::command(). + // The path inside filter becomes like + // "D:ServerswwwPHP-FFMpegtestsfileswatermark.png" (without slashes). + // But on Linux systems filter works as expected. + //->watermark('[0:v]', $watermark, '[v]') + ->pad('[0:v]', new Dimension(300, 100), '[v]'); + $complexMedia + ->map(array('0:a', '[v]'), $format, $output) + ->save(); + + $this->assertFileExists($output); + $this->assertEquals('QuickTime / MOV', + $ffmpeg->open($output)->getFormat()->get('format_long_name')); + unlink($output); + } + + public function testForceDisableAudio() + { + $ffmpeg = $this->getFFMpeg(); + $format = new X264(); + + $complexMedia1 = $ffmpeg->openComplex(array(__FILE__)); + $complexMedia1 + ->map(array('test'), $format, 'outputFile.mp4', false); + $this->assertContains('acodec', $complexMedia1->getFinalCommand()); + + $complexMedia2 = $ffmpeg->openComplex(array(__FILE__)); + $complexMedia2 + ->map(array('test'), $format, 'outputFile.mp4', true); + $this->assertNotContains('acodec', $complexMedia2->getFinalCommand()); + } + + public function testForceDisableVideo() + { + $ffmpeg = $this->getFFMpeg(); + $format = new X264(); + + $complexMedia1 = $ffmpeg->openComplex(array(__FILE__)); + $complexMedia1->map(array('test'), $format, + 'outputFile.mp4', false, false); + $this->assertContains('vcodec', $complexMedia1->getFinalCommand()); + + $complexMedia2 = $ffmpeg->openComplex(array(__FILE__)); + $complexMedia2->map(array('test'), $format, + 'outputFile.mp4', false, true); + $this->assertNotContains('vcodec', $complexMedia2->getFinalCommand()); + } +} diff --git a/tests/Unit/Media/ComplexMediaTest.php b/tests/Unit/Media/ComplexMediaTest.php new file mode 100644 index 0000000..b359a50 --- /dev/null +++ b/tests/Unit/Media/ComplexMediaTest.php @@ -0,0 +1,35 @@ +getFFMpegDriverMock(); + $ffprobe = $this->getFFProbeMock(); + + $complexMedia = new ComplexMedia(array(__FILE__, __FILE__), $driver, $ffprobe); + $this->assertSame(array(__FILE__, __FILE__), $complexMedia->getInputs()); + } + + public function testGetInputsCount() + { + $driver = $this->getFFMpegDriverMock(); + $ffprobe = $this->getFFProbeMock(); + + $complexMedia = new ComplexMedia(array(__FILE__, __FILE__), $driver, $ffprobe); + $this->assertEquals(2, $complexMedia->getInputsCount()); + } + + public function testFiltersReturnFilters() + { + $driver = $this->getFFMpegDriverMock(); + $ffprobe = $this->getFFProbeMock(); + + $complexMedia = new ComplexMedia(array(__FILE__, __FILE__), $driver, $ffprobe); + $this->assertInstanceOf('FFMpeg\Filters\ComplexMedia\ComplexFilters', $complexMedia->filters()); + } +} From 6dc260fd3dd8423d2df9e32b874eaeea151c35a9 Mon Sep 17 00:00:00 2001 From: CaliforniaMountainSnake Date: Thu, 20 Feb 2020 14:34:21 +0300 Subject: [PATCH 2/6] Added ffmpeg version checking in the ComplexMediaTest::testXStackFilter(). --- src/FFMpeg/Filters/ComplexMedia/XStackFilter.php | 1 + tests/Functional/ComplexMediaTest.php | 8 ++++++++ tests/Functional/FunctionalTestCase.php | 12 ++++++++++++ 3 files changed, 21 insertions(+) diff --git a/src/FFMpeg/Filters/ComplexMedia/XStackFilter.php b/src/FFMpeg/Filters/ComplexMedia/XStackFilter.php index a17b285..199e052 100644 --- a/src/FFMpeg/Filters/ComplexMedia/XStackFilter.php +++ b/src/FFMpeg/Filters/ComplexMedia/XStackFilter.php @@ -12,6 +12,7 @@ use FFMpeg\Media\ComplexMedia; */ class XStackFilter extends AbstractComplexFilter { + const MINIMAL_FFMPEG_VERSION = '4.1'; 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'; diff --git a/tests/Functional/ComplexMediaTest.php b/tests/Functional/ComplexMediaTest.php index 78cf080..bc8f5a7 100644 --- a/tests/Functional/ComplexMediaTest.php +++ b/tests/Functional/ComplexMediaTest.php @@ -156,6 +156,14 @@ class ComplexMediaTest extends FunctionalTestCase public function testXStackFilter() { $ffmpeg = $this->getFFMpeg(); + $ffmpegVersion = $this->getFFMpegVersion(); + if (version_compare($ffmpegVersion, XStackFilter::MINIMAL_FFMPEG_VERSION) === -1) { + $this->markTestSkipped('XStack filter is supported starting from ' + . XStackFilter::MINIMAL_FFMPEG_VERSION . ' ffmpeg version, your version is ' + . $ffmpegVersion); + return; + } + $inputs = array(realpath(__DIR__ . '/../files/Test.ogv')); $format = new X264('aac', 'libx264'); $output = __DIR__ . '/output/' . self::PATH_PREFIX . 'xstack_test.mp4'; diff --git a/tests/Functional/FunctionalTestCase.php b/tests/Functional/FunctionalTestCase.php index a7eac42..7f97a12 100644 --- a/tests/Functional/FunctionalTestCase.php +++ b/tests/Functional/FunctionalTestCase.php @@ -14,4 +14,16 @@ abstract class FunctionalTestCase extends TestCase { 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]; + } } From d6f95508a900c6267d609f1e7a02dd7efbb5d2bf Mon Sep 17 00:00:00 2001 From: CaliforniaMountainSnake Date: Wed, 26 Feb 2020 12:33:45 +0300 Subject: [PATCH 3/6] Added runtime check of the ffmpeg version before starting the command in the ComplexMedia. Added the FFMpegDriver::getVersion() method. --- src/FFMpeg/Driver/FFMpegDriver.php | 17 ++++++++++++ src/FFMpeg/FFMpeg.php | 2 ++ .../Filters/ComplexMedia/ANullSrcFilter.php | 12 ++++++++- .../ComplexMedia/AbstractComplexFilter.php | 10 +++++++ .../ComplexMedia/ComplexCompatibleFilter.php | 14 ++++++++++ .../ComplexMedia/ComplexFilterContainer.php | 20 ++++++++++++++ .../ComplexMedia/CustomComplexFilter.php | 10 +++++++ .../Filters/ComplexMedia/SineFilter.php | 22 ++++++++++++++- .../Filters/ComplexMedia/TestSrcFilter.php | 10 +++++++ .../Filters/ComplexMedia/XStackFilter.php | 27 ++++++++++++++++--- src/FFMpeg/Filters/Video/PadFilter.php | 20 ++++++++++++++ src/FFMpeg/Filters/Video/WatermarkFilter.php | 20 ++++++++++++++ src/FFMpeg/Media/ComplexMedia.php | 24 +++++++++++++++++ tests/Functional/ComplexMediaTest.php | 17 ++++++------ tests/Functional/FunctionalTestCase.php | 12 --------- 15 files changed, 212 insertions(+), 25 deletions(-) diff --git a/src/FFMpeg/Driver/FFMpegDriver.php b/src/FFMpeg/Driver/FFMpegDriver.php index 62aae29..fdb818c 100644 --- a/src/FFMpeg/Driver/FFMpegDriver.php +++ b/src/FFMpeg/Driver/FFMpegDriver.php @@ -16,6 +16,7 @@ use Alchemy\BinaryDriver\Configuration; use Alchemy\BinaryDriver\ConfigurationInterface; use Alchemy\BinaryDriver\Exception\ExecutableNotFoundException as BinaryDriverExecutableNotFound; use FFMpeg\Exception\ExecutableNotFoundException; +use FFMpeg\Exception\RuntimeException; use Psr\Log\LoggerInterface; class FFMpegDriver extends AbstractBinary @@ -54,4 +55,20 @@ class FFMpegDriver extends AbstractBinary 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]; + } } diff --git a/src/FFMpeg/FFMpeg.php b/src/FFMpeg/FFMpeg.php index cbf59f0..322c6bb 100644 --- a/src/FFMpeg/FFMpeg.php +++ b/src/FFMpeg/FFMpeg.php @@ -60,6 +60,8 @@ class FFMpeg /** * Sets the ffmpeg driver. * + * @param FFMpegDriver $ffmpeg + * * @return FFMpeg */ public function setFFMpegDriver(FFMpegDriver $ffmpeg) diff --git a/src/FFMpeg/Filters/ComplexMedia/ANullSrcFilter.php b/src/FFMpeg/Filters/ComplexMedia/ANullSrcFilter.php index f995bc0..b18a2d3 100644 --- a/src/FFMpeg/Filters/ComplexMedia/ANullSrcFilter.php +++ b/src/FFMpeg/Filters/ComplexMedia/ANullSrcFilter.php @@ -44,6 +44,16 @@ class ANullSrcFilter extends AbstractComplexFilter $this->nbSamples = $nbSamples; } + /** + * Get name of the filter. + * + * @return string + */ + public function getName() + { + return 'anullsrc'; + } + /** * {@inheritdoc} */ @@ -51,7 +61,7 @@ class ANullSrcFilter extends AbstractComplexFilter { return array( '-filter_complex', - 'anullsrc' . $this->buildFilterOptions(array( + $this->getName() . $this->buildFilterOptions(array( 'channel_layout' => $this->channelLayout, 'sample_rate' => $this->sampleRate, 'nb_samples' => $this->nbSamples, diff --git a/src/FFMpeg/Filters/ComplexMedia/AbstractComplexFilter.php b/src/FFMpeg/Filters/ComplexMedia/AbstractComplexFilter.php index 152d672..dab450f 100644 --- a/src/FFMpeg/Filters/ComplexMedia/AbstractComplexFilter.php +++ b/src/FFMpeg/Filters/ComplexMedia/AbstractComplexFilter.php @@ -27,6 +27,16 @@ abstract class AbstractComplexFilter implements ComplexCompatibleFilter 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. * diff --git a/src/FFMpeg/Filters/ComplexMedia/ComplexCompatibleFilter.php b/src/FFMpeg/Filters/ComplexMedia/ComplexCompatibleFilter.php index 38a2bb4..26310da 100644 --- a/src/FFMpeg/Filters/ComplexMedia/ComplexCompatibleFilter.php +++ b/src/FFMpeg/Filters/ComplexMedia/ComplexCompatibleFilter.php @@ -7,6 +7,20 @@ use FFMpeg\Media\ComplexMedia; 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. * diff --git a/src/FFMpeg/Filters/ComplexMedia/ComplexFilterContainer.php b/src/FFMpeg/Filters/ComplexMedia/ComplexFilterContainer.php index 3dd0391..cf04cb5 100644 --- a/src/FFMpeg/Filters/ComplexMedia/ComplexFilterContainer.php +++ b/src/FFMpeg/Filters/ComplexMedia/ComplexFilterContainer.php @@ -70,6 +70,26 @@ class ComplexFilterContainer implements ComplexFilterInterface 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} */ diff --git a/src/FFMpeg/Filters/ComplexMedia/CustomComplexFilter.php b/src/FFMpeg/Filters/ComplexMedia/CustomComplexFilter.php index 89fe626..1bc93f2 100644 --- a/src/FFMpeg/Filters/ComplexMedia/CustomComplexFilter.php +++ b/src/FFMpeg/Filters/ComplexMedia/CustomComplexFilter.php @@ -23,6 +23,16 @@ class CustomComplexFilter extends AbstractComplexFilter $this->filter = $filter; } + /** + * Get name of the filter. + * + * @return string + */ + public function getName() + { + return 'custom_filter'; + } + /** * {@inheritdoc} */ diff --git a/src/FFMpeg/Filters/ComplexMedia/SineFilter.php b/src/FFMpeg/Filters/ComplexMedia/SineFilter.php index 4726522..066701d 100644 --- a/src/FFMpeg/Filters/ComplexMedia/SineFilter.php +++ b/src/FFMpeg/Filters/ComplexMedia/SineFilter.php @@ -54,6 +54,26 @@ class SineFilter extends AbstractComplexFilter $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. * @@ -65,7 +85,7 @@ class SineFilter extends AbstractComplexFilter { return array( '-filter_complex', - 'sine' . $this->buildFilterOptions(array( + $this->getName() . $this->buildFilterOptions(array( 'frequency' => $this->frequency, 'beep_factor' => $this->beep_factor, 'sample_rate' => $this->sample_rate, diff --git a/src/FFMpeg/Filters/ComplexMedia/TestSrcFilter.php b/src/FFMpeg/Filters/ComplexMedia/TestSrcFilter.php index 88348e3..139731c 100644 --- a/src/FFMpeg/Filters/ComplexMedia/TestSrcFilter.php +++ b/src/FFMpeg/Filters/ComplexMedia/TestSrcFilter.php @@ -189,6 +189,16 @@ class TestSrcFilter extends AbstractComplexFilter $this->decimals = $decimals; } + /** + * Get name of the filter. + * + * @return string + */ + public function getName() + { + return $this->type; + } + /** * {@inheritdoc} */ diff --git a/src/FFMpeg/Filters/ComplexMedia/XStackFilter.php b/src/FFMpeg/Filters/ComplexMedia/XStackFilter.php index 199e052..f90a02e 100644 --- a/src/FFMpeg/Filters/ComplexMedia/XStackFilter.php +++ b/src/FFMpeg/Filters/ComplexMedia/XStackFilter.php @@ -6,13 +6,14 @@ use FFMpeg\Media\ComplexMedia; /** * "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 */ class XStackFilter extends AbstractComplexFilter { - const MINIMAL_FFMPEG_VERSION = '4.1'; 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'; @@ -56,6 +57,26 @@ class XStackFilter extends AbstractComplexFilter 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} */ @@ -63,7 +84,7 @@ class XStackFilter extends AbstractComplexFilter { return array( '-filter_complex', - 'xstack' . $this->buildFilterOptions(array( + $this->getName() . $this->buildFilterOptions(array( 'inputs' => $this->inputsCount, 'layout' => $this->layout )) diff --git a/src/FFMpeg/Filters/Video/PadFilter.php b/src/FFMpeg/Filters/Video/PadFilter.php index 501c412..edee7b3 100644 --- a/src/FFMpeg/Filters/Video/PadFilter.php +++ b/src/FFMpeg/Filters/Video/PadFilter.php @@ -46,6 +46,26 @@ class PadFilter implements VideoFilterInterface, ComplexCompatibleFilter 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} */ diff --git a/src/FFMpeg/Filters/Video/WatermarkFilter.php b/src/FFMpeg/Filters/Video/WatermarkFilter.php index 45e3532..bb4cae8 100644 --- a/src/FFMpeg/Filters/Video/WatermarkFilter.php +++ b/src/FFMpeg/Filters/Video/WatermarkFilter.php @@ -45,6 +45,26 @@ class WatermarkFilter implements VideoFilterInterface, ComplexCompatibleFilter 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} */ diff --git a/src/FFMpeg/Media/ComplexMedia.php b/src/FFMpeg/Media/ComplexMedia.php index bf1db1d..bc92b61 100644 --- a/src/FFMpeg/Media/ComplexMedia.php +++ b/src/FFMpeg/Media/ComplexMedia.php @@ -221,9 +221,11 @@ class ComplexMedia extends AbstractMediaType * Apply added filters and execute ffmpeg command. * * @return ComplexMedia + * @throws RuntimeException */ public function save() { + $this->assertFiltersAreCompatibleToCurrentFFMpegVersion(); $command = $this->buildCommand(); try { @@ -319,6 +321,28 @@ class ComplexMedia extends AbstractMediaType 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 */ diff --git a/tests/Functional/ComplexMediaTest.php b/tests/Functional/ComplexMediaTest.php index bc8f5a7..b7ad40e 100644 --- a/tests/Functional/ComplexMediaTest.php +++ b/tests/Functional/ComplexMediaTest.php @@ -155,11 +155,12 @@ class ComplexMediaTest extends FunctionalTestCase */ public function testXStackFilter() { + $xStack = new XStackFilter('', 0); $ffmpeg = $this->getFFMpeg(); - $ffmpegVersion = $this->getFFMpegVersion(); - if (version_compare($ffmpegVersion, XStackFilter::MINIMAL_FFMPEG_VERSION) === -1) { - $this->markTestSkipped('XStack filter is supported starting from ' - . XStackFilter::MINIMAL_FFMPEG_VERSION . ' ffmpeg version, your version is ' + $ffmpegVersion = $ffmpeg->getFFMpegDriver()->getVersion(); + if (version_compare($ffmpegVersion, $xStack->getMinimalFFMpegVersion(), '<')) { + $this->markTestSkipped('XStack filter is supported starting from ffmpeg version ' + . $xStack->getMinimalFFMpegVersion() . ', your version is ' . $ffmpegVersion); return; } @@ -223,12 +224,12 @@ class ComplexMediaTest extends FunctionalTestCase $complexMedia1 = $ffmpeg->openComplex(array(__FILE__)); $complexMedia1 ->map(array('test'), $format, 'outputFile.mp4', false); - $this->assertContains('acodec', $complexMedia1->getFinalCommand()); + $this->assertStringContainsString('acodec', $complexMedia1->getFinalCommand()); $complexMedia2 = $ffmpeg->openComplex(array(__FILE__)); $complexMedia2 ->map(array('test'), $format, 'outputFile.mp4', true); - $this->assertNotContains('acodec', $complexMedia2->getFinalCommand()); + $this->assertStringNotContainsString('acodec', $complexMedia2->getFinalCommand()); } public function testForceDisableVideo() @@ -239,11 +240,11 @@ class ComplexMediaTest extends FunctionalTestCase $complexMedia1 = $ffmpeg->openComplex(array(__FILE__)); $complexMedia1->map(array('test'), $format, 'outputFile.mp4', false, false); - $this->assertContains('vcodec', $complexMedia1->getFinalCommand()); + $this->assertStringContainsString('vcodec', $complexMedia1->getFinalCommand()); $complexMedia2 = $ffmpeg->openComplex(array(__FILE__)); $complexMedia2->map(array('test'), $format, 'outputFile.mp4', false, true); - $this->assertNotContains('vcodec', $complexMedia2->getFinalCommand()); + $this->assertStringNotContainsString('vcodec', $complexMedia2->getFinalCommand()); } } diff --git a/tests/Functional/FunctionalTestCase.php b/tests/Functional/FunctionalTestCase.php index dc97876..15fcc26 100644 --- a/tests/Functional/FunctionalTestCase.php +++ b/tests/Functional/FunctionalTestCase.php @@ -14,16 +14,4 @@ abstract class FunctionalTestCase extends BaseTestCase { 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]; - } } From 71766fed47a21ac445fce6f250c0846d1d4e2ba3 Mon Sep 17 00:00:00 2001 From: CaliforniaMountainSnake Date: Thu, 27 Feb 2020 20:59:26 +0300 Subject: [PATCH 4/6] Added the BC Layer alias for some phpunit methods to fix build at php <= 5.5. --- README.md | 4 +-- .../Filters/ComplexMedia/TestSrcFilter.php | 25 +++++++++++++++++++ tests/BaseTestCase.php | 13 +++++++++- 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ae908ec..a6603ee 100644 --- a/README.md +++ b/README.md @@ -532,7 +532,7 @@ ComplexMedia already contains some built-in filters. For example: ```php -$complexMedia = $ffmpeg->openComplex(array ('video_1.mp4', 'video_2.mp4')); +$complexMedia = $ffmpeg->openComplex(array('video_1.mp4', 'video_2.mp4')); $complexMedia->filters() ->custom('[0:v][1:v]', 'hstack', '[v]'); $complexMedia @@ -580,7 +580,7 @@ As you can see, you can take multiple input sources, perform the complicated pro You do not have to use `-filter_complex`. For example, just extract the audio from the video: ```php -$complexMedia = $ffmpeg->openComplex(array ('video.mp4')); +$complexMedia = $ffmpeg->openComplex(array('video.mp4')); $complexMedia ->map(array('0:a'), new Mp3(), 'output.mp3') ->save(); diff --git a/src/FFMpeg/Filters/ComplexMedia/TestSrcFilter.php b/src/FFMpeg/Filters/ComplexMedia/TestSrcFilter.php index 139731c..265f60f 100644 --- a/src/FFMpeg/Filters/ComplexMedia/TestSrcFilter.php +++ b/src/FFMpeg/Filters/ComplexMedia/TestSrcFilter.php @@ -199,6 +199,31 @@ class TestSrcFilter extends AbstractComplexFilter return $this->type; } + /** + * Get minimal version of ffmpeg starting with which this filter is supported. + * + * @return string + */ + public function getMinimalFFMpegVersion() + { + switch ($this->type) { + case self::PAL75BARS: + case self::PAL100BARS: + return '4.1'; + case self::YUVTESTSRC: + return '3.2'; + case self::ALLRGB: + case self::ALLYUV: + return '2.8'; + case self::SMPTEHDBARS: + return '2.0'; + case self::SMPTEBARS: + return '1.0'; + default: + return '0.3'; + } + } + /** * {@inheritdoc} */ diff --git a/tests/BaseTestCase.php b/tests/BaseTestCase.php index 75f8877..f32ff49 100644 --- a/tests/BaseTestCase.php +++ b/tests/BaseTestCase.php @@ -8,7 +8,8 @@ use PHPUnit\Framework\TestCase; * This is a BC Layer to support phpunit 4.8 needed for php <= 5.5. */ if (class_exists('PHPUnit_Runner_Version') - && version_compare(\PHPUnit_Runner_Version::id(), '5', '<')) { + && version_compare(\PHPUnit_Runner_Version::id(), '5', '<') +) { class BaseTestCase extends TestCase { public static function assertScalar($value, $message = '') @@ -40,6 +41,16 @@ if (class_exists('PHPUnit_Runner_Version') { $this->setExpectedException($exception, $message); } + + public static function assertStringContainsString($needle, $haystack, $message = '') + { + self::assertContains($needle, $haystack, $message); + } + + public static function assertStringNotContainsString($needle, $haystack, $message = '') + { + self::assertNotContains($needle, $haystack, $message); + } } } else { class BaseTestCase extends TestCase From 4469847d469eaeda3c84dbb080f550875a7cb5e0 Mon Sep 17 00:00:00 2001 From: CaliforniaMountainSnake Date: Mon, 2 Mar 2020 14:50:42 +0300 Subject: [PATCH 5/6] Minor improvements of the ComplexMedia --- README.md | 4 +- src/FFMpeg/Media/ComplexMedia.php | 63 ++++++++++++++++--------- tests/Functional/ComplexMediaTest.php | 38 +++++++++++---- tests/Functional/FunctionalTestCase.php | 6 ++- 4 files changed, 75 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index a6603ee..45d4673 100644 --- a/README.md +++ b/README.md @@ -576,8 +576,8 @@ Then saves audios from the original videos into the 4 different formats and save As you can see, you can take multiple input sources, perform the complicated processing for them and produce multiple output files in the same time, in the one ffmpeg command. -#### Just give me map! -You do not have to use `-filter_complex`. For example, just extract the audio from the video: +#### Just give me a map! +You do not have to use `-filter_complex`. You can use only `-map` options. For example, just extract the audio from the video: ```php $complexMedia = $ffmpeg->openComplex(array('video.mp4')); diff --git a/src/FFMpeg/Media/ComplexMedia.php b/src/FFMpeg/Media/ComplexMedia.php index bc92b61..a199086 100644 --- a/src/FFMpeg/Media/ComplexMedia.php +++ b/src/FFMpeg/Media/ComplexMedia.php @@ -14,11 +14,12 @@ use FFMpeg\Filters\FiltersCollection; use FFMpeg\Format\AudioInterface; use FFMpeg\Format\FormatInterface; use FFMpeg\Format\ProgressableInterface; +use FFMpeg\Format\ProgressListener\AbstractProgressListener; use FFMpeg\Format\VideoInterface; /** * Complex media may have multiple inputs and multiple outputs. - * This class accepts only filters for -filter_complex. + * This class accepts only filters for -filter_complex option. * But you can set initial and additional parameters of the ffmpeg command. * * @see http://trac.ffmpeg.org/wiki/Creating%20multiple%20outputs @@ -46,7 +47,7 @@ class ComplexMedia extends AbstractMediaType private $mapCommands; /** - * @var array + * @var AbstractProgressListener[] */ private $listeners; @@ -88,6 +89,8 @@ class ComplexMedia extends AbstractMediaType } /** + * Add complex filter. + * * @param string $in * @param ComplexCompatibleFilter $filter * @param string $out @@ -117,7 +120,6 @@ class ComplexMedia extends AbstractMediaType /** * @return string[] - * @return void */ public function getInitialParameters() { @@ -137,7 +139,6 @@ class ComplexMedia extends AbstractMediaType /** * @return string[] - * @return void */ public function getAdditionalParameters() { @@ -180,18 +181,21 @@ class ComplexMedia extends AbstractMediaType } /** - * @param string[] $outs Output labels of the -filter_complex part. - * @param FormatInterface $format - * @param string $outputPathfile + * Select the streams for output. + * + * @param string[] $outs Output labels of the -filter_complex part. + * @param FormatInterface $format Format of the output file. + * @param string $outputFilename Output filename. * @param bool $forceDisableAudio * @param bool $forceDisableVideo * * @return $this + * @see https://ffmpeg.org/ffmpeg.html#Manual-stream-selection */ public function map( array $outs, FormatInterface $format, - $outputPathfile, + $outputFilename, $forceDisableAudio = false, $forceDisableVideo = false ) { @@ -202,12 +206,13 @@ class ComplexMedia extends AbstractMediaType } // Apply format params. - $commands = array_merge($commands, $this->applyFormatParams($format, $forceDisableAudio, $forceDisableVideo)); + $commands = array_merge($commands, + $this->applyFormatParams($format, $forceDisableAudio, $forceDisableVideo)); // Set output file. - $commands[] = $outputPathfile; + $commands[] = $outputFilename; - // Create listener. + // Create a listener. if ($format instanceof ProgressableInterface) { $listener = $format->createProgressListener($this, $this->ffprobe, 1, 1, 0); $this->listeners = array_merge($this->listeners, $listener); @@ -220,7 +225,7 @@ class ComplexMedia extends AbstractMediaType /** * Apply added filters and execute ffmpeg command. * - * @return ComplexMedia + * @return void * @throws RuntimeException */ public function save() @@ -233,8 +238,6 @@ class ComplexMedia extends AbstractMediaType } catch (ExecutionFailureException $e) { throw new RuntimeException('Encoding failed', $e->getCode(), $e); } - - return $this; } /** @@ -256,7 +259,7 @@ class ComplexMedia extends AbstractMediaType $commands[] = '-vcodec'; $commands[] = $format->getVideoCodec(); } - // If the user passed some additional parameters. + // If the user passed some additional format parameters. if ($format->getAdditionalParameters() !== null) { $commands = array_merge($commands, $format->getAdditionalParameters()); } @@ -348,13 +351,9 @@ class ComplexMedia extends AbstractMediaType */ 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, + $globalOptions = array('threads', 'filter_threads', 'filter_complex_threads'); + return array_merge(array('-y'), + $this->buildConfiguredGlobalOptions($globalOptions), $this->getInitialParameters(), $this->buildInputsPart($this->inputs), $this->buildComplexFilterPart($this->filters), @@ -363,6 +362,26 @@ class ComplexMedia extends AbstractMediaType ); } + /** + * @param string[] $optionNames + * + * @return array + */ + private function buildConfiguredGlobalOptions($optionNames) + { + $commands = array(); + foreach ($optionNames as $optionName) { + if (!$this->driver->getConfiguration()->has('ffmpeg.' . $optionName)) { + continue; + } + + $commands[] = '-' . $optionName; + $commands[] = $this->driver->getConfiguration()->get('ffmpeg.' . $optionName); + } + + return $commands; + } + /** * Build inputs part of the ffmpeg command. * diff --git a/tests/Functional/ComplexMediaTest.php b/tests/Functional/ComplexMediaTest.php index b7ad40e..fe11a2b 100644 --- a/tests/Functional/ComplexMediaTest.php +++ b/tests/Functional/ComplexMediaTest.php @@ -13,14 +13,14 @@ class ComplexMediaTest extends FunctionalTestCase /** * Path prefix to avoid conflicts with another tests. */ - const PATH_PREFIX = 'complex_media_'; + const OUTPUT_PATH_PREFIX = 'output/complex_media_'; public function testRunWithoutComplexFilterTestExtractAudio() { $ffmpeg = $this->getFFMpeg(); $inputs = array(realpath(__DIR__ . '/../files/Test.ogv')); $format = new Mp3(); - $output = __DIR__ . '/output/' . self::PATH_PREFIX . 'extracted_with_map.mp3'; + $output = __DIR__ . '/' . self::OUTPUT_PATH_PREFIX . 'extracted_with_map.mp3'; // You can run it without -filter_complex, just using -map. $complexMedia = $ffmpeg->openComplex($inputs); @@ -40,7 +40,7 @@ class ComplexMediaTest extends FunctionalTestCase $inputs = array(realpath(__DIR__ . '/../files/Audio.mp3')); $format = new Mp3(); $format->setAudioKiloBitrate(30); - $output = __DIR__ . '/output/' . self::PATH_PREFIX . 'audio_test.mp3'; + $output = __DIR__ . '/' . self::OUTPUT_PATH_PREFIX . 'audio_test.mp3'; $complexMedia = $ffmpeg->openComplex($inputs); $complexMedia @@ -61,7 +61,7 @@ class ComplexMediaTest extends FunctionalTestCase realpath(__DIR__ . '/../files/portrait.MOV') ); $format = new X264('aac', 'libx264'); - $output = __DIR__ . '/output/' . self::PATH_PREFIX . 'multiple_inputs_test.mp4'; + $output = __DIR__ . '/' . self::OUTPUT_PATH_PREFIX . 'multiple_inputs_test.mp4'; $complexMedia = $ffmpeg->openComplex($inputs); $complexMedia->filters() @@ -88,9 +88,9 @@ class ComplexMediaTest extends FunctionalTestCase $formatX264 = new X264('aac', 'libx264'); $formatMp3 = new Mp3(); - $outputMp3 = __DIR__ . '/output/' . self::PATH_PREFIX . 'test_multiple_outputs.mp3'; - $outputVideo1 = __DIR__ . '/output/' . self::PATH_PREFIX . 'test_multiple_outputs_v1.mp4'; - $outputVideo2 = __DIR__ . '/output/' . self::PATH_PREFIX . 'test_multiple_outputs_v2.mp4'; + $outputMp3 = __DIR__ . '/' . self::OUTPUT_PATH_PREFIX . 'test_multiple_outputs.mp3'; + $outputVideo1 = __DIR__ . '/' . self::OUTPUT_PATH_PREFIX . 'test_multiple_outputs_v1.mp4'; + $outputVideo2 = __DIR__ . '/' . self::OUTPUT_PATH_PREFIX . 'test_multiple_outputs_v2.mp4'; $complexMedia = $ffmpeg->openComplex($inputs); $complexMedia->filters() @@ -131,7 +131,7 @@ class ComplexMediaTest extends FunctionalTestCase $ffmpeg = $this->getFFMpeg(); $inputs = array(realpath(__DIR__ . '/../files/Test.ogv')); $format = new X264('aac', 'libx264'); - $output = __DIR__ . '/output/' . self::PATH_PREFIX . 'testsrc.mp4'; + $output = __DIR__ . '/' . self::OUTPUT_PATH_PREFIX . 'testsrc.mp4'; $complexMedia = $ffmpeg->openComplex($inputs); $complexMedia->filters() @@ -167,7 +167,7 @@ class ComplexMediaTest extends FunctionalTestCase $inputs = array(realpath(__DIR__ . '/../files/Test.ogv')); $format = new X264('aac', 'libx264'); - $output = __DIR__ . '/output/' . self::PATH_PREFIX . 'xstack_test.mp4'; + $output = __DIR__ . '/' . self::OUTPUT_PATH_PREFIX . 'xstack_test.mp4'; $complexMedia = $ffmpeg->openComplex($inputs); $complexMedia->filters() @@ -194,7 +194,7 @@ class ComplexMediaTest extends FunctionalTestCase $inputs = array(realpath(__DIR__ . '/../files/Test.ogv')); $watermark = realpath(__DIR__ . '/../files/watermark.png'); $format = new X264('aac', 'libx264'); - $output = __DIR__ . '/output/' . self::PATH_PREFIX . 'test_of_compatibility_with_existed_filters.mp4'; + $output = __DIR__ . '/' . self::OUTPUT_PATH_PREFIX . 'test_of_compatibility_with_existed_filters.mp4'; $complexMedia = $ffmpeg->openComplex($inputs); $complexMedia->filters() @@ -247,4 +247,22 @@ class ComplexMediaTest extends FunctionalTestCase 'outputFile.mp4', false, true); $this->assertStringNotContainsString('vcodec', $complexMedia2->getFinalCommand()); } + + public function testGlobalOptions() + { + $configuration = array( + 'ffmpeg.threads' => 3, + 'ffmpeg.filter_threads' => 13, + 'ffmpeg.filter_complex_threads' => 24, + ); + + $ffmpeg = $this->getFFMpeg($configuration); + $complexMedia = $ffmpeg->openComplex(array(__FILE__)); + $command = $complexMedia->getFinalCommand(); + + foreach ($configuration as $optionName => $optionValue) { + $optionName = str_replace('ffmpeg.', '', $optionName); + $this->assertStringContainsString('-' . $optionName . ' ' . $optionValue, $command); + } + } } diff --git a/tests/Functional/FunctionalTestCase.php b/tests/Functional/FunctionalTestCase.php index 15fcc26..6a2656a 100644 --- a/tests/Functional/FunctionalTestCase.php +++ b/tests/Functional/FunctionalTestCase.php @@ -8,10 +8,12 @@ use Tests\FFMpeg\BaseTestCase; abstract class FunctionalTestCase extends BaseTestCase { /** + * @param array $configuration + * * @return FFMpeg */ - public function getFFMpeg() + public function getFFMpeg($configuration = array()) { - return FFMpeg::create(array('timeout' => 300)); + return FFMpeg::create(array_merge(array('timeout' => 300), $configuration)); } } From 0d3bed21a727bb46810a3c0ae5f2f2f6cc1e922c Mon Sep 17 00:00:00 2001 From: CaliforniaMountainSnake Date: Mon, 23 Mar 2020 17:47:36 +0300 Subject: [PATCH 6/6] ComplexMedia has been renamed to the AdvancedMedia. --- README.md | 30 +++---- src/FFMpeg/Driver/FFMpegDriver.php | 2 +- src/FFMpeg/FFMpeg.php | 8 +- .../ANullSrcFilter.php | 6 +- .../AbstractComplexFilter.php | 2 +- .../ComplexCompatibleFilter.php | 11 ++- .../ComplexFilterContainer.php | 8 +- .../ComplexFilterInterface.php | 5 +- .../ComplexFilters.php | 10 +-- .../CustomComplexFilter.php | 6 +- .../SineFilter.php | 8 +- .../TestSrcFilter.php | 6 +- .../XStackFilter.php | 6 +- src/FFMpeg/Filters/Video/PadFilter.php | 6 +- src/FFMpeg/Filters/Video/WatermarkFilter.php | 6 +- .../{ComplexMedia.php => AdvancedMedia.php} | 20 ++--- ...lexMediaTest.php => AdvancedMediaTest.php} | 84 +++++++++---------- tests/Unit/Media/AdvancedMediaTest.php | 35 ++++++++ tests/Unit/Media/ComplexMediaTest.php | 35 -------- 19 files changed, 150 insertions(+), 144 deletions(-) rename src/FFMpeg/Filters/{ComplexMedia => AdvancedMedia}/ANullSrcFilter.php (91%) rename src/FFMpeg/Filters/{ComplexMedia => AdvancedMedia}/AbstractComplexFilter.php (97%) rename src/FFMpeg/Filters/{ComplexMedia => AdvancedMedia}/ComplexCompatibleFilter.php (69%) rename src/FFMpeg/Filters/{ComplexMedia => AdvancedMedia}/ComplexFilterContainer.php (90%) rename src/FFMpeg/Filters/{ComplexMedia => AdvancedMedia}/ComplexFilterInterface.php (64%) rename src/FFMpeg/Filters/{ComplexMedia => AdvancedMedia}/ComplexFilters.php (95%) rename src/FFMpeg/Filters/{ComplexMedia => AdvancedMedia}/CustomComplexFilter.php (83%) rename src/FFMpeg/Filters/{ComplexMedia => AdvancedMedia}/SineFilter.php (92%) rename src/FFMpeg/Filters/{ComplexMedia => AdvancedMedia}/TestSrcFilter.php (98%) rename src/FFMpeg/Filters/{ComplexMedia => AdvancedMedia}/XStackFilter.php (94%) rename src/FFMpeg/Media/{ComplexMedia.php => AdvancedMedia.php} (95%) rename tests/Functional/{ComplexMediaTest.php => AdvancedMediaTest.php} (79%) create mode 100644 tests/Unit/Media/AdvancedMediaTest.php delete mode 100644 tests/Unit/Media/ComplexMediaTest.php diff --git a/README.md b/README.md index 45d4673..ab458cf 100644 --- a/README.md +++ b/README.md @@ -520,22 +520,22 @@ $video More details about concatenation in FFMPEG can be found [here](https://trac.ffmpeg.org/wiki/Concatenate), [here](https://ffmpeg.org/ffmpeg-formats.html#concat-1) and [here](https://ffmpeg.org/ffmpeg.html#Stream-copy). -### ComplexMedia -ComplexMedia may have multiple inputs and multiple outputs. +### AdvancedMedia +AdvancedMedia may have multiple inputs and multiple outputs. This class has been developed primarily to use with `-filter_complex`. So, its `filters()` method accepts only filters that can be used inside `-filter_complex` command. -ComplexMedia already contains some built-in filters. +AdvancedMedia already contains some built-in filters. #### Base usage For example: ```php -$complexMedia = $ffmpeg->openComplex(array('video_1.mp4', 'video_2.mp4')); -$complexMedia->filters() +$advancedMedia = $ffmpeg->openAdvanced(array('video_1.mp4', 'video_2.mp4')); +$advancedMedia->filters() ->custom('[0:v][1:v]', 'hstack', '[v]'); -$complexMedia +$advancedMedia ->map(array('0:a', '[v]'), new X264('aac', 'libx264'), 'output.mp4') ->save(); ``` @@ -545,7 +545,7 @@ This code takes 2 input videos, stacks they horizontally in 1 output video and a #### Complicated example -A more difficult example of possibilities of the ComplexMedia. Consider all input videos already have the same resolution and duration. ("xstack" filter has been added in the 4.1 version of the ffmpeg). +A more difficult example of possibilities of the AdvancedMedia. Consider all input videos already have the same resolution and duration. ("xstack" filter has been added in the 4.1 version of the ffmpeg). ```php $inputs = array( @@ -555,14 +555,14 @@ $inputs = array( 'video_4.mp4', ); -$complexMedia = $ffmpeg->openComplex($inputs); -$complexMedia->filters() +$advancedMedia = $ffmpeg->openAdvanced($inputs); +$advancedMedia->filters() ->custom('[0:v]', 'negate', '[v0negate]') ->custom('[1:v]', 'edgedetect', '[v1edgedetect]') ->custom('[2:v]', 'hflip', '[v2hflip]') ->custom('[3:v]', 'vflip', '[v3vflip]') ->xStack('[v0negate][v1edgedetect][v2hflip][v3vflip]', XStackFilter::LAYOUT_2X2, 4, '[resultv]'); -$complexMedia +$advancedMedia ->map(array('0:a'), new Mp3(), 'video_1.mp3') ->map(array('1:a'), new Flac(), 'video_2.flac') ->map(array('2:a'), new Wav(), 'video_3.wav') @@ -580,18 +580,18 @@ As you can see, you can take multiple input sources, perform the complicated pro You do not have to use `-filter_complex`. You can use only `-map` options. For example, just extract the audio from the video: ```php -$complexMedia = $ffmpeg->openComplex(array('video.mp4')); -$complexMedia +$advancedMedia = $ffmpeg->openAdvanced(array('video.mp4')); +$advancedMedia ->map(array('0:a'), new Mp3(), 'output.mp3') ->save(); ``` #### Customisation -If you need you can extra customize the result ffmpeg command of the ComplexMedia: +If you need you can extra customize the result ffmpeg command of the AdvancedMedia: ```php -$complexMedia = $ffmpeg->openComplex($inputs); -$complexMedia +$advancedMedia = $ffmpeg->openAdvanced($inputs); +$advancedMedia ->setInitialParameters(array('the', 'params', 'that', 'will', 'be', 'added', 'before', '-i', 'part', 'of', 'the', 'command')) ->setAdditionalParameters(array('the', 'params', 'that', 'will', 'be', 'added', 'at', 'the', 'end', 'of', 'the', 'command')); ``` diff --git a/src/FFMpeg/Driver/FFMpegDriver.php b/src/FFMpeg/Driver/FFMpegDriver.php index fdb818c..a0de7be 100644 --- a/src/FFMpeg/Driver/FFMpegDriver.php +++ b/src/FFMpeg/Driver/FFMpegDriver.php @@ -66,7 +66,7 @@ class FFMpegDriver extends AbstractBinary { preg_match('#version\s(\S+)#', $this->command('-version'), $version); if (!isset($version[1])) { - throw new RuntimeException('Cannot to parse the ffmpeg version!'); + throw new RuntimeException('Cannot to parse the ffmpeg version!'); } return $version[1]; diff --git a/src/FFMpeg/FFMpeg.php b/src/FFMpeg/FFMpeg.php index 322c6bb..16a7ff1 100644 --- a/src/FFMpeg/FFMpeg.php +++ b/src/FFMpeg/FFMpeg.php @@ -16,7 +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\AdvancedMedia; use FFMpeg\Media\Video; use Psr\Log\LoggerInterface; @@ -110,11 +110,11 @@ class FFMpeg * * @param string[] $inputs Array of files to be opened. * - * @return ComplexMedia + * @return AdvancedMedia */ - public function openComplex($inputs) + public function openAdvanced($inputs) { - return new ComplexMedia($inputs, $this->driver, $this->ffprobe); + return new AdvancedMedia($inputs, $this->driver, $this->ffprobe); } /** diff --git a/src/FFMpeg/Filters/ComplexMedia/ANullSrcFilter.php b/src/FFMpeg/Filters/AdvancedMedia/ANullSrcFilter.php similarity index 91% rename from src/FFMpeg/Filters/ComplexMedia/ANullSrcFilter.php rename to src/FFMpeg/Filters/AdvancedMedia/ANullSrcFilter.php index b18a2d3..ca575b1 100644 --- a/src/FFMpeg/Filters/ComplexMedia/ANullSrcFilter.php +++ b/src/FFMpeg/Filters/AdvancedMedia/ANullSrcFilter.php @@ -1,8 +1,8 @@ baseFilter->applyComplex($media); } diff --git a/src/FFMpeg/Filters/ComplexMedia/ComplexFilterInterface.php b/src/FFMpeg/Filters/AdvancedMedia/ComplexFilterInterface.php similarity index 64% rename from src/FFMpeg/Filters/ComplexMedia/ComplexFilterInterface.php rename to src/FFMpeg/Filters/AdvancedMedia/ComplexFilterInterface.php index 6276f90..58568d6 100644 --- a/src/FFMpeg/Filters/ComplexMedia/ComplexFilterInterface.php +++ b/src/FFMpeg/Filters/AdvancedMedia/ComplexFilterInterface.php @@ -1,7 +1,10 @@ media = $media; } diff --git a/src/FFMpeg/Filters/ComplexMedia/CustomComplexFilter.php b/src/FFMpeg/Filters/AdvancedMedia/CustomComplexFilter.php similarity index 83% rename from src/FFMpeg/Filters/ComplexMedia/CustomComplexFilter.php rename to src/FFMpeg/Filters/AdvancedMedia/CustomComplexFilter.php index 1bc93f2..25e0d71 100644 --- a/src/FFMpeg/Filters/ComplexMedia/CustomComplexFilter.php +++ b/src/FFMpeg/Filters/AdvancedMedia/CustomComplexFilter.php @@ -1,8 +1,8 @@ filter); } diff --git a/src/FFMpeg/Filters/ComplexMedia/SineFilter.php b/src/FFMpeg/Filters/AdvancedMedia/SineFilter.php similarity index 92% rename from src/FFMpeg/Filters/ComplexMedia/SineFilter.php rename to src/FFMpeg/Filters/AdvancedMedia/SineFilter.php index 066701d..cdfe4ed 100644 --- a/src/FFMpeg/Filters/ComplexMedia/SineFilter.php +++ b/src/FFMpeg/Filters/AdvancedMedia/SineFilter.php @@ -1,8 +1,8 @@ getCommands(); } diff --git a/src/FFMpeg/Filters/Video/WatermarkFilter.php b/src/FFMpeg/Filters/Video/WatermarkFilter.php index bb4cae8..98d21c1 100644 --- a/src/FFMpeg/Filters/Video/WatermarkFilter.php +++ b/src/FFMpeg/Filters/Video/WatermarkFilter.php @@ -12,9 +12,9 @@ namespace FFMpeg\Filters\Video; use FFMpeg\Exception\InvalidArgumentException; -use FFMpeg\Filters\ComplexMedia\ComplexCompatibleFilter; +use FFMpeg\Filters\AdvancedMedia\ComplexCompatibleFilter; use FFMpeg\Format\VideoInterface; -use FFMpeg\Media\ComplexMedia; +use FFMpeg\Media\AdvancedMedia; use FFMpeg\Media\Video; class WatermarkFilter implements VideoFilterInterface, ComplexCompatibleFilter @@ -76,7 +76,7 @@ class WatermarkFilter implements VideoFilterInterface, ComplexCompatibleFilter /** * {@inheritdoc} */ - public function applyComplex(ComplexMedia $media) + public function applyComplex(AdvancedMedia $media) { return $this->getCommands(); } diff --git a/src/FFMpeg/Media/ComplexMedia.php b/src/FFMpeg/Media/AdvancedMedia.php similarity index 95% rename from src/FFMpeg/Media/ComplexMedia.php rename to src/FFMpeg/Media/AdvancedMedia.php index a199086..8524f49 100644 --- a/src/FFMpeg/Media/ComplexMedia.php +++ b/src/FFMpeg/Media/AdvancedMedia.php @@ -6,10 +6,10 @@ 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\AdvancedMedia\ComplexCompatibleFilter; +use FFMpeg\Filters\AdvancedMedia\ComplexFilterContainer; +use FFMpeg\Filters\AdvancedMedia\ComplexFilterInterface; +use FFMpeg\Filters\AdvancedMedia\ComplexFilters; use FFMpeg\Filters\FiltersCollection; use FFMpeg\Format\AudioInterface; use FFMpeg\Format\FormatInterface; @@ -18,13 +18,13 @@ use FFMpeg\Format\ProgressListener\AbstractProgressListener; use FFMpeg\Format\VideoInterface; /** - * Complex media may have multiple inputs and multiple outputs. + * AdvancedMedia may have multiple inputs and multiple outputs. * This class accepts only filters for -filter_complex option. * 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 +class AdvancedMedia extends AbstractMediaType { /** * @var string[] @@ -52,7 +52,7 @@ class ComplexMedia extends AbstractMediaType private $listeners; /** - * ComplexMedia constructor. + * AdvancedMedia constructor. * * @param string[] $inputs Array of files to be opened. * @param FFMpegDriver $driver @@ -110,7 +110,7 @@ class ComplexMedia extends AbstractMediaType { foreach ($filters as $filter) { if (!($filter instanceof ComplexFilterInterface)) { - throw new RuntimeException ('For ComplexMedia you can set filters collection' + throw new RuntimeException ('For AdvancedMedia you can set filters collection' . ' contains only objects that implement ComplexFilterInterface!'); } } @@ -129,7 +129,7 @@ class ComplexMedia extends AbstractMediaType /** * @param string[] $initialParameters * - * @return ComplexMedia + * @return AdvancedMedia */ public function setInitialParameters(array $initialParameters) { @@ -148,7 +148,7 @@ class ComplexMedia extends AbstractMediaType /** * @param string[] $additionalParameters * - * @return ComplexMedia + * @return AdvancedMedia */ public function setAdditionalParameters(array $additionalParameters) { diff --git a/tests/Functional/ComplexMediaTest.php b/tests/Functional/AdvancedMediaTest.php similarity index 79% rename from tests/Functional/ComplexMediaTest.php rename to tests/Functional/AdvancedMediaTest.php index fe11a2b..cb6d904 100644 --- a/tests/Functional/ComplexMediaTest.php +++ b/tests/Functional/AdvancedMediaTest.php @@ -3,17 +3,17 @@ namespace Tests\FFMpeg\Functional; use FFMpeg\Coordinate\Dimension; -use FFMpeg\Filters\ComplexMedia\TestSrcFilter; -use FFMpeg\Filters\ComplexMedia\XStackFilter; +use FFMpeg\Filters\AdvancedMedia\TestSrcFilter; +use FFMpeg\Filters\AdvancedMedia\XStackFilter; use FFMpeg\Format\Audio\Mp3; use FFMpeg\Format\Video\X264; -class ComplexMediaTest extends FunctionalTestCase +class AdvancedMediaTest extends FunctionalTestCase { /** * Path prefix to avoid conflicts with another tests. */ - const OUTPUT_PATH_PREFIX = 'output/complex_media_'; + const OUTPUT_PATH_PREFIX = 'output/advanced_media_'; public function testRunWithoutComplexFilterTestExtractAudio() { @@ -23,8 +23,8 @@ class ComplexMediaTest extends FunctionalTestCase $output = __DIR__ . '/' . self::OUTPUT_PATH_PREFIX . 'extracted_with_map.mp3'; // You can run it without -filter_complex, just using -map. - $complexMedia = $ffmpeg->openComplex($inputs); - $complexMedia + $advancedMedia = $ffmpeg->openAdvanced($inputs); + $advancedMedia ->map(array('0:a'), $format, $output) ->save(); @@ -42,8 +42,8 @@ class ComplexMediaTest extends FunctionalTestCase $format->setAudioKiloBitrate(30); $output = __DIR__ . '/' . self::OUTPUT_PATH_PREFIX . 'audio_test.mp3'; - $complexMedia = $ffmpeg->openComplex($inputs); - $complexMedia + $advancedMedia = $ffmpeg->openAdvanced($inputs); + $advancedMedia ->map(array('0:a'), $format, $output) ->save(); @@ -63,10 +63,10 @@ class ComplexMediaTest extends FunctionalTestCase $format = new X264('aac', 'libx264'); $output = __DIR__ . '/' . self::OUTPUT_PATH_PREFIX . 'multiple_inputs_test.mp4'; - $complexMedia = $ffmpeg->openComplex($inputs); - $complexMedia->filters() + $advancedMedia = $ffmpeg->openAdvanced($inputs); + $advancedMedia->filters() ->custom('[0:v][1:v]', 'hstack', '[v]'); - $complexMedia + $advancedMedia ->map(array('0:a', '[v]'), $format, $output) ->save(); @@ -77,7 +77,7 @@ class ComplexMediaTest extends FunctionalTestCase } /** - * @covers \FFMpeg\Media\ComplexMedia::map + * @covers \FFMpeg\Media\AdvancedMedia::map */ public function testMultipleOutputsTestAbsenceOfInputs() { @@ -92,14 +92,14 @@ class ComplexMediaTest extends FunctionalTestCase $outputVideo1 = __DIR__ . '/' . self::OUTPUT_PATH_PREFIX . 'test_multiple_outputs_v1.mp4'; $outputVideo2 = __DIR__ . '/' . self::OUTPUT_PATH_PREFIX . 'test_multiple_outputs_v2.mp4'; - $complexMedia = $ffmpeg->openComplex($inputs); - $complexMedia->filters() + $advancedMedia = $ffmpeg->openAdvanced($inputs); + $advancedMedia->filters() ->sine('[a]', 5) ->testSrc('[v1]', TestSrcFilter::TESTSRC, '160x120', 5) ->testSrc('[v2]', TestSrcFilter::TESTSRC, '160x120', 5) ->custom('[v1]', 'negate', '[v1negate]') ->custom('[v2]', 'edgedetect', '[v2edgedetect]'); - $complexMedia + $advancedMedia ->map(array('[a]'), $formatMp3, $outputMp3) ->map(array('[v1negate]'), $formatX264, $outputVideo1) ->map(array('[v2edgedetect]'), $formatX264, $outputVideo2) @@ -123,8 +123,8 @@ class ComplexMediaTest extends FunctionalTestCase } /** - * @covers \FFMpeg\Filters\ComplexMedia\TestSrcFilter - * @covers \FFMpeg\Filters\ComplexMedia\SineFilter + * @covers \FFMpeg\Filters\AdvancedMedia\TestSrcFilter + * @covers \FFMpeg\Filters\AdvancedMedia\SineFilter */ public function testTestSrcFilterTestSineFilter() { @@ -133,11 +133,11 @@ class ComplexMediaTest extends FunctionalTestCase $format = new X264('aac', 'libx264'); $output = __DIR__ . '/' . self::OUTPUT_PATH_PREFIX . 'testsrc.mp4'; - $complexMedia = $ffmpeg->openComplex($inputs); - $complexMedia->filters() + $advancedMedia = $ffmpeg->openAdvanced($inputs); + $advancedMedia->filters() ->sine('[a]', 10) ->testSrc('[v]', TestSrcFilter::TESTSRC, '160x120', 10); - $complexMedia + $advancedMedia ->map(array('[a]', '[v]'), $format, $output) ->save(); @@ -150,8 +150,8 @@ class ComplexMediaTest extends FunctionalTestCase /** * XStack filter is supported starting from 4.1 ffmpeg version. * - * @covers \FFMpeg\Filters\ComplexMedia\XStackFilter - * @covers \FFMpeg\Filters\ComplexMedia\SineFilter + * @covers \FFMpeg\Filters\AdvancedMedia\XStackFilter + * @covers \FFMpeg\Filters\AdvancedMedia\SineFilter */ public function testXStackFilter() { @@ -169,8 +169,8 @@ class ComplexMediaTest extends FunctionalTestCase $format = new X264('aac', 'libx264'); $output = __DIR__ . '/' . self::OUTPUT_PATH_PREFIX . 'xstack_test.mp4'; - $complexMedia = $ffmpeg->openComplex($inputs); - $complexMedia->filters() + $advancedMedia = $ffmpeg->openAdvanced($inputs); + $advancedMedia->filters() ->sine('[a]', 5) ->testSrc('[v1]', TestSrcFilter::TESTSRC, '160x120', 5) ->testSrc('[v2]', TestSrcFilter::TESTSRC, '160x120', 5) @@ -178,7 +178,7 @@ class ComplexMediaTest extends FunctionalTestCase ->testSrc('[v4]', TestSrcFilter::TESTSRC, '160x120', 5) ->xStack('[v1][v2][v3][v4]', XStackFilter::LAYOUT_2X2, 4, '[v]'); - $complexMedia + $advancedMedia ->map(array('[a]', '[v]'), $format, $output) ->save(); @@ -196,8 +196,8 @@ class ComplexMediaTest extends FunctionalTestCase $format = new X264('aac', 'libx264'); $output = __DIR__ . '/' . self::OUTPUT_PATH_PREFIX . 'test_of_compatibility_with_existed_filters.mp4'; - $complexMedia = $ffmpeg->openComplex($inputs); - $complexMedia->filters() + $advancedMedia = $ffmpeg->openAdvanced($inputs); + $advancedMedia->filters() // For unknown reasons WatermarkFilter produce an error on Windows, // because the path to the watermark becomes corrupted. // This behaviour related with Alchemy\BinaryDriver\AbstractBinary::command(). @@ -206,7 +206,7 @@ class ComplexMediaTest extends FunctionalTestCase // But on Linux systems filter works as expected. //->watermark('[0:v]', $watermark, '[v]') ->pad('[0:v]', new Dimension(300, 100), '[v]'); - $complexMedia + $advancedMedia ->map(array('0:a', '[v]'), $format, $output) ->save(); @@ -221,15 +221,15 @@ class ComplexMediaTest extends FunctionalTestCase $ffmpeg = $this->getFFMpeg(); $format = new X264(); - $complexMedia1 = $ffmpeg->openComplex(array(__FILE__)); - $complexMedia1 + $advancedMedia1 = $ffmpeg->openAdvanced(array(__FILE__)); + $advancedMedia1 ->map(array('test'), $format, 'outputFile.mp4', false); - $this->assertStringContainsString('acodec', $complexMedia1->getFinalCommand()); + $this->assertStringContainsString('acodec', $advancedMedia1->getFinalCommand()); - $complexMedia2 = $ffmpeg->openComplex(array(__FILE__)); - $complexMedia2 + $advancedMedia2 = $ffmpeg->openAdvanced(array(__FILE__)); + $advancedMedia2 ->map(array('test'), $format, 'outputFile.mp4', true); - $this->assertStringNotContainsString('acodec', $complexMedia2->getFinalCommand()); + $this->assertStringNotContainsString('acodec', $advancedMedia2->getFinalCommand()); } public function testForceDisableVideo() @@ -237,15 +237,15 @@ class ComplexMediaTest extends FunctionalTestCase $ffmpeg = $this->getFFMpeg(); $format = new X264(); - $complexMedia1 = $ffmpeg->openComplex(array(__FILE__)); - $complexMedia1->map(array('test'), $format, + $advancedMedia1 = $ffmpeg->openAdvanced(array(__FILE__)); + $advancedMedia1->map(array('test'), $format, 'outputFile.mp4', false, false); - $this->assertStringContainsString('vcodec', $complexMedia1->getFinalCommand()); + $this->assertStringContainsString('vcodec', $advancedMedia1->getFinalCommand()); - $complexMedia2 = $ffmpeg->openComplex(array(__FILE__)); - $complexMedia2->map(array('test'), $format, + $advancedMedia2 = $ffmpeg->openAdvanced(array(__FILE__)); + $advancedMedia2->map(array('test'), $format, 'outputFile.mp4', false, true); - $this->assertStringNotContainsString('vcodec', $complexMedia2->getFinalCommand()); + $this->assertStringNotContainsString('vcodec', $advancedMedia2->getFinalCommand()); } public function testGlobalOptions() @@ -257,8 +257,8 @@ class ComplexMediaTest extends FunctionalTestCase ); $ffmpeg = $this->getFFMpeg($configuration); - $complexMedia = $ffmpeg->openComplex(array(__FILE__)); - $command = $complexMedia->getFinalCommand(); + $advancedMedia = $ffmpeg->openAdvanced(array(__FILE__)); + $command = $advancedMedia->getFinalCommand(); foreach ($configuration as $optionName => $optionValue) { $optionName = str_replace('ffmpeg.', '', $optionName); diff --git a/tests/Unit/Media/AdvancedMediaTest.php b/tests/Unit/Media/AdvancedMediaTest.php new file mode 100644 index 0000000..3310e12 --- /dev/null +++ b/tests/Unit/Media/AdvancedMediaTest.php @@ -0,0 +1,35 @@ +getFFMpegDriverMock(); + $ffprobe = $this->getFFProbeMock(); + + $advancedMedia = new AdvancedMedia(array(__FILE__, __FILE__), $driver, $ffprobe); + $this->assertSame(array(__FILE__, __FILE__), $advancedMedia->getInputs()); + } + + public function testGetInputsCount() + { + $driver = $this->getFFMpegDriverMock(); + $ffprobe = $this->getFFProbeMock(); + + $advancedMedia = new AdvancedMedia(array(__FILE__, __FILE__), $driver, $ffprobe); + $this->assertEquals(2, $advancedMedia->getInputsCount()); + } + + public function testFiltersReturnFilters() + { + $driver = $this->getFFMpegDriverMock(); + $ffprobe = $this->getFFProbeMock(); + + $advancedMedia = new AdvancedMedia(array(__FILE__, __FILE__), $driver, $ffprobe); + $this->assertInstanceOf('FFMpeg\Filters\AdvancedMedia\ComplexFilters', $advancedMedia->filters()); + } +} diff --git a/tests/Unit/Media/ComplexMediaTest.php b/tests/Unit/Media/ComplexMediaTest.php deleted file mode 100644 index b359a50..0000000 --- a/tests/Unit/Media/ComplexMediaTest.php +++ /dev/null @@ -1,35 +0,0 @@ -getFFMpegDriverMock(); - $ffprobe = $this->getFFProbeMock(); - - $complexMedia = new ComplexMedia(array(__FILE__, __FILE__), $driver, $ffprobe); - $this->assertSame(array(__FILE__, __FILE__), $complexMedia->getInputs()); - } - - public function testGetInputsCount() - { - $driver = $this->getFFMpegDriverMock(); - $ffprobe = $this->getFFProbeMock(); - - $complexMedia = new ComplexMedia(array(__FILE__, __FILE__), $driver, $ffprobe); - $this->assertEquals(2, $complexMedia->getInputsCount()); - } - - public function testFiltersReturnFilters() - { - $driver = $this->getFFMpegDriverMock(); - $ffprobe = $this->getFFProbeMock(); - - $complexMedia = new ComplexMedia(array(__FILE__, __FILE__), $driver, $ffprobe); - $this->assertInstanceOf('FFMpeg\Filters\ComplexMedia\ComplexFilters', $complexMedia->filters()); - } -}