🔀 Merge branch 'release/0.1.3' into stable
Some checks failed
Check & fix styling / php-cs-fixer (push) Has been cancelled
Tests / P8 - prefer-lowest - ubuntu-latest (push) Has been cancelled
Tests / P8 - prefer-stable - ubuntu-latest (push) Has been cancelled
Tests / P8 - prefer-lowest - windows-latest (push) Has been cancelled
Tests / P8 - prefer-stable - windows-latest (push) Has been cancelled
Some checks failed
Check & fix styling / php-cs-fixer (push) Has been cancelled
Tests / P8 - prefer-lowest - ubuntu-latest (push) Has been cancelled
Tests / P8 - prefer-stable - ubuntu-latest (push) Has been cancelled
Tests / P8 - prefer-lowest - windows-latest (push) Has been cancelled
Tests / P8 - prefer-stable - windows-latest (push) Has been cancelled
This commit is contained in:
commit
77d43e9f69
5 changed files with 103 additions and 117 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -12,3 +12,5 @@ vendor
|
|||
.php-cs-fixer.cache
|
||||
|
||||
*.mkv
|
||||
tags
|
||||
*.mp3
|
||||
|
|
|
|||
|
|
@ -2,6 +2,11 @@
|
|||
|
||||
All notable changes to `ffmpeg-mappable-media` will be documented in this file.
|
||||
|
||||
## 0.1.3
|
||||
|
||||
- ✨ Support progress listeners
|
||||
- 📝 Improve documentation
|
||||
|
||||
## 0.1.0
|
||||
|
||||
🎉 Initial release. Basic functionality works.
|
||||
|
|
|
|||
57
README.md
57
README.md
|
|
@ -88,11 +88,62 @@ ffmpeg -y -i video.mp4 -i audio.opus -i sub.srt -map 0:0 -c:0 libvp9 -map 1:0 -c
|
|||
|
||||
This will result in two files. The first will be a webm file (suitable for play in a browser) with a `title` field on the file set to "The Greatest Story Ever Hulaed". The second file will copy the first stream from each file directly, and set the language of the subtitle stream to English.
|
||||
|
||||
## What's not yet done?
|
||||
### Using FormatInterface
|
||||
|
||||
Mostly documentation. There are a lot more ways to use this, including using the `Format` objects from PHP-FFMpeg to set the codecs, and the ability to use callbacks to set additional data on both maps and individual streams.
|
||||
It is also possible to set the codec from a stream using the [Formats](https://github.com/PHP-FFMpeg/PHP-FFMpeg/#formats) from PHP-FFMPEG.
|
||||
|
||||
I also need to figure out how to set the listeners on the codecs to get realtime progress updates (see [Formats](https://github.com/PHP-FFMpeg/PHP-FFMpeg/#formats)). Once that's figured out, I need to document it properly here.
|
||||
```php
|
||||
use FFMpeg\Format\Video\X264;
|
||||
|
||||
MappableMedia::make($ffmpeg)
|
||||
->addInput('input.mkv')
|
||||
->map()
|
||||
->saveAs('output.mk')
|
||||
->stream()->setCodec(new X264())->saveStream()
|
||||
->saveMap()
|
||||
->save()
|
||||
```
|
||||
|
||||
### Using callbacks
|
||||
|
||||
The `map` and `stream` methods can take an optional callback, allowing you to set the properties on those individual objects. When a callback is passed the object itself is returned, allowing you to continue the chain.
|
||||
|
||||
```php
|
||||
MappableMedia::make($ffmpeg)
|
||||
->addInput('input.mkv')
|
||||
->map(function (Map $map) {
|
||||
$map->saveAs('output.mk')
|
||||
->stream(function (Stream $stream) {
|
||||
$stream->copy()->setInput('0:0');
|
||||
});
|
||||
})->save()
|
||||
```
|
||||
|
||||
Note that when using callbacks, you also don't need to call `saveMap()` or `saveStream()`.
|
||||
|
||||
### Getting progress updates
|
||||
|
||||
It is possible to set a listener on the individual streams, using the Format class. However, this don't reliably report the progress of a particular stream. So, this package adds a listener on the `MappableMedia` object itself, which represents the progress of the entire job.
|
||||
|
||||
```php
|
||||
MappableMedia::make($ffmpeg)
|
||||
->addInput('input.mkv')
|
||||
->map()
|
||||
->saveAs('output.mk')
|
||||
->stream()->copy()->saveStream()
|
||||
->saveMap()
|
||||
->on('progress', function (MappableMedia $media, int $percent, int $remaining, int $rate) {
|
||||
echo "Finished {$percent}% of ", $media->getPathfile(), "\n";
|
||||
})
|
||||
->save()
|
||||
```
|
||||
|
||||
## Future Plans
|
||||
|
||||
- [ ] Add listeners that return all the stdin/stderr
|
||||
- [ ] Support itsoffset for inputs
|
||||
- [ ] Support -t and -ss
|
||||
+ I need to figure out how this will affect the progress listener.
|
||||
|
||||
## Contributing
|
||||
|
||||
|
|
|
|||
19
src/Map.php
19
src/Map.php
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
namespace Danjones\FFMpeg;
|
||||
|
||||
use FFMpeg\Format\ProgressableInterface;
|
||||
use FFMpeg\Format\ProgressListener\AbstractProgressListener;
|
||||
|
||||
class Map
|
||||
{
|
||||
use Traits\HasMetadata;
|
||||
|
|
@ -10,6 +13,8 @@ class Map
|
|||
protected string $path;
|
||||
/** @var Stream[] */
|
||||
protected array $streams = [];
|
||||
/** @var AbstractProgressListener[] */
|
||||
protected array $listeners = [];
|
||||
|
||||
public function __construct(MappableMedia $media)
|
||||
{
|
||||
|
|
@ -46,10 +51,24 @@ class Map
|
|||
public function saveStream(Stream $stream): static
|
||||
{
|
||||
$this->streams[] = $stream;
|
||||
$format = $stream->getCodec();
|
||||
if ($format instanceof ProgressableInterface) {
|
||||
$listener = $format->createProgressListener(
|
||||
$this->media,
|
||||
$this->media->getFFProbe(),
|
||||
1, 1, 0
|
||||
);
|
||||
$this->listeners = array_merge($this->listeners, $listener);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getListeners(): array
|
||||
{
|
||||
return $this->listeners;
|
||||
}
|
||||
|
||||
public function buildCommand(): array
|
||||
{
|
||||
$commands = [];
|
||||
|
|
|
|||
|
|
@ -3,31 +3,27 @@
|
|||
namespace Danjones\FFMpeg;
|
||||
|
||||
use Alchemy\BinaryDriver\Exception\ExecutionFailureException;
|
||||
use Evenement\EventEmitterInterface;
|
||||
use Evenement\EventEmitterTrait;
|
||||
use FFMpeg\Driver\FFMpegDriver;
|
||||
use FFMpeg\Exception\RuntimeException;
|
||||
use FFMpeg\FFMpeg;
|
||||
use FFMpeg\FFProbe;
|
||||
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;
|
||||
use FFMpeg\Format\ProgressableInterface;
|
||||
use FFMpeg\Format\ProgressListener\AbstractProgressListener;
|
||||
use FFMpeg\Format\VideoInterface;
|
||||
use FFMpeg\Format\ProgressListener\VideoProgressListener;
|
||||
use FFMpeg\Media\AbstractMediaType;
|
||||
|
||||
/**
|
||||
* AdvancedMedia may have multiple inputs and multiple outputs.
|
||||
* This class accepts only filters for -filter_complex option.
|
||||
* MappableMedia may have multiple inputs and multiple outputs.
|
||||
* This class does not accept filters.
|
||||
* But you can set initial and additional parameters of the ffmpeg command.
|
||||
*
|
||||
* @see http://trac.ffmpeg.org/wiki/Creating%20multiple%20outputs
|
||||
*/
|
||||
class MappableMedia extends AbstractMediaType
|
||||
class MappableMedia extends AbstractMediaType implements EventEmitterInterface
|
||||
{
|
||||
use EventEmitterTrait;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
|
|
@ -46,16 +42,12 @@ class MappableMedia extends AbstractMediaType
|
|||
*/
|
||||
protected array $additionalParameters = [];
|
||||
|
||||
/**
|
||||
* @var AbstractProgressListener[]
|
||||
*/
|
||||
protected array $listeners = [];
|
||||
/** @var AbstractProgressListener[] */
|
||||
protected array $progressListeners = [];
|
||||
|
||||
public function __construct(FFMpegDriver $driver, FFProbe $ffprobe)
|
||||
{
|
||||
// In case of error user will see this text in the error log.
|
||||
// But absence of inputs is a correct situation for some cases.
|
||||
// For example, if the user will use filters such as "testsrc".
|
||||
$pathfile = 'you_can_pass_empty_inputs_array_only_if_you_use_computed_inputs';
|
||||
|
||||
parent::__construct($pathfile, $driver, $ffprobe);
|
||||
|
|
@ -68,7 +60,7 @@ class MappableMedia extends AbstractMediaType
|
|||
|
||||
public function filters()
|
||||
{
|
||||
return $this->filters;
|
||||
return null;
|
||||
}
|
||||
|
||||
public function addInput(string $path): static
|
||||
|
|
@ -152,118 +144,35 @@ class MappableMedia extends AbstractMediaType
|
|||
public function saveMap(Map $map): static
|
||||
{
|
||||
$this->maps[] = $map;
|
||||
$this->progressListeners = array_merge($this->progressListeners, $map->getListeners());
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @todo Redo all of this.
|
||||
* @see https://ffmpeg.org/ffmpeg.html#Manual-stream-selection
|
||||
*/
|
||||
private function map2(
|
||||
array $outs,
|
||||
FormatInterface $format,
|
||||
$outputFilename,
|
||||
$forceDisableAudio = false,
|
||||
$forceDisableVideo = false
|
||||
) {
|
||||
$commands = [];
|
||||
foreach ($outs as $label) {
|
||||
$commands[] = '-map';
|
||||
$commands[] = $label;
|
||||
}
|
||||
|
||||
// Apply format params.
|
||||
$commands = array_merge(
|
||||
$commands,
|
||||
$this->applyFormatParams($format, $forceDisableAudio, $forceDisableVideo)
|
||||
);
|
||||
|
||||
// Set output file.
|
||||
$commands[] = $outputFilename;
|
||||
|
||||
// Create a 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.
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function save(): void
|
||||
{
|
||||
$command = $this->buildCommand();
|
||||
$this->addListener();
|
||||
|
||||
try {
|
||||
$this->driver->command($command, false, $this->listeners);
|
||||
$this->driver->command($command, false, $this->progressListeners);
|
||||
} catch (ExecutionFailureException $e) {
|
||||
throw new RuntimeException('Encoding failed', $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $forceDisableAudio
|
||||
* @param bool $forceDisableVideo
|
||||
*
|
||||
* @return array
|
||||
* @todo Redo it all
|
||||
*/
|
||||
protected function applyFormatParams(
|
||||
FormatInterface $format,
|
||||
$forceDisableAudio = false,
|
||||
$forceDisableVideo = false
|
||||
) {
|
||||
// Set format params.
|
||||
$commands = [];
|
||||
if (!$forceDisableVideo && $format instanceof VideoInterface) {
|
||||
if (null !== $format->getVideoCodec()) {
|
||||
$commands[] = '-vcodec';
|
||||
$commands[] = $format->getVideoCodec();
|
||||
}
|
||||
// If the user passed some additional format parameters.
|
||||
if (null !== $format->getAdditionalParameters()) {
|
||||
$commands = array_merge($commands, $format->getAdditionalParameters());
|
||||
}
|
||||
}
|
||||
if (!$forceDisableAudio && $format instanceof AudioInterface) {
|
||||
if (null !== $format->getAudioCodec()) {
|
||||
$commands[] = '-acodec';
|
||||
$commands[] = $format->getAudioCodec();
|
||||
}
|
||||
if (null !== $format->getAudioKiloBitrate()) {
|
||||
$commands[] = '-b:a';
|
||||
$commands[] = $format->getAudioKiloBitrate().'k';
|
||||
}
|
||||
if (null !== $format->getAudioChannels()) {
|
||||
$commands[] = '-ac';
|
||||
$commands[] = $format->getAudioChannels();
|
||||
}
|
||||
}
|
||||
protected function addListener(): void
|
||||
{
|
||||
$self = $this;
|
||||
$listener = new VideoProgressListener($this->ffprobe, $this->getPathfile(), 1, 1, 0);
|
||||
|
||||
// If the user passed some extra parameters.
|
||||
if ($format->getExtraParams()) {
|
||||
$commands = array_merge($commands, $format->getExtraParams());
|
||||
}
|
||||
$listener->on('progress', function (...$args) use ($self) {
|
||||
$self->emit('progress', array_merge([$self], $args));
|
||||
});
|
||||
|
||||
return $commands;
|
||||
$this->progressListeners[] = $listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue