🔥 Delete almost all the code

This commit is contained in:
Dan Jones 2022-08-21 15:13:03 -05:00
commit 001f55939b
118 changed files with 0 additions and 9383 deletions

View file

@ -1,218 +0,0 @@
<?php
/*
* This file is part of Alchemy\BinaryDriver.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\BinaryDriver;
use Alchemy\BinaryDriver\Exception\ExecutableNotFoundException;
use Alchemy\BinaryDriver\Exception\ExecutionFailureException;
use Alchemy\BinaryDriver\Listeners\Listeners;
use Alchemy\BinaryDriver\Listeners\ListenerInterface;
use Evenement\EventEmitter;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Symfony\Component\Process\ExecutableFinder;
use Symfony\Component\Process\Process;
abstract class AbstractBinary extends EventEmitter implements BinaryInterface
{
/** @var ConfigurationInterface */
protected $configuration;
/** @var ProcessBuilderFactoryInterface */
protected $factory;
/** @var ProcessRunner */
private $processRunner;
/** @var Listeners */
private $listenersManager;
public function __construct(ProcessBuilderFactoryInterface $factory, LoggerInterface $logger, ConfigurationInterface $configuration)
{
$this->factory = $factory;
$this->configuration = $configuration;
$this->processRunner = new ProcessRunner($logger, $this->getName());
$this->listenersManager = new Listeners();
$this->applyProcessConfiguration();
}
/**
* {@inheritdoc}
*/
public function listen(ListenerInterface $listener)
{
$this->listenersManager->register($listener, $this);
return $this;
}
/**
* {@inheritdoc}
*/
public function unlisten(ListenerInterface $listener)
{
$this->listenersManager->unregister($listener, $this);
return $this;
}
/**
* {@inheritdoc}
*/
public function getConfiguration()
{
return $this->configuration;
}
/**
* {@inheritdoc}
*
* @return BinaryInterface
*/
public function setConfiguration(ConfigurationInterface $configuration)
{
$this->configuration = $configuration;
$this->applyProcessConfiguration();
return $this;
}
/**
* {@inheritdoc}
*/
public function getProcessBuilderFactory()
{
return $this->factory;
}
/**
* {@inheritdoc}
*
* @return BinaryInterface
*/
public function setProcessBuilderFactory(ProcessBuilderFactoryInterface $factory)
{
$this->factory = $factory;
$this->applyProcessConfiguration();
return $this;
}
/**
* {@inheritdoc}
*/
public function getProcessRunner()
{
return $this->processRunner;
}
/**
* {@inheritdoc}
*/
public function setProcessRunner(ProcessRunnerInterface $runner)
{
$this->processRunner = $runner;
return $this;
}
/**
* {@inheritdoc}
*/
public function command($command, $bypassErrors = false, $listeners = null)
{
if (!is_array($command)) {
$command = array($command);
}
return $this->run($this->factory->create($command), $bypassErrors, $listeners);
}
/**
* {@inheritdoc}
*/
public static function load($binaries, LoggerInterface $logger = null, $configuration = array())
{
$finder = new ExecutableFinder();
$binary = null;
$binaries = is_array($binaries) ? $binaries : array($binaries);
foreach ($binaries as $candidate) {
if (file_exists($candidate) && is_executable($candidate)) {
$binary = $candidate;
break;
}
if (null !== $binary = $finder->find($candidate)) {
break;
}
}
if (null === $binary) {
throw new ExecutableNotFoundException(sprintf(
'Executable not found, proposed : %s', implode(', ', $binaries)
));
}
if (null === $logger) {
$logger = new NullLogger();
}
$configuration = $configuration instanceof ConfigurationInterface ? $configuration : new Configuration($configuration);
return new static(new ProcessBuilderFactory($binary), $logger, $configuration);
}
/**
* Returns the name of the driver
*
* @return string
*/
abstract public function getName();
/**
* Executes a process, logs events
*
* @param Process $process
* @param Boolean $bypassErrors Set to true to disable throwing ExecutionFailureExceptions
* @param ListenerInterface|array $listeners A listener or an array of listener to register for this unique run
*
* @return string The Process output
*
* @throws ExecutionFailureException in case of process failure.
*/
protected function run(Process $process, $bypassErrors = false, $listeners = null)
{
if (null !== $listeners) {
if (!is_array($listeners)) {
$listeners = array($listeners);
}
$listenersManager = clone $this->listenersManager;
foreach ($listeners as $listener) {
$listenersManager->register($listener, $this);
}
} else {
$listenersManager = $this->listenersManager;
}
return $this->processRunner->run($process, $listenersManager->storage, $bypassErrors);
}
private function applyProcessConfiguration()
{
if ($this->configuration->has('timeout')) {
$this->factory->setTimeout($this->configuration->get('timeout'));
}
return $this;
}
}

View file

@ -1,77 +0,0 @@
<?php
namespace Alchemy\BinaryDriver;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
use Symfony\Component\Process\Process;
/**
* Convenient PHPUnit methods for testing BinaryDriverInterface implementations.
*/
class BinaryDriverTestCase extends TestCase
{
/**
* @return ProcessBuilderFactoryInterface
*/
public function createProcessBuilderFactoryMock()
{
return $this->getMockBuilder('Alchemy\BinaryDriver\ProcessBuilderFactoryInterface')->getMock();
}
/**
* @param integer $runs The number of runs expected
* @param Boolean $success True if the process expects to be successfull
* @param string $commandLine The commandline executed
* @param string $output The process output
* @param string $error The process error output
*
* @return Process
*/
public function createProcessMock($runs = 1, $success = true, $commandLine = null, $output = null, $error = null, $callback = false)
{
$process = $this->getMockBuilder('Symfony\Component\Process\Process')
->disableOriginalConstructor()
->getMock();
$builder = $process->expects($this->exactly($runs))
->method('run');
if (true === $callback) {
$builder->with($this->isInstanceOf('Closure'));
}
$process->expects($this->any())
->method('isSuccessful')
->will($this->returnValue($success));
foreach ([
'getOutput' => $output,
'getErrorOutput' => $error ?: "",
'getCommandLine' => $commandLine,
] as $command => $value) {
$process
->expects($this->any())
->method($command)
->will($this->returnValue($value));
}
return $process;
}
/**
* @return LoggerInterface
*/
public function createLoggerMock()
{
return $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
}
/**
* @return ConfigurationInterface
*/
public function createConfigurationMock()
{
return $this->getMockBuilder('Alchemy\BinaryDriver\ConfigurationInterface')->getMock();
}
}

View file

@ -1,67 +0,0 @@
<?php
/*
* This file is part of Alchemy\BinaryDriver.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\BinaryDriver;
use Alchemy\BinaryDriver\Exception\ExecutableNotFoundException;
use Alchemy\BinaryDriver\Exception\ExecutionFailureException;
use Alchemy\BinaryDriver\Listeners\ListenerInterface;
use Psr\Log\LoggerInterface;
use Evenement\EventEmitterInterface;
interface BinaryInterface extends ConfigurationAwareInterface, ProcessBuilderFactoryAwareInterface, ProcessRunnerAwareInterface, EventEmitterInterface
{
/**
* Adds a listener to the binary driver
*
* @param ListenerInterface $listener
*
* @return BinaryInterface
*/
public function listen(ListenerInterface $listener);
/**
* Removes a listener from the binary driver
*
* @param ListenerInterface $listener
*
* @return BinaryInterface
*/
public function unlisten(ListenerInterface $listener);
/**
* Runs a command against the driver.
*
* Calling this method on a `ls` driver with the command `-a` would run `ls -a`.
*
* @param array|string $command A command or an array of command
* @param Boolean $bypassErrors If set to true, an erronous process will not throw an exception
* @param ListenerInterface|array $listeners A listener or an array of listeners to register for this unique run
*
* @return string The command output
*
* @throws ExecutionFailureException in case of process failure.
*/
public function command($command, $bypassErrors = false, $listeners = null);
/**
* Loads a binary
*
* @param string|array $binaries A binary name or an array of binary names
* @param null|LoggerInterface $logger A Logger
* @param array|ConfigurationInterface $configuration The configuration
*
* @throws ExecutableNotFoundException In case none of the binaries were found
*
* @return BinaryInterface
*/
public static function load($binaries, LoggerInterface $logger = null, $configuration = array());
}

View file

@ -1,109 +0,0 @@
<?php
/*
* This file is part of Alchemy\BinaryDriver.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\BinaryDriver;
use Traversable;
class Configuration implements ConfigurationInterface
{
private $data;
public function __construct(array $data = [])
{
$this->data = $data;
}
/**
* {@inheritdoc}
*/
public function getIterator(): Traversable
{
return new \ArrayIterator($this->data);
}
/**
* {@inheritdoc}
*/
public function get($key, $default = null)
{
return isset($this->data[$key]) ? $this->data[$key] : $default;
}
/**
* {@inheritdoc}
*/
public function set($key, $value)
{
$this->data[$key] = $value;
return $this;
}
/**
* {@inheritdoc}
*/
public function has($key)
{
return array_key_exists($key, $this->data);
}
/**
* {@inheritdoc}
*/
public function remove($key)
{
$value = $this->get($key);
unset($this->data[$key]);
return $value;
}
/**
* {@inheritdoc}
*/
public function all()
{
return $this->data;
}
/**
* {@inheritdoc}
*/
public function offsetExists($offset): bool
{
return $this->has($offset);
}
/**
* {@inheritdoc}
*/
public function offsetGet($offset): mixed
{
return $this->get($offset);
}
/**
* {@inheritdoc}
*/
public function offsetSet($offset, $value): void
{
$this->set($offset, $value);
}
/**
* {@inheritdoc}
*/
public function offsetUnset($offset): void
{
$this->remove($offset);
}
}

View file

@ -1,29 +0,0 @@
<?php
/*
* This file is part of Alchemy\BinaryDriver.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\BinaryDriver;
interface ConfigurationAwareInterface
{
/**
* Returns the configuration
*
* @return ConfigurationInterface
*/
public function getConfiguration();
/**
* Set the configuration
*
* @param ConfigurationInterface $configuration
*/
public function setConfiguration(ConfigurationInterface $configuration);
}

View file

@ -1,58 +0,0 @@
<?php
/*
* This file is part of Alchemy\BinaryDriver.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\BinaryDriver;
interface ConfigurationInterface extends \ArrayAccess, \IteratorAggregate
{
/**
* Returns the value given a key from configuration
*
* @param string $key
* @param mixed $default The default value in case the key does not exist
*
* @return mixed
*/
public function get($key, $default = null);
/**
* Set a value to configuration
*
* @param string $key The key
* @param mixed $value The value corresponding to the key
*/
public function set($key, $value);
/**
* Tells if Configuration contains `$key`
*
* @param string $key
*
* @return Boolean
*/
public function has($key);
/**
* Removes a value given a key
*
* @param string $key
*
* @return mixed The previous value
*/
public function remove($key);
/**
* Returns all values set in the configuration
*
* @return array
*/
public function all();
}

View file

@ -1,16 +0,0 @@
<?php
/*
* This file is part of Alchemy\BinaryDriver.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\BinaryDriver\Exception;
interface ExceptionInterface
{
}

View file

@ -1,16 +0,0 @@
<?php
/*
* This file is part of Alchemy\BinaryDriver.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\BinaryDriver\Exception;
class ExecutableNotFoundException extends \RuntimeException implements ExceptionInterface
{
}

View file

@ -1,37 +0,0 @@
<?php
/*
* This file is part of Alchemy\BinaryDriver.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\BinaryDriver\Exception;
class ExecutionFailureException extends \RuntimeException implements ExceptionInterface
{
/** @var string */
protected $command;
/** @var string */
protected $errorOutput;
public function __construct($binaryName, $command, $errorOutput = null, $code = 0, $previous = null)
{
$message = sprintf("%s failed to execute command %s:\n\nError Output:\n\n %s", $binaryName, $command, $errorOutput);
parent::__construct($message, $code, $previous);
$this->command = $command;
$this->errorOutput = $errorOutput;
}
public function getCommand(){
return $this->command;
}
public function getErrorOutput(){
return $this->errorOutput;
}
}

View file

@ -1,16 +0,0 @@
<?php
/*
* This file is part of Alchemy\BinaryDriver.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\BinaryDriver\Exception;
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
{
}

View file

@ -1,58 +0,0 @@
<?php
/*
* This file is part of Alchemy\BinaryDriver.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\BinaryDriver\Listeners;
use Evenement\EventEmitter;
use Symfony\Component\Process\Process;
class DebugListener extends EventEmitter implements ListenerInterface
{
private $prefixOut;
private $prefixErr;
private $eventOut;
private $eventErr;
public function __construct($prefixOut = '[OUT] ', $prefixErr = '[ERROR] ', $eventOut = 'debug', $eventErr = 'debug')
{
$this->prefixOut = $prefixOut;
$this->prefixErr = $prefixErr;
$this->eventOut = $eventOut;
$this->eventErr = $eventErr;
}
/**
* {@inheritdoc}
*/
public function handle($type, $data)
{
if (Process::ERR === $type) {
$this->emitLines($this->eventErr, $this->prefixErr, $data);
} elseif (Process::OUT === $type) {
$this->emitLines($this->eventOut, $this->prefixOut, $data);
}
}
/**
* {@inheritdoc}
*/
public function forwardedEvents()
{
return array_unique(array($this->eventErr, $this->eventOut));
}
private function emitLines($event, $prefix, $lines)
{
foreach (explode("\n", $lines) as $line) {
$this->emit($event, array($prefix . $line));
}
}
}

View file

@ -1,32 +0,0 @@
<?php
/*
* This file is part of Alchemy\BinaryDriver.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\BinaryDriver\Listeners;
use Evenement\EventEmitterInterface;
interface ListenerInterface extends EventEmitterInterface
{
/**
* Handle the output of a ProcessRunner
*
* @param string $type The data type, one of Process::ERR, Process::OUT constants
* @param string $data The output
*/
public function handle($type, $data);
/**
* An array of events that should be forwarded to BinaryInterface
*
* @return array
*/
public function forwardedEvents();
}

View file

@ -1,88 +0,0 @@
<?php
namespace Alchemy\BinaryDriver\Listeners;
use SplObjectStorage;
use Evenement\EventEmitter;
class Listeners extends EventEmitter
{
/** @var SplObjectStorage */
public $storage;
public function __construct()
{
$this->storage = new SplObjectStorage();
}
public function __clone()
{
$storage = $this->storage;
$this->storage = new SplObjectStorage();
$this->storage->addAll($storage);
}
/**
* Registers a listener, pass the listener events to the target.
*
* @param ListenerInterface $listener
* @param null|EventEmitter $target
*
* @return ListenersInterface
*/
public function register(ListenerInterface $listener, EventEmitter $target = null)
{
$EElisteners = array();
if (null !== $target) {
$EElisteners = $this->forwardEvents($listener, $target, $listener->forwardedEvents());
}
$this->storage->attach($listener, $EElisteners);
return $this;
}
/**
* Unregisters a listener, removes the listener events from the target.
*
* @param ListenerInterface $listener
*
* @return ListenersInterface
*
* @throws InvalidArgumentException In case the listener is not registered
*/
public function unregister(ListenerInterface $listener)
{
if (!isset($this->storage[$listener])) {
throw new InvalidArgumentException('Listener is not registered.');
}
foreach ($this->storage[$listener] as $event => $EElistener) {
$listener->removeListener($event, $EElistener);
}
$this->storage->detach($listener);
return $this;
}
private function forwardEvents($source, $target, array $events)
{
$EElisteners = array();
foreach ($events as $event) {
$listener = $this->createListener($event, $target);
$source->on($event, $EElisteners[$event] = $listener);
}
return $EElisteners;
}
private function createListener($event, $target)
{
return function () use ($event, $target) {
$target->emit($event, func_get_args());
};
}
}

View file

@ -1,186 +0,0 @@
<?php
/*
* This file is part of Alchemy\BinaryDriver.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\BinaryDriver;
use Alchemy\BinaryDriver\Exception\InvalidArgumentException;
use Symfony\Component\Process\Process;
use Symfony\Component\Process\ProcessBuilder;
class ProcessBuilderFactory implements ProcessBuilderFactoryInterface
{
/**
* The binary path
*
* @var String
*/
protected $binary;
/**
* The timeout for the generated processes
*
* @var integer|float
*/
private $timeout;
/**
* An internal ProcessBuilder.
*
* Note that this one is used only if Symfony ProcessBuilder has method
* setPrefix (2.3)
*
* @var ProcessBuilder
*/
private $builder;
/**
* Tells whether Symfony LTS ProcessBuilder should be emulated or not.
*
* This symfony version provided a brand new ::setPrefix method.
*
* @var Boolean
*/
public static $emulateSfLTS;
/**
* Constructor
*
* @param String $binary The path to the binary
*
* @throws InvalidArgumentException In case binary path is invalid
*/
public function __construct($binary)
{
$this->detectEmulation();
if (!self::$emulateSfLTS) {
$this->builder = new ProcessBuilder();
}
$this->useBinary($binary);
}
/**
* Covenient method for unit testing
*
* @return type
*/
public function getBuilder()
{
return $this->builder;
}
/**
* Covenient method for unit testing
*
* @param ProcessBuilder $builder
* @return ProcessBuilderFactory
*/
public function setBuilder(ProcessBuilder $builder)
{
$this->builder = $builder;
return $this;
}
/**
* @inheritdoc
*/
public function getBinary()
{
return $this->binary;
}
/**
* @inheritdoc
*/
public function useBinary($binary)
{
if (!is_executable($binary)) {
throw new InvalidArgumentException(sprintf('`%s` is not an executable binary', $binary));
}
$this->binary = $binary;
if (!static::$emulateSfLTS) {
$this->builder->setPrefix($binary);
}
return $this;
}
/**
* @inheritdoc
*/
public function setTimeout($timeout)
{
$this->timeout = $timeout;
if (!static::$emulateSfLTS) {
$this->builder->setTimeout($this->timeout);
}
return $this;
}
/**
* @inheritdoc
*/
public function getTimeout()
{
return $this->timeout;
}
/**
* @inheritdoc
*/
public function create($arguments = array())
{
if (null === $this->binary) {
throw new InvalidArgumentException('No binary set');
}
if (!is_array($arguments)) {
$arguments = array($arguments);
}
if (static::$emulateSfLTS) {
array_unshift($arguments, $this->binary);
if (method_exists('Symfony\Component\Process\ProcessUtils', 'escapeArgument')) {
$script = implode(' ', array_map(array('Symfony\Component\Process\ProcessUtils', 'escapeArgument'), $arguments));
} else {
$script = $arguments;
}
$env = array_replace($_ENV, $_SERVER);
$env = array_filter($env, function ($value) {
return !is_array($value);
});
return new Process($script, null, $env, null, $this->timeout);
} else {
return $this->builder
->setArguments($arguments)
->getProcess();
}
}
private function detectEmulation()
{
if (null !== static::$emulateSfLTS) {
return $this;
}
static::$emulateSfLTS = !method_exists('Symfony\Component\Process\ProcessBuilder', 'setPrefix');
return $this;
}
}

View file

@ -1,29 +0,0 @@
<?php
/*
* This file is part of Alchemy\BinaryDriver.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\BinaryDriver;
interface ProcessBuilderFactoryAwareInterface
{
/**
* Returns the current process builder factory
*
* @return ProcessBuilderFactoryInterface
*/
public function getProcessBuilderFactory();
/**
* Set a process builder factory
*
* @param ProcessBuilderFactoryInterface $factory
*/
public function setProcessBuilderFactory(ProcessBuilderFactoryInterface $factory);
}

View file

@ -1,65 +0,0 @@
<?php
/*
* This file is part of Alchemy\BinaryDriver.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\BinaryDriver;
use Alchemy\BinaryDriver\Exception\InvalidArgumentException;
use Symfony\Component\Process\Process;
interface ProcessBuilderFactoryInterface
{
/**
* Returns a new instance of Symfony Process
*
* @param string|array $arguments An argument or an array of arguments
*
* @return Process
*
* @throws InvalidArgumentException
*/
public function create($arguments = array());
/**
* Returns the path to the binary that is used
*
* @return String
*/
public function getBinary();
/**
* Sets the path to the binary
*
* @param String $binary A path to a binary
*
* @return ProcessBuilderFactoryInterface
*
* @throws InvalidArgumentException In case binary is not executable
*/
public function useBinary($binary);
/**
* Set the default timeout to apply on created processes.
*
* @param integer|float $timeout
*
* @return ProcessBuilderFactoryInterface
*
* @throws InvalidArgumentException In case the timeout is not valid
*/
public function setTimeout($timeout);
/**
* Returns the current timeout applied to the created processes.
*
* @return integer|float
*/
public function getTimeout();
}

View file

@ -1,107 +0,0 @@
<?php
/*
* This file is part of Alchemy\BinaryDriver.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\BinaryDriver;
use Alchemy\BinaryDriver\Exception\ExecutionFailureException;
use Psr\Log\LoggerInterface;
use SplObjectStorage;
use Symfony\Component\Process\Exception\RuntimeException;
use Symfony\Component\Process\Process;
class ProcessRunner implements ProcessRunnerInterface
{
/** @var LoggerInterface */
private $logger;
/** @var string */
private $name;
public function __construct(LoggerInterface $logger, $name)
{
$this->logger = $logger;
$this->name = $name;
}
/**
* {@inheritdoc}
*
* @return void
*/
public function setLogger(LoggerInterface $logger): void
{
$this->logger = $logger;
}
/**
* @return LoggerInterface
*/
public function getLogger()
{
return $this->logger;
}
/**
* {@inheritdoc}
*/
public function run(Process $process, SplObjectStorage $listeners, $bypassErrors)
{
$this->logger->info(sprintf(
'%s running command %s',
$this->name,
$process->getCommandLine()
));
try {
$process->run($this->buildCallback($listeners));
} catch (RuntimeException $e) {
if (!$bypassErrors) {
$this->doExecutionFailure($process->getCommandLine(), $process->getErrorOutput(), $e);
}
}
if (!$bypassErrors && !$process->isSuccessful()) {
$this->doExecutionFailure($process->getCommandLine(), $process->getErrorOutput());
} elseif (!$process->isSuccessful()) {
$this->logger->error($this->createErrorMessage($process->getCommandLine(), $process->getErrorOutput()));
return;
} else {
$this->logger->info(sprintf('%s executed command successfully', $this->name));
return $process->getOutput();
}
}
private function buildCallback(SplObjectStorage $listeners)
{
return function ($type, $data) use ($listeners) {
foreach ($listeners as $listener) {
$listener->handle($type, $data);
}
};
}
private function doExecutionFailure($command, $errorOutput, \Exception $e = null)
{
$this->logger->error($this->createErrorMessage($command, $errorOutput));
throw new ExecutionFailureException(
$this->name,
$command,
$errorOutput,
$e ? $e->getCode() : 0,
$e ?: null
);
}
private function createErrorMessage($command, $errorOutput)
{
return sprintf('%s failed to execute command %s: %s', $this->name, $command, $errorOutput);
}
}

View file

@ -1,29 +0,0 @@
<?php
/*
* This file is part of Alchemy\BinaryDriver.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\BinaryDriver;
interface ProcessRunnerAwareInterface
{
/**
* Returns the current process runner
*
* @return ProcessRunnerInterface
*/
public function getProcessRunner();
/**
* Sets a process runner
*
* @param ProcessRunnerInterface $runner
*/
public function setProcessRunner(ProcessRunnerInterface $runner);
}

View file

@ -1,33 +0,0 @@
<?php
/*
* This file is part of Alchemy\BinaryDriver.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\BinaryDriver;
use Alchemy\BinaryDriver\Exception\ExecutionFailureException;
use Psr\Log\LoggerAwareInterface;
use SplObjectStorage;
use Symfony\Component\Process\Process;
interface ProcessRunnerInterface extends LoggerAwareInterface
{
/**
* Executes a process, logs events
*
* @param Process $process
* @param SplObjectStorage $listeners Some listeners
* @param Boolean $bypassErrors Set to true to disable throwing ExecutionFailureExceptions
*
* @return string The Process output
*
* @throws ExecutionFailureException in case of process failure.
*/
public function run(Process $process, SplObjectStorage $listeners, $bypassErrors);
}

View file

@ -1,259 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <dev.team@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Coordinate;
use FFMpeg\Exception\InvalidArgumentException;
// see http://en.wikipedia.org/wiki/List_of_common_resolutions
class AspectRatio
{
// named 4:3 or 1.33:1 Traditional TV
public const AR_4_3 = '4/3';
// named 16:9 or 1.77:1 HD video standard
public const AR_16_9 = '16/9';
// named 8:5 or 16:10 or 1.6:1
public const AR_8_5 = '8/5';
// named 25:16 or 1.56:1
public const AR_25_16 = '25/16';
// named 3:2 or 1.5:1 see http://en.wikipedia.org/wiki/135_film
public const AR_3_2 = '3/2';
// named 5:3 or 1.66:1 see http://en.wikipedia.org/wiki/Super_16_mm
public const AR_5_3 = '5/3';
// mostly used in Photography
public const AR_5_4 = '5/4';
public const AR_1_1 = '1/1';
// 1.85:1 US widescreen cinema standard see http://en.wikipedia.org/wiki/Widescreen#Film
public const AR_1_DOT_85_1 = '1.85:1';
// 2.39:1 or 2.40:1 Current widescreen cinema standard see http://en.wikipedia.org/wiki/Anamorphic_format
public const AR_2_DOT_39_1 = '2.39:1';
// Rotated constants
// Rotated 4:3
public const AR_ROTATED_3_4 = '3/4';
// Rotated 16:9
public const AR_ROTATED_9_16 = '9/16';
// Rotated 3:2
public const AR_ROTATED_2_3 = '2/3';
// Rotated 5:3
public const AR_ROTATED_3_5 = '3/5';
// Rotated 5:4
public const AR_ROTATED_4_5 = '4/5';
// Rotated 1.85
public const AR_ROTATED_1_DOT_85 = '1/1.85';
// Rotated 2.39
public const AR_ROTATED_2_DOT_39 = '1/2.39';
/** @var float */
private $ratio;
public function __construct($ratio)
{
$this->ratio = $ratio;
}
/**
* Returns the value of the ratio.
*
* @return float
*/
public function getValue()
{
return $this->ratio;
}
/**
* Computes the best width for given height and modulus.
*
* @param int $height
* @param int $modulus
*
* @return int
*/
public function calculateWidth($height, $modulus = 1)
{
$maxPossibleWidth = $this->getMultipleUp(ceil($this->ratio * $height), $modulus);
$minPossibleWidth = $this->getMultipleDown(floor($this->ratio * $height), $modulus);
$maxRatioDiff = abs($this->ratio - ($maxPossibleWidth / $height));
$minRatioDiff = abs($this->ratio - ($minPossibleWidth / $height));
return $maxRatioDiff < $minRatioDiff ? $maxPossibleWidth : $minPossibleWidth;
}
/**
* Computes the best height for given width and modulus.
*
* @param int $width
* @param int $modulus
*
* @return int
*/
public function calculateHeight($width, $modulus = 1)
{
$maxPossibleHeight = $this->getMultipleUp(ceil($width / $this->ratio), $modulus);
$minPossibleHeight = $this->getMultipleDown(floor($width / $this->ratio), $modulus);
$maxRatioDiff = abs($this->ratio - ($width / $maxPossibleHeight));
$minRatioDiff = abs($this->ratio - ($width / $minPossibleHeight));
return $maxRatioDiff < $minRatioDiff ? $maxPossibleHeight : $minPossibleHeight;
}
private function getMultipleUp($value, $multiple)
{
while (0 !== $value % $multiple) {
++$value;
}
return $value;
}
private function getMultipleDown($value, $multiple)
{
while (0 !== $value % $multiple) {
--$value;
}
return $value;
}
/**
* Creates a ratio based on Dimension.
*
* The strategy parameter forces by default to use standardized ratios. If
* custom ratio need to be used, disable it.
*
* @param bool $forceStandards Whether to force or not standard ratios
*
* @return AspectRatio
*
* @throws InvalidArgumentException
*/
public static function create(Dimension $dimension, $forceStandards = true)
{
$incoming = $dimension->getWidth() / $dimension->getHeight();
if ($forceStandards) {
return new static(static::nearestStrategy($incoming));
} else {
return new static(static::customStrategy($incoming));
}
}
private static function valueFromName($name)
{
switch ($name) {
case static::AR_4_3:
return 4 / 3;
case static::AR_16_9:
return 16 / 9;
case static::AR_8_5:
return 8 / 5;
case static::AR_25_16:
return 25 / 16;
case static::AR_1_1:
return 1 / 1;
case static::AR_1_DOT_85_1:
return 1.85;
case static::AR_2_DOT_39_1:
return 2.39;
case static::AR_3_2:
return 3 / 2;
case static::AR_5_3:
return 5 / 3;
case static::AR_5_4:
return 5 / 4;
case static::AR_ROTATED_3_4:
return 3 / 4;
case static::AR_ROTATED_9_16:
return 9 / 16;
case static::AR_ROTATED_2_3:
return 2 / 3;
case static::AR_ROTATED_3_5:
return 3 / 5;
case static::AR_ROTATED_4_5:
return 4 / 5;
case static::AR_ROTATED_1_DOT_85:
return 1 / 1.85;
case static::AR_ROTATED_2_DOT_39:
return 1 / 2.39;
default:
throw new InvalidArgumentException(sprintf('Unable to find value for %s', $name));
}
}
private static function customStrategy($incoming)
{
$try = static::nearestStrategy($incoming);
if (abs($try - $incoming) < $try * 0.05) {
return $try;
}
return $incoming;
}
private static function nearestStrategy($incoming)
{
$availables = [
static::AR_4_3 => static::valueFromName(static::AR_4_3),
static::AR_16_9 => static::valueFromName(static::AR_16_9),
static::AR_8_5 => static::valueFromName(static::AR_8_5),
static::AR_25_16 => static::valueFromName(static::AR_25_16),
static::AR_1_1 => static::valueFromName(static::AR_1_1),
static::AR_1_DOT_85_1 => static::valueFromName(static::AR_1_DOT_85_1),
static::AR_2_DOT_39_1 => static::valueFromName(static::AR_2_DOT_39_1),
static::AR_3_2 => static::valueFromName(static::AR_3_2),
static::AR_5_3 => static::valueFromName(static::AR_5_3),
static::AR_5_4 => static::valueFromName(static::AR_5_4),
// Rotated
static::AR_ROTATED_4_5 => static::valueFromName(static::AR_ROTATED_4_5),
static::AR_ROTATED_9_16 => static::valueFromName(static::AR_ROTATED_9_16),
static::AR_ROTATED_2_3 => static::valueFromName(static::AR_ROTATED_2_3),
static::AR_ROTATED_3_5 => static::valueFromName(static::AR_ROTATED_3_5),
static::AR_ROTATED_3_4 => static::valueFromName(static::AR_ROTATED_3_4),
static::AR_ROTATED_1_DOT_85 => static::valueFromName(static::AR_ROTATED_1_DOT_85),
static::AR_ROTATED_2_DOT_39 => static::valueFromName(static::AR_ROTATED_2_DOT_39),
];
asort($availables);
$previous = $current = null;
foreach ($availables as $name => $value) {
$current = $value;
if ($incoming <= $value) {
break;
}
$previous = $value;
}
if (null === $previous) {
return $current;
}
if (($current - $incoming) < ($incoming - $previous)) {
return $current;
}
return $previous;
}
}

