Skip to content

Commit

Permalink
Merge pull request #700 from CaliforniaMountainSnake/master
Browse files Browse the repository at this point in the history
ComplexMedia, -filter_complex and -map features
  • Loading branch information
jens1o committed Mar 29, 2020
2 parents 984dbd0 + 5b85b6b commit 4175c02
Show file tree
Hide file tree
Showing 20 changed files with 1,875 additions and 10 deletions.
76 changes: 76 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).

### 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.
AdvancedMedia already contains some built-in filters.

#### Base usage
For example:

```php
$advancedMedia = $ffmpeg->openAdvanced(array('video_1.mp4', 'video_2.mp4'));
$advancedMedia->filters()
->custom('[0:v][1:v]', 'hstack', '[v]');
$advancedMedia
->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 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(
'video_1.mp4',
'video_2.mp4',
'video_3.mp4',
'video_4.mp4',
);

$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]');
$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')
->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 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
$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 AdvancedMedia:

```php
$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'));
```

#### Formats

A format implements `FFMpeg\Format\FormatInterface`. To save to a video file,
Expand Down
17 changes: 17 additions & 0 deletions src/FFMpeg/Driver/FFMpegDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
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
Expand Down Expand Up @@ -54,4 +55,20 @@ public static function create(LoggerInterface $logger = null, $configuration = a
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];
}
}
15 changes: 15 additions & 0 deletions src/FFMpeg/FFMpeg.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use FFMpeg\Exception\InvalidArgumentException;
use FFMpeg\Exception\RuntimeException;
use FFMpeg\Media\Audio;
use FFMpeg\Media\AdvancedMedia;
use FFMpeg\Media\Video;
use Psr\Log\LoggerInterface;

Expand Down Expand Up @@ -59,6 +60,8 @@ public function getFFProbe()
/**
* Sets the ffmpeg driver.
*
* @param FFMpegDriver $ffmpeg
*
* @return FFMpeg
*/
public function setFFMpegDriver(FFMpegDriver $ffmpeg)
Expand Down Expand Up @@ -102,6 +105,18 @@ public function open($pathfile)
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 AdvancedMedia
*/
public function openAdvanced($inputs)
{
return new AdvancedMedia($inputs, $this->driver, $this->ffprobe);
}

/**
* Creates a new FFMpeg instance.
*
Expand Down
71 changes: 71 additions & 0 deletions src/FFMpeg/Filters/AdvancedMedia/ANullSrcFilter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php

namespace FFMpeg\Filters\AdvancedMedia;

use FFMpeg\Media\AdvancedMedia;

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

/**
* Get name of the filter.
*
* @return string
*/
public function getName()
{
return 'anullsrc';
}

/**
* {@inheritdoc}
*/
public function applyComplex(AdvancedMedia $media)
{
return array(
'-filter_complex',
$this->getName() . $this->buildFilterOptions(array(
'channel_layout' => $this->channelLayout,
'sample_rate' => $this->sampleRate,
'nb_samples' => $this->nbSamples,
))
);
}
}
62 changes: 62 additions & 0 deletions src/FFMpeg/Filters/AdvancedMedia/AbstractComplexFilter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

namespace FFMpeg\Filters\AdvancedMedia;

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

/**
* 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.
*
* @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 '';
}
}
35 changes: 35 additions & 0 deletions src/FFMpeg/Filters/AdvancedMedia/ComplexCompatibleFilter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

namespace FFMpeg\Filters\AdvancedMedia;

use FFMpeg\Filters\FilterInterface;
use FFMpeg\Media\AdvancedMedia;

/**
* A filter that can be used inside "-filter_complex" option.
*/
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.
*
* @param AdvancedMedia $media
*
* @return string[] An array of arguments.
*/
public function applyComplex(AdvancedMedia $media);
}

0 comments on commit 4175c02

Please sign in to comment.