View file

@ -1,71 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Coordinate;
use FFMpeg\Exception\InvalidArgumentException;
/**
* Dimension object, used for manipulating width and height couples.
*/
class Dimension
{
private $width;
private $height;
/**
* @param int $width
* @param int $height
*
* @throws InvalidArgumentException when one of the parameteres is invalid
*/
public function __construct($width, $height)
{
if ($width <= 0 || $height <= 0) {
throw new InvalidArgumentException('Width and height should be positive integer');
}
$this->width = (int) $width;
$this->height = (int) $height;
}
/**
* Returns width.
*
* @return int
*/
public function getWidth()
{
return $this->width;
}
/**
* Returns height.
*
* @return int
*/
public function getHeight()
{
return $this->height;
}
/**
* Returns the ratio.
*
* @param bool $forceStandards Whether or not force the use of standards ratios;
*
* @return AspectRatio
*/
public function getRatio($forceStandards = true)
{
return AspectRatio::create($this, $forceStandards);
}
}

View file

@ -1,36 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Coordinate;
use FFMpeg\Exception\InvalidArgumentException;
class FrameRate
{
private $value;
public function __construct($value)
{
if ($value <= 0) {
throw new InvalidArgumentException('Invalid frame rate, must be positive value.');
}
$this->value = $value;
}
/**
* @return float
*/
public function getValue()
{
return $this->value;
}
}

View file

@ -1,45 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Coordinate;
class Point
{
private $x;
private $y;
public function __construct($x, $y, $dynamic = false)
{
if ($dynamic) {
$this->x = $x;
$this->y = $y;
} else {
$this->x = (int) $x;
$this->y = (int) $y;
}
}
/**
* @return int
*/
public function getX()
{
return $this->x;
}
/**
* @return int
*/
public function getY()
{
return $this->y;
}
}

View file

@ -1,123 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Coordinate;
use FFMpeg\Exception\InvalidArgumentException;
class TimeCode
{
//see http://www.dropframetimecode.org/
private $hours;
private $minutes;
private $seconds;
private $frames;
public function __construct($hours, $minutes, $seconds, $frames)
{
$this->hours = $hours;
$this->minutes = $minutes;
$this->seconds = $seconds;
$this->frames = $frames;
}
public function __toString()
{
return sprintf('%02d:%02d:%02d.%02d', $this->hours, $this->minutes, $this->seconds, $this->frames);
}
/**
* Creates timecode from string.
*
* @param string $timecode
*
* @return TimeCode
*
* @throws InvalidArgumentException In case an invalid timecode is supplied
*/
public static function fromString($timecode)
{
$days = 0;
if (preg_match('/^[0-9]+:[0-9]+:[0-9]+:[0-9]+\.[0-9]+$/', $timecode)) {
list($days, $hours, $minutes, $seconds, $frames) = sscanf($timecode, '%d:%d:%d:%d.%d');
} elseif (preg_match('/^[0-9]+:[0-9]+:[0-9]+:[0-9]+:[0-9]+$/', $timecode)) {
list($days, $hours, $minutes, $seconds, $frames) = sscanf($timecode, '%d:%d:%d:%d:%d');
} elseif (preg_match('/^[0-9]+:[0-9]+:[0-9]+\.[0-9]+$/', $timecode)) {
list($hours, $minutes, $seconds, $frames) = sscanf($timecode, '%d:%d:%d.%s');
} elseif (preg_match('/^[0-9]+:[0-9]+:[0-9]+:[0-9]+$/', $timecode)) {
list($hours, $minutes, $seconds, $frames) = sscanf($timecode, '%d:%d:%d:%s');
} else {
throw new InvalidArgumentException(sprintf('Unable to parse timecode %s', $timecode));
}
$hours += $days * 24;
return new static($hours, $minutes, $seconds, $frames);
}
/**
* Creates timecode from number of seconds.
*
* @param float $quantity
*
* @return TimeCode
*/
public static function fromSeconds($quantity)
{
$minutes = $hours = $frames = 0;
$frames = round(100 * ($quantity - floor($quantity)));
$seconds = floor($quantity);
if ($seconds > 59) {
$minutes = floor($seconds / 60);
$seconds = $seconds % 60;
}
if ($minutes > 59) {
$hours = floor($minutes / 60);
$minutes = $minutes % 60;
}
return new static($hours, $minutes, $seconds, $frames);
}
/**
* Returns this timecode in seconds.
*
* @return int
*/
public function toSeconds()
{
$seconds = 0;
$seconds += $this->hours * 60 * 60;
$seconds += $this->minutes * 60;
$seconds += $this->seconds;
// TODO: Handle frames?
return (int) $seconds;
}
/**
* Helper function wether `$timecode` is after this one.
*
* @param TimeCode $timecode The Timecode to compare
*
* @return bool
*/
public function isAfter(TimeCode $timecode)
{
// convert everything to seconds and compare
return $this->toSeconds() > $timecode->toSeconds();
}
}

View file

@ -1,75 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Driver;
use Alchemy\BinaryDriver\AbstractBinary;
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
{
/**
* {@inheritdoc}
*/
public function getName()
{
return 'ffmpeg';
}
/**
* Creates an FFMpegDriver.
*
* @param LoggerInterface $logger
* @param array|Configuration $configuration
*
* @return FFMpegDriver
*/
public static function create(LoggerInterface $logger = null, $configuration = [])
{
if (!$configuration instanceof ConfigurationInterface) {
$configuration = new Configuration($configuration);
}
$binaries = $configuration->get('ffmpeg.binaries', ['avconv', 'ffmpeg']);
if (!$configuration->has('timeout')) {
$configuration->set('timeout', 300);
}
try {
return static::load($binaries, $logger, $configuration);
} catch (BinaryDriverExecutableNotFound $e) {
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];
}
}

View file

@ -1,53 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Driver;
use Alchemy\BinaryDriver\AbstractBinary;
use Alchemy\BinaryDriver\Configuration;
use Alchemy\BinaryDriver\ConfigurationInterface;
use Alchemy\BinaryDriver\Exception\ExecutableNotFoundException as BinaryDriverExecutableNotFound;
use FFMpeg\Exception\ExecutableNotFoundException;
use Psr\Log\LoggerInterface;
class FFProbeDriver extends AbstractBinary
{
/**
* {@inheritdoc}
*/
public function getName()
{
return 'ffprobe';
}
/**
* Creates an FFProbeDriver.
*
* @param array|ConfigurationInterface $configuration
* @param LoggerInterface $logger
*
* @return FFProbeDriver
*/
public static function create($configuration, LoggerInterface $logger = null)
{
if (!$configuration instanceof ConfigurationInterface) {
$configuration = new Configuration($configuration);
}
$binaries = $configuration->get('ffprobe.binaries', ['avprobe', 'ffprobe']);
try {
return static::load($binaries, $logger, $configuration);
} catch (BinaryDriverExecutableNotFound $e) {
throw new ExecutableNotFoundException('Unable to load FFProbe', $e->getCode(), $e);
}
}
}

View file

@ -1,16 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Exception;
interface ExceptionInterface
{
}

View file

@ -1,16 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Exception;
class ExecutableNotFoundException extends RuntimeException
{
}

View file

@ -1,16 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Exception;
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
{
}

View file

@ -1,16 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Exception;
class LogicException extends \LogicException implements ExceptionInterface
{
}

View file

@ -1,16 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Exception;
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}

View file

@ -1,135 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg;
use Alchemy\BinaryDriver\ConfigurationInterface;
use FFMpeg\Driver\FFMpegDriver;
use FFMpeg\Exception\InvalidArgumentException;
use FFMpeg\Exception\RuntimeException;
use FFMpeg\Media\AdvancedMedia;
use FFMpeg\Media\Audio;
use FFMpeg\Media\Video;
use Psr\Log\LoggerInterface;
class FFMpeg
{
/** @var FFMpegDriver */
private $driver;
/** @var FFProbe */
private $ffprobe;
public function __construct(FFMpegDriver $ffmpeg, FFProbe $ffprobe)
{
$this->driver = $ffmpeg;
$this->ffprobe = $ffprobe;
}
/**
* Sets FFProbe.
*
* @param FFProbe
*
* @return FFMpeg
*/
public function setFFProbe(FFProbe $ffprobe)
{
$this->ffprobe = $ffprobe;
return $this;
}
/**
* Gets FFProbe.
*
* @return FFProbe
*/
public function getFFProbe()
{
return $this->ffprobe;
}
/**
* Sets the ffmpeg driver.
*
* @return FFMpeg
*/
public function setFFMpegDriver(FFMpegDriver $ffmpeg)
{
$this->driver = $ffmpeg;
return $this;
}
/**
* Gets the ffmpeg driver.
*
* @return FFMpegDriver
*/
public function getFFMpegDriver()
{
return $this->driver;
}
/**
* Opens a file in order to be processed.
*
* @param string $pathfile A pathfile
*
* @return Audio|Video
*
* @throws InvalidArgumentException
*/
public function open($pathfile)
{
if (null === $streams = $this->ffprobe->streams($pathfile)) {
throw new RuntimeException(sprintf('Unable to probe "%s".', $pathfile));
}
if (0 < count($streams->videos())) {
return new Video($pathfile, $this->driver, $this->ffprobe);
} elseif (0 < count($streams->audios())) {
return new Audio($pathfile, $this->driver, $this->ffprobe);
}
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.
*
* @param array|ConfigurationInterface $configuration
* @param LoggerInterface $logger
* @param FFProbe $probe
*
* @return FFMpeg
*/
public static function create($configuration = [], LoggerInterface $logger = null, FFProbe $probe = null)
{
if (null === $probe) {
$probe = FFProbe::create($configuration, $logger, null);
}
return new static(FFMpegDriver::create($logger, $configuration), $probe);
}
}

View file

@ -1,287 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg;
use Alchemy\BinaryDriver\ConfigurationInterface;
use Alchemy\BinaryDriver\Exception\ExecutionFailureException;
use FFMpeg\Driver\FFProbeDriver;
use FFMpeg\Exception\InvalidArgumentException;
use FFMpeg\Exception\RuntimeException;
use FFMpeg\FFProbe\DataMapping\Format;
use FFMpeg\FFProbe\DataMapping\StreamCollection;
use FFMpeg\FFProbe\Mapper;
use FFMpeg\FFProbe\MapperInterface;
use FFMpeg\FFProbe\OptionsTester;
use FFMpeg\FFProbe\OptionsTesterInterface;
use FFMpeg\FFProbe\OutputParser;
use FFMpeg\FFProbe\OutputParserInterface;
use Psr\Cache\CacheItemPoolInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
class FFProbe
{
public const TYPE_STREAMS = 'streams';
public const TYPE_FORMAT = 'format';
/** @var CacheItemPoolInterface */
private $cache;
/** @var OptionsTesterInterface */
private $optionsTester;
/** @var OutputParserInterface */
private $parser;
/** @var FFProbeDriver */
private $ffprobe;
/** @var MapperInterface */
private $mapper;
public function __construct(FFProbeDriver $ffprobe, CacheItemPoolInterface $cache)
{
$this->ffprobe = $ffprobe;
$this->optionsTester = new OptionsTester($ffprobe, $cache);
$this->parser = new OutputParser();
$this->mapper = new Mapper();
$this->cache = $cache;
}
/**
* @return OutputParserInterface
*/
public function getParser()
{
return $this->parser;
}
/**
* @return FFProbe
*/
public function setParser(OutputParserInterface $parser)
{
$this->parser = $parser;
return $this;
}
/**
* @return FFProbeDriver
*/
public function getFFProbeDriver()
{
return $this->ffprobe;
}
/**
* @return FFProbe
*/
public function setFFProbeDriver(FFProbeDriver $ffprobe)
{
$this->ffprobe = $ffprobe;
return $this;
}
/**
* @return FFProbe
*/
public function setOptionsTester(OptionsTesterInterface $tester)
{
$this->optionsTester = $tester;
return $this;
}
/**
* @return OptionsTesterInterface
*/
public function getOptionsTester()
{
return $this->optionsTester;
}
/**
* @param CacheItemPoolInterface $cache
*
* @return FFProbe
*/
public function setCache(CacheItemPoolInterface $cache)
{
$this->cache = $cache;
return $this;
}
/**
* @return Cache
*/
public function getCache()
{
return $this->cache;
}
/**
* @return MapperInterface
*/
public function getMapper()
{
return $this->mapper;
}
/**
* @return FFProbe
*/
public function setMapper(MapperInterface $mapper)
{
$this->mapper = $mapper;
return $this;
}
/**
* @api
*
* Probes the format of a given file.
*
* @param string $pathfile
*
* @return Format A Format object
*
* @throws InvalidArgumentException
* @throws RuntimeException
*/
public function format($pathfile)
{
return $this->probe($pathfile, '-show_format', static::TYPE_FORMAT);
}
/**
* @api
*
* Checks wether the given `$pathfile` is considered a valid media file.
*
* @param string $pathfile
*
* @return bool
*
* @since 0.10.0
*/
public function isValid($pathfile)
{
try {
return $this->format($pathfile)->get('duration') > 0;
} catch (\Exception $e) {
// complete invalid data
return false;
}
}
/**
* @api
*
* Probes the streams contained in a given file.
*
* @param string $pathfile
*
* @return StreamCollection A collection of streams
*
* @throws InvalidArgumentException
* @throws RuntimeException
*/
public function streams($pathfile)
{
return $this->probe($pathfile, '-show_streams', static::TYPE_STREAMS);
}
/**
* @api
*
* Creates an FFProbe.
*
* @param array|ConfigurationInterface $configuration
* @param LoggerInterface $logger
* @param CacheItemPoolInterface $cache
*
* @return FFProbe
*/
public static function create($configuration = [], LoggerInterface $logger = null, CacheItemPoolInterface $cache = null)
{
if (null === $cache) {
$cache = new ArrayAdapter();
}
return new static(FFProbeDriver::create($configuration, $logger), $cache);
}
private function probe($pathfile, $command, $type, $allowJson = true)
{
$id = md5(sprintf('%s-%s', $command, $pathfile));
if ($this->cache->hasItem($id)) {
return $this->cache->getItem($id)->get();
}
if (!$this->optionsTester->has($command)) {
throw new RuntimeException(sprintf('This version of ffprobe is too old and ' . 'does not support `%s` option, please upgrade', $command));
}
$commands = [$pathfile, $command];
$parseIsToDo = false;
if ($allowJson && $this->optionsTester->has('-print_format')) {
// allowed in latest PHP-FFmpeg version
$commands[] = '-print_format';
$commands[] = 'json';
} elseif ($allowJson && $this->optionsTester->has('-of')) {
// option has changed in avconv 9
$commands[] = '-of';
$commands[] = 'json';
} else {
$parseIsToDo = true;
}
try {
$output = $this->ffprobe->command($commands);
} catch (ExecutionFailureException $e) {
throw new RuntimeException(sprintf('Unable to probe %s', $pathfile), $e->getCode(), $e);
}
if ($parseIsToDo) {
$data = $this->parser->parse($type, $output);
} else {
try {
// Malformed json may be retrieved
$data = $this->parseJson($output);
} catch (RuntimeException $e) {
return $this->probe($pathfile, $command, $type, false);
}
}
$ret = $this->mapper->map($type, $data);
$cacheItem = $this->cache->getItem($id);
$cacheItem->set($ret);
$this->cache->save($cacheItem);
return $ret;
}
private function parseJson($data)
{
$ret = @json_decode($data, true);
if (JSON_ERROR_NONE !== json_last_error()) {
throw new RuntimeException(sprintf('Unable to parse json %s', $ret));
}
return $ret;
}
}

View file

@ -1,94 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\FFProbe\DataMapping;
abstract class AbstractData implements \Countable
{
private $properties;
public function __construct(array $properties)
{
$this->properties = $properties;
}
/**
* Returns true if data has property.
*
* @param string $property
*
* @return bool
*/
public function has($property)
{
return isset($this->properties[$property]);
}
/**
* Returns the property value given its name.
*
* @param string $property
* @param mixed $default
*
* @return mixed
*/
public function get($property, $default = null)
{
if (!isset($this->properties[$property])) {
return $default;
}
return $this->properties[$property];
}
/**
* Sets the property value given its name.
*
* @param string $property
* @param mixed $value
*
* @return AbstractData
*/
public function set($property, $value)
{
$this->properties[$property] = $value;
return $this;
}
/**
* Returns all property names.
*
* @return array
*/
public function keys()
{
return array_keys($this->properties);
}
/**
* Returns all properties and their values.
*
* @return array
*/
public function all()
{
return $this->properties;
}
/**
* {@inheritdoc}
*/
public function count(): int
{
return count($this->properties);
}
}

View file

@ -1,16 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\FFProbe\DataMapping;
class Format extends AbstractData
{
}

View file

@ -1,110 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\FFProbe\DataMapping;
use FFMpeg\Coordinate\Dimension;
use FFMpeg\Exception\LogicException;
use FFMpeg\Exception\RuntimeException;
class Stream extends AbstractData
{
/**
* Returns true if the stream is an audio stream.
*
* @return bool
*/
public function isAudio()
{
return 'audio' === $this->get('codec_type');
}
/**
* Returns true if the stream is a video stream.
*
* @return bool
*/
public function isVideo()
{
return 'video' === $this->get('codec_type');
}
/**
* Returns the dimension of the video stream.
*
* @return Dimension
*
* @throws LogicException in case the stream is not a video stream
* @throws RuntimeException in case the dimensions can not be extracted
*/
public function getDimensions()
{
if (!$this->isVideo()) {
throw new LogicException('Dimensions can only be retrieved from video streams.');
}
$sampleRatio = $displayRatio = null;
$width = $this->get('width');
$height = $this->get('height');
if (null !== $ratio = $this->extractRatio($this, 'sample_aspect_ratio')) {
$sampleRatio = $ratio;
}
if (null !== $ratio = $this->extractRatio($this, 'display_aspect_ratio')) {
$displayRatio = $ratio;
}
if (null === $height || null === $width) {
throw new RuntimeException('Unable to extract dimensions.');
}
if (null !== $displayRatio && null !== $sampleRatio) {
if (1 !== $sampleRatio[0] && 1 !== $sampleRatio[1]) {
if (null !== $width && null !== $height) {
// stretch video according to pixel sample aspect ratio
$width = round($width * ($sampleRatio[0] / $sampleRatio[1]));
// set height according to display aspect ratio
$height = round($width * ($displayRatio[1] / $displayRatio[0]));
}
}
}
return new Dimension($width, $height);
}
/**
* Extracts a ratio from a string in a \d+:\d+ format given a key name.
*
* @param Stream $stream the stream where to look for the ratio
* @param string $name the name of the key
*
* @return array|null an array containing the width and the height, null if not found
*/
private function extractRatio(Stream $stream, $name)
{
if (!$stream->has($name)) {
return;
}
$ratio = $stream->get($name);
if (preg_match('/\d+:\d+/', $ratio)) {
$data = array_filter(explode(':', $ratio), function ($int) {
return $int > 0;
});
if (2 === count($data)) {
return array_map(function ($int) {
return (int) $int;
}, $data);
}
}
}
}

View file

@ -1,99 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\FFProbe\DataMapping;
use Traversable;
class StreamCollection implements \Countable, \IteratorAggregate
{
private $streams;
public function __construct(array $streams = [])
{
$this->streams = array_values($streams);
}
/**
* Returns the first stream of the collection, null if the collection is
* empty.
*
* @return Stream|null
*/
public function first()
{
$stream = reset($this->streams);
return $stream ?: null;
}
/**
* Adds a stream to the collection.
*
* @return StreamCollection
*/
public function add(Stream $stream)
{
$this->streams[] = $stream;
return $this;
}
/**
* Returns a new StreamCollection with only video streams.
*
* @return StreamCollection
*/
public function videos()
{
return new static(array_filter($this->streams, function (Stream $stream) {
return $stream->isVideo();
}));
}
/**
* Returns a new StreamCollection with only audio streams.
*
* @return StreamCollection
*/
public function audios()
{
return new static(array_filter($this->streams, function (Stream $stream) {
return $stream->isAudio();
}));
}
/**
* {@inheritdoc}
*/
public function count(): int
{
return count($this->streams);
}
/**
* Returns the array of contained streams.
*
* @return array
*/
public function all()
{
return $this->streams;
}
/**
* {@inheritdoc}
*/
public function getIterator(): Traversable
{
return new \ArrayIterator($this->streams);
}
}

View file

@ -1,52 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\FFProbe;
use FFMpeg\Exception\InvalidArgumentException;
use FFMpeg\FFProbe;
use FFMpeg\FFProbe\DataMapping\Format;
use FFMpeg\FFProbe\DataMapping\Stream;
use FFMpeg\FFProbe\DataMapping\StreamCollection;
class Mapper implements MapperInterface
{
/**
* {@inheritdoc}
*/
public function map($type, $data)
{
switch ($type) {
case FFProbe::TYPE_FORMAT:
return $this->mapFormat($data);
case FFProbe::TYPE_STREAMS:
return $this->mapStreams($data);
default:
throw new InvalidArgumentException(sprintf('Invalid type `%s`.', $type));
}
}
private function mapFormat($data)
{
return new Format($data['format']);
}
private function mapStreams($data)
{
$streams = new StreamCollection();
foreach ($data['streams'] as $properties) {
$streams->add(new Stream($properties));
}
return $streams;
}
}

View file

@ -1,27 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\FFProbe;
interface MapperInterface
{
/**
* Maps data given its type.
*
* @param string $type One of FFProbe::TYPE_* constant
* @param string $data The data
*
* @return Format|Stream
*
* @throws InvalidArgumentException In case the type is not supported
*/
public function map($type, $data);
}

View file

@ -1,74 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\FFProbe;
use Alchemy\BinaryDriver\Exception\ExecutionFailureException;
use FFMpeg\Driver\FFProbeDriver;
use FFMpeg\Exception\RuntimeException;
use Psr\Cache\CacheItemPoolInterface;
class OptionsTester implements OptionsTesterInterface
{
/** @var FFProbeDriver */
private $ffprobe;
/** @var CacheItemPoolInterface */
private $cache;
public function __construct(FFProbeDriver $ffprobe, CacheItemPoolInterface $cache)
{
$this->ffprobe = $ffprobe;
$this->cache = $cache;
}
/**
* {@inheritdoc}
*/
public function has($name)
{
$id = md5(sprintf('option-%s', $name));
if ($this->cache->hasItem($id)) {
return $this->cache->getItem($id)->get();
}
$output = $this->retrieveHelpOutput();
$ret = (bool) preg_match('/^'.$name.'/m', $output);
$cacheItem = $this->cache->getItem($id);
$cacheItem->set($ret);
$this->cache->save($cacheItem);
return $ret;
}
private function retrieveHelpOutput()
{
$id = 'help';
if ($this->cache->hasItem($id)) {
return $this->cache->getItem($id)->get();
}
try {
$output = $this->ffprobe->command(['-help', '-loglevel', 'quiet']);
} catch (ExecutionFailureException $e) {
throw new RuntimeException('Your FFProbe version is too old and does not support `-help` option, please upgrade.', $e->getCode(), $e);
}
$cacheItem = $this->cache->getItem($id);
$cacheItem->set($output);
$this->cache->save($cacheItem);
return $output;
}
}

View file

@ -1,24 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\FFProbe;
interface OptionsTesterInterface
{
/**
* Tells if the given option is supported by ffprobe.
*
* @param string $name
*
* @return bool
*/
public function has($name);
}

View file

@ -1,123 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\FFProbe;
use FFMpeg\Exception\InvalidArgumentException;
use FFMpeg\FFProbe;
class OutputParser implements OutputParserInterface
{
/**
* {@inheritdoc}
*/
public function parse($type, $data)
{
switch ($type) {
case FFProbe::TYPE_FORMAT:
return $this->parseFormat($data);
break;
case FFProbe::TYPE_STREAMS:
return $this->parseStreams($data);
break;
default:
throw new InvalidArgumentException(sprintf('Unknown data type %s', $type));
}
}
private function parseFormat($data)
{
$ret = [];
foreach (explode(PHP_EOL, $data) as $line) {
if (in_array($line, ['[FORMAT]', '[/FORMAT]'])) {
continue;
}
$chunks = explode('=', $line);
$key = array_shift($chunks);
if ('' === trim($key)) {
continue;
}
$value = trim(implode('=', $chunks));
if ('nb_streams' === $key) {
$value = (int) $value;
}
if (0 === strpos($key, 'TAG:')) {
if (!isset($ret['tags'])) {
$ret['tags'] = [];
}
$ret['tags'][substr($key, 4)] = $value;
} else {
$ret[$key] = $value;
}
}
return ['format' => $ret];
}
private function parseStreams($data)
{
$ret = [];
$n = -1;
foreach (explode(PHP_EOL, $data) as $line) {
if ('[STREAM]' == $line) {
++$n;
$ret[$n] = [];
continue;
}
if ('[/STREAM]' == $line) {
continue;
}
$chunks = explode('=', $line);
$key = array_shift($chunks);
if ('' === trim($key)) {
continue;
}
$value = trim(implode('=', $chunks));
if ('N/A' === $value) {
continue;
}
if ('profile' === $key && 'unknown' === $value) {
continue;
}
if (in_array($key, ['index', 'width', 'height', 'channels', 'bits_per_sample', 'has_b_frames', 'level', 'start_pts', 'duration_ts'])) {
$value = (int) $value;
}
if (0 === strpos($key, 'TAG:')) {
if (!isset($ret[$n]['tags'])) {
$ret[$n]['tags'] = [];
}
$ret[$n]['tags'][substr($key, 4)] = $value;
} elseif (0 === strpos($key, 'DISPOSITION:')) {
if (!isset($ret[$n]['disposition'])) {
$ret[$n]['disposition'] = [];
}
$ret[$n]['disposition'][substr($key, 12)] = $value;
} else {
$ret[$n][$key] = $value;
}
}
return ['streams' => $ret];
}
}

View file

@ -1,27 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\FFProbe;
interface OutputParserInterface
{
/**
* Parses ffprobe raw output.
*
* @param string $type One of FFProbe::TYPE_* constant
* @param string $data The data
*
* @return array
*
* @throws InvalidArgumentException In case the type is not supported
*/
public function parse($type, $data);
}

View file

@ -1,71 +0,0 @@
<?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 [
'-filter_complex',
$this->getName().$this->buildFilterOptions([
'channel_layout' => $this->channelLayout,
'sample_rate' => $this->sampleRate,
'nb_samples' => $this->nbSamples,
]),
];
}
}

View file

@ -1,62 +0,0 @@
<?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 = [];
foreach ($params as $paramName => $paramValue) {
if (null !== $paramValue) {
$config[] = $paramName.'='.$paramValue;
}
}
if (!empty($config)) {
return '='.implode(':', $config);
}
return '';
}
}

View file

@ -1,33 +0,0 @@
<?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.
*
* @return string[] an array of arguments
*/
public function applyComplex(AdvancedMedia $media);
}

View file

@ -1,99 +0,0 @@
<?php
namespace FFMpeg\Filters\AdvancedMedia;
use FFMpeg\Media\AdvancedMedia;
/**
* Container for the complex compatible filter.
*/
class ComplexFilterContainer implements ComplexFilterInterface
{
/**
* @var int
*/
private $priority;
/**
* @var ComplexCompatibleFilter
*/
private $baseFilter;
/**
* @var string
*/
private $inLabels;
/**
* @var string
*/
private $outLabels;
/**
* ComplexFilter constructor.
*
* @param string $inLabels
* @param string $outLabels
*/
public function __construct($inLabels, ComplexCompatibleFilter $baseFilter, $outLabels)
{
$this->priority = $baseFilter->getPriority();
$this->inLabels = $inLabels;
$this->baseFilter = $baseFilter;
$this->outLabels = $outLabels;
}
/**
* Returns the priority of the filter.
*
* @return int
*/
public function getPriority()
{
return $this->priority;
}
/**
* @return string
*/
public function getInLabels()
{
return $this->inLabels;
}
/**
* @return string
*/
public function getOutLabels()
{
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}
*/
public function applyComplex(AdvancedMedia $media)
{
return $this->baseFilter->applyComplex($media);
}
}

View file

@ -1,19 +0,0 @@
<?php
namespace FFMpeg\Filters\AdvancedMedia;
/**
* A filter that is completely ready to use inside "-filter_complex" option.
*/
interface ComplexFilterInterface extends ComplexCompatibleFilter
{
/**
* @return string
*/
public function getInLabels();
/**
* @return string
*/
public function getOutLabels();
}

View file

@ -1,182 +0,0 @@
<?php
namespace FFMpeg\Filters\AdvancedMedia;
use FFMpeg\Coordinate\Dimension;
use FFMpeg\Filters\Video\PadFilter;
use FFMpeg\Filters\Video\WatermarkFilter;
use FFMpeg\Media\AdvancedMedia;
class ComplexFilters
{
/**
* @var AdvancedMedia
*/
protected $media;
/**
* ComplexFilters constructor.
*/
public function __construct(AdvancedMedia $media)
{
$this->media = $media;
}
/**
* @param string $in
* @param string $parameters
* @param string $out
*
* @return ComplexFilters
*/
public function custom($in, $parameters, $out)
{
$this->media->addFilter($in, new CustomComplexFilter($parameters), $out);
return $this;
}
/**
* Adds padding (black bars) to a video.
*
* @param string $in
* @param 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
*
* @return $this
*/
public function watermark($in, $imagePath, $out, array $coordinates = [])
{
$this->media->addFilter($in, new WatermarkFilter($imagePath, $coordinates), $out);
return $this;
}
/**
* Apply "xstack" filter.
* Warning: this filter is supported starting from 4.1 ffmpeg version.
*
* @param string $in
* @param string $layout
* @param int $inputsCount
* @param string $out
*
* @return ComplexFilters
*
* @see https://ffmpeg.org/ffmpeg-filters.html#xstack
*/
public function xStack($in, $layout, $inputsCount, $out)
{
$this->media->addFilter($in, new XStackFilter($layout, $inputsCount), $out);
return $this;
}
/**
* This filter build various types of computed inputs.
*
* @param string $out
* @param string|null $type
* @param string|null $size
* @param string|null $duration
* @param string|null $sar
* @param string|null $rate
* @param string|null $level
* @param string|null $color
* @param int|null $alpha
* @param float|null $decimals
*
* @return ComplexFilters
*
* @see https://ffmpeg.org/ffmpeg-filters.html#allrgb_002c-allyuv_002c-color_002c-haldclutsrc_002c-nullsrc_002c-pal75bars_002c-pal100bars_002c-rgbtestsrc_002c-smptebars_002c-smptehdbars_002c-testsrc_002c-testsrc2_002c-yuvtestsrc
*/
public function testSrc(
$out,
$type = TestSrcFilter::TESTSRC,
$size = '320x240',
$duration = null,
$sar = null,
$rate = null,
$level = null,
$color = null,
$alpha = null,
$decimals = null
) {
$this->media->addFilter(
'',
new TestSrcFilter($type, $size, $duration, $sar, $rate, $level, $color, $alpha, $decimals),
$out
);
return $this;
}
/**
* Apply "anullsrc" filter.
*
* @param string $out
* @param string|null $channelLayout
* @param int|null $sampleRate
* @param int|null $nbSamples
*
* @return ComplexFilters
*
* @see https://ffmpeg.org/ffmpeg-filters.html#anullsrc
*/
public function aNullSrc(
$out,
$channelLayout = null,
$sampleRate = null,
$nbSamples = null
) {
$this->media->addFilter('', new ANullSrcFilter($channelLayout, $sampleRate, $nbSamples), $out);
return $this;
}
/**
* Apply "sine" filter.
*
* @param $out
* @param string $duration
* @param int|null $frequency
* @param string|null $beep_factor
* @param int|null $sample_rate
* @param string|null $samples_per_frame
*
* @return $this
*
* @see https://ffmpeg.org/ffmpeg-filters.html#sine
*/
public function sine(
$out,
$duration,
$frequency = null,
$beep_factor = null,
$sample_rate = null,
$samples_per_frame = null
) {
$this->media->addFilter(
'',
new SineFilter($duration, $frequency, $beep_factor, $sample_rate, $samples_per_frame),
$out
);
return $this;
}
}

View file

@ -1,43 +0,0 @@
<?php
namespace FFMpeg\Filters\AdvancedMedia;
use FFMpeg\Media\AdvancedMedia;
class CustomComplexFilter extends AbstractComplexFilter
{
/**
* @var string
*/
private $filter;
/**
* CustomComplexFilter constructor.
*
* @param string $filter
* @param int $priority
*/
public function __construct($filter, $priority = 0)
{
parent::__construct($priority);
$this->filter = $filter;
}
/**
* Get name of the filter.
*
* @return string
*/
public function getName()
{
return 'custom_filter';
}
/**
* {@inheritdoc}
*/
public function applyComplex(AdvancedMedia $media)
{
return ['-filter_complex', $this->filter];
}
}

View file

@ -1,95 +0,0 @@
<?php
namespace FFMpeg\Filters\AdvancedMedia;
use FFMpeg\Media\AdvancedMedia;
/**
* @see https://ffmpeg.org/ffmpeg-filters.html#sine
*/
class SineFilter extends AbstractComplexFilter
{
/**
* @var int|null
*/
private $frequency;
/**
* @var string|null
*/
private $beep_factor;
/**
* @var int|null
*/
private $sample_rate;
/**
* @var string
*/
private $duration;
/**
* @var string|null
*/
private $samples_per_frame;
/**
* SineComplexFilter constructor.
*
* @param string $duration
* @param int|null $frequency
* @param string|null $beep_factor
* @param int|null $sample_rate
* @param string|null $samples_per_frame
* @param int $priority
*/
public function __construct($duration, $frequency, $beep_factor, $sample_rate, $samples_per_frame, $priority = 0)
{
parent::__construct($priority);
$this->duration = $duration;
$this->frequency = $frequency;
$this->beep_factor = $beep_factor;
$this->sample_rate = $sample_rate;
$this->samples_per_frame = $samples_per_frame;
}
/**
* 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.
*
* @return string[] an array of arguments
*/
public function applyComplex(AdvancedMedia $media)
{
return [
'-filter_complex',
$this->getName().$this->buildFilterOptions([
'frequency' => $this->frequency,
'beep_factor' => $this->beep_factor,
'sample_rate' => $this->sample_rate,
'duration' => $this->duration,
'samples_per_frame' => $this->samples_per_frame,
]),
];
}
}

View file

@ -1,246 +0,0 @@
<?php
namespace FFMpeg\Filters\AdvancedMedia;
use FFMpeg\Media\AdvancedMedia;
/**
* This filter builds various types of computed inputs.
*
* @see https://ffmpeg.org/ffmpeg-filters.html#allrgb_002c-allyuv_002c-color_002c-haldclutsrc_002c-nullsrc_002c-pal75bars_002c-pal100bars_002c-rgbtestsrc_002c-smptebars_002c-smptehdbars_002c-testsrc_002c-testsrc2_002c-yuvtestsrc
*/
class TestSrcFilter extends AbstractComplexFilter
{
/**
* Source returns frames of size 4096x4096 of all rgb colors.
*/
public const ALLRGB = 'allrgb';
/**
* Source returns frames of size 4096x4096 of all yuv colors.
*/
public const ALLYUV = 'allyuv';
/**
* Source provides an uniformly colored input.
*/
public const COLOR = 'color';
/**
* Source provides an identity Hald CLUT.
*/
public const HALDCLUTSRC = 'haldclutsrc';
/**
* Source returns unprocessed video frames.
* It is mainly useful to be employed in analysis / debugging tools,
* or as the source for filters which ignore the input data.
*/
public const NULLSRC = 'nullsrc';
/**
* Source generates a color bars pattern, based on EBU PAL recommendations with 75% color levels.
*/
public const PAL75BARS = 'pal75bars';
/**
* Source generates a color bars pattern, based on EBU PAL recommendations with 100% color levels.
*/
public const PAL100BARS = 'pal100bars';
/**
* Source generates an RGB test pattern useful for detecting RGB vs BGR issues.
* You should see a red, green and blue stripe from top to bottom.
*/
public const RGBTESTSRC = 'rgbtestsrc';
/**
* Source generates a color bars pattern, based on the SMPTE Engineering Guideline EG 1-1990.
*/
public const SMPTEBARS = 'smptebars';
/**
* Source generates a color bars pattern, based on the SMPTE RP 219-2002.
*/
public const SMPTEHDBARS = 'smptehdbars';
/**
* Source generates a test video pattern, showing a color pattern, a scrolling gradient and a timestamp.
* This is mainly intended for testing purposes.
*/
public const TESTSRC = 'testsrc';
/**
* Source is similar to testsrc, but supports more pixel formats instead of just rgb24.
* This allows using it as an input for other tests without requiring a format conversion.
*/
public const TESTSRC2 = 'testsrc2';
/**
* Source generates an YUV test pattern. You should see a y, cb and cr stripe from top to bottom.
*/
public const YUVTESTSRC = 'yuvtestsrc';
/**
* @var string|null
*/
private $type;
/**
* Specify the level of the Hald CLUT, only available in the haldclutsrc source.
* A level of N generates a picture of N*N*N by N*N*N pixels to be used as identity matrix for 3D lookup tables.
* Each component is coded on a 1/(N*N) scale.
*
* @var string|null
*/
private $level;
/**
* Specify the color of the source, only available in the color source.
*
* @var string|null
*/
private $color;
/**
* Specify the size of the sourced video.
* This option is not available with the allrgb, allyuv, and haldclutsrc filters.
*
* @var string|null
*/
private $size;
/**
* Specify the frame rate of the sourced video, as the number of frames generated per second.
* It has to be a string in the format frame_rate_num/frame_rate_den, an integer number,
* a floating point number or a valid video frame rate abbreviation. The default value is "25".
*
* @var string|null
*/
private $rate;
/**
* Set the duration of the sourced video.
* If not specified, or the expressed duration is negative, the video is supposed to be generated forever.
*
* @var string|null
*/
private $duration;
/**
* Set the sample aspect ratio of the sourced video.
*
* @var string|null
*/
private $sar;
/**
* Specify the alpha (opacity) of the background, only available in the testsrc2 source.
* The value must be between 0 (fully transparent) and 255 (fully opaque, the default).
*
* @var int|null
*/
private $alpha;
/**
* Set the number of decimals to show in the timestamp, only available in the testsrc source.
* The displayed timestamp value will correspond to the original timestamp value multiplied
* by the power of 10 of the specified value. Default value is 0.
*
* @var float|null
*/
private $decimals;
/**
* TestSrcComplexFilter constructor.
*
* @param string|null $type
* @param string|null $size
* @param string|null $duration
* @param string|null $sar
* @param string|null $rate
* @param string|null $level
* @param string|null $color
* @param int|null $alpha
* @param float|null $decimals
* @param int|null $priority
*/
public function __construct(
$type = self::TESTSRC,
$size = '320x240',
$duration = null,
$sar = null,
$rate = null,
$level = null,
$color = null,
$alpha = null,
$decimals = null,
$priority = 0
) {
parent::__construct($priority);
$this->type = $type;
$this->level = $level;
$this->color = $color;
$this->size = $size;
$this->rate = $rate;
$this->duration = $duration;
$this->sar = $sar;
$this->alpha = $alpha;
$this->decimals = $decimals;
}
/**
* Get name of the filter.
*
* @return string
*/
public function getName()
{
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}
*/
public function applyComplex(AdvancedMedia $media)
{
return [
'-filter_complex',
$this->type.$this->buildFilterOptions([
'level' => $this->level,
'color' => $this->color,
'size' => $this->size,
'rate' => $this->rate,
'duration' => $this->duration,
'sar' => $this->sar,
'alpha' => $this->alpha,
'decimals' => $this->decimals,
]),
];
}
}

View file

@ -1,94 +0,0 @@
<?php
namespace FFMpeg\Filters\AdvancedMedia;
use FFMpeg\Media\AdvancedMedia;
/**
* "xstack" filter.
* 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
{
public const LAYOUT_2X2 = '0_0|0_h0|w0_0|w0_h0';
public const LAYOUT_1X4 = '0_0|0_h0|0_h0+h1|0_h0+h1+h2';
public 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';
public const LAYOUT_4X4 = '0_0|0_h0|0_h0+h1|0_h0+h1+h2|w0_0|w0_h0|w0_h0+h1|w0_h0+h1+h2|w0+w4_0|w0+w4_h0|w0+w4_h0+h1|w0+w4_h0+h1+h2|w0+w4+w8_0|w0+w4+w8_h0|w0+w4+w8_h0+h1|w0+w4+w8_h0+h1+h2';
/**
* @var string
*/
private $layout;
/**
* @var int
*/
private $inputsCount;
/**
* CustomComplexFilter constructor.
*
* @param string $layout
* @param int $inputsCount
* @param int $priority
*/
public function __construct($layout, $inputsCount, $priority = 0)
{
parent::__construct($priority);
$this->layout = $layout;
$this->inputsCount = $inputsCount;
}
/**
* @param int $count
*
* @return string
*/
public static function getInputByCount($count)
{
$result = '';
for ($i = 0; $i < $count; ++$i) {
$result .= '['.$i.':v]';
}
return $result;
}
/**
* 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}
*/
public function applyComplex(AdvancedMedia $media)
{
return [
'-filter_complex',
$this->getName().$this->buildFilterOptions([
'inputs' => $this->inputsCount,
'layout' => $this->layout,
]),
];
}
}

View file

@ -1,57 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Filters\Audio;
use FFMpeg\Format\AudioInterface;
use FFMpeg\Media\Audio;
class AddMetadataFilter implements AudioFilterInterface
{
/** @var array */
private $metaArr;
/** @var int */
private $priority;
public function __construct($metaArr = null, $priority = 9)
{
$this->metaArr = $metaArr;
$this->priority = $priority;
}
public function getPriority()
{
//must be of high priority in case theres a second input stream (artwork) to register with audio
return $this->priority;
}
public function apply(Audio $audio, AudioInterface $format)
{
$meta = $this->metaArr;
if (is_null($meta)) {
return ['-map_metadata', '-1', '-vn'];
}
$metadata = [];
if (array_key_exists('artwork', $meta)) {
array_push($metadata, '-i', $meta['artwork'], '-map', '0', '-map', '1');
unset($meta['artwork']);
}
foreach ($meta as $k => $v) {
array_push($metadata, '-metadata', "$k=$v");
}
return $metadata;
}
}

View file

@ -1,87 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Filters\Audio;
use FFMpeg\Coordinate\TimeCode;
use FFMpeg\Format\AudioInterface;
use FFMpeg\Media\Audio;
class AudioClipFilter implements AudioFilterInterface
{
/**
* @var TimeCode
*/
private $start;
/**
* @var TimeCode
*/
private $duration;
/**
* @var int
*/
private $priority;
public function __construct(TimeCode $start, TimeCode $duration = null, $priority = 0)
{
$this->start = $start;
$this->duration = $duration;
$this->priority = $priority;
}
/**
* {@inheritDoc}
*/
public function getPriority()
{
return $this->priority;
}
/**
* Returns the start position the audio is being cutted.
*
* @return TimeCode
*/
public function getStart()
{
return $this->start;
}
/**
* Returns how long the audio is being cutted. Returns null when the duration is infinite,.
*
* @return TimeCode|null
*/
public function getDuration()
{
return $this->duration;
}
/**
* {@inheritDoc}
*/
public function apply(Audio $audio, AudioInterface $format)
{
$commands = ['-ss', (string) $this->start];
if (null !== $this->duration) {
$commands[] = '-t';
$commands[] = (string) $this->duration;
}
$commands[] = '-acodec';
$commands[] = 'copy';
return $commands;
}
}

View file

@ -1,26 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <dev.team@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Filters\Audio;
use FFMpeg\Filters\FilterInterface;
use FFMpeg\Format\AudioInterface;
use FFMpeg\Media\Audio;
interface AudioFilterInterface extends FilterInterface
{
/**
* Applies the filter on the the Audio media given an format.
*
* @return array An array of arguments
*/
public function apply(Audio $audio, AudioInterface $format);
}

View file

@ -1,90 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Filters\Audio;
use FFMpeg\Coordinate\TimeCode;
use FFMpeg\Media\Audio;
class AudioFilters
{
protected $media;
public function __construct(Audio $media)
{
$this->media = $media;
}
/**
* Resamples the audio file.
*
* @param int $rate
*
* @return AudioFilters
*/
public function resample($rate)
{
$this->media->addFilter(new AudioResamplableFilter($rate));
return $this;
}
/**
* Add metadata to an audio file. If no arguments are given then filter
* will remove all metadata from the audio file.
*
* @param array|null $data If array must contain one of these key/value pairs:
* - "title": Title metadata
* - "artist": Artist metadata
* - "composer": Composer metadata
* - "album": Album metadata
* - "track": Track metadata
* - "artwork": Song artwork. String of file path
* - "year": Year metadata
* - "genre": Genre metadata
* - "description": Description metadata
*/
public function addMetadata($data = null)
{
$this->media->addFilter(new AddMetadataFilter($data));
return $this;
}
/**
* Cuts the audio at `$start`, optionally define the end.
*
* @param TimeCode $start Where the clipping starts(seek to time)
* @param TimeCode $duration How long the clipped audio should be
*
* @return AudioFilters
*/
public function clip($start, $duration = null)
{
$this->media->addFilter(new AudioClipFilter($start, $duration));
return $this;
}
/**
* Applies a custom filter.
*
* @param string $parameters
*
* @return AudioFilters
*/
public function custom($parameters)
{
$this->media->addFilter(new CustomFilter($parameters));
return $this;
}
}

View file

@ -1,53 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <dev.team@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Filters\Audio;
use FFMpeg\Format\AudioInterface;
use FFMpeg\Media\Audio;
class AudioResamplableFilter implements AudioFilterInterface
{
/** @var string */
private $rate;
/** @var int */
private $priority;
public function __construct($rate, $priority = 0)
{
$this->rate = $rate;
$this->priority = $priority;
}
/**
* {@inheritdoc}
*/
public function getPriority()
{
return $this->priority;
}
/**
* @return int
*/
public function getRate()
{
return $this->rate;
}
/**
* {@inheritdoc}
*/
public function apply(Audio $audio, AudioInterface $format)
{
return ['-ac', 2, '-ar', $this->rate];
}
}

View file

@ -1,53 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <dev.team@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Filters\Audio;
use FFMpeg\Format\AudioInterface;
use FFMpeg\Media\Audio;
class CustomFilter implements AudioFilterInterface
{
/** @var string */
private $filter;
/** @var int */
private $priority;
/**
* A custom filter, useful if you want to build complex filters.
*
* @param string $filter
* @param int $priority
*/
public function __construct($filter, $priority = 0)
{
$this->filter = $filter;
$this->priority = $priority;
}
/**
* {@inheritdoc}
*/
public function getPriority()
{
return $this->priority;
}
/**
* {@inheritdoc}
*/
public function apply(Audio $audio, AudioInterface $format)
{
$commands = ['-af', $this->filter];
return $commands;
}
}

View file

@ -1,43 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Filters\Audio;
use FFMpeg\Format\AudioInterface;
use FFMpeg\Media\Audio;
class SimpleFilter implements AudioFilterInterface
{
private $params;
private $priority;
public function __construct(array $params, $priority = 0)
{
$this->params = $params;
$this->priority = $priority;
}
/**
* {@inheritdoc}
*/
public function getPriority()
{
return $this->priority;
}
/**
* {@inheritdoc}
*/
public function apply(Audio $audio, AudioInterface $format)
{
return $this->params;
}
}

View file

@ -1,20 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Strime <contact@strime.io>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Filters\Concat;
use FFMpeg\Filters\FilterInterface;
use FFMpeg\Media\Concat;
interface ConcatFilterInterface extends FilterInterface
{
public function apply(Concat $concat);
}

View file

@ -1,24 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Strime <contact@strime.io>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Filters\Concat;
use FFMpeg\Media\Concat;
class ConcatFilters
{
private $concat;
public function __construct(Concat $concat)
{
$this->concat = $concat;
}
}

View file

@ -1,22 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <dev.team@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Filters;
interface FilterInterface
{
/**
* Returns the priority of the filter.
*
* @return int
*/
public function getPriority();
}

View file

@ -1,60 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <dev.team@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Filters;
use Traversable;
class FiltersCollection implements \Countable, \IteratorAggregate
{
private $sorted;
private $filters = [];
/**
* @return FiltersCollection
*/
public function add(FilterInterface $filter)
{
$this->filters[$filter->getPriority()][] = $filter;
$this->sorted = null;
return $this;
}
/**
* {@inheritdoc}
*/
public function count(): int
{
if (0 === count($this->filters)) {
return 0;
}
return count(call_user_func_array('array_merge', array_values($this->filters)));
}
/**
* {@inheritdoc}
*/
public function getIterator(): Traversable
{
if (null === $this->sorted) {
if (0 === count($this->filters)) {
$this->sorted = $this->filters;
} else {
krsort($this->filters);
$this->sorted = call_user_func_array('array_merge', array_values($this->filters));
}
}
return new \ArrayIterator($this->sorted);
}
}

View file

@ -1,52 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <dev.team@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Filters\Frame;
use FFMpeg\Media\Frame;
class CustomFrameFilter implements FrameFilterInterface
{
/** @var string */
private $filter;
/** @var int */
private $priority;
/**
* A custom filter, useful if you want to build complex filters.
*
* @param string $filter
* @param int $priority
*/
public function __construct($filter, $priority = 0)
{
$this->filter = $filter;
$this->priority = $priority;
}
/**
* {@inheritdoc}
*/
public function getPriority()
{
return $this->priority;
}
/**
* {@inheritdoc}
*/
public function apply(Frame $frame)
{
$commands = ['-vf', $this->filter];
return $commands;
}
}

View file

@ -1,57 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <dev.team@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Filters\Frame;
use FFMpeg\Exception\RuntimeException;
use FFMpeg\Media\Frame;
class DisplayRatioFixerFilter implements FrameFilterInterface
{
/** @var int */
private $priority;
public function __construct($priority = 0)
{
$this->priority = $priority;
}
/**
* {@inheritdoc}
*/
public function getPriority()
{
return $this->priority;
}
/**
* {@inheritdoc}
*/
public function apply(Frame $frame)
{
$dimensions = null;
$commands = [];
foreach ($frame->getVideo()->getStreams() as $stream) {
if ($stream->isVideo()) {
try {
$dimensions = $stream->getDimensions();
$commands[] = '-s';
$commands[] = $dimensions->getWidth().'x'.$dimensions->getHeight();
break;
} catch (RuntimeException $e) {
}
}
}
return $commands;
}
}

View file

@ -1,20 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <dev.team@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Filters\Frame;
use FFMpeg\Filters\FilterInterface;
use FFMpeg\Media\Frame;
interface FrameFilterInterface extends FilterInterface
{
public function apply(Frame $frame);
}

View file

@ -1,53 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <dev.team@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Filters\Frame;
use FFMpeg\Media\Frame;
class FrameFilters
{
private $frame;
public function __construct(Frame $frame)
{
$this->frame = $frame;
}
/**
* Fixes the display ratio of the output frame.
*
* In case the sample ratio and display ratio are different, image may be
* anamorphozed. This filter fixes this by specifying the output size.
*
* @return FrameFilters
*/
public function fixDisplayRatio()
{
$this->frame->addFilter(new DisplayRatioFixerFilter());
return $this;
}
/**
* Applies a custom filter: -vf foo bar.
*
* @param string $parameters
*
* @return FrameFilters
*/
public function custom($parameters)
{
$this->frame->addFilter(new CustomFrameFilter($parameters));
return $this;
}
}

View file

@ -1,20 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Strime <contact@strime.io>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Filters\Gif;
use FFMpeg\Filters\FilterInterface;
use FFMpeg\Media\Gif;
interface GifFilterInterface extends FilterInterface
{
public function apply(Gif $gif);
}

View file

@ -1,24 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Strime <contact@strime.io>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Filters\Gif;
use FFMpeg\Media\Gif;
class GifFilters
{
private $gif;
public function __construct(Gif $gif)
{
$this->gif = $gif;
}
}

View file

@ -1,72 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <dev.team@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Filters\Video;
use FFMpeg\Coordinate\TimeCode;
use FFMpeg\Format\VideoInterface;
use FFMpeg\Media\Video;
class ClipFilter implements VideoFilterInterface
{
/** @var TimeCode */
private $start;
/** @var TimeCode */
private $duration;
/** @var int */
private $priority;
public function __construct(TimeCode $start, TimeCode $duration = null, $priority = 0)
{
$this->start = $start;
$this->duration = $duration;
$this->priority = $priority;
}
/**
* {@inheritdoc}
*/
public function getPriority()
{
return $this->priority;
}
/**
* @return TimeCode
*/
public function getStart()
{
return $this->start;
}
/**
* @return TimeCode
*/
public function getDuration()
{
return $this->duration;
}
/**
* {@inheritdoc}
*/
public function apply(Video $video, VideoInterface $format)
{
$commands = ['-ss', (string) $this->start];
if (null !== $this->duration) {
$commands[] = '-t';
$commands[] = (string) $this->duration;
}
return $commands;
}
}

View file

@ -1,61 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <dev.team@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Filters\Video;
use FFMpeg\Coordinate\Dimension;
use FFMpeg\Coordinate\Point;
use FFMpeg\Format\VideoInterface;
use FFMpeg\Media\Video;
class CropFilter implements VideoFilterInterface
{
/** @var int */
protected $priority;
/** @var Dimension */
protected $dimension;
/** @var Point */
protected $point;
public function __construct(Point $point, Dimension $dimension, $priority = 0)
{
$this->priority = $priority;
$this->dimension = $dimension;
$this->point = $point;
}
/**
* {@inheritdoc}
*/
public function getPriority()
{
return $this->priority;
}
/**
* {@inheritdoc}
*/
public function apply(Video $video, VideoInterface $format)
{
foreach ($video->getStreams()->videos() as $stream) {
if ($stream->has('width') && $stream->has('height')) {
$stream->set('width', $this->dimension->getWidth());
$stream->set('height', $this->dimension->getHeight());
}
}
return [
'-filter:v',
'crop='.
$this->dimension->getWidth().':'.$this->dimension->getHeight().':'.$this->point->getX().':'.$this->point->getY(),
];
}
}

View file

@ -1,53 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <dev.team@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Filters\Video;
use FFMpeg\Format\VideoInterface;
use FFMpeg\Media\Video;
class CustomFilter implements VideoFilterInterface
{
/** @var string */
private $filter;
/** @var int */
private $priority;
/**
* A custom filter, useful if you want to build complex filters.
*
* @param string $filter
* @param int $priority
*/
public function __construct($filter, $priority = 0)
{
$this->filter = $filter;
$this->priority = $priority;
}
/**
* {@inheritdoc}
*/
public function getPriority()
{
return $this->priority;
}
/**
* {@inheritdoc}
*/
public function apply(Video $video, VideoInterface $format)
{
$commands = ['-vf', $this->filter];
return $commands;
}
}

View file

@ -1,151 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Strime <romain@strime.io>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Filters\Video;
use FFMpeg\Exception\InvalidArgumentException;
use FFMpeg\Exception\RuntimeException;
use FFMpeg\Format\VideoInterface;
use FFMpeg\Media\Video;
class ExtractMultipleFramesFilter implements VideoFilterInterface
{
/** will extract a frame every second */
public const FRAMERATE_EVERY_SEC = '1/1';
/** will extract a frame every 2 seconds */
public const FRAMERATE_EVERY_2SEC = '1/2';
/** will extract a frame every 5 seconds */
public const FRAMERATE_EVERY_5SEC = '1/5';
/** will extract a frame every 10 seconds */
public const FRAMERATE_EVERY_10SEC = '1/10';
/** will extract a frame every 30 seconds */
public const FRAMERATE_EVERY_30SEC = '1/30';
/** will extract a frame every minute */
public const FRAMERATE_EVERY_60SEC = '1/60';
/** @var int */
private $priority;
private $frameRate;
private $destinationFolder;
private $frameFileType = 'jpg';
/** @var array */
private static $supportedFrameFileTypes = array('jpg', 'jpeg', 'png');
public function __construct($frameRate = self::FRAMERATE_EVERY_SEC, $destinationFolder = __DIR__, $priority = 0)
{
$this->priority = $priority;
$this->frameRate = $frameRate;
// Make sure that the destination folder has a trailing slash
if (0 != strcmp(substr($destinationFolder, -1), '/')) {
$destinationFolder .= '/';
}
// Set the destination folder
$this->destinationFolder = $destinationFolder;
}
/**
* @param string $frameFileType
*
* @throws \FFMpeg\Exception\InvalidArgumentException
*
* @return ExtractMultipleFramesFilter
*/
public function setFrameFileType($frameFileType)
{
if (in_array($frameFileType, self::$supportedFrameFileTypes)) {
$this->frameFileType = $frameFileType;
return $this;
}
throw new InvalidArgumentException('Invalid frame file type, use: '.implode(',', self::$supportedFrameFileTypes));
}
/**
* {@inheritdoc}
*/
public function getPriority()
{
return $this->priority;
}
/**
* {@inheritdoc}
*/
public function getFrameRate()
{
return $this->frameRate;
}
/**
* {@inheritdoc}
*/
public function getDestinationFolder()
{
return $this->destinationFolder;
}
/**
* {@inheritdoc}
*/
public function apply(Video $video, VideoInterface $format)
{
$commands = [];
$duration = 0;
try {
// Get the duration of the video
foreach ($video->getStreams()->videos() as $stream) {
if ($stream->has('duration')) {
$duration = $stream->get('duration');
}
}
// Get the number of frames per second we have to extract.
if (false !== preg_match('/(\d+)(?:\s*)([\+\-\*\/])(?:\s*)(\d+)/', $this->frameRate, $matches)) {
$operator = $matches[2];
switch ($operator) {
case '/':
$nbFramesPerSecond = $matches[1] / $matches[3];
break;
default:
throw new InvalidArgumentException('The frame rate is not a proper division: '.$this->frameRate);
break;
}
}
// Set the number of digits to use in the exported filenames
$nbImages = ceil($duration * $nbFramesPerSecond);
if ($nbImages < 100) {
$nbDigitsInFileNames = '02';
} elseif ($nbImages < 1000) {
$nbDigitsInFileNames = '03';
} else {
$nbDigitsInFileNames = '06';
}
// Set the parameters
$commands[] = '-vf';
$commands[] = 'fps='.$this->frameRate;
$commands[] = $this->destinationFolder.'frame-%'.$nbDigitsInFileNames.'d.'.$this->frameFileType;
} catch (RuntimeException $e) {
throw new RuntimeException('An error occured while extracting the frames: '.$e->getMessage().'. The code: '.$e->getCode());
}
return $commands;
}
}

View file

@ -1,82 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <dev.team@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Filters\Video;
use FFMpeg\Coordinate\FrameRate;
use FFMpeg\Format\VideoInterface;
use FFMpeg\Media\Video;
class FrameRateFilter implements VideoFilterInterface
{
private $rate;
private $gop;
private $priority;
public function __construct(FrameRate $rate, $gop, $priority = 0)
{
$this->rate = $rate;
$this->gop = $gop;
$this->priority = $priority;
}
/**
* {@inheritdoc}
*/
public function getPriority()
{
return $this->priority;
}
/**
* Returns the frame rate.
*
* @return FrameRate
*/
public function getFrameRate()
{
return $this->rate;
}
/**
* Returns the GOP size.
*
* @see https://wikipedia.org/wiki/Group_of_pictures
*
* @return int
*/
public function getGOP()
{
return $this->gop;
}
/**
* {@inheritdoc}
*/
public function apply(Video $video, VideoInterface $format)
{
$commands = ['-r', $this->rate->getValue()];
/*
* @see http://sites.google.com/site/linuxencoding/x264-ffmpeg-mapping
*/
if ($format->supportBFrames()) {
$commands[] = '-b_strategy';
$commands[] = '1';
$commands[] = '-bf';
$commands[] = '3';
$commands[] = '-g';
$commands[] = $this->gop;
}
return $commands;
}
}

View file

@ -1,100 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Strime <contact@strime.io>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Filters\Video;
use FFMpeg\Coordinate\Dimension;
use FFMpeg\Filters\AdvancedMedia\ComplexCompatibleFilter;
use FFMpeg\Format\VideoInterface;
use FFMpeg\Media\AdvancedMedia;
use FFMpeg\Media\Video;
class PadFilter implements VideoFilterInterface, ComplexCompatibleFilter
{
/** @var Dimension */
private $dimension;
/** @var int */
private $priority;
public function __construct(Dimension $dimension, $priority = 0)
{
$this->dimension = $dimension;
$this->priority = $priority;
}
/**
* {@inheritdoc}
*/
public function getPriority()
{
return $this->priority;
}
/**
* @return Dimension
*/
public function getDimension()
{
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}
*/
public function apply(Video $video, VideoInterface $format)
{
return $this->getCommands();
}
/**
* {@inheritdoc}
*/
public function applyComplex(AdvancedMedia $media)
{
return $this->getCommands();
}
/**
* @return array
*/
protected function getCommands()
{
$commands = [];
$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';
return $commands;
}
}

View file

@ -1,141 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <dev.team@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Filters\Video;
use FFMpeg\Coordinate\Dimension;
use FFMpeg\Exception\RuntimeException;
use FFMpeg\Format\VideoInterface;
use FFMpeg\Media\Video;
class ResizeFilter implements VideoFilterInterface
{
/** fits to the dimensions, might introduce anamorphosis */
public const RESIZEMODE_FIT = 'fit';
/** resizes the video inside the given dimension, no anamorphosis */
public const RESIZEMODE_INSET = 'inset';
/** resizes the video to fit the dimension width, no anamorphosis */
public const RESIZEMODE_SCALE_WIDTH = 'width';
/** resizes the video to fit the dimension height, no anamorphosis */
public const RESIZEMODE_SCALE_HEIGHT = 'height';
/** @var Dimension */
private $dimension;
/** @var string */
private $mode;
/** @var bool */
private $forceStandards;
/** @var int */
private $priority;
public function __construct(Dimension $dimension, $mode = self::RESIZEMODE_FIT, $forceStandards = true, $priority = 0)
{
$this->dimension = $dimension;
$this->mode = $mode;
$this->forceStandards = $forceStandards;
$this->priority = $priority;
}
/**
* {@inheritdoc}
*/
public function getPriority()
{
return $this->priority;
}
/**
* @return Dimension
*/
public function getDimension()
{
return $this->dimension;
}
/**
* @return string
*/
public function getMode()
{
return $this->mode;
}
/**
* @return bool
*/
public function areStandardsForced()
{
return $this->forceStandards;
}
/**
* {@inheritdoc}
*/
public function apply(Video $video, VideoInterface $format)
{
$dimensions = null;
$commands = [];
foreach ($video->getStreams() as $stream) {
if ($stream->isVideo()) {
try {
$dimensions = $stream->getDimensions();
break;
} catch (RuntimeException $e) {
}
}
}
if (null !== $dimensions) {
$dimensions = $this->getComputedDimensions($dimensions, $format->getModulus());
// Using Filter to have ordering
$commands[] = '-vf';
$commands[] = '[in]scale='.$dimensions->getWidth().':'.$dimensions->getHeight().' [out]';
}
return $commands;
}
private function getComputedDimensions(Dimension $dimension, $modulus)
{
$originalRatio = $dimension->getRatio($this->forceStandards);
switch ($this->mode) {
case self::RESIZEMODE_SCALE_WIDTH:
$height = $this->dimension->getHeight();
$width = $originalRatio->calculateWidth($height, $modulus);
break;
case self::RESIZEMODE_SCALE_HEIGHT:
$width = $this->dimension->getWidth();
$height = $originalRatio->calculateHeight($width, $modulus);
break;
case self::RESIZEMODE_INSET:
$targetRatio = $this->dimension->getRatio($this->forceStandards);
if ($targetRatio->getValue() > $originalRatio->getValue()) {
$height = $this->dimension->getHeight();
$width = $originalRatio->calculateWidth($height, $modulus);
} else {
$width = $this->dimension->getWidth();
$height = $originalRatio->calculateHeight($width, $modulus);
}
break;
case self::RESIZEMODE_FIT:
default:
$width = $this->dimension->getWidth();
$height = $this->dimension->getHeight();
break;
}
return new Dimension($width, $height);
}
}

View file

@ -1,82 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <dev.team@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Filters\Video;
use FFMpeg\Coordinate\Dimension;
use FFMpeg\Exception\InvalidArgumentException;
use FFMpeg\Format\VideoInterface;
use FFMpeg\Media\Video;
class RotateFilter implements VideoFilterInterface
{
public const ROTATE_90 = 'transpose=1';
public const ROTATE_180 = 'hflip,vflip';
public const ROTATE_270 = 'transpose=2';
/** @var string */
private $angle;
/** @var int */
private $priority;
public function __construct($angle, $priority = 0)
{
$this->setAngle($angle);
$this->priority = (int) $priority;
}
/**
* {@inheritdoc}
*/
public function getPriority()
{
return $this->priority;
}
/**
* @return Dimension
*/
public function getAngle()
{
return $this->angle;
}
/**
* {@inheritdoc}
*/
public function apply(Video $video, VideoInterface $format)
{
if (in_array($this->angle, [self::ROTATE_90, self::ROTATE_270], true)) {
foreach ($video->getStreams()->videos() as $stream) {
if ($stream->has('width') && $stream->has('height')) {
$width = $stream->get('width');
$stream->set('width', $stream->get('height'));
$stream->set('height', $width);
}
}
}
return ['-vf', $this->angle, '-metadata:s:v:0', 'rotate=0'];
}
private function setAngle($angle)
{
switch ($angle) {
case self::ROTATE_90:
case self::ROTATE_180:
case self::ROTATE_270:
$this->angle = $angle;
break;
default:
throw new InvalidArgumentException('Invalid angle value.');
}
}
}

View file

@ -1,44 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <dev.team@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Filters\Video;
use FFMpeg\Format\VideoInterface;
use FFMpeg\Media\Video;
/**
* Synchronizes audio and video in case of desynchronized movies.
*/
class SynchronizeFilter implements VideoFilterInterface
{
private $priority;
public function __construct($priority = 12)
{
$this->priority = $priority;
}
/**
* {@inheritdoc}
*/
public function getPriority()
{
return $this->priority;
}
/**
* {@inheritdoc}
*/
public function apply(Video $video, VideoInterface $format)
{
return ['-async', '1', '-metadata:s:v:0', 'start_time=0'];
}
}

View file

@ -1,26 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <dev.team@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Filters\Video;
use FFMpeg\Filters\FilterInterface;
use FFMpeg\Format\VideoInterface;
use FFMpeg\Media\Video;
interface VideoFilterInterface extends FilterInterface
{
/**
* Applies the filter on the the Video media given an format.
*
* @return array An array of arguments
*/
public function apply(Video $video, VideoInterface $format);
}

View file

@ -1,170 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <dev.team@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Filters\Video;
use FFMpeg\Coordinate\Dimension;
use FFMpeg\Coordinate\FrameRate;
use FFMpeg\Coordinate\Point;
use FFMpeg\Coordinate\TimeCode;
use FFMpeg\Filters\Audio\AudioFilters;
use FFMpeg\Filters\Audio\AudioResamplableFilter;
use FFMpeg\Media\Video;
class VideoFilters extends AudioFilters
{
public function __construct(Video $media)
{
parent::__construct($media);
}
/**
* Resizes a video to a given dimension.
*
* @param string $mode
* @param bool $forceStandards
*
* @return VideoFilters
*/
public function resize(Dimension $dimension, $mode = ResizeFilter::RESIZEMODE_FIT, $forceStandards = true)
{
$this->media->addFilter(new ResizeFilter($dimension, $mode, $forceStandards));
return $this;
}
/**
* Changes the video framerate.
*
* @param int $gop
*
* @return VideoFilters
*/
public function framerate(FrameRate $framerate, $gop)
{
$this->media->addFilter(new FrameRateFilter($framerate, $gop));
return $this;
}
/**
* Extract multiple frames from the video.
*
* @param string $frameRate
* @param string $destinationFolder
*
* @return $this
*/
public function extractMultipleFrames($frameRate = ExtractMultipleFramesFilter::FRAMERATE_EVERY_2SEC, $destinationFolder = __DIR__)
{
$this->media->addFilter(new ExtractMultipleFramesFilter($frameRate, $destinationFolder));
return $this;
}
/**
* Synchronizes audio and video.
*
* @return VideoFilters
*/
public function synchronize()
{
$this->media->addFilter(new SynchronizeFilter());
return $this;
}
/**
* Clips (cuts) the video.
*
* @param TimeCode $start
* @param TimeCode $duration
*
* @return VideoFilters
*/
public function clip($start, $duration = null)
{
$this->media->addFilter(new ClipFilter($start, $duration));
return $this;
}
/**
* Resamples the audio file.
*
* @param int $rate
*
* @return AudioFilters
*/
public function audioResample($rate)
{
$this->media->addFilter(new AudioResamplableFilter($rate));
return $this;
}
/**
* Adds padding (black bars) to a video.
*
* @return VideoFilters
*/
public function pad(Dimension $dimension)
{
$this->media->addFilter(new PadFilter($dimension));
return $this;
}
public function rotate($angle)
{
$this->media->addFilter(new RotateFilter($angle, 30));
return $this;
}
/**
* Crops the video.
*
* @return VideoFilters
*/
public function crop(Point $point, Dimension $dimension)
{
$this->media->addFilter(new CropFilter($point, $dimension));
return $this;
}
/**
* @param string $imagePath
*
* @return $this
*/
public function watermark($imagePath, array $coordinates = [])
{
$this->media->addFilter(new WatermarkFilter($imagePath, $coordinates));
return $this;
}
/**
* Applies a custom filter: -vf foo bar.
*
* @param string $parameters
*
* @return VideoFilters
*/
public function custom($parameters)
{
$this->media->addFilter(new CustomFilter($parameters));
return $this;
}
}

View file

@ -1,121 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <dev.team@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Filters\Video;
use FFMpeg\Exception\InvalidArgumentException;
use FFMpeg\Filters\AdvancedMedia\ComplexCompatibleFilter;
use FFMpeg\Format\VideoInterface;
use FFMpeg\Media\AdvancedMedia;
use FFMpeg\Media\Video;
class WatermarkFilter implements VideoFilterInterface, ComplexCompatibleFilter
{
/** @var string */
private $watermarkPath;
/** @var array */
private $coordinates;
/** @var int */
private $priority;
public function __construct($watermarkPath, array $coordinates = [], $priority = 0)
{
if (!file_exists($watermarkPath)) {
throw new InvalidArgumentException(sprintf('File %s does not exist', $watermarkPath));
}
$this->watermarkPath = $watermarkPath;
$this->coordinates = $coordinates;
$this->priority = $priority;
}
/**
* {@inheritdoc}
*/
public function getPriority()
{
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}
*/
public function apply(Video $video, VideoInterface $format)
{
return $this->getCommands();
}
/**
* {@inheritdoc}
*/
public function applyComplex(AdvancedMedia $media)
{
return $this->getCommands();
}
/**
* @return array
*/
protected function getCommands()
{
$position = isset($this->coordinates['position']) ? $this->coordinates['position'] : 'absolute';
switch ($position) {
case 'relative':
if (isset($this->coordinates['top'])) {
$y = $this->coordinates['top'];
} elseif (isset($this->coordinates['bottom'])) {
$y = 'main_h - '.$this->coordinates['bottom'].' - overlay_h';
} else {
$y = 0;
}
if (isset($this->coordinates['left'])) {
$x = $this->coordinates['left'];
} elseif (isset($this->coordinates['right'])) {
$x = 'main_w - '.$this->coordinates['right'].' - overlay_w';
} else {
$x = 0;
}
break;
default:
$x = isset($this->coordinates['x']) ? $this->coordinates['x'] : 0;
$y = isset($this->coordinates['y']) ? $this->coordinates['y'] : 0;
break;
}
return [
'-vf',
'movie='.$this->watermarkPath.' [watermark]; [in][watermark] overlay='.$x.':'.$y.' [out]',
];
}
}

View file

@ -1,70 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Strime <contact@strime.io>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Filters\Waveform;
use FFMpeg\Exception\RuntimeException;
use FFMpeg\Media\Waveform;
class WaveformDownmixFilter implements WaveformFilterInterface
{
/** @var bool */
private $downmix;
/** @var int */
private $priority;
// By default, the downmix value is set to FALSE.
public function __construct($downmix = false, $priority = 0)
{
$this->downmix = $downmix;
$this->priority = $priority;
}
/**
* {@inheritdoc}
*/
public function getDownmix()
{
return $this->downmix;
}
/**
* {@inheritdoc}
*/
public function getPriority()
{
return $this->priority;
}
/**
* {@inheritdoc}
*/
public function apply(Waveform $waveform)
{
$commands = [];
foreach ($waveform->getAudio()->getStreams() as $stream) {
if ($stream->isAudio()) {
try {
// If the downmix parameter is set to TRUE, we add an option to the FFMPEG command
if (true == $this->downmix) {
$commands[] = '"aformat=channel_layouts=mono"';
}
break;
} catch (RuntimeException $e) {
}
}
}
return $commands;
}
}

View file

@ -1,20 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Strime <contact@strime.io>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Filters\Waveform;
use FFMpeg\Filters\FilterInterface;
use FFMpeg\Media\Waveform;
interface WaveformFilterInterface extends FilterInterface
{
public function apply(Waveform $waveform);
}

View file

@ -1,38 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Strime <contact@strime.io>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Filters\Waveform;
use FFMpeg\Media\Waveform;
class WaveformFilters
{
private $waveform;
public function __construct(Waveform $waveform)
{
$this->waveform = $waveform;
}
/**
* Sets the downmix of the output waveform.
*
* If you want a simpler waveform, sets the downmix to TRUE.
*
* @return WaveformFilters
*/
public function setDownmix()
{
$this->waveform->addFilter(new WaveformDownmixFilter());
return $this;
}
}

View file

@ -1,31 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Format\Audio;
/**
* The AAC audio format.
*/
class Aac extends DefaultAudio
{
public function __construct()
{
$this->audioCodec = 'libfdk_aac';
}
/**
* {@inheritDoc}
*/
public function getAvailableAudioCodecs()
{
return ['libfdk_aac'];
}
}

View file

@ -1,141 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Format\Audio;
use Evenement\EventEmitter;
use FFMpeg\Exception\InvalidArgumentException;
use FFMpeg\FFProbe;
use FFMpeg\Format\AudioInterface;
use FFMpeg\Format\ProgressableInterface;
use FFMpeg\Format\ProgressListener\AudioProgressListener;
use FFMpeg\Media\MediaTypeInterface;
abstract class DefaultAudio extends EventEmitter implements AudioInterface, ProgressableInterface
{
/** @var string */
protected $audioCodec;
/** @var int */
protected $audioKiloBitrate = 128;
/** @var int */
protected $audioChannels = null;
/**
* {@inheritdoc}
*/
public function getExtraParams()
{
return [];
}
/**
* {@inheritdoc}
*/
public function getAudioCodec()
{
return $this->audioCodec;
}
/**
* Sets the audio codec, Should be in the available ones, otherwise an
* exception is thrown.
*
* @param string $audioCodec
*
* @throws InvalidArgumentException
*/
public function setAudioCodec($audioCodec)
{
if (!in_array($audioCodec, $this->getAvailableAudioCodecs())) {
throw new InvalidArgumentException(sprintf('Wrong audiocodec value for %s, available formats are %s', $audioCodec, implode(', ', $this->getAvailableAudioCodecs())));
}
$this->audioCodec = $audioCodec;
return $this;
}
/**
* {@inheritdoc}
*/
public function getAudioKiloBitrate()
{
return $this->audioKiloBitrate;
}
/**
* Sets the kiloBitrate value.
*
* @param int $kiloBitrate
*
* @throws InvalidArgumentException
*/
public function setAudioKiloBitrate($kiloBitrate)
{
if ($kiloBitrate < 1) {
throw new InvalidArgumentException('Wrong kiloBitrate value');
}
$this->audioKiloBitrate = (int) $kiloBitrate;
return $this;
}
/**
* {@inheritdoc}
*/
public function getAudioChannels()
{
return $this->audioChannels;
}
/**
* Sets the channels value.
*
* @param int $channels
*
* @throws InvalidArgumentException
*/
public function setAudioChannels($channels)
{
if ($channels < 1) {
throw new InvalidArgumentException('Wrong channels value');
}
$this->audioChannels = (int) $channels;
return $this;
}
/**
* {@inheritdoc}
*/
public function createProgressListener(MediaTypeInterface $media, FFProbe $ffprobe, $pass, $total, $duration = 0)
{
$format = $this;
$listener = new AudioProgressListener($ffprobe, $media->getPathfile(), $pass, $total, $duration);
$listener->on('progress', function () use ($media, $format) {
$format->emit('progress', array_merge([$media, $format], func_get_args()));
});
return [$listener];
}
/**
* {@inheritDoc}
*/
public function getPasses()
{
return 1;
}
}

View file

@ -1,31 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Format\Audio;
/**
* The Flac audio format.
*/
class Flac extends DefaultAudio
{
public function __construct()
{
$this->audioCodec = 'flac';
}
/**
* {@inheritDoc}
*/
public function getAvailableAudioCodecs()
{
return ['flac'];
}
}

View file

@ -1,31 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Format\Audio;
/**
* The MP3 audio format.
*/
class Mp3 extends DefaultAudio
{
public function __construct()
{
$this->audioCodec = 'libmp3lame';
}
/**
* {@inheritDoc}
*/
public function getAvailableAudioCodecs()
{
return ['libmp3lame'];
}
}

View file

@ -1,39 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Format\Audio;
/**
* The Vorbis audio format.
*/
class Vorbis extends DefaultAudio
{
public function __construct()
{
$this->audioCodec = 'vorbis';
}
/**
* {@inheritdoc}
*/
public function getExtraParams()
{
return ['-strict', '-2'];
}
/**
* {@inheritDoc}
*/
public function getAvailableAudioCodecs()
{
return ['vorbis'];
}
}

View file

@ -1,31 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Format\Audio;
/**
* The WAV audio format.
*/
class Wav extends DefaultAudio
{
public function __construct()
{
$this->audioCodec = 'pcm_s16le';
}
/**
* {@inheritDoc}
*/
public function getAvailableAudioCodecs()
{
return ['pcm_s16le'];
}
}

View file

@ -1,43 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Format;
interface AudioInterface extends FormatInterface
{
/**
* Gets the audio kiloBitrate value.
*
* @return int
*/
public function getAudioKiloBitrate();
/**
* Gets the audio channels value.
*
* @return int
*/
public function getAudioChannels();
/**
* Returns the audio codec.
*
* @return string
*/
public function getAudioCodec();
/**
* Returns the list of available audio codecs for this format.
*
* @return array
*/
public function getAvailableAudioCodecs();
}

View file

@ -1,29 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Format;
interface FormatInterface
{
/**
* Returns the number of passes.
*
* @return string
*/
public function getPasses();
/**
* Returns an array of extra parameters to add to ffmpeg commandline.
*
* @return array
*/
public function getExtraParams();
}

View file

@ -1,16 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <dev.team@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Format;
interface FrameInterface extends FormatInterface
{
}

View file

@ -1,260 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Format\ProgressListener;
use Alchemy\BinaryDriver\Listeners\ListenerInterface;
use Evenement\EventEmitter;
use FFMpeg\Exception\RuntimeException;
use FFMpeg\FFProbe;
/**
* @author Robert Gruendler <r.gruendler@gmail.com>
*/
abstract class AbstractProgressListener extends EventEmitter implements ListenerInterface
{
/** @var int */
private $duration;
/** @var int */
private $totalSize;
/** @var int */
private $currentSize;
/** @var int */
private $currentTime;
/** @var float */
private $lastOutput = null;
/** @var FFProbe */
private $ffprobe;
/** @var string */
private $pathfile;
/** @var bool */
private $initialized = false;
/** @var int */
private $currentPass;
/** @var int */
private $totalPass;
/**
* Transcoding rate in kb/s.
*
* @var int
*/
private $rate;
/**
* Percentage of transcoding progress (0 - 100).
*
* @var int
*/
private $percent = 0;
/**
* Time remaining (seconds).
*
* @var int
*/
private $remaining = null;
/**
* @param string $pathfile
* @param int $currentPass The current pass number
* @param int $totalPass The total number of passes
* @param int $duration
*/
public function __construct(FFProbe $ffprobe, $pathfile, $currentPass, $totalPass, $duration = 0)
{
$this->ffprobe = $ffprobe;
$this->pathfile = $pathfile;
$this->currentPass = $currentPass;
$this->totalPass = $totalPass;
$this->duration = $duration;
}
/**
* @return FFProbe
*/
public function getFFProbe()
{
return $this->ffprobe;
}
/**
* @return string
*/
public function getPathfile()
{
return $this->pathfile;
}
/**
* @return int
*/
public function getCurrentPass()
{
return $this->currentPass;
}
/**
* @return int
*/
public function getTotalPass()
{
return $this->totalPass;
}
/**
* @return int
*/
public function getCurrentTime()
{
return $this->currentTime;
}
/**
* {@inheritdoc}
*/
public function handle($type, $data)
{
if (null !== $progress = $this->parseProgress($data)) {
$this->emit('progress', array_values($progress));
}
}
/**
* {@inheritdoc}
*/
public function forwardedEvents()
{
return [];
}
/**
* Get the regex pattern to match a ffmpeg stderr status line.
*/
abstract protected function getPattern();
/**
* @param string $progress A ffmpeg stderr progress output
*
* @return array the progressinfo array or null if there's no progress available yet
*/
private function parseProgress($progress)
{
if (!$this->initialized) {
$this->initialize();
}
if (null === $this->totalSize || null === $this->duration) {
return;
}
$matches = [];
if (1 !== preg_match($this->getPattern(), $progress, $matches)) {
return null;
}
$currentDuration = $this->convertDuration($matches[2]);
$currentTime = microtime(true);
$currentSize = trim(str_replace('kb', '', strtolower(($matches[1]))));
$percent = max(0, min(1, $currentDuration / $this->duration));
if (null !== $this->lastOutput) {
$delta = $currentTime - $this->lastOutput;
// Check the type of the currentSize variable and convert it to an integer if needed.
if (!is_numeric($currentSize)) {
$currentSize = (int) $currentSize;
}
$deltaSize = $currentSize - $this->currentSize;
$rate = $deltaSize * $delta;
if ($rate > 0) {
$totalDuration = $this->totalSize / $rate;
$this->remaining = floor($totalDuration - ($totalDuration * $percent));
$this->rate = floor($rate);
} else {
$this->remaining = 0;
$this->rate = 0;
}
}
$percent = $percent / $this->totalPass + ($this->currentPass - 1) / $this->totalPass;
$this->percent = floor($percent * 100);
$this->lastOutput = $currentTime;
$this->currentSize = (int) $currentSize;
$this->currentTime = $currentDuration;
return $this->getProgressInfo();
}
/**
* @param string $rawDuration in the format 00:00:00.00
*
* @return number
*/
private function convertDuration($rawDuration)
{
$ar = array_reverse(explode(':', $rawDuration));
$duration = floatval($ar[0]);
if (!empty($ar[1])) {
$duration += intval($ar[1]) * 60;
}
if (!empty($ar[2])) {
$duration += intval($ar[2]) * 60 * 60;
}
return $duration;
}
/**
* @return array
*/
private function getProgressInfo()
{
if (null === $this->remaining) {
return null;
}
return [
'percent' => $this->percent,
'remaining' => $this->remaining,
'rate' => $this->rate,
];
}
private function initialize()
{
try {
$format = $this->ffprobe->format($this->pathfile);
} catch (RuntimeException $e) {
return;
}
if (false === $format->has('size') || false === $format->has('duration')) {
return;
}
$this->duration = (int) $this->duration > 0 ? $this->duration : $format->get('duration');
$this->totalSize = $format->get('size') / 1024 * ($this->duration / $format->get('duration'));
$this->initialized = true;
}
}

View file

@ -1,29 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Format\ProgressListener;
/**
* Parses ffmpeg stderr progress information. An example:.
*
* <pre>
* size= 3552kB time=00:03:47.29 bitrate= 128.0kbits/s
* </pre>
*
* @author Robert Gruendler <r.gruendler@gmail.com>
*/
class AudioProgressListener extends AbstractProgressListener
{
public function getPattern()
{
return '/size=(.*?) time=(.*?) /';
}
}

View file

@ -1,29 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Format\ProgressListener;
/**
* Parses ffmpeg stderr progress information for video files. An example:.
*
* <pre>
* frame= 171 fps=0.0 q=10.0 size= 18kB time=00:00:05.72 bitrate= 26.4kbits/s dup=8 drop=0
* </pre>
*
* @author Robert Gruendler <r.gruendler@gmail.com>
*/
class VideoProgressListener extends AbstractProgressListener
{
public function getPattern()
{
return '/size=(.*?) time=(.*?) /';
}
}

View file

@ -1,30 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <dev.team@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Format;
use Evenement\EventEmitterInterface;
use FFMpeg\FFProbe;
use FFMpeg\Media\MediaTypeInterface;
interface ProgressableInterface extends EventEmitterInterface
{
/**
* Creates the progress listener.
*
* @param int $pass The current pas snumber
* @param int $total The total pass number
* @param int $duration The new video duration
*
* @return array An array of listeners
*/
public function createProgressListener(MediaTypeInterface $media, FFProbe $ffprobe, $pass, $total, $duration = 0);
}

View file

@ -1,170 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Format\Video;
use FFMpeg\Exception\InvalidArgumentException;
use FFMpeg\FFProbe;
use FFMpeg\Format\Audio\DefaultAudio;
use FFMpeg\Format\ProgressListener\VideoProgressListener;
use FFMpeg\Format\VideoInterface;
use FFMpeg\Media\MediaTypeInterface;
/**
* The abstract default Video format.
*/
abstract class DefaultVideo extends DefaultAudio implements VideoInterface
{
/** @var string */
protected $videoCodec;
/** @var int */
protected $kiloBitrate = 1000;
/** @var int */
protected $modulus = 16;
/** @var array */
protected $additionalParamaters;
/** @var array */
protected $initialParamaters;
/**
* {@inheritdoc}
*/
public function getKiloBitrate()
{
return $this->kiloBitrate;
}
/**
* Sets the kiloBitrate value.
*
* @param int $kiloBitrate
*
* @throws InvalidArgumentException
*/
public function setKiloBitrate($kiloBitrate)
{
if ($kiloBitrate < 0) {
throw new InvalidArgumentException('Wrong kiloBitrate value');
}
$this->kiloBitrate = (int) $kiloBitrate;
return $this;
}
/**
* {@inheritdoc}
*/
public function getVideoCodec()
{
return $this->videoCodec;
}
/**
* Sets the video codec, Should be in the available ones, otherwise an
* exception is thrown.
*
* @param string $videoCodec
*
* @throws InvalidArgumentException
*/
public function setVideoCodec($videoCodec)
{
if (!in_array($videoCodec, $this->getAvailableVideoCodecs())) {
throw new InvalidArgumentException(sprintf('Wrong videocodec value for %s, available formats are %s', $videoCodec, implode(', ', $this->getAvailableVideoCodecs())));
}
$this->videoCodec = $videoCodec;
return $this;
}
/**
* @return int
*/
public function getModulus()
{
return $this->modulus;
}
/**
* {@inheritdoc}
*/
public function getAdditionalParameters()
{
return $this->additionalParamaters;
}
/**
* Sets additional parameters.
*
* @param array $additionalParamaters
*
* @throws InvalidArgumentException
*/
public function setAdditionalParameters($additionalParamaters)
{
if (!is_array($additionalParamaters)) {
throw new InvalidArgumentException('Wrong additionalParamaters value');
}
$this->additionalParamaters = $additionalParamaters;
return $this;
}
/**
* {@inheritdoc}
*/
public function getInitialParameters()
{
return $this->initialParamaters;
}
/**
* Sets initial parameters.
*
* @param array $initialParamaters
*
* @throws InvalidArgumentException
*/
public function setInitialParameters($initialParamaters)
{
if (!is_array($initialParamaters)) {
throw new InvalidArgumentException('Wrong initialParamaters value');
}
$this->initialParamaters = $initialParamaters;
return $this;
}
/**
* {@inheritdoc}
*/
public function createProgressListener(MediaTypeInterface $media, FFProbe $ffprobe, $pass, $total, $duration = 0)
{
$format = $this;
$listeners = [new VideoProgressListener($ffprobe, $media->getPathfile(), $pass, $total, $duration)];
foreach ($listeners as $listener) {
$listener->on('progress', function () use ($format, $media) {
$format->emit('progress', array_merge([$media, $format], func_get_args()));
});
}
return $listeners;
}
}

View file

@ -1,49 +0,0 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Format\Video;
/**
* The Ogg video format.
*/
class Ogg extends DefaultVideo
{
public function __construct($audioCodec = 'libvorbis', $videoCodec = 'libtheora')
{
$this
->setAudioCodec($audioCodec)
->setVideoCodec($videoCodec);
}
/**
* {@inheritDoc}
*/
public function supportBFrames()
{
return true;
}
/**
* {@inheritDoc}
*/
public function getAvailableAudioCodecs()
{
return ['libvorbis'];
}
/**
* {@inheritDoc}
*/
public function getAvailableVideoCodecs()
{
return ['libtheora'];
}
}

Some files were not shown because too many files have changed in this diff Show more