Version 0.3
This commit is contained in:
parent
0d69145ec3
commit
ad3a5af623
130 changed files with 7283 additions and 2627 deletions
|
|
@ -17,25 +17,23 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"php" : ">=5.3.3",
|
"php" : ">=5.3.3",
|
||||||
"symfony/process" : "~2.1",
|
"alchemy/binary-driver" : "~1.5",
|
||||||
"monolog/monolog" : "~1.0"
|
"doctrine/cache" : "~1.0",
|
||||||
|
"evenement/evenement" : "~1.0",
|
||||||
|
"symfony/process" : "~2.0"
|
||||||
},
|
},
|
||||||
"suggest": {
|
"suggest": {
|
||||||
"php-ffmpeg/extras": "A compilation of common audio & video drivers for PHP-FFMpeg"
|
"php-ffmpeg/extras" : "A compilation of common audio & video drivers for PHP-FFMpeg"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"sami/sami" : "~1.0",
|
"sami/sami" : "dev-master@dev",
|
||||||
"silex/silex" : "~1.0"
|
"silex/silex" : "~1.0",
|
||||||
|
"phpunit/phpunit" : "~3.7"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-0": {
|
"psr-0": {
|
||||||
"FFMpeg": "src"
|
"FFMpeg": "src"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"extra": {
|
|
||||||
"branch-alias": {
|
|
||||||
"dev-master": "0.3-dev"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
799
composer.lock
generated
799
composer.lock
generated
File diff suppressed because it is too large
Load diff
27
phpunit-functional.xml.dist
Normal file
27
phpunit-functional.xml.dist
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<phpunit backupGlobals="false"
|
||||||
|
backupStaticAttributes="false"
|
||||||
|
colors="true"
|
||||||
|
convertErrorsToExceptions="true"
|
||||||
|
convertNoticesToExceptions="true"
|
||||||
|
convertWarningsToExceptions="true"
|
||||||
|
processIsolation="false"
|
||||||
|
stopOnFailure="false"
|
||||||
|
syntaxCheck="true"
|
||||||
|
verbose="false"
|
||||||
|
bootstrap="tests/bootstrap.php"
|
||||||
|
>
|
||||||
|
<testsuites>
|
||||||
|
<testsuite name="FFMpeg Tests Suite">
|
||||||
|
<directory>tests/FFMpeg/Functional</directory>
|
||||||
|
</testsuite>
|
||||||
|
</testsuites>
|
||||||
|
<filter>
|
||||||
|
<blacklist>
|
||||||
|
<directory>vendor</directory>
|
||||||
|
<directory>tests</directory>
|
||||||
|
</blacklist>
|
||||||
|
</filter>
|
||||||
|
|
||||||
|
</phpunit>
|
||||||
|
|
||||||
|
|
@ -9,20 +9,11 @@
|
||||||
stopOnFailure="false"
|
stopOnFailure="false"
|
||||||
syntaxCheck="true"
|
syntaxCheck="true"
|
||||||
verbose="false"
|
verbose="false"
|
||||||
bootstrap="vendor/autoload.php"
|
bootstrap="tests/bootstrap.php"
|
||||||
>
|
>
|
||||||
<logging>
|
|
||||||
<log type="coverage-html" target="tests/phpunit_report/report" charset="UTF-8"
|
|
||||||
yui="true" highlight="false"
|
|
||||||
lowUpperBound="35" highLowerBound="70"/>
|
|
||||||
</logging>
|
|
||||||
<php>
|
|
||||||
<ini name="display_errors" value="on"/>
|
|
||||||
</php>
|
|
||||||
|
|
||||||
<testsuites>
|
<testsuites>
|
||||||
<testsuite name="FFMpeg Tests Suite">
|
<testsuite name="FFMpeg Tests Suite">
|
||||||
<directory>tests</directory>
|
<directory>tests/FFMpeg/Tests</directory>
|
||||||
</testsuite>
|
</testsuite>
|
||||||
</testsuites>
|
</testsuites>
|
||||||
<filter>
|
<filter>
|
||||||
|
|
|
||||||
|
|
@ -1,128 +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 FFMpeg\Exception\BinaryNotFoundException;
|
|
||||||
use FFMpeg\Exception\InvalidArgumentException;
|
|
||||||
use Monolog\Logger;
|
|
||||||
use Symfony\Component\Process\ExecutableFinder;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Binary abstract class
|
|
||||||
*
|
|
||||||
* @author Romain Neutron imprec@gmail.com
|
|
||||||
*/
|
|
||||||
abstract class Binary implements AdapterInterface
|
|
||||||
{
|
|
||||||
protected $binary;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @var Logger
|
|
||||||
*/
|
|
||||||
protected $logger;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var Integer
|
|
||||||
*/
|
|
||||||
protected $timeout;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Binary constructor
|
|
||||||
*
|
|
||||||
* @param type $binary The path file to the binary
|
|
||||||
* @param Logger $logger A logger
|
|
||||||
* @param Integer $timeout The timout for the underlying process, 0 means no timeout
|
|
||||||
*/
|
|
||||||
public function __construct($binary, Logger $logger, $timeout = 60)
|
|
||||||
{
|
|
||||||
if (!is_executable($binary)) {
|
|
||||||
throw new \FFMpeg\Exception\BinaryNotFoundException(sprintf('`%s` is not a valid binary', $binary));
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->binary = $binary;
|
|
||||||
$this->logger = $logger;
|
|
||||||
$this->setTimeout($timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the current timeout for underlying processes.
|
|
||||||
*
|
|
||||||
* @return integer|float
|
|
||||||
*/
|
|
||||||
public function getTimeout()
|
|
||||||
{
|
|
||||||
return $this->timeout;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the timeout for the underlying processes, use 0 to disable timeout.
|
|
||||||
*
|
|
||||||
* @param integer|float $timeout
|
|
||||||
*
|
|
||||||
* @return Binary
|
|
||||||
*/
|
|
||||||
public function setTimeout($timeout)
|
|
||||||
{
|
|
||||||
if (0 > $timeout) {
|
|
||||||
throw new InvalidArgumentException('Timeout must be a non-negative value');
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->timeout = $timeout;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Destructor
|
|
||||||
*/
|
|
||||||
public function __destruct()
|
|
||||||
{
|
|
||||||
$this->binary = $binary = $this->logger = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*
|
|
||||||
* @param Logger $logger A logger
|
|
||||||
* @param Integer $timeout The timout for the underlying process, 0 means no timeout
|
|
||||||
*
|
|
||||||
* @return Binary The binary
|
|
||||||
*
|
|
||||||
* @throws Exception\BinaryNotFoundException
|
|
||||||
*/
|
|
||||||
public static function load(Logger $logger, $timeout = 60)
|
|
||||||
{
|
|
||||||
$finder = new ExecutableFinder();
|
|
||||||
$binary = null;
|
|
||||||
|
|
||||||
foreach (static::getBinaryName() as $candidate) {
|
|
||||||
if (null !== $binary = $finder->find($candidate)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (null === $binary) {
|
|
||||||
throw new BinaryNotFoundException('Binary not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
return new static($binary, $logger, $timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the binary name
|
|
||||||
*/
|
|
||||||
protected static function getBinaryName()
|
|
||||||
{
|
|
||||||
throw new \Exception('Should be implemented');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
248
src/FFMpeg/Coordinate/AspectRatio.php
Normal file
248
src/FFMpeg/Coordinate/AspectRatio.php
Normal file
|
|
@ -0,0 +1,248 @@
|
||||||
|
<?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
|
||||||
|
const AR_4_3 = '4/3';
|
||||||
|
// named 16:9 or 1.77:1 HD video standard
|
||||||
|
const AR_16_9 = '16/9';
|
||||||
|
|
||||||
|
// named 3:2 or 1.5:1 see http://en.wikipedia.org/wiki/135_film
|
||||||
|
const AR_3_2 = '3/2';
|
||||||
|
// named 5:3 or 1.66:1 see http://en.wikipedia.org/wiki/Super_16_mm
|
||||||
|
const AR_5_3 = '5/3';
|
||||||
|
|
||||||
|
// mostly used in Photography
|
||||||
|
const AR_5_4 = '5/4';
|
||||||
|
const AR_1_1 = '1/1';
|
||||||
|
|
||||||
|
// 1.85:1 US widescreen cinema standard see http://en.wikipedia.org/wiki/Widescreen#Film
|
||||||
|
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
|
||||||
|
const AR_2_DOT_39_1 = '2.39:1';
|
||||||
|
|
||||||
|
// Rotated constants
|
||||||
|
|
||||||
|
// Rotated 4:3
|
||||||
|
const AR_ROTATED_3_4 = '3/4';
|
||||||
|
// Rotated 16:9
|
||||||
|
const AR_ROTATED_9_16 = '9/16';
|
||||||
|
|
||||||
|
// Rotated 3:2
|
||||||
|
const AR_ROTATED_2_3 = '2/3';
|
||||||
|
// Rotated 5:3
|
||||||
|
const AR_ROTATED_3_5 = '3/5';
|
||||||
|
|
||||||
|
// Rotated 5:4
|
||||||
|
const AR_ROTATED_4_5 = '4/5';
|
||||||
|
|
||||||
|
// Rotated 1.85
|
||||||
|
const AR_ROTATED_1_DOT_85 = '1/1.85';
|
||||||
|
// Rotated 2.39
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the best width for given height and modulus.
|
||||||
|
*
|
||||||
|
* @param Integer $height
|
||||||
|
* @param Integer $modulus
|
||||||
|
*
|
||||||
|
* @return Integer
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the best height for given width and modulus.
|
||||||
|
*
|
||||||
|
* @param Integer $width
|
||||||
|
* @param Integer $modulus
|
||||||
|
*
|
||||||
|
* @return Integer
|
||||||
|
*/
|
||||||
|
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 Dimension $dimension
|
||||||
|
* @param Boolean $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_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 = array(
|
||||||
|
static::AR_4_3 => static::valueFromName(static::AR_4_3),
|
||||||
|
static::AR_16_9 => static::valueFromName(static::AR_16_9),
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,19 +9,17 @@
|
||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace FFMpeg\Format;
|
namespace FFMpeg\Coordinate;
|
||||||
|
|
||||||
use FFMpeg\Exception\InvalidArgumentException;
|
use FFMpeg\Exception\InvalidArgumentException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dimension object, used for manipulating width and height couples
|
* Dimension object, used for manipulating width and height couples
|
||||||
*
|
|
||||||
* @author Romain Neutron imprec@gmail.com
|
|
||||||
*/
|
*/
|
||||||
class Dimension
|
class Dimension
|
||||||
{
|
{
|
||||||
protected $width;
|
private $width;
|
||||||
protected $height;
|
private $height;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
|
|
@ -59,4 +57,16 @@ class Dimension
|
||||||
{
|
{
|
||||||
return $this->height;
|
return $this->height;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the ratio
|
||||||
|
*
|
||||||
|
* @param type $forceStandards Whether or not force the use of standards ratios;
|
||||||
|
*
|
||||||
|
* @return AspectRatio
|
||||||
|
*/
|
||||||
|
public function getRatio($forceStandards = true)
|
||||||
|
{
|
||||||
|
return AspectRatio::create($this, $forceStandards);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
36
src/FFMpeg/Coordinate/FrameRate.php
Normal file
36
src/FFMpeg/Coordinate/FrameRate.php
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
<?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;
|
||||||
|
}
|
||||||
|
}
|
||||||
40
src/FFMpeg/Coordinate/Point.php
Normal file
40
src/FFMpeg/Coordinate/Point.php
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
<?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)
|
||||||
|
{
|
||||||
|
$this->x = (int) $x;
|
||||||
|
$this->y = (int) $y;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return integer
|
||||||
|
*/
|
||||||
|
public function getX()
|
||||||
|
{
|
||||||
|
return $this->x;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return integer
|
||||||
|
*/
|
||||||
|
public function getY()
|
||||||
|
{
|
||||||
|
return $this->y;
|
||||||
|
}
|
||||||
|
}
|
||||||
66
src/FFMpeg/Coordinate/TimeCode.php
Normal file
66
src/FFMpeg/Coordinate/TimeCode.php
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
<?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);
|
||||||
|
}
|
||||||
|
}
|
||||||
40
src/FFMpeg/Driver/FFMpegDriver.php
Normal file
40
src/FFMpeg/Driver/FFMpegDriver.php
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
<?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 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, $configuration)
|
||||||
|
{
|
||||||
|
return static::load(array('avconv', 'ffmpeg'), $logger, $configuration);
|
||||||
|
}
|
||||||
|
}
|
||||||
47
src/FFMpeg/Driver/FFProbeDriver.php
Normal file
47
src/FFMpeg/Driver/FFProbeDriver.php
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
<?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 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', array('avprobe', 'ffprobe'));
|
||||||
|
|
||||||
|
return static::load($binaries, $logger, $configuration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -13,5 +13,4 @@ namespace FFMpeg\Exception;
|
||||||
|
|
||||||
interface ExceptionInterface
|
interface ExceptionInterface
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,5 +13,4 @@ namespace FFMpeg\Exception;
|
||||||
|
|
||||||
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
|
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,5 +13,4 @@ namespace FFMpeg\Exception;
|
||||||
|
|
||||||
class RuntimeException extends \RuntimeException implements ExceptionInterface
|
class RuntimeException extends \RuntimeException implements ExceptionInterface
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,466 +11,126 @@
|
||||||
|
|
||||||
namespace FFMpeg;
|
namespace FFMpeg;
|
||||||
|
|
||||||
|
use Alchemy\BinaryDriver\ConfigurationInterface;
|
||||||
|
use FFMpeg\Driver\FFMpegDriver;
|
||||||
use FFMpeg\Exception\InvalidArgumentException;
|
use FFMpeg\Exception\InvalidArgumentException;
|
||||||
use FFMpeg\Exception\LogicException;
|
use FFMpeg\Media\Audio;
|
||||||
use FFMpeg\Exception\RuntimeException;
|
use FFMpeg\Media\Video;
|
||||||
use FFMpeg\Format\AudioInterface;
|
use Alchemy\BinaryDriver\Configuration;
|
||||||
use FFMpeg\Format\VideoInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use FFMpeg\Format\Video\Resamplable as VideoResamplable;
|
|
||||||
use FFMpeg\Format\Video\Resizable as VideoResizable;
|
|
||||||
use FFMpeg\Format\Video\Transcodable as VideoTranscodable;
|
|
||||||
use FFMpeg\Format\Audio\Resamplable as AudioResamplable;
|
|
||||||
use FFMpeg\Format\Audio\Transcodable as AudioTranscodable;
|
|
||||||
use FFMpeg\Helper\HelperInterface;
|
|
||||||
use Symfony\Component\Process\Process;
|
|
||||||
use Symfony\Component\Process\ProcessBuilder;
|
|
||||||
|
|
||||||
/**
|
class FFMpeg
|
||||||
* FFMpeg driver
|
|
||||||
*
|
|
||||||
* @author Romain Neutron imprec@gmail.com
|
|
||||||
*/
|
|
||||||
class FFMpeg extends Binary
|
|
||||||
{
|
{
|
||||||
protected $pathfile;
|
/** @var FFMpegDriver */
|
||||||
|
private $driver;
|
||||||
|
/** @var FFProbe */
|
||||||
|
private $ffprobe;
|
||||||
|
|
||||||
|
public function __construct(FFMpegDriver $ffmpeg, FFProbe $ffprobe)
|
||||||
|
{
|
||||||
|
$this->driver = $ffmpeg;
|
||||||
|
$this->ffprobe = $ffprobe;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Sets ffprobe
|
||||||
*
|
*
|
||||||
* @var FFProbe
|
* @param FFProbe
|
||||||
|
*
|
||||||
|
* @return FFMpeg
|
||||||
*/
|
*/
|
||||||
protected $prober;
|
public function setFFProbe(FFProbe $ffprobe)
|
||||||
protected $threads = 1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var HelperInterface[]
|
|
||||||
*/
|
|
||||||
protected $helpers = array();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Destructor
|
|
||||||
*/
|
|
||||||
public function __destruct()
|
|
||||||
{
|
{
|
||||||
$this->prober = null;
|
$this->ffprobe = $ffprobe;
|
||||||
parent::__destruct();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param HelperInterface $helper
|
|
||||||
* @return \FFMpeg\FFMpeg
|
|
||||||
*/
|
|
||||||
public function attachHelper(HelperInterface $helper)
|
|
||||||
{
|
|
||||||
$this->helpers[] = $helper;
|
|
||||||
$helper->setProber($this->prober);
|
|
||||||
|
|
||||||
// ensure the helpers have the path to the file in case
|
|
||||||
// they need to probe for format information
|
|
||||||
if ($this->pathfile !== null) {
|
|
||||||
$helper->open($this->pathfile);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setThreads($threads)
|
/**
|
||||||
|
* Gets FFProbe
|
||||||
|
*
|
||||||
|
* @return FFProbe
|
||||||
|
*/
|
||||||
|
public function getFFProbe()
|
||||||
{
|
{
|
||||||
if ($threads > 64 || $threads < 1) {
|
return $this->ffprobe;
|
||||||
throw new InvalidArgumentException('Invalid `threads` value ; threads must fit in range 1 - 64');
|
}
|
||||||
}
|
|
||||||
|
|
||||||
$this->threads = (int) $threads;
|
/**
|
||||||
|
* Sets ffmpeg driver
|
||||||
|
*
|
||||||
|
* @return FFMpeg
|
||||||
|
*/
|
||||||
|
public function setFFMpegDriver(FFMpegDriver $ffmpeg)
|
||||||
|
{
|
||||||
|
$this->driver = $ffmpeg;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getThreads()
|
/**
|
||||||
|
* Gets the ffmpeg driver
|
||||||
|
*
|
||||||
|
* @return FFMpegDriver
|
||||||
|
*/
|
||||||
|
public function getFFMpegDriver()
|
||||||
{
|
{
|
||||||
return $this->threads;
|
return $this->driver;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens a file in order to be processed
|
* Opens a file in order to be processed
|
||||||
*
|
*
|
||||||
* @param string $pathfile A pathfile
|
* @param string $pathfile A pathfile
|
||||||
* @return \FFMpeg\FFMpeg
|
*
|
||||||
|
* @return Audio|Video
|
||||||
|
*
|
||||||
* @throws InvalidArgumentException
|
* @throws InvalidArgumentException
|
||||||
*/
|
*/
|
||||||
public function open($pathfile)
|
public function open($pathfile)
|
||||||
{
|
{
|
||||||
if (!file_exists($pathfile)) {
|
if (!file_exists($pathfile)) {
|
||||||
$this->logger->addError(sprintf('FFmpeg failed to open %s', $pathfile));
|
throw new InvalidArgumentException(sprintf('File %s does not exists', $pathfile));
|
||||||
|
|
||||||
throw new InvalidArgumentException(sprintf('File %s does not exist', $pathfile));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->logger->addInfo(sprintf('FFmpeg opens %s', $pathfile));
|
$streams = $this->ffprobe->streams($pathfile);
|
||||||
$this->pathfile = $pathfile;
|
|
||||||
|
|
||||||
foreach ($this->helpers as $helper) {
|
if (0 < count($streams->videos())) {
|
||||||
$helper->open($pathfile);
|
return new Video($pathfile, $this->driver, $this->ffprobe);
|
||||||
|
} elseif (0 < count($streams->audios())) {
|
||||||
|
return new Audio($pathfile, $this->driver, $this->ffprobe);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this;
|
throw new InvalidArgumentException('Unable to detect file format, only audio and video supported');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set a prober
|
* Creates a new FFMpeg instance
|
||||||
*
|
*
|
||||||
* @return \FFMpeg\FFMpeg
|
* @param array|ConfigurationInterface $configuration
|
||||||
|
* @param LoggerInterface $logger
|
||||||
|
* @param FFProbe $probe
|
||||||
|
*
|
||||||
|
* @return FFMpeg
|
||||||
*/
|
*/
|
||||||
public function setProber(FFProbe $prober)
|
public static function create($configuration = array(), LoggerInterface $logger = null, FFProbe $probe = null)
|
||||||
{
|
{
|
||||||
$this->prober = $prober;
|
if (!$configuration instanceof ConfigurationInterface) {
|
||||||
|
$configuration = new Configuration($configuration);
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Close a file
|
|
||||||
*
|
|
||||||
* @return \FFMpeg\FFMpeg
|
|
||||||
*/
|
|
||||||
public function close()
|
|
||||||
{
|
|
||||||
$this->logger->addInfo(sprintf('FFmpeg closes %s', $this->pathfile));
|
|
||||||
|
|
||||||
$this->pathfile = null;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extract an image from a media file
|
|
||||||
*
|
|
||||||
* @param integer|string $time The time where to take the snapshot, time could either be in second or in hh:mm:ss[.xxx] form.
|
|
||||||
* @param string $output The pathfile where to write
|
|
||||||
* @param Boolean $accurate Whether to decode the whole video until position or seek and extract. See -ss option in FFMpeg manual (http://ffmpeg.org/ffmpeg.html#Main-options)
|
|
||||||
*
|
|
||||||
* @return \FFMpeg\FFMpeg
|
|
||||||
*
|
|
||||||
* @throws RuntimeException
|
|
||||||
* @throws LogicException
|
|
||||||
*/
|
|
||||||
public function extractImage($time, $output, $accurate = false)
|
|
||||||
{
|
|
||||||
if (!$this->pathfile) {
|
|
||||||
throw new LogicException('No file open');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
$binaries = $configuration->get('ffmpeg.binaries', array('avconv', 'ffmpeg'));
|
||||||
* @see http://ffmpeg.org/ffmpeg.html#Main-options
|
|
||||||
*/
|
if (!$configuration->has('timeout')) {
|
||||||
if (!$accurate) {
|
$configuration->set('timeout', 300);
|
||||||
$options = array(
|
|
||||||
$this->binary, '-ss', $time,
|
|
||||||
'-i', $this->pathfile,
|
|
||||||
'-vframes', '1',
|
|
||||||
'-f', 'image2', $output
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
$options = array(
|
|
||||||
$this->binary,
|
|
||||||
'-i', $this->pathfile,
|
|
||||||
'-vframes', '1', '-ss', $time,
|
|
||||||
'-f', 'image2', $output
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$builder = ProcessBuilder::create($options);
|
$driver = FFMpegDriver::load($binaries, $logger, $configuration);
|
||||||
$process = $builder->getProcess();
|
|
||||||
$process->setTimeout($this->timeout);
|
|
||||||
|
|
||||||
$this->logger->addInfo(sprintf('FFmpeg executes command %s', $process->getCommandline()));
|
|
||||||
|
|
||||||
try {
|
|
||||||
$process->run(array($this, 'transcodeCallback'));
|
|
||||||
} catch (\RuntimeException $e) {
|
|
||||||
|
|
||||||
|
if (null === $probe) {
|
||||||
|
$probe = FFProbe::create($configuration, $logger, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$process->isSuccessful()) {
|
return new static($driver, $probe);
|
||||||
$this->logger->addError(sprintf('FFmpeg command failed: %s', $process->getErrorOutput()));
|
|
||||||
|
|
||||||
$this->cleanupTemporaryFile($output);
|
|
||||||
|
|
||||||
throw new RuntimeException('Failed to extract image');
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->logger->addInfo(sprintf('FFmpeg command successful'));
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encode the file to the specified format
|
|
||||||
*
|
|
||||||
* @param AudioInterface $format The output format
|
|
||||||
* @param string $outputPathfile The pathfile where to write
|
|
||||||
* @return \FFMpeg\FFMpeg
|
|
||||||
* @throws RuntimeException
|
|
||||||
* @throws LogicException
|
|
||||||
*/
|
|
||||||
public function encode(AudioInterface $format, $outputPathfile)
|
|
||||||
{
|
|
||||||
if (!$this->pathfile) {
|
|
||||||
throw new LogicException('No file open');
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (true) {
|
|
||||||
case $format instanceof VideoInterface:
|
|
||||||
$this->encodeVideo($format, $outputPathfile);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
case $format instanceof AudioInterface:
|
|
||||||
$this->encodeAudio($format, $outputPathfile);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encode to audio
|
|
||||||
*
|
|
||||||
* @param Audio $format The output format
|
|
||||||
* @param string $outputPathfile The pathfile where to write
|
|
||||||
* @return \FFMpeg\FFMpeg
|
|
||||||
* @throws RuntimeException
|
|
||||||
*/
|
|
||||||
protected function encodeAudio(AudioInterface $format, $outputPathfile)
|
|
||||||
{
|
|
||||||
$builder = ProcessBuilder::create(array(
|
|
||||||
$this->binary,
|
|
||||||
'-y', '-i',
|
|
||||||
$this->pathfile,
|
|
||||||
'-threads', $this->threads,
|
|
||||||
'-ab', $format->getKiloBitrate() . 'k ',
|
|
||||||
));
|
|
||||||
|
|
||||||
foreach ($format->getExtraParams() as $parameter) {
|
|
||||||
$builder->add($parameter);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($format instanceof AudioTranscodable) {
|
|
||||||
$builder->add('-acodec')->add($format->getAudioCodec());
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($format instanceof AudioResamplable) {
|
|
||||||
$builder->add('-ac')->add(2)->add('-ar')->add($format->getAudioSampleRate());
|
|
||||||
}
|
|
||||||
|
|
||||||
$builder->add($outputPathfile);
|
|
||||||
|
|
||||||
$process = $builder->getProcess();
|
|
||||||
|
|
||||||
$process->setTimeout($this->timeout);
|
|
||||||
|
|
||||||
$this->logger->addInfo(sprintf('FFmpeg executes command %s', $process->getCommandLine()));
|
|
||||||
|
|
||||||
try {
|
|
||||||
$process->run(array($this, 'transcodeCallback'));
|
|
||||||
} catch (\RuntimeException $e) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$process->isSuccessful()) {
|
|
||||||
$this->logger->addInfo(sprintf('FFmpeg command failed'));
|
|
||||||
throw new RuntimeException(sprintf('Encoding failed: %s', $process->getErrorOutput()));
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->logger->addInfo(sprintf('FFmpeg command successful'));
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encode to video
|
|
||||||
*
|
|
||||||
* @param VideoInterface $format The output format
|
|
||||||
* @param string $outputPathfile The pathfile where to write
|
|
||||||
* @return \FFMpeg\FFMpeg
|
|
||||||
* @throws RuntimeException
|
|
||||||
*/
|
|
||||||
protected function encodeVideo(VideoInterface $format, $outputPathfile)
|
|
||||||
{
|
|
||||||
$builder = ProcessBuilder::create(array(
|
|
||||||
$this->binary, '-y', '-i',
|
|
||||||
$this->pathfile
|
|
||||||
));
|
|
||||||
|
|
||||||
foreach ($format->getExtraParams() as $parameter) {
|
|
||||||
$builder->add($parameter);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($format instanceof VideoResizable) {
|
|
||||||
if (!$this->prober) {
|
|
||||||
throw new LogicException('You must set a valid prober if you use a resizable format');
|
|
||||||
}
|
|
||||||
|
|
||||||
$result = json_decode($this->prober->probeStreams($this->pathfile), true);
|
|
||||||
|
|
||||||
$originalWidth = $originalHeight = null;
|
|
||||||
|
|
||||||
foreach ($result as $stream) {
|
|
||||||
foreach ($stream as $name => $value) {
|
|
||||||
if ($name == 'width') {
|
|
||||||
$originalWidth = $value;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if ($name == 'height') {
|
|
||||||
$originalHeight = $value;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($originalHeight !== null && $originalWidth !== null) {
|
|
||||||
$this->logger->addInfo(sprintf('Read dimension for resizing succesful : %s x %s', $originalWidth, $originalHeight));
|
|
||||||
} else {
|
|
||||||
$this->logger->addInfo(sprintf('Read dimension for resizing failed !'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($originalHeight !== null && $originalWidth !== null) {
|
|
||||||
$dimensions = $format->getComputedDimensions($originalWidth, $originalHeight);
|
|
||||||
$width = $this->getMultiple($dimensions->getWidth(), $format->getModulus());
|
|
||||||
$height = $this->getMultiple($dimensions->getHeight(), $format->getModulus());
|
|
||||||
|
|
||||||
$builder->add('-s')->add($width . 'x' . $height);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if ($format instanceof VideoResamplable) {
|
|
||||||
$builder->add('-r')->add($format->getFrameRate());
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see http://sites.google.com/site/linuxencoding/x264-ffmpeg-mapping
|
|
||||||
*/
|
|
||||||
if ($format->supportBFrames()) {
|
|
||||||
$builder->add('-b_strategy')
|
|
||||||
->add('1')
|
|
||||||
->add('-bf')
|
|
||||||
->add('3')
|
|
||||||
->add('-g')
|
|
||||||
->add($format->getGOPSize());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($format instanceof VideoTranscodable) {
|
|
||||||
$builder->add('-vcodec')->add($format->getVideoCodec());
|
|
||||||
}
|
|
||||||
|
|
||||||
$builder->add('-b:v')->add($format->getKiloBitrate() . 'k')
|
|
||||||
->add('-threads')->add($this->threads)
|
|
||||||
->add('-refs')->add('6')
|
|
||||||
->add('-coder')->add('1')
|
|
||||||
->add('-sc_threshold')->add('40')
|
|
||||||
->add('-flags')->add('+loop')
|
|
||||||
->add('-me_range')->add('16')
|
|
||||||
->add('-subq')->add('7')
|
|
||||||
->add('-i_qfactor')->add('0.71')
|
|
||||||
->add('-qcomp')->add('0.6')
|
|
||||||
->add('-qdiff')->add('4')
|
|
||||||
->add('-trellis')->add('1')
|
|
||||||
->add('-b:a')->add('92k');
|
|
||||||
|
|
||||||
if ($format instanceof AudioTranscodable) {
|
|
||||||
$builder->add('-acodec')->add($format->getAudioCodec());
|
|
||||||
}
|
|
||||||
|
|
||||||
$passPrefix = uniqid('pass-');
|
|
||||||
|
|
||||||
$pass1 = $builder;
|
|
||||||
$pass2 = clone $builder;
|
|
||||||
|
|
||||||
$passes[] = $pass1
|
|
||||||
->add('-pass')->add('1')
|
|
||||||
->add('-passlogfile')->add($passPrefix)
|
|
||||||
->add('-an')->add($outputPathfile)
|
|
||||||
->getProcess();
|
|
||||||
$passes[] = $pass2
|
|
||||||
->add('-pass')->add('2')
|
|
||||||
->add('-passlogfile')->add($passPrefix)
|
|
||||||
->add('-ac')->add('2')
|
|
||||||
->add('-ar')->add('44100')->add($outputPathfile)
|
|
||||||
->getProcess();
|
|
||||||
|
|
||||||
foreach ($passes as $process) {
|
|
||||||
|
|
||||||
$process->setTimeout($this->timeout);
|
|
||||||
|
|
||||||
$this->logger->addInfo(sprintf('FFmpeg executes command %s', $process->getCommandline()));
|
|
||||||
|
|
||||||
try {
|
|
||||||
$process->run(array($this, 'transcodeCallback'));
|
|
||||||
} catch (\RuntimeException $e) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->cleanupTemporaryFile(getcwd() . '/' . $passPrefix . '-0.log');
|
|
||||||
$this->cleanupTemporaryFile(getcwd() . '/' . $passPrefix . '-0.log');
|
|
||||||
$this->cleanupTemporaryFile(getcwd() . '/' . $passPrefix . '-0.log.mbtree');
|
|
||||||
|
|
||||||
if (!$process->isSuccessful()) {
|
|
||||||
$this->logger->addInfo(sprintf('FFmpeg command failed'));
|
|
||||||
throw new RuntimeException(sprintf('Encoding failed : %s', $process->getErrorOutput()));
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->logger->addInfo(sprintf('FFmpeg command successful'));
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The main transcoding callback, delegates the content to the helpers.
|
|
||||||
*
|
|
||||||
* @param string $channel (stdio|stderr)
|
|
||||||
* @param string $content the current line of the ffmpeg output
|
|
||||||
*/
|
|
||||||
public function transcodeCallback($channel, $content)
|
|
||||||
{
|
|
||||||
foreach ($this->helpers as $helper) {
|
|
||||||
$helper->transcodeCallback($channel, $content);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes unnecessary file
|
|
||||||
*
|
|
||||||
* @param string $pathfile
|
|
||||||
*/
|
|
||||||
protected function cleanupTemporaryFile($pathfile)
|
|
||||||
{
|
|
||||||
if (file_exists($pathfile) && is_writable($pathfile)) {
|
|
||||||
unlink($pathfile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the nearest multiple for a value
|
|
||||||
*
|
|
||||||
* @param integer $value
|
|
||||||
* @param integer $multiple
|
|
||||||
* @return integer
|
|
||||||
*/
|
|
||||||
protected function getMultiple($value, $multiple)
|
|
||||||
{
|
|
||||||
while (0 !== $value % $multiple) {
|
|
||||||
$value++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
protected static function getBinaryName()
|
|
||||||
{
|
|
||||||
return array('avconv', 'ffmpeg');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,49 +11,52 @@
|
||||||
|
|
||||||
namespace FFMpeg;
|
namespace FFMpeg;
|
||||||
|
|
||||||
|
use Doctrine\Common\Cache\ArrayCache;
|
||||||
use FFMpeg\FFMpeg;
|
use FFMpeg\FFMpeg;
|
||||||
use FFMpeg\FFProbe;
|
use FFMpeg\FFProbe;
|
||||||
use Monolog\Logger;
|
|
||||||
use Monolog\Handler\NullHandler;
|
|
||||||
use Silex\Application;
|
use Silex\Application;
|
||||||
use Silex\ServiceProviderInterface;
|
use Silex\ServiceProviderInterface;
|
||||||
|
|
||||||
class FFMpegServiceProvider implements ServiceProviderInterface
|
class FFMpegServiceProvider implements ServiceProviderInterface
|
||||||
{
|
{
|
||||||
|
|
||||||
public function register(Application $app)
|
public function register(Application $app)
|
||||||
{
|
{
|
||||||
if (isset($app['monolog'])) {
|
$app['ffmpeg.configuration'] = array();
|
||||||
$app['ffmpeg.logger'] = function() use ($app) {
|
$app['ffmpeg.default.configuration'] = array(
|
||||||
return $app['monolog'];
|
'ffmpeg.threads' => 4,
|
||||||
};
|
'ffmpeg.timeout' => 300,
|
||||||
} else {
|
'ffmpeg.binaries' => array('avconv', 'ffmpeg'),
|
||||||
$app['ffmpeg.logger'] = $app->share(function(Application $app) {
|
'ffprobe.timeout' => 30,
|
||||||
$logger = new Logger('FFMpeg logger');
|
'ffprobe.binaries' => array('avprobe', 'ffprobe'),
|
||||||
$logger->pushHandler(new NullHandler());
|
);
|
||||||
|
$app['ffmpeg.logger'] = null;
|
||||||
|
|
||||||
return $logger;
|
$app['ffmpeg.configuration.build'] = $app->share(function (Application $app) {
|
||||||
});
|
return array_replace($app['ffmpeg.default.configuration'], $app['ffmpeg.configuration']);
|
||||||
}
|
});
|
||||||
|
|
||||||
$app['ffmpeg.ffmpeg'] = $app->share(function(Application $app) {
|
$app['ffmpeg'] = $app['ffmpeg.ffmpeg'] = $app->share(function(Application $app) {
|
||||||
if (isset($app['ffmpeg.ffmpeg.binary'])) {
|
$configuration = $app['ffmpeg.configuration.build'];
|
||||||
$ffmpeg = new FFMpeg($app['ffmpeg.ffmpeg.binary'], $app['ffmpeg.logger']);
|
|
||||||
} else {
|
if (isset($configuration['ffmpeg.timeout'])) {
|
||||||
$ffmpeg = FFMpeg::load($app['ffmpeg.logger']);
|
$configuration['timeout'] = $configuration['ffmpeg.timeout'];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $ffmpeg
|
return FFMpeg::create($configuration, $app['ffmpeg.logger'], $app['ffmpeg.ffprobe']);
|
||||||
->setProber($app['ffmpeg.ffprobe'])
|
});
|
||||||
->setThreads(isset($app['ffmpeg.threads']) ? $app['ffmpeg.threads'] : 1);
|
|
||||||
|
$app['ffprobe.cache'] = $app->share(function () {
|
||||||
|
return new ArrayCache();
|
||||||
});
|
});
|
||||||
|
|
||||||
$app['ffmpeg.ffprobe'] = $app->share(function(Application $app) {
|
$app['ffmpeg.ffprobe'] = $app->share(function(Application $app) {
|
||||||
if (isset($app['ffmpeg.ffprobe.binary'])) {
|
$configuration = $app['ffmpeg.configuration.build'];
|
||||||
return new FFProbe($app['ffmpeg.ffprobe.binary'], $app['ffmpeg.logger']);
|
|
||||||
} else {
|
if (isset($configuration['ffmpeg.timeout'])) {
|
||||||
return FFProbe::load($app['ffmpeg.logger']);
|
$configuration['timeout'] = $configuration['ffprobe.timeout'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return FFProbe::create($configuration, $app['ffmpeg.logger'], $app['ffprobe.cache']);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,163 +11,258 @@
|
||||||
|
|
||||||
namespace FFMpeg;
|
namespace FFMpeg;
|
||||||
|
|
||||||
|
use Alchemy\BinaryDriver\ConfigurationInterface;
|
||||||
|
use Doctrine\Common\Cache\ArrayCache;
|
||||||
|
use Doctrine\Common\Cache\Cache;
|
||||||
|
use FFMpeg\Driver\FFProbeDriver;
|
||||||
|
use FFMpeg\FFProbe\DataMapping\Format;
|
||||||
|
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 FFMpeg\Exception\InvalidArgumentException;
|
use FFMpeg\Exception\InvalidArgumentException;
|
||||||
use FFMpeg\Exception\RuntimeException;
|
use FFMpeg\Exception\RuntimeException;
|
||||||
use Symfony\Component\Process\Process;
|
use Psr\Log\LoggerInterface;
|
||||||
use Symfony\Component\Process\ProcessBuilder;
|
|
||||||
|
|
||||||
/**
|
class FFProbe
|
||||||
* FFProbe driver
|
|
||||||
*
|
|
||||||
* @author Romain Neutron imprec@gmail.com
|
|
||||||
*/
|
|
||||||
class FFProbe extends Binary
|
|
||||||
{
|
{
|
||||||
|
const TYPE_STREAMS = 'streams';
|
||||||
|
const TYPE_FORMAT = 'format';
|
||||||
|
|
||||||
protected $cachedFormats = array();
|
/** @var Cache */
|
||||||
|
private $cache;
|
||||||
|
/** @var OptionsTesterInterface */
|
||||||
|
private $optionsTester;
|
||||||
|
/** @var OutputParserInterface */
|
||||||
|
private $parser;
|
||||||
|
/** @var FFProbeDriver */
|
||||||
|
private $ffprobe;
|
||||||
|
/** @var MapperInterface */
|
||||||
|
private $mapper;
|
||||||
|
|
||||||
|
public function __construct(FFProbeDriver $ffprobe, Cache $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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param OutputParserInterface $parser
|
||||||
|
*
|
||||||
|
* @return FFProbe
|
||||||
|
*/
|
||||||
|
public function setParser(OutputParserInterface $parser)
|
||||||
|
{
|
||||||
|
$this->parser = $parser;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return FFProbeDriver
|
||||||
|
*/
|
||||||
|
public function getFFProbeDriver()
|
||||||
|
{
|
||||||
|
return $this->ffprobe;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param FFProbeDriver $ffprobe
|
||||||
|
*
|
||||||
|
* @return FFProbe
|
||||||
|
*/
|
||||||
|
public function setFFProbeDriver(FFProbeDriver $ffprobe)
|
||||||
|
{
|
||||||
|
$this->ffprobe = $ffprobe;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param OptionsTesterInterface $tester
|
||||||
|
*
|
||||||
|
* @return FFProbe
|
||||||
|
*/
|
||||||
|
public function setOptionsTester(OptionsTesterInterface $tester)
|
||||||
|
{
|
||||||
|
$this->optionsTester = $tester;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return OptionsTesterInterface
|
||||||
|
*/
|
||||||
|
public function getOptionsTester()
|
||||||
|
{
|
||||||
|
return $this->optionsTester;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Cache $cache
|
||||||
|
*
|
||||||
|
* @return FFProbe
|
||||||
|
*/
|
||||||
|
public function setCache(Cache $cache)
|
||||||
|
{
|
||||||
|
$this->cache = $cache;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Cache
|
||||||
|
*/
|
||||||
|
public function getCache()
|
||||||
|
{
|
||||||
|
return $this->cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return MapperInterface
|
||||||
|
*/
|
||||||
|
public function getMapper()
|
||||||
|
{
|
||||||
|
return $this->mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param MapperInterface $mapper
|
||||||
|
*
|
||||||
|
* @return FFProbe
|
||||||
|
*/
|
||||||
|
public function setMapper(MapperInterface $mapper)
|
||||||
|
{
|
||||||
|
$this->mapper = $mapper;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api
|
||||||
|
*
|
||||||
* Probe the format of a given file
|
* Probe the format of a given file
|
||||||
*
|
*
|
||||||
* @param string $pathfile
|
* @param string $pathfile
|
||||||
* @return string A Json object containing the key/values of the probe output
|
*
|
||||||
|
* @return Format A Format object
|
||||||
*
|
*
|
||||||
* @throws InvalidArgumentException
|
* @throws InvalidArgumentException
|
||||||
* @throws RuntimeException
|
* @throws RuntimeException
|
||||||
*/
|
*/
|
||||||
public function probeFormat($pathfile)
|
public function format($pathfile)
|
||||||
{
|
{
|
||||||
if ( ! is_file($pathfile)) {
|
return $this->probe($pathfile, '-show_format', static::TYPE_FORMAT);
|
||||||
throw new InvalidArgumentException($pathfile);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($this->cachedFormats[$pathfile])) {
|
|
||||||
return $this->cachedFormats[$pathfile];
|
|
||||||
}
|
|
||||||
|
|
||||||
$builder = ProcessBuilder::create(array(
|
|
||||||
$this->binary, $pathfile, '-show_format'
|
|
||||||
));
|
|
||||||
|
|
||||||
$output = $this->executeProbe($builder->getProcess());
|
|
||||||
|
|
||||||
$ret = array();
|
|
||||||
|
|
||||||
foreach (explode(PHP_EOL, $output) as $line) {
|
|
||||||
|
|
||||||
if (in_array($line, array('[FORMAT]', '[/FORMAT]'))) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$chunks = explode('=', $line);
|
|
||||||
$key = array_shift($chunks);
|
|
||||||
|
|
||||||
if ('' === trim($key)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$value = trim(implode('=', $chunks));
|
|
||||||
|
|
||||||
if (ctype_digit($value)) {
|
|
||||||
$value = (int) $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
$ret[$key] = $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->cachedFormats[$pathfile] = json_encode($ret);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @api
|
||||||
|
*
|
||||||
* Probe the streams contained in a given file
|
* Probe the streams contained in a given file
|
||||||
*
|
*
|
||||||
* @param string $pathfile
|
* @param string $pathfile
|
||||||
* @return array An array of streams array
|
*
|
||||||
|
* @return StreamCollection A collection of streams
|
||||||
*
|
*
|
||||||
* @throws InvalidArgumentException
|
* @throws InvalidArgumentException
|
||||||
* @throws RuntimeException
|
* @throws RuntimeException
|
||||||
*/
|
*/
|
||||||
public function probeStreams($pathfile)
|
public function streams($pathfile)
|
||||||
{
|
{
|
||||||
if ( ! is_file($pathfile)) {
|
return $this->probe($pathfile, '-show_streams', static::TYPE_STREAMS);
|
||||||
throw new InvalidArgumentException($pathfile);
|
|
||||||
}
|
|
||||||
|
|
||||||
$builder = ProcessBuilder::create(array(
|
|
||||||
$this->binary, $pathfile, '-show_streams'
|
|
||||||
));
|
|
||||||
|
|
||||||
$output = explode(PHP_EOL, $this->executeProbe($builder->getProcess()));
|
|
||||||
|
|
||||||
$ret = array();
|
|
||||||
$n = 0;
|
|
||||||
|
|
||||||
foreach ($output as $line) {
|
|
||||||
|
|
||||||
if ($line == '[STREAM]') {
|
|
||||||
$n ++;
|
|
||||||
$ret[$n] = array();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if ($line == '[/STREAM]') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$chunks = explode('=', $line);
|
|
||||||
$key = array_shift($chunks);
|
|
||||||
|
|
||||||
if ('' === trim($key)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$value = trim(implode('=', $chunks));
|
|
||||||
|
|
||||||
if (ctype_digit($value)) {
|
|
||||||
$value = (int) $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
$ret[$n][$key] = $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return json_encode(array_values($ret));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @api
|
||||||
*
|
*
|
||||||
* @param Process $process
|
* @param array|ConfigurationInterface $configuration
|
||||||
* @return string
|
* @param LoggerInterface $logger
|
||||||
* @throws RuntimeException
|
* @param Cache $cache
|
||||||
|
*
|
||||||
|
* @return FFProbe
|
||||||
*/
|
*/
|
||||||
protected function executeProbe(Process $process)
|
public static function create($configuration = array(), LoggerInterface $logger = null, Cache $cache = null)
|
||||||
{
|
{
|
||||||
$this->logger->addInfo(sprintf('FFprobe executes command %s', $process->getCommandline()));
|
if (null === $cache) {
|
||||||
|
$cache = new ArrayCache();
|
||||||
try {
|
|
||||||
$process->run();
|
|
||||||
} catch (\RuntimeException $e) {
|
|
||||||
$this->logger->addInfo('FFprobe command failed');
|
|
||||||
|
|
||||||
throw new RuntimeException(sprintf('Failed to run the given command %s', $process->getCommandline()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( ! $process->isSuccessful()) {
|
return new static(FFProbeDriver::create($configuration, $logger), $cache);
|
||||||
$this->logger->addInfo('FFprobe command failed');
|
|
||||||
|
|
||||||
throw new RuntimeException(sprintf('Failed to probe %s', $process->getCommandline()));
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->logger->addInfo('FFprobe command successful');
|
|
||||||
|
|
||||||
return $process->getOutput();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private function probe($pathfile, $command, $type, $allowJson = true)
|
||||||
* {@inheritdoc}
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
protected static function getBinaryName()
|
|
||||||
{
|
{
|
||||||
return array('avprobe', 'ffprobe');
|
if (!is_file($pathfile)) {
|
||||||
|
throw new InvalidArgumentException(sprintf(
|
||||||
|
'Invalid filepath %s, unable to read.', $pathfile
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = sprintf('%s-%s', $command, $pathfile);
|
||||||
|
|
||||||
|
if ($this->cache->contains($id)) {
|
||||||
|
return $this->cache->fetch($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = array($pathfile, $command);
|
||||||
|
|
||||||
|
$parseIsToDo = false;
|
||||||
|
|
||||||
|
if ($allowJson && $this->optionsTester->has('-print_format')) {
|
||||||
|
$commands[] = '-print_format';
|
||||||
|
$commands[] = 'json';
|
||||||
|
} else {
|
||||||
|
$parseIsToDo = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$output = $this->ffprobe->command($commands);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
$this->cache->save($id, $ret);
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
80
src/FFMpeg/FFProbe/DataMapping/AbstractData.php
Normal file
80
src/FFMpeg/FFProbe/DataMapping/AbstractData.php
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
<?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\Exception\InvalidArgumentException;
|
||||||
|
|
||||||
|
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 Boolean
|
||||||
|
*/
|
||||||
|
public function has($property)
|
||||||
|
{
|
||||||
|
return isset($this->properties[$property]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the property value given its name
|
||||||
|
*
|
||||||
|
* @param string $property
|
||||||
|
* @return mixed
|
||||||
|
*
|
||||||
|
* @throws InvalidArgumentException In case the data does not have the property
|
||||||
|
*/
|
||||||
|
public function get($property)
|
||||||
|
{
|
||||||
|
if (!isset($this->properties[$property])) {
|
||||||
|
throw new InvalidArgumentException(sprintf('Invalid property `%s`.', $property));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->properties[$property];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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()
|
||||||
|
{
|
||||||
|
return count($this->properties);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,9 +9,8 @@
|
||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace FFMpeg\Exception;
|
namespace FFMpeg\FFProbe\DataMapping;
|
||||||
|
|
||||||
class LogicException extends \LogicException implements ExceptionInterface
|
class Format extends AbstractData
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
35
src/FFMpeg/FFProbe/DataMapping/Stream.php
Normal file
35
src/FFMpeg/FFProbe/DataMapping/Stream.php
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
<?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 Stream extends AbstractData
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Returns true if the stream is an audio stream
|
||||||
|
*
|
||||||
|
* @return Boolean
|
||||||
|
*/
|
||||||
|
public function isAudio()
|
||||||
|
{
|
||||||
|
return $this->has('codec_type') ? 'audio' === $this->get('codec_type') : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the stream is a video stream
|
||||||
|
*
|
||||||
|
* @return Boolean
|
||||||
|
*/
|
||||||
|
public function isVideo()
|
||||||
|
{
|
||||||
|
return $this->has('codec_type') ? 'video' === $this->get('codec_type') : false;
|
||||||
|
}
|
||||||
|
}
|
||||||
99
src/FFMpeg/FFProbe/DataMapping/StreamCollection.php
Normal file
99
src/FFMpeg/FFProbe/DataMapping/StreamCollection.php
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
<?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 StreamCollection implements \Countable, \IteratorAggregate
|
||||||
|
{
|
||||||
|
private $streams;
|
||||||
|
|
||||||
|
public function __construct(array $streams = array())
|
||||||
|
{
|
||||||
|
$this->streams = array_values($streams);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the first stream of the collection, null if the collection is
|
||||||
|
* empty.
|
||||||
|
*
|
||||||
|
* @return null|Stream
|
||||||
|
*/
|
||||||
|
public function first()
|
||||||
|
{
|
||||||
|
$stream = reset($this->streams);
|
||||||
|
|
||||||
|
return $stream ?: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a stream to the collection
|
||||||
|
*
|
||||||
|
* @param Stream $stream
|
||||||
|
*
|
||||||
|
* @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()
|
||||||
|
{
|
||||||
|
return count($this->streams);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the array of contained streams
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function all()
|
||||||
|
{
|
||||||
|
return $this->streams;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getIterator()
|
||||||
|
{
|
||||||
|
return new \ArrayIterator($this->streams);
|
||||||
|
}
|
||||||
|
}
|
||||||
54
src/FFMpeg/FFProbe/Mapper.php
Normal file
54
src/FFMpeg/FFProbe/Mapper.php
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
<?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\FFProbe;
|
||||||
|
use FFMpeg\FFProbe\DataMapping\Format;
|
||||||
|
use FFMpeg\FFProbe\DataMapping\StreamCollection;
|
||||||
|
use FFMpeg\FFProbe\DataMapping\Stream;
|
||||||
|
use FFMpeg\Exception\InvalidArgumentException;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
27
src/FFMpeg/FFProbe/MapperInterface.php
Normal file
27
src/FFMpeg/FFProbe/MapperInterface.php
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?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);
|
||||||
|
}
|
||||||
64
src/FFMpeg/FFProbe/OptionsTester.php
Normal file
64
src/FFMpeg/FFProbe/OptionsTester.php
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
<?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 Doctrine\Common\Cache\Cache;
|
||||||
|
use FFMpeg\Driver\FFProbeDriver;
|
||||||
|
|
||||||
|
class OptionsTester implements OptionsTesterInterface
|
||||||
|
{
|
||||||
|
/** @var FFProbeDriver */
|
||||||
|
private $ffprobe;
|
||||||
|
/** @var Cache */
|
||||||
|
private $cache;
|
||||||
|
|
||||||
|
public function __construct(FFProbeDriver $ffprobe, Cache $cache)
|
||||||
|
{
|
||||||
|
$this->ffprobe = $ffprobe;
|
||||||
|
$this->cache = $cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function has($name)
|
||||||
|
{
|
||||||
|
$id = sprintf('option-%s', $name);
|
||||||
|
|
||||||
|
if ($this->cache->contains($id)) {
|
||||||
|
return $this->cache->fetch($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
$output = $this->retrieveHelpOutput();
|
||||||
|
|
||||||
|
$ret = (Boolean) preg_match('/^'.$name.'/m', $output);
|
||||||
|
|
||||||
|
$this->cache->save($id, $ret);
|
||||||
|
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function retrieveHelpOutput()
|
||||||
|
{
|
||||||
|
$id = 'help';
|
||||||
|
|
||||||
|
if ($this->cache->contains($id)) {
|
||||||
|
return $this->cache->fetch($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
$output = $this->ffprobe->command(array('-help', '-loglevel', 'quiet'));
|
||||||
|
|
||||||
|
$this->cache->save($id, $output);
|
||||||
|
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,20 +9,16 @@
|
||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace FFMpeg;
|
namespace FFMpeg\FFProbe;
|
||||||
|
|
||||||
use Monolog\Logger;
|
interface OptionsTesterInterface
|
||||||
|
|
||||||
/**
|
|
||||||
* FFMpeg Adapter interface
|
|
||||||
*
|
|
||||||
* @author Romain Neutron imprec@gmail.com
|
|
||||||
*/
|
|
||||||
interface AdapterInterface
|
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads the adapter
|
* Tells if the given option is supported by ffprobe
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
*
|
||||||
|
* @return Boolean
|
||||||
*/
|
*/
|
||||||
public static function load(Logger $logger);
|
public function has($name);
|
||||||
}
|
}
|
||||||
125
src/FFMpeg/FFProbe/OutputParser.php
Normal file
125
src/FFMpeg/FFProbe/OutputParser.php
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
<?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\FFProbe;
|
||||||
|
use FFMpeg\Exception\InvalidArgumentException;
|
||||||
|
|
||||||
|
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 = array();
|
||||||
|
|
||||||
|
foreach (explode(PHP_EOL, $data) as $line) {
|
||||||
|
|
||||||
|
if (in_array($line, array('[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'] = array();
|
||||||
|
}
|
||||||
|
$ret['tags'][substr($key, 4)] = $value;
|
||||||
|
} else {
|
||||||
|
$ret[$key] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return array('format' => $ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function parseStreams($data)
|
||||||
|
{
|
||||||
|
$ret = array();
|
||||||
|
$n = -1;
|
||||||
|
|
||||||
|
foreach (explode(PHP_EOL, $data) as $line) {
|
||||||
|
|
||||||
|
if ($line == '[STREAM]') {
|
||||||
|
$n ++;
|
||||||
|
$ret[$n] = array();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($line == '[/STREAM]') {
|
||||||
|
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, array('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'] = array();
|
||||||
|
}
|
||||||
|
$ret[$n]['tags'][substr($key, 4)] = $value;
|
||||||
|
} elseif (0 === strpos($key, 'DISPOSITION:')) {
|
||||||
|
if (!isset($ret[$n]['disposition'])) {
|
||||||
|
$ret[$n]['disposition'] = array();
|
||||||
|
}
|
||||||
|
$ret[$n]['disposition'][substr($key, 12)] = $value;
|
||||||
|
} else {
|
||||||
|
$ret[$n][$key] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return array('streams' => $ret);
|
||||||
|
}
|
||||||
|
}
|
||||||
27
src/FFMpeg/FFProbe/OutputParserInterface.php
Normal file
27
src/FFMpeg/FFProbe/OutputParserInterface.php
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?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);
|
||||||
|
}
|
||||||
29
src/FFMpeg/Filters/Audio/AudioFilterInterface.php
Normal file
29
src/FFMpeg/Filters/Audio/AudioFilterInterface.php
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?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.
|
||||||
|
*
|
||||||
|
* @param Audio $audio
|
||||||
|
* @param AudioInterface $format
|
||||||
|
*
|
||||||
|
* @return array An array of arguments
|
||||||
|
*/
|
||||||
|
public function apply(Audio $audio, AudioInterface $format);
|
||||||
|
}
|
||||||
30
src/FFMpeg/Filters/Audio/AudioFilters.php
Normal file
30
src/FFMpeg/Filters/Audio/AudioFilters.php
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace FFMpeg\Filters\Audio;
|
||||||
|
|
||||||
|
use FFMpeg\Media\Audio;
|
||||||
|
use FFMpeg\Filters\Audio\AudioResamplableFilter;
|
||||||
|
|
||||||
|
class AudioFilters
|
||||||
|
{
|
||||||
|
private $audio;
|
||||||
|
|
||||||
|
public function __construct(Audio $audio)
|
||||||
|
{
|
||||||
|
$this->audio = $audio;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resamples the audio file.
|
||||||
|
*
|
||||||
|
* @param Integer $rate
|
||||||
|
*
|
||||||
|
* @return AudioFilters
|
||||||
|
*/
|
||||||
|
public function resample($rate)
|
||||||
|
{
|
||||||
|
$this->audio->addFilter(new AudioResamplableFilter($rate));
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
||||||
43
src/FFMpeg/Filters/Audio/AudioResamplableFilter.php
Normal file
43
src/FFMpeg/Filters/Audio/AudioResamplableFilter.php
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
<?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;
|
||||||
|
|
||||||
|
public function __construct($rate)
|
||||||
|
{
|
||||||
|
$this->rate = $rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return Integer
|
||||||
|
*/
|
||||||
|
public function getRate()
|
||||||
|
{
|
||||||
|
return $this->rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function apply(Audio $audio, AudioInterface $format)
|
||||||
|
{
|
||||||
|
return array('-ac', 2, '-ar', $this->rate);
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/FFMpeg/Filters/FilterInterface.php
Normal file
16
src/FFMpeg/Filters/FilterInterface.php
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?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
|
||||||
|
{
|
||||||
|
}
|
||||||
45
src/FFMpeg/Filters/FiltersCollection.php
Normal file
45
src/FFMpeg/Filters/FiltersCollection.php
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
<?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;
|
||||||
|
|
||||||
|
class FiltersCollection implements \Countable, \IteratorAggregate
|
||||||
|
{
|
||||||
|
private $filters = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param FilterInterface $filter
|
||||||
|
*
|
||||||
|
* @return FiltersCollection
|
||||||
|
*/
|
||||||
|
public function add(FilterInterface $filter)
|
||||||
|
{
|
||||||
|
$this->filters[] = $filter;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function count()
|
||||||
|
{
|
||||||
|
return count($this->filters);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getIterator()
|
||||||
|
{
|
||||||
|
return new \ArrayIterator($this->filters);
|
||||||
|
}
|
||||||
|
}
|
||||||
21
src/FFMpeg/Filters/Frame/FrameFilterInterface.php
Normal file
21
src/FFMpeg/Filters/Frame/FrameFilterInterface.php
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
<?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;
|
||||||
|
use FFMpeg\Format\FrameInterface;
|
||||||
|
|
||||||
|
interface FrameFilterInterface extends FilterInterface
|
||||||
|
{
|
||||||
|
public function apply(Frame $frame, FrameInterface $format);
|
||||||
|
}
|
||||||
24
src/FFMpeg/Filters/Frame/FrameFilters.php
Normal file
24
src/FFMpeg/Filters/Frame/FrameFilters.php
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?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;
|
||||||
|
}
|
||||||
|
}
|
||||||
126
src/FFMpeg/Filters/Video/ResizeFilter.php
Normal file
126
src/FFMpeg/Filters/Video/ResizeFilter.php
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
<?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\Media\Video;
|
||||||
|
use FFMpeg\Format\VideoInterface;
|
||||||
|
|
||||||
|
class ResizeFilter implements VideoFilterInterface
|
||||||
|
{
|
||||||
|
const RESIZEMODE_FIT = 'fit';
|
||||||
|
const RESIZEMODE_INSET = 'inset';
|
||||||
|
const RESIZEMODE_SCALE_WIDTH = 'width';
|
||||||
|
const RESIZEMODE_SCALE_HEIGHT = 'height';
|
||||||
|
|
||||||
|
/** @var Dimension */
|
||||||
|
private $dimension;
|
||||||
|
/** @var string */
|
||||||
|
private $mode;
|
||||||
|
/** @var Boolean */
|
||||||
|
private $forceStandards;
|
||||||
|
|
||||||
|
public function __construct(Dimension $dimension, $mode = self::RESIZEMODE_FIT, $forceStandards = true)
|
||||||
|
{
|
||||||
|
$this->dimension = $dimension;
|
||||||
|
$this->mode = $mode;
|
||||||
|
$this->forceStandards = $forceStandards;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Dimension
|
||||||
|
*/
|
||||||
|
public function getDimension()
|
||||||
|
{
|
||||||
|
return $this->dimension;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getMode()
|
||||||
|
{
|
||||||
|
return $this->mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Boolean
|
||||||
|
*/
|
||||||
|
public function areStandardsForced()
|
||||||
|
{
|
||||||
|
return $this->forceStandards;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function apply(Video $video, VideoInterface $format)
|
||||||
|
{
|
||||||
|
$originalWidth = $originalHeight = null;
|
||||||
|
|
||||||
|
foreach ($video->getStreams() as $stream) {
|
||||||
|
if ($stream->isVideo()) {
|
||||||
|
if ($stream->has('width')) {
|
||||||
|
$originalWidth = $stream->get('width');
|
||||||
|
}
|
||||||
|
if ($stream->has('height')) {
|
||||||
|
$originalHeight = $stream->get('height');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$commands = array();
|
||||||
|
|
||||||
|
if ($originalHeight !== null && $originalWidth !== null) {
|
||||||
|
$dimensions = $this->getComputedDimensions(new Dimension($originalWidth, $originalHeight), $format->getModulus());
|
||||||
|
|
||||||
|
$commands[] = '-s';
|
||||||
|
$commands[] = $dimensions->getWidth() . 'x' . $dimensions->getHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
49
src/FFMpeg/Filters/Video/SynchronizeFilter.php
Normal file
49
src/FFMpeg/Filters/Video/SynchronizeFilter.php
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
<?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 SynchronizeFilter implements VideoFilterInterface
|
||||||
|
{
|
||||||
|
public function apply(Video $video, VideoInterface $format)
|
||||||
|
{
|
||||||
|
$streams = $video->getStreams();
|
||||||
|
|
||||||
|
if (null === $videoStream = $streams->videos()->first()) {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
if (!$videoStream->has('start_time')) {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
$params = array(
|
||||||
|
'-itsoffset',
|
||||||
|
$videoStream->get('start_time'),
|
||||||
|
'-i',
|
||||||
|
$video->getPathfile(),
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($streams as $stream) {
|
||||||
|
if ($videoStream === $stream) {
|
||||||
|
$params[] = '-map';
|
||||||
|
$params[] = '1:' . $stream->get('index');
|
||||||
|
} else {
|
||||||
|
$params[] = '-map';
|
||||||
|
$params[] = '0:' . $stream->get('index');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $params;
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/FFMpeg/Filters/Video/VideoFilterInterface.php
Normal file
29
src/FFMpeg/Filters/Video/VideoFilterInterface.php
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?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.
|
||||||
|
*
|
||||||
|
* @param Video $video
|
||||||
|
* @param VideoInterface $format
|
||||||
|
*
|
||||||
|
* @return array An array of arguments
|
||||||
|
*/
|
||||||
|
public function apply(Video $video, VideoInterface $format);
|
||||||
|
}
|
||||||
69
src/FFMpeg/Filters/Video/VideoFilters.php
Normal file
69
src/FFMpeg/Filters/Video/VideoFilters.php
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
<?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\Media\Video;
|
||||||
|
use FFMpeg\Coordinate\Dimension;
|
||||||
|
use FFMpeg\Coordinate\FrameRate;
|
||||||
|
|
||||||
|
class VideoFilters
|
||||||
|
{
|
||||||
|
private $video;
|
||||||
|
|
||||||
|
public function __construct(Video $video)
|
||||||
|
{
|
||||||
|
$this->video = $video;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resizes a video to a given dimension
|
||||||
|
*
|
||||||
|
* @param Dimension $dimension
|
||||||
|
* @param string $mode
|
||||||
|
* @param Boolean $forceStandards
|
||||||
|
*
|
||||||
|
* @return VideoFilters
|
||||||
|
*/
|
||||||
|
public function resize(Dimension $dimension, $mode = ResizeFilter::RESIZEMODE_FIT, $forceStandards = true)
|
||||||
|
{
|
||||||
|
$this->video->addFilter(new ResizeFilter($dimension, $mode, $forceStandards));
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resamples the video to the given framerate.
|
||||||
|
*
|
||||||
|
* @param FrameRate $framerate
|
||||||
|
* @param type $gop
|
||||||
|
*
|
||||||
|
* @return VideoFilters
|
||||||
|
*/
|
||||||
|
public function resample(FrameRate $framerate, $gop)
|
||||||
|
{
|
||||||
|
$this->video->addFilter(new VideoResampleFilter($framerate, $gop));
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronizes audio and video.
|
||||||
|
*
|
||||||
|
* @return VideoFilters
|
||||||
|
*/
|
||||||
|
public function synchronize()
|
||||||
|
{
|
||||||
|
$this->video->addFilter(new SynchronizeFilter());
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
||||||
72
src/FFMpeg/Filters/Video/VideoResampleFilter.php
Normal file
72
src/FFMpeg/Filters/Video/VideoResampleFilter.php
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
<?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\Media\Video;
|
||||||
|
use FFMpeg\Format\VideoInterface;
|
||||||
|
|
||||||
|
class VideoResampleFilter implements VideoFilterInterface
|
||||||
|
{
|
||||||
|
private $rate;
|
||||||
|
private $gop;
|
||||||
|
|
||||||
|
public function __construct(FrameRate $rate, $gop)
|
||||||
|
{
|
||||||
|
$this->rate = $rate;
|
||||||
|
$this->gop = $gop;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 Integer
|
||||||
|
*/
|
||||||
|
public function getGOP()
|
||||||
|
{
|
||||||
|
return $this->gop;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function apply(Video $video, VideoInterface $format)
|
||||||
|
{
|
||||||
|
$commands = array('-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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -11,23 +11,24 @@
|
||||||
|
|
||||||
namespace FFMpeg\Format\Audio;
|
namespace FFMpeg\Format\Audio;
|
||||||
|
|
||||||
|
use Evenement\EventEmitter;
|
||||||
use FFMpeg\Exception\InvalidArgumentException;
|
use FFMpeg\Exception\InvalidArgumentException;
|
||||||
|
use FFMpeg\Format\AudioInterface;
|
||||||
|
use FFMpeg\Media\MediaTypeInterface;
|
||||||
|
use FFMpeg\Format\ProgressableInterface;
|
||||||
|
use FFMpeg\Format\ProgressListener\AudioProgressListener;
|
||||||
|
use FFMpeg\FFProbe;
|
||||||
|
|
||||||
/**
|
abstract class DefaultAudio extends EventEmitter implements AudioInterface, ProgressableInterface
|
||||||
* The abstract default Audio format
|
|
||||||
*
|
|
||||||
* @author Romain Neutron imprec@gmail.com
|
|
||||||
*/
|
|
||||||
abstract class DefaultAudio implements Resamplable, Interactive
|
|
||||||
{
|
{
|
||||||
|
/** @var string */
|
||||||
protected $audioCodec;
|
protected $audioCodec;
|
||||||
protected $audioSampleRate = 44100;
|
|
||||||
protected $kiloBitrate = 128;
|
/** @var integer */
|
||||||
|
protected $audioKiloBitrate = 128;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns extra parameters for the encoding
|
* {@inheritdoc}
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
*/
|
||||||
public function getExtraParams()
|
public function getExtraParams()
|
||||||
{
|
{
|
||||||
|
|
@ -43,11 +44,12 @@ abstract class DefaultAudio implements Resamplable, Interactive
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the audio codec, Should be in the available ones, otherwise an
|
* Sets the audio codec, Should be in the available ones, otherwise an
|
||||||
* exception is thrown
|
* exception is thrown
|
||||||
*
|
*
|
||||||
* @param string $audioCodec
|
* @param string $audioCodec
|
||||||
* @throws \InvalidArgumentException
|
*
|
||||||
|
* @throws InvalidArgumentException
|
||||||
*/
|
*/
|
||||||
public function setAudioCodec($audioCodec)
|
public function setAudioCodec($audioCodec)
|
||||||
{
|
{
|
||||||
|
|
@ -66,24 +68,24 @@ abstract class DefaultAudio implements Resamplable, Interactive
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function getAudioSampleRate()
|
public function getAudioKiloBitrate()
|
||||||
{
|
{
|
||||||
return $this->audioSampleRate;
|
return $this->audioKiloBitrate;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the audio sample rate
|
* Sets the kiloBitrate value
|
||||||
*
|
*
|
||||||
* @param integer $audioSampleRate
|
* @param integer $kiloBitrate
|
||||||
* @throws \InvalidArgumentException
|
* @throws InvalidArgumentException
|
||||||
*/
|
*/
|
||||||
public function setAudioSampleRate($audioSampleRate)
|
public function setAudioKiloBitrate($kiloBitrate)
|
||||||
{
|
{
|
||||||
if ($audioSampleRate < 1) {
|
if ($kiloBitrate < 1) {
|
||||||
throw new InvalidArgumentException('Wrong audio sample rate value');
|
throw new InvalidArgumentException('Wrong kiloBitrate value');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->audioSampleRate = (int) $audioSampleRate;
|
$this->audioKiloBitrate = (int) $kiloBitrate;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
@ -91,25 +93,14 @@ abstract class DefaultAudio implements Resamplable, Interactive
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function getKiloBitrate()
|
public function createProgressListener(MediaTypeInterface $media, FFProbe $ffprobe, $pass, $total)
|
||||||
{
|
{
|
||||||
return $this->kiloBitrate;
|
$format = $this;
|
||||||
}
|
$listener = new AudioProgressListener($ffprobe, $media->getPathfile(), $pass, $total);
|
||||||
|
$listener->on('progress', function () use ($media, $format) {
|
||||||
|
$format->emit('progress', array_merge(array($media, $format), func_get_args()));
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
return array($listener);
|
||||||
* Set the kiloBitrate value
|
|
||||||
*
|
|
||||||
* @param int integer $kiloBitrate
|
|
||||||
* @throws \InvalidArgumentException
|
|
||||||
*/
|
|
||||||
public function setKiloBitrate($kiloBitrate)
|
|
||||||
{
|
|
||||||
if ($kiloBitrate < 1) {
|
|
||||||
throw new InvalidArgumentException('Wrong kiloBitrate value');
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->kiloBitrate = (int) $kiloBitrate;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,13 @@ namespace FFMpeg\Format\Audio;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Flac audio format
|
* The Flac audio format
|
||||||
*
|
|
||||||
* @author Romain Neutron imprec@gmail.com
|
|
||||||
*/
|
*/
|
||||||
class Flac extends DefaultAudio
|
class Flac extends DefaultAudio
|
||||||
{
|
{
|
||||||
protected $audioCodec = 'flac';
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->audioCodec = 'flac';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
|
|
|
||||||
|
|
@ -1,30 +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 interactive audio interface. This provide a method to list available
|
|
||||||
* codecs. This is usefull to build interactive development and switch between
|
|
||||||
* different codecs
|
|
||||||
*
|
|
||||||
* @author Romain Neutron imprec@gmail.com
|
|
||||||
*/
|
|
||||||
interface Interactive extends Transcodable
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the list of available audio codecs for this format
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function getAvailableAudioCodecs();
|
|
||||||
}
|
|
||||||
|
|
@ -13,12 +13,13 @@ namespace FFMpeg\Format\Audio;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The MP3 audio format
|
* The MP3 audio format
|
||||||
*
|
|
||||||
* @author Romain Neutron imprec@gmail.com
|
|
||||||
*/
|
*/
|
||||||
class Mp3 extends DefaultAudio
|
class Mp3 extends DefaultAudio
|
||||||
{
|
{
|
||||||
protected $audioCodec = 'libmp3lame';
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->audioCodec = 'libmp3lame';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
|
|
|
||||||
|
|
@ -1,32 +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 FFMpeg\Format\AudioInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The resamplable audio interface
|
|
||||||
*
|
|
||||||
* This provide a method to define the AudiosampleRate
|
|
||||||
*
|
|
||||||
* @author Romain Neutron imprec@gmail.com
|
|
||||||
*/
|
|
||||||
interface Resamplable extends AudioInterface
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the audio sample rate
|
|
||||||
*
|
|
||||||
* @return integer
|
|
||||||
*/
|
|
||||||
public function getAudioSampleRate();
|
|
||||||
}
|
|
||||||
|
|
@ -8,23 +8,16 @@
|
||||||
* For the full copyright and license information, please view the LICENSE
|
* For the full copyright and license information, please view the LICENSE
|
||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace FFMpeg\Format;
|
namespace FFMpeg\Format;
|
||||||
|
|
||||||
/**
|
interface AudioInterface extends FormatInterface
|
||||||
* The base audio interface
|
|
||||||
*
|
|
||||||
* @author Romain Neutron imprec@gmail.com
|
|
||||||
*/
|
|
||||||
interface AudioInterface
|
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the kiloBitrate value
|
* Get the audio kiloBitrate value
|
||||||
*
|
*
|
||||||
* @return integer
|
* @return integer
|
||||||
*/
|
*/
|
||||||
public function getKiloBitrate();
|
public function getAudioKiloBitrate();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return an array of extra parameters to add to ffmpeg commandline
|
* Return an array of extra parameters to add to ffmpeg commandline
|
||||||
|
|
@ -33,4 +26,17 @@ interface AudioInterface
|
||||||
*/
|
*/
|
||||||
public function getExtraParams();
|
public function getExtraParams();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the audio codec
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getAudioCodec();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the list of available audio codecs for this format
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getAvailableAudioCodecs();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,8 @@
|
||||||
* For the full copyright and license information, please view the LICENSE
|
* For the full copyright and license information, please view the LICENSE
|
||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
namespace FFMpeg\Format;
|
||||||
|
|
||||||
namespace FFMpeg\Exception;
|
interface FormatInterface
|
||||||
|
|
||||||
class BinaryNotFoundException extends \Exception implements ExceptionInterface
|
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
16
src/FFMpeg/Format/FrameInterface.php
Normal file
16
src/FFMpeg/Format/FrameInterface.php
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?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
|
||||||
|
{
|
||||||
|
}
|
||||||
238
src/FFMpeg/Format/ProgressListener/AbstractProgressListener.php
Normal file
238
src/FFMpeg/Format/ProgressListener/AbstractProgressListener.php
Normal file
|
|
@ -0,0 +1,238 @@
|
||||||
|
<?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\FFProbe;
|
||||||
|
use FFMpeg\Exception\RuntimeException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Robert Gruendler <r.gruendler@gmail.com>
|
||||||
|
*/
|
||||||
|
abstract class AbstractProgressListener extends EventEmitter implements ListenerInterface
|
||||||
|
{
|
||||||
|
/** @var integer */
|
||||||
|
private $duration;
|
||||||
|
|
||||||
|
/** @var integer */
|
||||||
|
private $totalSize;
|
||||||
|
|
||||||
|
/** @var integer */
|
||||||
|
private $currentSize;
|
||||||
|
|
||||||
|
/** @var integer */
|
||||||
|
private $currentTime;
|
||||||
|
|
||||||
|
/** @var double */
|
||||||
|
private $lastOutput = null;
|
||||||
|
|
||||||
|
/** @var FFProbe */
|
||||||
|
private $ffprobe;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $pathfile;
|
||||||
|
|
||||||
|
/** @var Boolean */
|
||||||
|
private $initialized = false;
|
||||||
|
|
||||||
|
/** @var integer */
|
||||||
|
private $currentPass;
|
||||||
|
|
||||||
|
/** @var integer */
|
||||||
|
private $totalPass;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transcoding rate in kb/s
|
||||||
|
*
|
||||||
|
* @var integer
|
||||||
|
*/
|
||||||
|
private $rate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Percentage of transcoding progress (0 - 100)
|
||||||
|
*
|
||||||
|
* @var integer
|
||||||
|
*/
|
||||||
|
private $percent = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Time remaining (seconds)
|
||||||
|
*
|
||||||
|
* @var integer
|
||||||
|
*/
|
||||||
|
private $remaining = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param FFProbe $ffprobe
|
||||||
|
* @param string $pathfile
|
||||||
|
*
|
||||||
|
* @throws RuntimeException
|
||||||
|
*/
|
||||||
|
public function __construct(FFProbe $ffprobe, $pathfile, $currentPass, $totalPass)
|
||||||
|
{
|
||||||
|
$this->ffprobe = $ffprobe;
|
||||||
|
$this->pathfile = $pathfile;
|
||||||
|
$this->currentPass = $currentPass;
|
||||||
|
$this->totalPass = $totalPass;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return FFProbe
|
||||||
|
*/
|
||||||
|
public function getFFProbe()
|
||||||
|
{
|
||||||
|
return $this->ffprobe;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getPathfile()
|
||||||
|
{
|
||||||
|
return $this->pathfile;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return integer
|
||||||
|
*/
|
||||||
|
public function getCurrentPass()
|
||||||
|
{
|
||||||
|
return $this->currentPass;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return integer
|
||||||
|
*/
|
||||||
|
public function getTotalPass()
|
||||||
|
{
|
||||||
|
return $this->totalPass;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function handle($type, $data)
|
||||||
|
{
|
||||||
|
if (null !== $progress = $this->parseProgress($data)) {
|
||||||
|
$this->emit('progress', array_values($progress));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function forwardedEvents()
|
||||||
|
{
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
$matches = array();
|
||||||
|
|
||||||
|
if (preg_match($this->getPattern(), $progress, $matches) !== 1) {
|
||||||
|
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 ($this->lastOutput !== null) {
|
||||||
|
$delta = $currentTime - $this->lastOutput;
|
||||||
|
$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 ($this->remaining === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'percent' => $this->percent,
|
||||||
|
'remaining' => $this->remaining,
|
||||||
|
'rate' => $this->rate
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function initialize()
|
||||||
|
{
|
||||||
|
$format = $this->ffprobe->format($this->pathfile);
|
||||||
|
|
||||||
|
if (false === $format->has('size') || false === $format->has('duration')) {
|
||||||
|
throw new RuntimeException(sprintf('Unable to probe format for %s', $this->pathfile));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->totalSize = $format->get('size') / 1024;
|
||||||
|
$this->duration = $format->get('duration');
|
||||||
|
|
||||||
|
$this->initialized = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace FFMpeg\Helper;
|
namespace FFMpeg\Format\ProgressListener;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses ffmpeg stderr progress information. An example:
|
* Parses ffmpeg stderr progress information. An example:
|
||||||
|
|
@ -20,7 +20,7 @@ namespace FFMpeg\Helper;
|
||||||
*
|
*
|
||||||
* @author Robert Gruendler <r.gruendler@gmail.com>
|
* @author Robert Gruendler <r.gruendler@gmail.com>
|
||||||
*/
|
*/
|
||||||
class AudioProgressHelper extends ProgressHelper
|
class AudioProgressListener extends AbstractProgressListener
|
||||||
{
|
{
|
||||||
public function getPattern()
|
public function getPattern()
|
||||||
{
|
{
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace FFMpeg\Helper;
|
namespace FFMpeg\Format\ProgressListener;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses ffmpeg stderr progress information for video files. An example:
|
* Parses ffmpeg stderr progress information for video files. An example:
|
||||||
|
|
@ -20,7 +20,7 @@ namespace FFMpeg\Helper;
|
||||||
*
|
*
|
||||||
* @author Robert Gruendler <r.gruendler@gmail.com>
|
* @author Robert Gruendler <r.gruendler@gmail.com>
|
||||||
*/
|
*/
|
||||||
class VideoProgressHelper extends ProgressHelper
|
class VideoProgressListener extends AbstractProgressListener
|
||||||
{
|
{
|
||||||
public function getPattern()
|
public function getPattern()
|
||||||
{
|
{
|
||||||
31
src/FFMpeg/Format/ProgressableInterface.php
Normal file
31
src/FFMpeg/Format/ProgressableInterface.php
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
<?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 MediaTypeInterface $media
|
||||||
|
* @param FFProbe $ffprobe
|
||||||
|
* @param Integer $pass The current pas snumber
|
||||||
|
* @param Integer $total The total pass number
|
||||||
|
*
|
||||||
|
* @return array An array of listeners
|
||||||
|
*/
|
||||||
|
public function createProgressListener(MediaTypeInterface $media, FFProbe $ffprobe, $pass, $total);
|
||||||
|
}
|
||||||
|
|
@ -11,171 +11,48 @@
|
||||||
|
|
||||||
namespace FFMpeg\Format\Video;
|
namespace FFMpeg\Format\Video;
|
||||||
|
|
||||||
use FFMpeg\Format\Audio\DefaultAudio;
|
use FFMpeg\FFProbe;
|
||||||
use FFMpeg\Format\Dimension;
|
|
||||||
use FFMpeg\Exception\InvalidArgumentException;
|
use FFMpeg\Exception\InvalidArgumentException;
|
||||||
|
use FFMpeg\Format\Audio\DefaultAudio;
|
||||||
|
use FFMpeg\Format\VideoInterface;
|
||||||
|
use FFMpeg\Media\MediaTypeInterface;
|
||||||
|
use FFMpeg\Format\ProgressListener\VideoProgressListener;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The abstract default Video format
|
* The abstract default Video format
|
||||||
*
|
|
||||||
* @author Romain Neutron imprec@gmail.com
|
|
||||||
*/
|
*/
|
||||||
abstract class DefaultVideo extends DefaultAudio implements Interactive, Resamplable, Resizable
|
abstract class DefaultVideo extends DefaultAudio implements VideoInterface
|
||||||
{
|
{
|
||||||
const RESIZEMODE_FIT = 'fit';
|
/** @var string */
|
||||||
const RESIZEMODE_INSET = 'inset';
|
|
||||||
const RESIZEMODE_SCALE_WIDTH = 'width';
|
|
||||||
const RESIZEMODE_SCALE_HEIGHT = 'height';
|
|
||||||
|
|
||||||
protected $width;
|
|
||||||
protected $height;
|
|
||||||
protected $frameRate = 25;
|
|
||||||
protected $resizeMode = self::RESIZEMODE_FIT;
|
|
||||||
protected $videoCodec;
|
protected $videoCodec;
|
||||||
protected $GOPsize = 25;
|
|
||||||
|
/** @var Integer */
|
||||||
protected $kiloBitrate = 1000;
|
protected $kiloBitrate = 1000;
|
||||||
|
|
||||||
|
/** @var Integer */
|
||||||
protected $modulus = 16;
|
protected $modulus = 16;
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the width setting.
|
|
||||||
* The return of this method should not depend on a media file size
|
|
||||||
*
|
|
||||||
* @return integer
|
|
||||||
*/
|
|
||||||
public function getWidth()
|
|
||||||
{
|
|
||||||
return $this->width;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the height setting
|
|
||||||
* The return of this method should not depend on a media file size
|
|
||||||
*
|
|
||||||
* @return integer
|
|
||||||
*/
|
|
||||||
public function getHeight()
|
|
||||||
{
|
|
||||||
return $this->height;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the dimensions
|
|
||||||
*
|
|
||||||
* @param integer $width The heigth
|
|
||||||
* @param integer $height The width
|
|
||||||
* @throws \InvalidArgumentException
|
|
||||||
*/
|
|
||||||
public function setDimensions($width, $height)
|
|
||||||
{
|
|
||||||
if ($width < 1) {
|
|
||||||
throw new InvalidArgumentException('Wrong width value');
|
|
||||||
}
|
|
||||||
if ($height < 1) {
|
|
||||||
throw new InvalidArgumentException('Wrong height value');
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->width = $width;
|
|
||||||
$this->height = $height;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc)
|
|
||||||
*/
|
|
||||||
public function getComputedDimensions($originalWidth, $originalHeight)
|
|
||||||
{
|
|
||||||
$originalRatio = $originalWidth / $originalHeight;
|
|
||||||
|
|
||||||
switch ($this->getResizeMode()) {
|
|
||||||
case self::RESIZEMODE_SCALE_WIDTH:
|
|
||||||
$height = $this->height;
|
|
||||||
$width = round($originalRatio * $this->height);
|
|
||||||
break;
|
|
||||||
case self::RESIZEMODE_SCALE_HEIGHT:
|
|
||||||
$width = $this->width;
|
|
||||||
$height = round($this->width / $originalRatio);
|
|
||||||
break;
|
|
||||||
case self::RESIZEMODE_INSET:
|
|
||||||
$targetRatio = $this->width / $this->height;
|
|
||||||
|
|
||||||
if ($targetRatio > $originalRatio) {
|
|
||||||
$height = $this->height;
|
|
||||||
$width = round($originalRatio * $this->height);
|
|
||||||
} else {
|
|
||||||
$width = $this->width;
|
|
||||||
$height = round($this->width / $originalRatio);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case self::RESIZEMODE_FIT:
|
|
||||||
default:
|
|
||||||
if (null !== $this->width && null !== $this->height) {
|
|
||||||
$width = $this->width;
|
|
||||||
$height = $this->height;
|
|
||||||
} else {
|
|
||||||
$width = $originalWidth;
|
|
||||||
$height = $originalHeight;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Dimension($width, $height);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the resize mode
|
|
||||||
*
|
|
||||||
* @param string $mode The mode, one of the self::RESIZEMODE_* constants
|
|
||||||
*
|
|
||||||
* @throws InvalidArgumentException
|
|
||||||
*/
|
|
||||||
public function setResizeMode($mode)
|
|
||||||
{
|
|
||||||
if ( ! in_array($mode, array(self::RESIZEMODE_FIT, self::RESIZEMODE_INSET, self::RESIZEMODE_SCALE_WIDTH, self::RESIZEMODE_SCALE_HEIGHT))) {
|
|
||||||
throw new InvalidArgumentException(
|
|
||||||
'Resize mode `%s` is not valid , avalaible values are %s',
|
|
||||||
$mode,
|
|
||||||
implode(', ', array(self::RESIZEMODE_FIT, self::RESIZEMODE_INSET, self::RESIZEMODE_SCALE_WIDTH, self::RESIZEMODE_SCALE_HEIGHT))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->resizeMode = $mode;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the current resize mode name
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function getResizeMode()
|
|
||||||
{
|
|
||||||
return $this->resizeMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function getFrameRate()
|
public function getKiloBitrate()
|
||||||
{
|
{
|
||||||
return $this->frameRate;
|
return $this->kiloBitrate;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the framerate
|
* Sets the kiloBitrate value
|
||||||
*
|
*
|
||||||
* @param integer $frameRate
|
* @param integer $kiloBitrate
|
||||||
*
|
* @throws InvalidArgumentException
|
||||||
* @throws \InvalidArgumentException
|
|
||||||
*/
|
*/
|
||||||
public function setFrameRate($frameRate)
|
public function setKiloBitrate($kiloBitrate)
|
||||||
{
|
{
|
||||||
if ($frameRate < 1) {
|
if ($kiloBitrate < 1) {
|
||||||
throw new InvalidArgumentException('Wrong framerate value');
|
throw new InvalidArgumentException('Wrong kiloBitrate value');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->frameRate = (int) $frameRate;
|
$this->kiloBitrate = (int) $kiloBitrate;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
@ -189,11 +66,11 @@ abstract class DefaultVideo extends DefaultAudio implements Interactive, Resampl
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the video codec, Should be in the available ones, otherwise an
|
* Sets the video codec, Should be in the available ones, otherwise an
|
||||||
* exception is thrown
|
* exception is thrown
|
||||||
*
|
*
|
||||||
* @param string $videoCodec
|
* @param string $videoCodec
|
||||||
* @throws \InvalidArgumentException
|
* @throws InvalidArgumentException
|
||||||
*/
|
*/
|
||||||
public function setVideoCodec($videoCodec)
|
public function setVideoCodec($videoCodec)
|
||||||
{
|
{
|
||||||
|
|
@ -209,32 +86,6 @@ abstract class DefaultVideo extends DefaultAudio implements Interactive, Resampl
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function getGOPsize()
|
|
||||||
{
|
|
||||||
return $this->GOPsize;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the GOP size
|
|
||||||
*
|
|
||||||
* @param integer $GOPsize
|
|
||||||
*
|
|
||||||
* @throws \InvalidArgumentException
|
|
||||||
*/
|
|
||||||
public function setGOPsize($GOPsize)
|
|
||||||
{
|
|
||||||
if ($GOPsize < 1) {
|
|
||||||
throw new InvalidArgumentException('Wrong GOP size value');
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->GOPsize = (int) $GOPsize;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
|
|
@ -244,24 +95,27 @@ abstract class DefaultVideo extends DefaultAudio implements Interactive, Resampl
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to determine what resolutions sizes are valid.
|
* @return integer
|
||||||
*
|
|
||||||
* @param int $value
|
|
||||||
*/
|
|
||||||
public function setModulus($value)
|
|
||||||
{
|
|
||||||
if(!in_array($value, array(2, 4, 8, 16))){
|
|
||||||
throw new InvalidArgumentException('Wrong modulus division value. Valid values are 2, 4, 8 or 16');
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->modulus = $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return int
|
|
||||||
*/
|
*/
|
||||||
public function getModulus()
|
public function getModulus()
|
||||||
{
|
{
|
||||||
return $this->modulus;
|
return $this->modulus;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function createProgressListener(MediaTypeInterface $media, FFProbe $ffprobe, $pass, $total)
|
||||||
|
{
|
||||||
|
$format = $this;
|
||||||
|
$listeners = array(new VideoProgressListener($ffprobe, $media->getPathfile(), $pass, $total));
|
||||||
|
|
||||||
|
foreach ($listeners as $listener) {
|
||||||
|
$listener->on('progress', function () use ($format, $media) {
|
||||||
|
$format->emit('progress', array_merge(array($media, $format), func_get_args()));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return $listeners;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,30 +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 interactive video interface. This provide a method to list available
|
|
||||||
* codecs. This is usefull to build interactive development and switch between
|
|
||||||
* different codecs
|
|
||||||
*
|
|
||||||
* @author Romain Neutron imprec@gmail.com
|
|
||||||
*/
|
|
||||||
interface Interactive extends Transcodable
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the list of available video codecs for this format
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function getAvailableVideoCodecs();
|
|
||||||
}
|
|
||||||
|
|
@ -13,13 +13,15 @@ namespace FFMpeg\Format\Video;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Ogg video format
|
* The Ogg video format
|
||||||
*
|
|
||||||
* @author Romain Neutron imprec@gmail.com
|
|
||||||
*/
|
*/
|
||||||
class Ogg extends DefaultVideo
|
class Ogg extends DefaultVideo
|
||||||
{
|
{
|
||||||
protected $audioCodec = 'libvorbis';
|
public function __construct($audioCodec = 'libvorbis', $videoCodec = 'libtheora')
|
||||||
protected $videoCodec = 'libtheora';
|
{
|
||||||
|
$this
|
||||||
|
->setAudioCodec($audioCodec)
|
||||||
|
->setVideoCodec($videoCodec);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
|
|
|
||||||
|
|
@ -1,50 +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\Format\VideoInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The resamplable video interface
|
|
||||||
*
|
|
||||||
* This interface provides frame rate and GOP size settings for video encoding
|
|
||||||
*
|
|
||||||
* @author Romain Neutron imprec@gmail.com
|
|
||||||
*/
|
|
||||||
interface Resamplable extends VideoInterface
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the frame rate
|
|
||||||
*
|
|
||||||
* @return integer
|
|
||||||
*/
|
|
||||||
public function getFrameRate();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the current format supports B-Frames
|
|
||||||
*
|
|
||||||
* @see https://wikipedia.org/wiki/Video_compression_picture_types
|
|
||||||
*
|
|
||||||
* @return Boolean
|
|
||||||
*/
|
|
||||||
public function supportBFrames();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the GOP size
|
|
||||||
*
|
|
||||||
* @see https://wikipedia.org/wiki/Group_of_pictures
|
|
||||||
*
|
|
||||||
* @return integer
|
|
||||||
*/
|
|
||||||
public function getGOPSize();
|
|
||||||
}
|
|
||||||
|
|
@ -1,48 +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\Format\VideoInterface;
|
|
||||||
use FFMpeg\Format\Dimension;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The resizable video interface
|
|
||||||
*
|
|
||||||
* This interface provides methods for video resizing.
|
|
||||||
*
|
|
||||||
* @author Romain Neutron imprec@gmail.com
|
|
||||||
*/
|
|
||||||
interface Resizable extends VideoInterface
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the computed dimensions for the resize, after operation.
|
|
||||||
* This method return the actual dimensions that FFmpeg will use.
|
|
||||||
*
|
|
||||||
* @param integer $originalWidth
|
|
||||||
* @param integer $originalHeight
|
|
||||||
* @return Dimension A dimension
|
|
||||||
*/
|
|
||||||
public function getComputedDimensions($originalWidth, $originalHeight);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the modulus used by the Resizable video.
|
|
||||||
*
|
|
||||||
* This used to calculate the target dimensions while maintaining the best
|
|
||||||
* aspect ratio.
|
|
||||||
*
|
|
||||||
* @see http://www.undeadborn.net/tools/rescalculator.php
|
|
||||||
*
|
|
||||||
* @return integer
|
|
||||||
*/
|
|
||||||
public function getModulus();
|
|
||||||
}
|
|
||||||
|
|
@ -1,28 +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\Format\VideoInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Romain Neutron imprec@gmail.com
|
|
||||||
*/
|
|
||||||
interface Transcodable extends VideoInterface
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the video codec
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function getVideoCodec();
|
|
||||||
}
|
|
||||||
|
|
@ -13,13 +13,15 @@ namespace FFMpeg\Format\Video;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The WMV video format
|
* The WMV video format
|
||||||
*
|
|
||||||
* @author Romain Neutron imprec@gmail.com
|
|
||||||
*/
|
*/
|
||||||
class WMV extends DefaultVideo
|
class WMV extends DefaultVideo
|
||||||
{
|
{
|
||||||
protected $audioCodec = 'wmav2';
|
public function __construct($audioCodec = 'wmav2', $videoCodec = 'wmv2')
|
||||||
protected $videoCodec = 'wmv2';
|
{
|
||||||
|
$this
|
||||||
|
->setAudioCodec($audioCodec)
|
||||||
|
->setVideoCodec($videoCodec);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
|
|
|
||||||
|
|
@ -13,13 +13,15 @@ namespace FFMpeg\Format\Video;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The WMV video format
|
* The WMV video format
|
||||||
*
|
|
||||||
* @author Romain Neutron imprec@gmail.com
|
|
||||||
*/
|
*/
|
||||||
class WMV3 extends DefaultVideo
|
class WMV3 extends DefaultVideo
|
||||||
{
|
{
|
||||||
protected $audioCodec = 'wmav3';
|
public function __construct($audioCodec = 'wmav3', $videoCodec = 'wmv3')
|
||||||
protected $videoCodec = 'wmv3';
|
{
|
||||||
|
$this
|
||||||
|
->setAudioCodec($audioCodec)
|
||||||
|
->setVideoCodec($videoCodec);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
|
|
|
||||||
|
|
@ -13,13 +13,15 @@ namespace FFMpeg\Format\Video;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The WebM video format
|
* The WebM video format
|
||||||
*
|
|
||||||
* @author Romain Neutron imprec@gmail.com
|
|
||||||
*/
|
*/
|
||||||
class WebM extends DefaultVideo
|
class WebM extends DefaultVideo
|
||||||
{
|
{
|
||||||
protected $audioCodec = 'libvorbis';
|
public function __construct($audioCodec = 'libvorbis', $videoCodec = 'libvpx')
|
||||||
protected $videoCodec = 'libvpx';
|
{
|
||||||
|
$this
|
||||||
|
->setAudioCodec($audioCodec)
|
||||||
|
->setVideoCodec($videoCodec);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
|
|
|
||||||
|
|
@ -13,13 +13,15 @@ namespace FFMpeg\Format\Video;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The X264 video format
|
* The X264 video format
|
||||||
*
|
|
||||||
* @author Romain Neutron imprec@gmail.com
|
|
||||||
*/
|
*/
|
||||||
class X264 extends DefaultVideo
|
class X264 extends DefaultVideo
|
||||||
{
|
{
|
||||||
protected $audioCodec = 'libmp3lame';
|
public function __construct($audioCodec = 'libfaac', $videoCodec = 'libx264')
|
||||||
protected $videoCodec = 'libx264';
|
{
|
||||||
|
$this
|
||||||
|
->setAudioCodec($audioCodec)
|
||||||
|
->setVideoCodec($videoCodec);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
|
|
|
||||||
|
|
@ -11,17 +11,54 @@
|
||||||
|
|
||||||
namespace FFMpeg\Format;
|
namespace FFMpeg\Format;
|
||||||
|
|
||||||
/**
|
|
||||||
* The base video interface
|
|
||||||
*
|
|
||||||
* @author Romain Neutron imprec@gmail.com
|
|
||||||
*/
|
|
||||||
interface VideoInterface extends AudioInterface
|
interface VideoInterface extends AudioInterface
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Get the kiloBitrate value
|
||||||
|
*
|
||||||
|
* @return integer
|
||||||
|
*/
|
||||||
|
public function getKiloBitrate();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the number of passes
|
* Returns the number of passes
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getPasses();
|
public function getPasses();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the modulus used by the Resizable video.
|
||||||
|
*
|
||||||
|
* This used to calculate the target dimensions while maintaining the best
|
||||||
|
* aspect ratio.
|
||||||
|
*
|
||||||
|
* @see http://www.undeadborn.net/tools/rescalculator.php
|
||||||
|
*
|
||||||
|
* @return integer
|
||||||
|
*/
|
||||||
|
public function getModulus();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the video codec
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getVideoCodec();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the current format supports B-Frames
|
||||||
|
*
|
||||||
|
* @see https://wikipedia.org/wiki/Video_compression_picture_types
|
||||||
|
*
|
||||||
|
* @return Boolean
|
||||||
|
*/
|
||||||
|
public function supportBFrames();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the list of available video codecs for this format
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getAvailableVideoCodecs();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,42 +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\Helper;
|
|
||||||
|
|
||||||
use FFMpeg\FFProbe;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Robert Gruendler <r.gruendler@gmail.com>
|
|
||||||
*/
|
|
||||||
interface HelperInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* The callback from the ffmpeg process.
|
|
||||||
*
|
|
||||||
* @param string $channel (stdio|stderr)
|
|
||||||
* @param string $content the current line of the ffmpeg output
|
|
||||||
*/
|
|
||||||
function transcodeCallback($channel, $content);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The helper has access to a prober instance if available.
|
|
||||||
*
|
|
||||||
* @param FFProbe $prober
|
|
||||||
*/
|
|
||||||
function setProber(FFProbe $prober);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the input file is opened.
|
|
||||||
*
|
|
||||||
* @param string $pathfile
|
|
||||||
*/
|
|
||||||
function open($pathfile);
|
|
||||||
}
|
|
||||||
|
|
@ -1,218 +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\Helper;
|
|
||||||
|
|
||||||
use FFMpeg\FFProbe;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Robert Gruendler <r.gruendler@gmail.com>
|
|
||||||
*/
|
|
||||||
abstract class ProgressHelper implements HelperInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var number
|
|
||||||
*/
|
|
||||||
protected $duration = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* transcoding rate in kb/s
|
|
||||||
*
|
|
||||||
* @var number
|
|
||||||
*/
|
|
||||||
protected $rate;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $format;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var number
|
|
||||||
*/
|
|
||||||
protected $totalSize;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var number
|
|
||||||
*/
|
|
||||||
protected $currentSize;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var number
|
|
||||||
*/
|
|
||||||
protected $currentTime;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var double
|
|
||||||
*/
|
|
||||||
protected $lastOutput = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Percentage of transcoding progress (0 - 100)
|
|
||||||
*
|
|
||||||
* @var number
|
|
||||||
*/
|
|
||||||
protected $percent = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Time remaining (seconds)
|
|
||||||
*
|
|
||||||
* @var number
|
|
||||||
*/
|
|
||||||
protected $remaining = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var FFProbe
|
|
||||||
*/
|
|
||||||
protected $prober;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var Closure|string|array
|
|
||||||
*/
|
|
||||||
protected $callback;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param mixed $callback
|
|
||||||
*/
|
|
||||||
public function __construct($callback)
|
|
||||||
{
|
|
||||||
$this->callback = $callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to ease testing.
|
|
||||||
*
|
|
||||||
* @param number $duration
|
|
||||||
*/
|
|
||||||
public function setDuration($duration)
|
|
||||||
{
|
|
||||||
$this->duration = $duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function transcodeCallback($channel, $content)
|
|
||||||
{
|
|
||||||
$progress = $this->parseProgress($content);
|
|
||||||
|
|
||||||
if (is_array($progress)) {
|
|
||||||
call_user_func_array($this->callback, $progress);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function setProber(FFProbe $prober)
|
|
||||||
{
|
|
||||||
$this->prober = $prober;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function open($pathfile)
|
|
||||||
{
|
|
||||||
if ($this->prober === null) {
|
|
||||||
throw new \RuntimeException('Unable to report audio progress without a prober');
|
|
||||||
}
|
|
||||||
|
|
||||||
$format = json_decode($this->prober->probeFormat($pathfile), true);
|
|
||||||
|
|
||||||
if ($format === null || count($format) === 0 || isset($format['size']) === false) {
|
|
||||||
throw new \RuntimeException('Unable to probe format for ' . $pathfile);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->format = $format;
|
|
||||||
$this->totalSize = $format['size'] / 1024;
|
|
||||||
$this->duration = $format['duration'];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $progress A ffmpeg stderr progress output
|
|
||||||
* @return array the progressinfo array or null if there's no progress available yet.
|
|
||||||
*/
|
|
||||||
public function parseProgress($progress)
|
|
||||||
{
|
|
||||||
$matches = array();
|
|
||||||
|
|
||||||
if (preg_match($this->getPattern(), $progress, $matches) !== 1) {
|
|
||||||
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 ($this->lastOutput !== null) {
|
|
||||||
$delta = $currentTime - $this->lastOutput;
|
|
||||||
$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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$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
|
|
||||||
*/
|
|
||||||
protected 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
|
|
||||||
*/
|
|
||||||
public function getProgressInfo()
|
|
||||||
{
|
|
||||||
if ($this->remaining === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return array(
|
|
||||||
'percent' => $this->percent,
|
|
||||||
'remaining' => $this->remaining,
|
|
||||||
'rate' => $this->rate
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the regex pattern to match a ffmpeg stderr status line
|
|
||||||
*/
|
|
||||||
abstract function getPattern();
|
|
||||||
}
|
|
||||||
126
src/FFMpeg/Media/AbstractMediaType.php
Normal file
126
src/FFMpeg/Media/AbstractMediaType.php
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
<?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\Media;
|
||||||
|
|
||||||
|
use FFMpeg\Driver\FFMpegDriver;
|
||||||
|
use FFMpeg\Exception\InvalidArgumentException;
|
||||||
|
use FFMpeg\FFProbe;
|
||||||
|
use FFMpeg\Filters\FiltersCollection;
|
||||||
|
use FFMpeg\Media\MediaTypeInterface;
|
||||||
|
|
||||||
|
abstract class AbstractMediaType implements MediaTypeInterface
|
||||||
|
{
|
||||||
|
/** @var string */
|
||||||
|
protected $pathfile;
|
||||||
|
/** @var FFMpegDriver */
|
||||||
|
protected $driver;
|
||||||
|
/** @var FFProbe */
|
||||||
|
protected $ffprobe;
|
||||||
|
/** @var FiltersCollection */
|
||||||
|
protected $filters;
|
||||||
|
|
||||||
|
public function __construct($pathfile, FFMpegDriver $driver, FFProbe $ffprobe)
|
||||||
|
{
|
||||||
|
$this->ensureFileIsPresent($pathfile);
|
||||||
|
|
||||||
|
$this->pathfile = $pathfile;
|
||||||
|
$this->driver = $driver;
|
||||||
|
$this->ffprobe = $ffprobe;
|
||||||
|
$this->filters = new FiltersCollection();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return FFMpegDriver
|
||||||
|
*/
|
||||||
|
public function getFFMpegDriver()
|
||||||
|
{
|
||||||
|
return $this->driver;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param FFMpegDriver $driver
|
||||||
|
*
|
||||||
|
* @return MediaTypeInterface
|
||||||
|
*/
|
||||||
|
public function setFFMpegDriver(FFMpegDriver $driver)
|
||||||
|
{
|
||||||
|
$this->driver = $driver;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return FFProbe
|
||||||
|
*/
|
||||||
|
public function getFFProbe()
|
||||||
|
{
|
||||||
|
return $this->ffprobe;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param FFProbe $ffprobe
|
||||||
|
*
|
||||||
|
* @return MediaTypeInterface
|
||||||
|
*/
|
||||||
|
public function setFFProbe(FFProbe $ffprobe)
|
||||||
|
{
|
||||||
|
$this->ffprobe = $ffprobe;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getPathfile()
|
||||||
|
{
|
||||||
|
return $this->pathfile;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param FiltersCollection $filters
|
||||||
|
*
|
||||||
|
* @return MediaTypeInterface
|
||||||
|
*/
|
||||||
|
public function setFiltersCollection(FiltersCollection $filters)
|
||||||
|
{
|
||||||
|
$this->filters = $filters;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return MediaTypeInterface
|
||||||
|
*/
|
||||||
|
public function getFiltersCollection()
|
||||||
|
{
|
||||||
|
return $this->filters;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function ensureFileIsPresent($filename)
|
||||||
|
{
|
||||||
|
if (!is_file($filename) || !is_readable($filename)) {
|
||||||
|
throw new InvalidArgumentException(sprintf(
|
||||||
|
'%s is not present or not readable', $filename
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function cleanupTemporaryFile($filename)
|
||||||
|
{
|
||||||
|
if (file_exists($filename) && is_writable($filename)) {
|
||||||
|
unlink($filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
||||||
34
src/FFMpeg/Media/AbstractStreamableMedia.php
Normal file
34
src/FFMpeg/Media/AbstractStreamableMedia.php
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?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\Media;
|
||||||
|
|
||||||
|
use FFMpeg\FFProbe\DataMapping\Stream;
|
||||||
|
use FFMpeg\FFProbe\DataMapping\StreamCollection;
|
||||||
|
|
||||||
|
abstract class AbstractStreamableMedia extends AbstractMediaType
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return StreamCollection
|
||||||
|
*/
|
||||||
|
public function getStreams()
|
||||||
|
{
|
||||||
|
return $this->ffprobe->streams($this->pathfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Stream
|
||||||
|
*/
|
||||||
|
public function getFormat()
|
||||||
|
{
|
||||||
|
return $this->ffprobe->format($this->pathfile);
|
||||||
|
}
|
||||||
|
}
|
||||||
92
src/FFMpeg/Media/Audio.php
Normal file
92
src/FFMpeg/Media/Audio.php
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
<?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\Media;
|
||||||
|
|
||||||
|
use Alchemy\BinaryDriver\Exception\ExecutionFailureException;
|
||||||
|
use FFMpeg\Filters\Audio\AudioFilters;
|
||||||
|
use FFMpeg\Format\FormatInterface;
|
||||||
|
use FFMpeg\Exception\RuntimeException;
|
||||||
|
use FFMpeg\Filters\Audio\AudioFilterInterface;
|
||||||
|
use FFMpeg\Format\ProgressableInterface;
|
||||||
|
|
||||||
|
class Audio extends AbstractStreamableMedia
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @return AudioFilters
|
||||||
|
*/
|
||||||
|
public function filters()
|
||||||
|
{
|
||||||
|
return new AudioFilters($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @return Audio
|
||||||
|
*/
|
||||||
|
public function addFilter(AudioFilterInterface $filter)
|
||||||
|
{
|
||||||
|
$this->filters->add($filter);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export the audio in the desired format, applies registered filters
|
||||||
|
*
|
||||||
|
* @param FormatInterface $format
|
||||||
|
* @param string $outputPathfile
|
||||||
|
*
|
||||||
|
* @return Audio
|
||||||
|
*
|
||||||
|
* @throws RuntimeException
|
||||||
|
*/
|
||||||
|
public function save(FormatInterface $format, $outputPathfile)
|
||||||
|
{
|
||||||
|
$listeners = null;
|
||||||
|
|
||||||
|
if ($format instanceof ProgressableInterface) {
|
||||||
|
$listeners = $format->createProgressListener($this, $this->ffprobe, 1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$commands = array_merge(array('-y', '-i', $this->pathfile), $format->getExtraParams());
|
||||||
|
|
||||||
|
foreach ($this->filters as $filter) {
|
||||||
|
$commands = array_merge($commands, $filter->apply($this, $format));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->driver->getConfiguration()->has('ffmpeg.threads')) {
|
||||||
|
$commands[] = '-threads';
|
||||||
|
$commands[] = $this->driver->getConfiguration()->get('ffmpeg.threads');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null !== $format->getAudioCodec()) {
|
||||||
|
$commands[] = '-acodec';
|
||||||
|
$commands[] = $format->getAudioCodec();
|
||||||
|
}
|
||||||
|
|
||||||
|
$commands[] = '-b:a';
|
||||||
|
$commands[] = $format->getAudioKiloBitrate() . 'k';
|
||||||
|
$commands[] = $outputPathfile;
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->driver->command($commands, false, $listeners);
|
||||||
|
} catch (ExecutionFailureException $e) {
|
||||||
|
$this->cleanupTemporaryFile($outputPathfile);
|
||||||
|
throw new RuntimeException('Encoding failed', $e->getCode(), $e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
||||||
104
src/FFMpeg/Media/Frame.php
Normal file
104
src/FFMpeg/Media/Frame.php
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
<?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\Media;
|
||||||
|
|
||||||
|
use Alchemy\BinaryDriver\Exception\ExecutionFailureException;
|
||||||
|
use FFMpeg\Filters\Frame\FrameFilterInterface;
|
||||||
|
use FFMpeg\Filters\Frame\FrameFilters;
|
||||||
|
use FFMpeg\Driver\FFMpegDriver;
|
||||||
|
use FFMpeg\FFProbe;
|
||||||
|
use FFMpeg\Exception\RuntimeException;
|
||||||
|
use FFMpeg\Coordinate\TimeCode;
|
||||||
|
|
||||||
|
class Frame extends AbstractMediaType
|
||||||
|
{
|
||||||
|
/** @var TimeCode */
|
||||||
|
private $timecode;
|
||||||
|
|
||||||
|
public function __construct($pathfile, FFMpegDriver $driver, FFProbe $ffprobe, TimeCode $timecode)
|
||||||
|
{
|
||||||
|
parent::__construct($pathfile, $driver, $ffprobe);
|
||||||
|
$this->timecode = $timecode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @return FrameFilters
|
||||||
|
*/
|
||||||
|
public function filters()
|
||||||
|
{
|
||||||
|
return new FrameFilters($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @return Frame
|
||||||
|
*/
|
||||||
|
public function addFilter(FrameFilterInterface $filter)
|
||||||
|
{
|
||||||
|
$this->filters->add($filter);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return TimeCode
|
||||||
|
*/
|
||||||
|
public function getTimeCode()
|
||||||
|
{
|
||||||
|
return $this->timecode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the frame in the given filename.
|
||||||
|
*
|
||||||
|
* Uses the `unaccurate method by default.`
|
||||||
|
*
|
||||||
|
* @param string $pathfile
|
||||||
|
* @param Boolean $accurate
|
||||||
|
*
|
||||||
|
* @return Frame
|
||||||
|
*
|
||||||
|
* @throws RuntimeException
|
||||||
|
*/
|
||||||
|
public function saveAs($pathfile, $accurate = false)
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @see http://ffmpeg.org/ffmpeg.html#Main-options
|
||||||
|
*/
|
||||||
|
if (!$accurate) {
|
||||||
|
$commands = array(
|
||||||
|
'-ss', (string) $this->timecode,
|
||||||
|
'-i', $this->pathfile,
|
||||||
|
'-vframes', '1',
|
||||||
|
'-f', 'image2', $pathfile
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$commands = array(
|
||||||
|
'-i', $this->pathfile,
|
||||||
|
'-vframes', '1', '-ss', (string) $this->timecode,
|
||||||
|
'-f', 'image2', $pathfile
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->driver->command($commands);
|
||||||
|
} catch (ExecutionFailureException $e) {
|
||||||
|
$this->cleanupTemporaryFile($pathfile);
|
||||||
|
throw new RuntimeException('Unable to save frame', $e->getCode(), $e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,20 +9,17 @@
|
||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace FFMpeg\Format\Audio;
|
namespace FFMpeg\Media;
|
||||||
|
|
||||||
use FFMpeg\Format\AudioInterface;
|
interface MediaTypeInterface
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Romain Neutron imprec@gmail.com
|
|
||||||
*/
|
|
||||||
interface Transcodable extends AudioInterface
|
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Returns the available filters
|
||||||
|
*/
|
||||||
|
public function filters();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the audio codec
|
|
||||||
*
|
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getAudioCodec();
|
public function getPathfile();
|
||||||
}
|
}
|
||||||
166
src/FFMpeg/Media/Video.php
Normal file
166
src/FFMpeg/Media/Video.php
Normal file
|
|
@ -0,0 +1,166 @@
|
||||||
|
<?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\Media;
|
||||||
|
|
||||||
|
use Alchemy\BinaryDriver\Exception\ExecutionFailureException;
|
||||||
|
use FFMpeg\Coordinate\TimeCode;
|
||||||
|
use FFMpeg\Exception\RuntimeException;
|
||||||
|
use FFMpeg\Filters\Video\VideoFilters;
|
||||||
|
use FFMpeg\Filters\Video\VideoFilterInterface;
|
||||||
|
use FFMpeg\Format\VideoInterface;
|
||||||
|
use FFMpeg\Format\ProgressableInterface;
|
||||||
|
use FFMpeg\Media\Frame;
|
||||||
|
|
||||||
|
class Video extends AbstractStreamableMedia
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @return VideoFilters
|
||||||
|
*/
|
||||||
|
public function filters()
|
||||||
|
{
|
||||||
|
return new VideoFilters($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @return Video
|
||||||
|
*/
|
||||||
|
public function addFilter(VideoFilterInterface $filter)
|
||||||
|
{
|
||||||
|
$this->filters->add($filter);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export the video in the desired format, applies registered filters
|
||||||
|
*
|
||||||
|
* @param FormatInterface $format
|
||||||
|
* @param string $outputPathfile
|
||||||
|
*
|
||||||
|
* @return Video
|
||||||
|
*
|
||||||
|
* @throws RuntimeException
|
||||||
|
*/
|
||||||
|
public function save(VideoInterface $format, $outputPathfile)
|
||||||
|
{
|
||||||
|
$commands = array_merge(array('-y', '-i', $this->pathfile), $format->getExtraParams());
|
||||||
|
|
||||||
|
foreach ($this->filters as $filter) {
|
||||||
|
$commands = array_merge($commands, $filter->apply($this, $format));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->driver->getConfiguration()->has('ffmpeg.threads')) {
|
||||||
|
$commands[] = '-threads';
|
||||||
|
$commands[] = $this->driver->getConfiguration()->get('ffmpeg.threads');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null !== $format->getVideoCodec()) {
|
||||||
|
$commands[] = '-vcodec';
|
||||||
|
$commands[] = $format->getVideoCodec();
|
||||||
|
}
|
||||||
|
if (null !== $format->getAudioCodec()) {
|
||||||
|
$commands[] = '-acodec';
|
||||||
|
$commands[] = $format->getAudioCodec();
|
||||||
|
}
|
||||||
|
|
||||||
|
$commands[] = '-b:v';
|
||||||
|
$commands[] = $format->getKiloBitrate() . 'k';
|
||||||
|
$commands[] = '-refs';
|
||||||
|
$commands[] = '6';
|
||||||
|
$commands[] = '-coder';
|
||||||
|
$commands[] = '1';
|
||||||
|
$commands[] = '-sc_threshold';
|
||||||
|
$commands[] = '40';
|
||||||
|
$commands[] = '-flags';
|
||||||
|
$commands[] = '+loop';
|
||||||
|
$commands[] = '-me_range';
|
||||||
|
$commands[] = '16';
|
||||||
|
$commands[] = '-subq';
|
||||||
|
$commands[] = '7';
|
||||||
|
$commands[] = '-i_qfactor';
|
||||||
|
$commands[] = '0.71';
|
||||||
|
$commands[] = '-qcomp';
|
||||||
|
$commands[] = '0.6';
|
||||||
|
$commands[] = '-qdiff';
|
||||||
|
$commands[] = '4';
|
||||||
|
$commands[] = '-trellis';
|
||||||
|
$commands[] = '1';
|
||||||
|
$commands[] = '-b:a';
|
||||||
|
$commands[] = $format->getAudioKiloBitrate() . 'k';
|
||||||
|
|
||||||
|
$passPrefix = uniqid('pass-');
|
||||||
|
|
||||||
|
$pass1 = $commands;
|
||||||
|
$pass2 = $commands;
|
||||||
|
|
||||||
|
$pass1[] = '-pass';
|
||||||
|
$pass1[] = '1';
|
||||||
|
$pass1[] = '-passlogfile';
|
||||||
|
$pass1[] = $passPrefix;
|
||||||
|
$pass1[] = '-an';
|
||||||
|
$pass1[] = $outputPathfile;
|
||||||
|
|
||||||
|
$pass2[] = '-pass';
|
||||||
|
$pass2[] = '2';
|
||||||
|
$pass2[] = '-passlogfile';
|
||||||
|
$pass2[] = $passPrefix;
|
||||||
|
$pass2[] = '-ac';
|
||||||
|
$pass2[] = '2';
|
||||||
|
$pass2[] = '-ar';
|
||||||
|
$pass2[] = '44100';
|
||||||
|
$pass2[] = $outputPathfile;
|
||||||
|
|
||||||
|
$failure = null;
|
||||||
|
|
||||||
|
foreach (array($pass1, $pass2) as $pass => $passCommands) {
|
||||||
|
try {
|
||||||
|
/** add listeners here */
|
||||||
|
$listeners = null;
|
||||||
|
|
||||||
|
if ($format instanceof ProgressableInterface) {
|
||||||
|
$listeners = $format->createProgressListener($this, $this->ffprobe, $pass + 1, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->driver->command($passCommands, false, $listeners);
|
||||||
|
} catch (ExecutionFailureException $e) {
|
||||||
|
$failure = $e;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this
|
||||||
|
->cleanupTemporaryFile(getcwd() . '/' . $passPrefix . '-0.log')
|
||||||
|
->cleanupTemporaryFile(getcwd() . '/' . $passPrefix . '-0.log')
|
||||||
|
->cleanupTemporaryFile(getcwd() . '/' . $passPrefix . '-0.log.mbtree');
|
||||||
|
|
||||||
|
if (null !== $failure) {
|
||||||
|
throw new RuntimeException('Encoding failed', $failure->getCode(), $failure);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the frame at timecode
|
||||||
|
*
|
||||||
|
* @param Timecode $at
|
||||||
|
* @return Frame
|
||||||
|
*/
|
||||||
|
public function frame(Timecode $at)
|
||||||
|
{
|
||||||
|
return new Frame($this->pathfile, $this->driver, $this->ffprobe, $at);
|
||||||
|
}
|
||||||
|
}
|
||||||
13
tests/FFMpeg/Functional/FunctionalTestCase.php
Normal file
13
tests/FFMpeg/Functional/FunctionalTestCase.php
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace FFMpeg\Functional;
|
||||||
|
|
||||||
|
use FFMpeg\FFMpeg;
|
||||||
|
|
||||||
|
abstract class FunctionalTestCase extends \PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
public function getFFMpeg()
|
||||||
|
{
|
||||||
|
return FFMpeg::create(array('timeout' => 300));
|
||||||
|
}
|
||||||
|
}
|
||||||
38
tests/FFMpeg/Functional/VideoTranscodeTest.php
Normal file
38
tests/FFMpeg/Functional/VideoTranscodeTest.php
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace FFMpeg\Functional;
|
||||||
|
|
||||||
|
use FFMpeg\Format\Video\X264;
|
||||||
|
|
||||||
|
class VideoTranscodeTest extends FunctionalTestCase
|
||||||
|
{
|
||||||
|
public function testSimpleTranscodeX264()
|
||||||
|
{
|
||||||
|
$filename = __DIR__ . '/output/output-x264.mp4';
|
||||||
|
if (is_file($filename)) {
|
||||||
|
unlink(__DIR__ . '/output/output-x264.mp4');
|
||||||
|
}
|
||||||
|
|
||||||
|
$ffmpeg = $this->getFFMpeg();
|
||||||
|
$video = $ffmpeg->open(__DIR__ . '/../../files/Test.ogv');
|
||||||
|
|
||||||
|
$this->assertInstanceOf('FFMpeg\Media\Video', $video);
|
||||||
|
|
||||||
|
$lastPercentage = null;
|
||||||
|
$phpunit = $this;
|
||||||
|
|
||||||
|
$codec = new X264('libvo_aacenc');
|
||||||
|
$codec->on('progress', function ($video, $codec, $percentage) use ($phpunit, &$lastPercentage) {
|
||||||
|
if (null !== $lastPercentage) {
|
||||||
|
$phpunit->assertGreaterThanOrEqual($lastPercentage, $percentage);
|
||||||
|
}
|
||||||
|
$lastPercentage = $percentage;
|
||||||
|
$phpunit->assertGreaterThanOrEqual(0, $percentage);
|
||||||
|
$phpunit->assertLessThanOrEqual(100, $percentage);
|
||||||
|
});
|
||||||
|
|
||||||
|
$video->save($codec, $filename);
|
||||||
|
$this->assertFileExists($filename);
|
||||||
|
unlink($filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
0
tests/FFMpeg/Functional/output/.placeholder
Normal file
0
tests/FFMpeg/Functional/output/.placeholder
Normal file
83
tests/FFMpeg/Tests/Coordinate/AspectRatioTest.php
Normal file
83
tests/FFMpeg/Tests/Coordinate/AspectRatioTest.php
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace FFMpeg\Tests\Coordinate;
|
||||||
|
|
||||||
|
use FFMpeg\Coordinate\Dimension;
|
||||||
|
use FFMpeg\Tests\TestCase;
|
||||||
|
use FFMpeg\Coordinate\AspectRatio;
|
||||||
|
|
||||||
|
class AspectRatioTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @dataProvider provideDimensionsAndExpectedratio
|
||||||
|
*/
|
||||||
|
public function testFromDimensions($width, $height, $strategy, $expected, $calculatedWidth, $calculatedHeight, $modulus = 2)
|
||||||
|
{
|
||||||
|
$ratio = AspectRatio::create(new Dimension($width, $height), $strategy);
|
||||||
|
$this->assertEquals($expected, $ratio->getValue());
|
||||||
|
|
||||||
|
$this->assertEquals($calculatedHeight, $ratio->calculateHeight(240, $modulus));
|
||||||
|
$this->assertEquals($calculatedWidth, $ratio->calculateWidth(320, $modulus));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideDimensionsAndExpectedratio()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
//AR_5_4
|
||||||
|
array(720, 576, false, 5/4, 400, 192),
|
||||||
|
array(720, 577, false, 5/4, 400, 192),
|
||||||
|
array(720, 620, false, 720/620, 372, 206),
|
||||||
|
array(720, 576, true, 5/4, 400, 192),
|
||||||
|
//AR_ROTATED_4_5
|
||||||
|
array(576, 720, false, 4/5, 256, 300),
|
||||||
|
array(576, 720, true, 4/5, 256, 300),
|
||||||
|
//AR_4_3
|
||||||
|
array(320, 240, false, 4/3, 426, 180),
|
||||||
|
array(320, 240, true, 4/3, 426, 180),
|
||||||
|
//AR_ROTATED_3_4
|
||||||
|
array(240, 320, false, 3/4, 240, 320),
|
||||||
|
array(240, 320, true, 3/4, 240, 320),
|
||||||
|
//AR_16_9
|
||||||
|
array(1920, 1080, false, 16/9, 568, 136),
|
||||||
|
array(1920, 1080, true, 16/9, 568, 136),
|
||||||
|
array(1280, 720, false, 16/9, 568, 136),
|
||||||
|
array(1280, 720, true, 16/9, 568, 136),
|
||||||
|
array(3840, 2160, false, 16/9, 568, 136),
|
||||||
|
array(3840, 2160, true, 16/9, 568, 136),
|
||||||
|
// modulus 4
|
||||||
|
array(1920, 1080, false, 16/9, 568, 136, 4),
|
||||||
|
array(1920, 1080, true, 16/9, 568, 136, 4),
|
||||||
|
array(1280, 720, false, 16/9, 568, 136, 4),
|
||||||
|
array(1280, 720, true, 16/9, 568, 136, 4),
|
||||||
|
array(3840, 2160, false, 16/9, 568, 136, 4),
|
||||||
|
array(3840, 2160, true, 16/9, 568, 136, 4),
|
||||||
|
// modulus 16
|
||||||
|
array(1920, 1080, false, 16/9, 576, 128, 16),
|
||||||
|
array(1920, 1080, true, 16/9, 576, 128, 16),
|
||||||
|
array(1280, 720, false, 16/9, 576, 128, 16),
|
||||||
|
array(1280, 720, true, 16/9, 576, 128, 16),
|
||||||
|
array(3840, 2160, false, 16/9, 576, 128, 16),
|
||||||
|
array(3840, 2160, true, 16/9, 576, 128, 16),
|
||||||
|
//AR_ROTATED_9_16
|
||||||
|
array(1080, 1920, false, 9/16, 180, 426),
|
||||||
|
array(1080, 1920, true, 9/16, 180, 426),
|
||||||
|
array(720, 1280, false, 9/16, 180, 426),
|
||||||
|
array(720, 1280, true, 9/16, 180, 426),
|
||||||
|
array(2160, 3840, false, 9/16, 180, 426),
|
||||||
|
array(2160, 3840, true, 9/16, 180, 426),
|
||||||
|
//AR_3_2
|
||||||
|
array(360, 240, false, 3/2, 480, 160),
|
||||||
|
array(360, 240, true, 3/2, 480, 160),
|
||||||
|
//AR_ROTATED_2_3
|
||||||
|
array(240, 360, false, 2/3, 214, 360),
|
||||||
|
array(240, 360, true, 2/3, 214, 360),
|
||||||
|
//AR_5_3
|
||||||
|
//AR_ROTATED_3_5
|
||||||
|
//AR_1_1
|
||||||
|
//AR_1_DOT_85_1
|
||||||
|
//AR_ROTATED_1_DOT_85
|
||||||
|
//AR_2_DOT_39_1
|
||||||
|
//AR_ROTATED_2_DOT_39
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
38
tests/FFMpeg/Tests/Coordinate/DimensionTest.php
Normal file
38
tests/FFMpeg/Tests/Coordinate/DimensionTest.php
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace FFMpeg\Tests\Coordinate;
|
||||||
|
|
||||||
|
use FFMpeg\Tests\TestCase;
|
||||||
|
use FFMpeg\Coordinate\Dimension;
|
||||||
|
|
||||||
|
class DimensionTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @dataProvider provideInvalidDimensions
|
||||||
|
* @expectedException FFMpeg\Exception\InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function testInvalidDimensions($width, $height)
|
||||||
|
{
|
||||||
|
new Dimension($width, $height);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideInvalidDimensions()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
array(320, 0),
|
||||||
|
array(320, -10),
|
||||||
|
array(0, 240),
|
||||||
|
array(-10, 240),
|
||||||
|
array(0, 0),
|
||||||
|
array(0, -10),
|
||||||
|
array(-10, 0),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetters()
|
||||||
|
{
|
||||||
|
$dimension = new Dimension(320, 240);
|
||||||
|
$this->assertEquals(320, $dimension->getWidth());
|
||||||
|
$this->assertEquals(240, $dimension->getHeight());
|
||||||
|
}
|
||||||
|
}
|
||||||
31
tests/FFMpeg/Tests/Coordinate/FrameRateTest.php
Normal file
31
tests/FFMpeg/Tests/Coordinate/FrameRateTest.php
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace FFMpeg\Tests\Coordinate;
|
||||||
|
|
||||||
|
use FFMpeg\Tests\TestCase;
|
||||||
|
use FFMpeg\Coordinate\FrameRate;
|
||||||
|
|
||||||
|
class FrameRateTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testGetter()
|
||||||
|
{
|
||||||
|
$fr = new FrameRate(23.997);
|
||||||
|
$this->assertEquals(23.997, $fr->getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider provideInvalidFrameRates
|
||||||
|
* @expectedException FFMpeg\Exception\InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function testInvalidFrameRate($value)
|
||||||
|
{
|
||||||
|
new FrameRate($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideInvalidFrameRates()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
array(0), array(-1.5), array(-2),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
16
tests/FFMpeg/Tests/Coordinate/PointTest.php
Normal file
16
tests/FFMpeg/Tests/Coordinate/PointTest.php
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace FFMpeg\Tests\Coordinate;
|
||||||
|
|
||||||
|
use FFMpeg\Tests\TestCase;
|
||||||
|
use FFMpeg\Coordinate\Point;
|
||||||
|
|
||||||
|
class PointTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testGetters()
|
||||||
|
{
|
||||||
|
$point = new Point(4, 25);
|
||||||
|
$this->assertEquals(4, $point->getX());
|
||||||
|
$this->assertEquals(25, $point->getY());
|
||||||
|
}
|
||||||
|
}
|
||||||
38
tests/FFMpeg/Tests/Coordinate/TimeCodeTest.php
Normal file
38
tests/FFMpeg/Tests/Coordinate/TimeCodeTest.php
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace FFMpeg\Tests\Coordinate;
|
||||||
|
|
||||||
|
use FFMpeg\Tests\TestCase;
|
||||||
|
use FFMpeg\Coordinate\TimeCode;
|
||||||
|
|
||||||
|
class TimeCodeTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @dataProvider provideTimecodes
|
||||||
|
*/
|
||||||
|
public function testFromString($timecode, $expected)
|
||||||
|
{
|
||||||
|
$tc = TimeCode::fromString($timecode);
|
||||||
|
$this->assertEquals((string) $tc, $expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideTimeCodes()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
array('1:02:04:05:20', '26:04:05.20'),
|
||||||
|
array('1:02:04:05.20', '26:04:05.20'),
|
||||||
|
array('02:04:05:20', '02:04:05.20'),
|
||||||
|
array('02:04:05.20', '02:04:05.20'),
|
||||||
|
array('00:00:05.20', '00:00:05.20'),
|
||||||
|
array('00:00:00.00', '00:00:00.00'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException FFMpeg\Exception\InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function testFromInvalidString()
|
||||||
|
{
|
||||||
|
TimeCode::fromString('lalali lala');
|
||||||
|
}
|
||||||
|
}
|
||||||
43
tests/FFMpeg/Tests/Driver/FFMpegDriverTest.php
Normal file
43
tests/FFMpeg/Tests/Driver/FFMpegDriverTest.php
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace FFMpeg\Tests\Driver;
|
||||||
|
|
||||||
|
use Alchemy\BinaryDriver\Configuration;
|
||||||
|
use FFMpeg\Driver\FFMpegDriver;
|
||||||
|
use FFMpeg\Tests\TestCase;
|
||||||
|
use Symfony\Component\Process\ExecutableFinder;
|
||||||
|
|
||||||
|
class FFMpegDriverTest extends TestCase
|
||||||
|
{
|
||||||
|
public function setUp()
|
||||||
|
{
|
||||||
|
$executableFinder = new ExecutableFinder();
|
||||||
|
|
||||||
|
$found = false;
|
||||||
|
foreach (array('avconv', 'ffmpeg') as $name) {
|
||||||
|
if (null !== $executableFinder->find($name)) {
|
||||||
|
$found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$found) {
|
||||||
|
$this->markTestSkipped('Neither ffmpeg or avconv found');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCreate()
|
||||||
|
{
|
||||||
|
$logger = $this->getLoggerMock();
|
||||||
|
$ffmpeg = FFMpegDriver::create($logger, array());
|
||||||
|
$this->assertInstanceOf('FFMpeg\Driver\FFMpegDriver', $ffmpeg);
|
||||||
|
$this->assertEquals($logger, $ffmpeg->getProcessRunner()->getLogger());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCreateWithConfig()
|
||||||
|
{
|
||||||
|
$conf = new Configuration();
|
||||||
|
$ffmpeg = FFMpegDriver::create($this->getLoggerMock(), $conf);
|
||||||
|
$this->assertEquals($conf, $ffmpeg->getConfiguration());
|
||||||
|
}
|
||||||
|
}
|
||||||
43
tests/FFMpeg/Tests/Driver/FFProbeDriverTest.php
Normal file
43
tests/FFMpeg/Tests/Driver/FFProbeDriverTest.php
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace FFMpeg\Tests\Driver;
|
||||||
|
|
||||||
|
use Alchemy\BinaryDriver\Configuration;
|
||||||
|
use FFMpeg\Driver\FFProbeDriver;
|
||||||
|
use FFMpeg\Tests\TestCase;
|
||||||
|
use Symfony\Component\Process\ExecutableFinder;
|
||||||
|
|
||||||
|
class FFProbeDriverTest extends TestCase
|
||||||
|
{
|
||||||
|
public function setUp()
|
||||||
|
{
|
||||||
|
$executableFinder = new ExecutableFinder();
|
||||||
|
|
||||||
|
$found = false;
|
||||||
|
foreach (array('avprobe', 'ffprobe') as $name) {
|
||||||
|
if (null !== $executableFinder->find($name)) {
|
||||||
|
$found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$found) {
|
||||||
|
$this->markTestSkipped('Neither ffprobe or avprobe found');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCreate()
|
||||||
|
{
|
||||||
|
$logger = $this->getLoggerMock();
|
||||||
|
$ffprobe = FFProbeDriver::create(array(), $logger);
|
||||||
|
$this->assertInstanceOf('FFMpeg\Driver\FFProbeDriver', $ffprobe);
|
||||||
|
$this->assertEquals($logger, $ffprobe->getProcessRunner()->getLogger());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCreateWithConfig()
|
||||||
|
{
|
||||||
|
$conf = new Configuration();
|
||||||
|
$ffprobe = FFProbeDriver::create($conf, $this->getLoggerMock());
|
||||||
|
$this->assertEquals($conf, $ffprobe->getConfiguration());
|
||||||
|
}
|
||||||
|
}
|
||||||
69
tests/FFMpeg/Tests/FFMpegServiceProviderTest.php
Normal file
69
tests/FFMpeg/Tests/FFMpegServiceProviderTest.php
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace FFMpeg\Tests;
|
||||||
|
|
||||||
|
use FFMpeg\FFMpegServiceProvider;
|
||||||
|
use Silex\Application;
|
||||||
|
|
||||||
|
class FFMpegServiceProviderTest extends \PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
public function testWithConfig()
|
||||||
|
{
|
||||||
|
$app = new Application();
|
||||||
|
$app->register(new FFMpegServiceProvider(), array(
|
||||||
|
'ffmpeg.configuration' => array(
|
||||||
|
'ffmpeg.threads' => 12,
|
||||||
|
'ffmpeg.timeout' => 10666,
|
||||||
|
'ffprobe.timeout' => 4242,
|
||||||
|
)
|
||||||
|
));
|
||||||
|
|
||||||
|
$this->assertInstanceOf('FFMpeg\FFMpeg', $app['ffmpeg']);
|
||||||
|
$this->assertSame($app['ffmpeg'], $app['ffmpeg.ffmpeg']);
|
||||||
|
$this->assertInstanceOf('FFMpeg\FFProbe', $app['ffmpeg.ffprobe']);
|
||||||
|
|
||||||
|
$this->assertEquals(12, $app['ffmpeg']->getFFMpegDriver()->getConfiguration()->get('ffmpeg.threads'));
|
||||||
|
$this->assertEquals(10666, $app['ffmpeg']->getFFMpegDriver()->getProcessBuilderFactory()->getTimeout());
|
||||||
|
$this->assertEquals(4242, $app['ffmpeg.ffprobe']->getFFProbeDriver()->getProcessBuilderFactory()->getTimeout());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testWithoutConfig()
|
||||||
|
{
|
||||||
|
$app = new Application();
|
||||||
|
$app->register(new FFMpegServiceProvider());
|
||||||
|
|
||||||
|
$this->assertInstanceOf('FFMpeg\FFMpeg', $app['ffmpeg']);
|
||||||
|
$this->assertSame($app['ffmpeg'], $app['ffmpeg.ffmpeg']);
|
||||||
|
$this->assertInstanceOf('FFMpeg\FFProbe', $app['ffmpeg.ffprobe']);
|
||||||
|
|
||||||
|
$this->assertEquals(4, $app['ffmpeg']->getFFMpegDriver()->getConfiguration()->get('ffmpeg.threads'));
|
||||||
|
$this->assertEquals(300, $app['ffmpeg']->getFFMpegDriver()->getProcessBuilderFactory()->getTimeout());
|
||||||
|
$this->assertEquals(30, $app['ffmpeg.ffprobe']->getFFProbeDriver()->getProcessBuilderFactory()->getTimeout());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testWithFFMpegBinaryConfig()
|
||||||
|
{
|
||||||
|
$app = new Application();
|
||||||
|
$app->register(new FFMpegServiceProvider(), array(
|
||||||
|
'ffmpeg.configuration' => array(
|
||||||
|
'ffmpeg.binaries' => '/path/to/ffmpeg',
|
||||||
|
)
|
||||||
|
));
|
||||||
|
|
||||||
|
$this->setExpectedException('Alchemy\BinaryDriver\Exception\ExecutableNotFoundException', 'Executable not found, proposed : /path/to/ffmpeg');
|
||||||
|
$app['ffmpeg'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testWithFFMprobeBinaryConfig()
|
||||||
|
{
|
||||||
|
$app = new Application();
|
||||||
|
$app->register(new FFMpegServiceProvider(), array(
|
||||||
|
'ffmpeg.configuration' => array(
|
||||||
|
'ffprobe.binaries' => '/path/to/ffprobe',
|
||||||
|
)
|
||||||
|
));
|
||||||
|
|
||||||
|
$this->setExpectedException('Alchemy\BinaryDriver\Exception\ExecutableNotFoundException', 'Executable not found, proposed : /path/to/ffprobe');
|
||||||
|
$app['ffmpeg.ffprobe'];
|
||||||
|
}
|
||||||
|
}
|
||||||
112
tests/FFMpeg/Tests/FFMpegTest.php
Normal file
112
tests/FFMpeg/Tests/FFMpegTest.php
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace FFMpeg\Tests;
|
||||||
|
|
||||||
|
use FFMpeg\FFMpeg;
|
||||||
|
use FFMpeg\Tests\TestCase;
|
||||||
|
use FFMpeg\FFProbe\DataMapping\StreamCollection;
|
||||||
|
use FFMpeg\FFProbe\DataMapping\Stream;
|
||||||
|
|
||||||
|
class FFMpegTest Extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @expectedException FFMpeg\Exception\InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function testOpenInvalid()
|
||||||
|
{
|
||||||
|
$ffmpeg = new FFMpeg($this->getFFMpegDriverMock(), $this->getFFProbeMock());
|
||||||
|
$ffmpeg->open('/path/to/unknown/file');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOpenAudio()
|
||||||
|
{
|
||||||
|
$streams = $this->getStreamCollectionMock();
|
||||||
|
$streams->expects($this->once())
|
||||||
|
->method('audios')
|
||||||
|
->will($this->returnValue(new StreamCollection(array(new Stream(array())))));
|
||||||
|
$streams->expects($this->once())
|
||||||
|
->method('videos')
|
||||||
|
->will($this->returnValue(array()));
|
||||||
|
|
||||||
|
$ffprobe = $this->getFFProbeMock();
|
||||||
|
$ffprobe->expects($this->once())
|
||||||
|
->method('streams')
|
||||||
|
->with(__FILE__)
|
||||||
|
->will($this->returnValue($streams));
|
||||||
|
|
||||||
|
$ffmpeg = new FFMpeg($this->getFFMpegDriverMock(), $ffprobe);
|
||||||
|
$this->assertInstanceOf('FFMpeg\Media\Audio', $ffmpeg->open(__FILE__));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOpenVideo()
|
||||||
|
{
|
||||||
|
$streams = $this->getStreamCollectionMock();
|
||||||
|
$streams->expects($this->once())
|
||||||
|
->method('videos')
|
||||||
|
->will($this->returnValue(new StreamCollection(array(new Stream(array())))));
|
||||||
|
$streams->expects($this->never())
|
||||||
|
->method('audios');
|
||||||
|
|
||||||
|
$ffprobe = $this->getFFProbeMock();
|
||||||
|
$ffprobe->expects($this->once())
|
||||||
|
->method('streams')
|
||||||
|
->with(__FILE__)
|
||||||
|
->will($this->returnValue($streams));
|
||||||
|
|
||||||
|
$ffmpeg = new FFMpeg($this->getFFMpegDriverMock(), $ffprobe);
|
||||||
|
$this->assertInstanceOf('FFMpeg\Media\Video', $ffmpeg->open(__FILE__));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException FFMpeg\Exception\InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function testOpenUnknown()
|
||||||
|
{
|
||||||
|
$ffprobe = $this->getFFProbeMock();
|
||||||
|
$ffprobe->expects($this->once())
|
||||||
|
->method('streams')
|
||||||
|
->with(__FILE__)
|
||||||
|
->will($this->returnValue(new StreamCollection()));
|
||||||
|
|
||||||
|
$ffmpeg = new FFMpeg($this->getFFMpegDriverMock(), $ffprobe);
|
||||||
|
$ffmpeg->open(__FILE__);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCreateWithoutLoggerOrProbe()
|
||||||
|
{
|
||||||
|
$this->assertInstanceOf('FFMpeg\FFMpeg', FFMpeg::create());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCreateWithLoggerAndProbe()
|
||||||
|
{
|
||||||
|
$logger = $this->getLoggerMock();
|
||||||
|
$ffprobe = $this->getFFProbeMock();
|
||||||
|
|
||||||
|
$ffmpeg = FFMpeg::create(array('timeout' => 42), $logger, $ffprobe);
|
||||||
|
$this->assertInstanceOf('FFMpeg\FFMpeg', $ffmpeg);
|
||||||
|
|
||||||
|
$this->assertSame($logger, $ffmpeg->getFFMpegDriver()->getProcessRunner()->getLogger());
|
||||||
|
$this->assertSame($ffprobe, $ffmpeg->getFFProbe());
|
||||||
|
$this->assertSame(42, $ffmpeg->getFFMpegDriver()->getProcessBuilderFactory()->getTimeout());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetSetFFProbe()
|
||||||
|
{
|
||||||
|
$ffprobe = $this->getFFProbeMock();
|
||||||
|
$ffmpeg = new FFMpeg($this->getFFMpegDriverMock(), $ffprobe);
|
||||||
|
$this->assertSame($ffprobe, $ffmpeg->getFFProbe());
|
||||||
|
$anotherFFProbe = $this->getFFProbeMock();
|
||||||
|
$ffmpeg->setFFProbe($anotherFFProbe);
|
||||||
|
$this->assertSame($anotherFFProbe, $ffmpeg->getFFProbe());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetSetDriver()
|
||||||
|
{
|
||||||
|
$driver = $this->getFFMpegDriverMock();
|
||||||
|
$ffmpeg = new FFMpeg($driver, $this->getFFProbeMock());
|
||||||
|
$this->assertSame($driver, $ffmpeg->getFFMpegDriver());
|
||||||
|
$anotherDriver = $this->getFFMpegDriverMock();
|
||||||
|
$ffmpeg->setFFMpegDriver($anotherDriver);
|
||||||
|
$this->assertSame($anotherDriver, $ffmpeg->getFFMpegDriver());
|
||||||
|
}
|
||||||
|
}
|
||||||
56
tests/FFMpeg/Tests/FFProbe/DataMapping/AbstractDataTest.php
Normal file
56
tests/FFMpeg/Tests/FFProbe/DataMapping/AbstractDataTest.php
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace FFMpeg\Tests\FFProbe\DataMapping;
|
||||||
|
|
||||||
|
use FFMpeg\Tests\TestCase;
|
||||||
|
use FFMpeg\FFProbe\DataMapping\AbstractData;
|
||||||
|
|
||||||
|
class AbstractDataTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testHas()
|
||||||
|
{
|
||||||
|
$imp = new Implementation(array('key1' => 'value1', 'key2' => 'value2'));
|
||||||
|
|
||||||
|
$this->assertTrue($imp->has('key1'));
|
||||||
|
$this->assertTrue($imp->has('key2'));
|
||||||
|
$this->assertFalse($imp->has('value1'));
|
||||||
|
$this->assertFalse($imp->has('key3'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGet()
|
||||||
|
{
|
||||||
|
$imp = new Implementation(array('key1' => 'value1', 'key2' => 'value2'));
|
||||||
|
|
||||||
|
$this->assertEquals('value1', $imp->get('key1'));
|
||||||
|
$this->assertEquals('value2', $imp->get('key2'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException FFMpeg\Exception\InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function testGetInvalid()
|
||||||
|
{
|
||||||
|
$imp = new Implementation(array('key1' => 'value1', 'key2' => 'value2'));
|
||||||
|
|
||||||
|
$imp->get('key3');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testKeys()
|
||||||
|
{
|
||||||
|
$imp = new Implementation(array('key1' => 'value1', 'key2' => 'value2'));
|
||||||
|
|
||||||
|
$this->assertEquals(array('key1', 'key2'), $imp->keys());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAll()
|
||||||
|
{
|
||||||
|
$values = array('key1' => 'value1', 'key2' => 'value2');
|
||||||
|
$imp = new Implementation($values);
|
||||||
|
|
||||||
|
$this->assertEquals($values, $imp->all());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Implementation extends AbstractData
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,89 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace FFMpeg\Tests\FFProbe\DataMapping;
|
||||||
|
|
||||||
|
use FFMpeg\Tests\TestCase;
|
||||||
|
use FFMpeg\FFProbe\DataMapping\StreamCollection;
|
||||||
|
|
||||||
|
class StreamCollectionTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testAdd()
|
||||||
|
{
|
||||||
|
$stream = $this->getStreamMock();
|
||||||
|
|
||||||
|
$collection = new StreamCollection();
|
||||||
|
$this->assertEquals(array(), $collection->all());
|
||||||
|
$collection->add($stream);
|
||||||
|
$this->assertEquals(array($stream), $collection->all());
|
||||||
|
$collection->add($stream);
|
||||||
|
$this->assertEquals(array($stream, $stream), $collection->all());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testVideos()
|
||||||
|
{
|
||||||
|
$audio = $this->getStreamMock();
|
||||||
|
$audio->expects($this->once())
|
||||||
|
->method('isVideo')
|
||||||
|
->will($this->returnValue(false));
|
||||||
|
|
||||||
|
$video = $this->getStreamMock();
|
||||||
|
$video->expects($this->once())
|
||||||
|
->method('isVideo')
|
||||||
|
->will($this->returnValue(true));
|
||||||
|
|
||||||
|
$collection = new StreamCollection(array($audio, $video));
|
||||||
|
$videos = $collection->videos();
|
||||||
|
|
||||||
|
$this->assertInstanceOf('FFMpeg\FFProbe\DataMapping\StreamCollection', $videos);
|
||||||
|
$this->assertCount(1, $videos);
|
||||||
|
$this->assertEquals(array($video), $videos->all());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAudios()
|
||||||
|
{
|
||||||
|
$audio = $this->getStreamMock();
|
||||||
|
$audio->expects($this->once())
|
||||||
|
->method('isAudio')
|
||||||
|
->will($this->returnValue(true));
|
||||||
|
|
||||||
|
$video = $this->getStreamMock();
|
||||||
|
$video->expects($this->once())
|
||||||
|
->method('isAudio')
|
||||||
|
->will($this->returnValue(false));
|
||||||
|
|
||||||
|
$collection = new StreamCollection(array($audio, $video));
|
||||||
|
$audios = $collection->audios();
|
||||||
|
|
||||||
|
$this->assertInstanceOf('FFMpeg\FFProbe\DataMapping\StreamCollection', $audios);
|
||||||
|
$this->assertCount(1, $audios);
|
||||||
|
$this->assertEquals(array($audio), $audios->all());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCount()
|
||||||
|
{
|
||||||
|
$stream = $this->getStreamMock();
|
||||||
|
|
||||||
|
$collection = new StreamCollection(array($stream));
|
||||||
|
$this->assertCount(1, $collection);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetIterator()
|
||||||
|
{
|
||||||
|
$audio = $this->getStreamMock();
|
||||||
|
$video = $this->getStreamMock();
|
||||||
|
|
||||||
|
$collection = new StreamCollection(array($audio, $video));
|
||||||
|
$this->assertInstanceOf('\Iterator', $collection->getIterator());
|
||||||
|
$this->assertCount(2, $collection->getIterator());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFirst()
|
||||||
|
{
|
||||||
|
$stream1 = $this->getStreamMock();
|
||||||
|
$stream2 = $this->getStreamMock();
|
||||||
|
|
||||||
|
$coll = new StreamCollection(array($stream1, $stream2));
|
||||||
|
|
||||||
|
$this->assertSame($stream1, $coll->first());
|
||||||
|
}
|
||||||
|
}
|
||||||
43
tests/FFMpeg/Tests/FFProbe/DataMapping/StreamTest.php
Normal file
43
tests/FFMpeg/Tests/FFProbe/DataMapping/StreamTest.php
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace FFMpeg\Tests\FFProbe\DataMapping;
|
||||||
|
|
||||||
|
use FFMpeg\Tests\TestCase;
|
||||||
|
use FFMpeg\FFProbe\DataMapping\Stream;
|
||||||
|
|
||||||
|
class StreamTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @dataProvider provideAudioCases
|
||||||
|
*/
|
||||||
|
public function testIsAudio($isAudio, $properties)
|
||||||
|
{
|
||||||
|
$stream = new Stream($properties);
|
||||||
|
$this->assertTrue($isAudio === $stream->isAudio());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideAudioCases()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
array(true, array('codec_type' => 'audio')),
|
||||||
|
array(false, array('codec_type' => 'video')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider provideVideoCases
|
||||||
|
*/
|
||||||
|
public function testIsVideo($isVideo, $properties)
|
||||||
|
{
|
||||||
|
$stream = new Stream($properties);
|
||||||
|
$this->assertTrue($isVideo === $stream->isVideo());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideVideoCases()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
array(true, array('codec_type' => 'video')),
|
||||||
|
array(false, array('codec_type' => 'audio')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
44
tests/FFMpeg/Tests/FFProbe/MapperTest.php
Normal file
44
tests/FFMpeg/Tests/FFProbe/MapperTest.php
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace FFMpeg\Tests\FFProbe;
|
||||||
|
|
||||||
|
use FFMpeg\Tests\TestCase;
|
||||||
|
use FFMpeg\FFProbe\Mapper;
|
||||||
|
use FFMpeg\FFProbe;
|
||||||
|
use FFMpeg\FFProbe\DataMapping\Format;
|
||||||
|
use FFMpeg\FFProbe\DataMapping\Stream;
|
||||||
|
use FFMpeg\FFProbe\DataMapping\StreamCollection;
|
||||||
|
|
||||||
|
class MapperTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @dataProvider provideMappings
|
||||||
|
*/
|
||||||
|
public function testMap($type, $data, $expected)
|
||||||
|
{
|
||||||
|
$mapper = new Mapper();
|
||||||
|
$this->assertEquals($expected, $mapper->map($type, $data));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException FFMpeg\Exception\InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function testMapInvalidArgument()
|
||||||
|
{
|
||||||
|
$mapper = new Mapper();
|
||||||
|
$mapper->map('cool type', 'data');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideMappings()
|
||||||
|
{
|
||||||
|
$format = json_decode(file_get_contents(__DIR__ . '/../../../fixtures/ffprobe/show_format.json'), true);
|
||||||
|
$streams = json_decode(file_get_contents(__DIR__ . '/../../../fixtures/ffprobe/show_streams.json'), true);
|
||||||
|
|
||||||
|
return array(
|
||||||
|
array(FFProbe::TYPE_FORMAT, $format, new Format($format['format'])),
|
||||||
|
array(FFProbe::TYPE_STREAMS, $streams, new StreamCollection(array_map(function ($streamData) {
|
||||||
|
return new Stream($streamData);
|
||||||
|
}, $streams['streams']))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
101
tests/FFMpeg/Tests/FFProbe/OptionsTesterTest.php
Normal file
101
tests/FFMpeg/Tests/FFProbe/OptionsTesterTest.php
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace FFMpeg\Tests\FFProbe;
|
||||||
|
|
||||||
|
use FFMpeg\Tests\TestCase;
|
||||||
|
use FFMpeg\FFProbe\OptionsTester;
|
||||||
|
|
||||||
|
class OptionsTesterTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @dataProvider provideOptions
|
||||||
|
*/
|
||||||
|
public function testHasOptionWithCacheEmpty($isPresent, $data, $optionName)
|
||||||
|
{
|
||||||
|
$cache = $this->getCacheMock();
|
||||||
|
|
||||||
|
$cache->expects($this->never())
|
||||||
|
->method('fetch');
|
||||||
|
|
||||||
|
$cache->expects($this->exactly(2))
|
||||||
|
->method('contains')
|
||||||
|
->will($this->returnValue(false));
|
||||||
|
|
||||||
|
$cache->expects($this->exactly(2))
|
||||||
|
->method('save');
|
||||||
|
|
||||||
|
$ffprobe = $this->getFFProbeDriverMock();
|
||||||
|
$ffprobe->expects($this->once())
|
||||||
|
->method('command')
|
||||||
|
->with(array('-help', '-loglevel', 'quiet'))
|
||||||
|
->will($this->returnValue($data));
|
||||||
|
|
||||||
|
$tester = new OptionsTester($ffprobe, $cache);
|
||||||
|
$this->assertTrue($isPresent === $tester->has($optionName));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideOptions()
|
||||||
|
{
|
||||||
|
$data = file_get_contents(__DIR__ . '/../../../fixtures/ffprobe/help.raw');
|
||||||
|
|
||||||
|
return array(
|
||||||
|
array(true, $data, '-print_format'),
|
||||||
|
array(false, $data, '-another_print_format'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider provideOptions
|
||||||
|
*/
|
||||||
|
public function testHasOptionWithHelpCacheLoaded($isPresent, $data, $optionName)
|
||||||
|
{
|
||||||
|
$cache = $this->getCacheMock();
|
||||||
|
|
||||||
|
$cache->expects($this->once())
|
||||||
|
->method('fetch')
|
||||||
|
->will($this->returnValue($data));
|
||||||
|
|
||||||
|
$cache->expects($this->at(0))
|
||||||
|
->method('contains')
|
||||||
|
->will($this->returnValue(false));
|
||||||
|
|
||||||
|
$cache->expects($this->at(1))
|
||||||
|
->method('contains')
|
||||||
|
->will($this->returnValue(true));
|
||||||
|
|
||||||
|
$cache->expects($this->once())
|
||||||
|
->method('save');
|
||||||
|
|
||||||
|
$ffprobe = $this->getFFProbeDriverMock();
|
||||||
|
$ffprobe->expects($this->never())
|
||||||
|
->method('command');
|
||||||
|
|
||||||
|
$tester = new OptionsTester($ffprobe, $cache);
|
||||||
|
$this->assertTrue($isPresent === $tester->has($optionName));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider provideOptions
|
||||||
|
*/
|
||||||
|
public function testHasOptionWithCacheFullyLoaded($isPresent, $data, $optionName)
|
||||||
|
{
|
||||||
|
$cache = $this->getCacheMock();
|
||||||
|
|
||||||
|
$cache->expects($this->once())
|
||||||
|
->method('fetch')
|
||||||
|
->with('option-' . $optionName)
|
||||||
|
->will($this->returnValue($isPresent));
|
||||||
|
|
||||||
|
$cache->expects($this->once())
|
||||||
|
->method('contains')
|
||||||
|
->with('option-' . $optionName)
|
||||||
|
->will($this->returnValue(true));
|
||||||
|
|
||||||
|
$ffprobe = $this->getFFProbeDriverMock();
|
||||||
|
$ffprobe->expects($this->never())
|
||||||
|
->method('command');
|
||||||
|
|
||||||
|
$tester = new OptionsTester($ffprobe, $cache);
|
||||||
|
$this->assertTrue($isPresent === $tester->has($optionName));
|
||||||
|
}
|
||||||
|
}
|
||||||
42
tests/FFMpeg/Tests/FFProbe/OutputParserTest.php
Normal file
42
tests/FFMpeg/Tests/FFProbe/OutputParserTest.php
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace FFMpeg\Tests\FFProbe;
|
||||||
|
|
||||||
|
use FFMpeg\Tests\TestCase;
|
||||||
|
use FFMpeg\FFProbe\OutputParser;
|
||||||
|
use FFMpeg\FFProbe;
|
||||||
|
|
||||||
|
class OutputParserTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @dataProvider provideTypeDataAndOutput
|
||||||
|
*/
|
||||||
|
public function testParse($type, $data, $expectedOutput)
|
||||||
|
{
|
||||||
|
$parser = new OutputParser();
|
||||||
|
$this->assertEquals($expectedOutput, $parser->parse($type, $data));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException FFMpeg\Exception\InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function testParseWithInvalidArgument()
|
||||||
|
{
|
||||||
|
$parser = new OutputParser();
|
||||||
|
$parser->parse('comme ca', 'data');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideTypeDataAndOutput()
|
||||||
|
{
|
||||||
|
$expectedFormat = json_decode(file_get_contents(__DIR__ . '/../../../fixtures/ffprobe/show_format.json'), true);
|
||||||
|
$expectedStreams = json_decode(file_get_contents(__DIR__ . '/../../../fixtures/ffprobe/show_streams.json'), true);
|
||||||
|
|
||||||
|
$rawFormat = file_get_contents(__DIR__ . '/../../../fixtures/ffprobe/show_format.raw');
|
||||||
|
$rawStreams = file_get_contents(__DIR__ . '/../../../fixtures/ffprobe/show_streams.raw');
|
||||||
|
|
||||||
|
return array(
|
||||||
|
array(FFProbe::TYPE_FORMAT, $rawFormat, $expectedFormat),
|
||||||
|
array(FFProbe::TYPE_STREAMS, $rawStreams, $expectedStreams),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
308
tests/FFMpeg/Tests/FFProbeTest.php
Normal file
308
tests/FFMpeg/Tests/FFProbeTest.php
Normal file
|
|
@ -0,0 +1,308 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace FFMpeg\Tests;
|
||||||
|
|
||||||
|
use FFMpeg\Tests\TestCase;
|
||||||
|
use FFMpeg\FFProbe;
|
||||||
|
use Symfony\Component\Process\ExecutableFinder;
|
||||||
|
use Alchemy\BinaryDriver\ConfigurationInterface;
|
||||||
|
use Alchemy\BinaryDriver\Configuration;
|
||||||
|
|
||||||
|
class FFProbeTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testGetSetParser()
|
||||||
|
{
|
||||||
|
$ffprobe = new FFProbe($this->getFFProbeDriverMock(), $this->getCacheMock());
|
||||||
|
$parser = $this->getFFProbeParserMock();
|
||||||
|
|
||||||
|
$ffprobe->setParser($parser);
|
||||||
|
$this->assertSame($parser, $ffprobe->getParser());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetSetFFProbeDriver()
|
||||||
|
{
|
||||||
|
$ffprobe = new FFProbe($this->getFFProbeDriverMock(), $this->getCacheMock());
|
||||||
|
$driver = $this->getFFProbeDriverMock();
|
||||||
|
|
||||||
|
$ffprobe->setFFProbeDriver($driver);
|
||||||
|
$this->assertSame($driver, $ffprobe->getFFProbeDriver());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetSetFFProbeMapper()
|
||||||
|
{
|
||||||
|
$ffprobe = new FFProbe($this->getFFProbeDriverMock(), $this->getCacheMock());
|
||||||
|
$mapper = $this->getFFProbeMapperMock();
|
||||||
|
|
||||||
|
$ffprobe->setMapper($mapper);
|
||||||
|
$this->assertSame($mapper, $ffprobe->getMapper());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetSetOptionsTester()
|
||||||
|
{
|
||||||
|
$ffprobe = new FFProbe($this->getFFProbeDriverMock(), $this->getCacheMock());
|
||||||
|
$tester = $this->getFFProbeOptionsTesterMock();
|
||||||
|
|
||||||
|
$ffprobe->setOptionsTester($tester);
|
||||||
|
$this->assertSame($tester, $ffprobe->getOptionsTester());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetSetCache()
|
||||||
|
{
|
||||||
|
$ffprobe = new FFProbe($this->getFFProbeDriverMock(), $this->getCacheMock());
|
||||||
|
$cache = $this->getCacheMock();
|
||||||
|
|
||||||
|
$ffprobe->setCache($cache);
|
||||||
|
$this->assertSame($cache, $ffprobe->getCache());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideDataWhitoutCache()
|
||||||
|
{
|
||||||
|
$stream = $this->getStreamMock();
|
||||||
|
$format = $this->getFormatMock();
|
||||||
|
|
||||||
|
return array(
|
||||||
|
array($stream, 'streams', array('-show_streams', '-print_format'), FFProbe::TYPE_STREAMS, array(__FILE__, '-show_streams', '-print_format', 'json'), false),
|
||||||
|
array($format, 'format', array('-show_format', '-print_format'), FFProbe::TYPE_FORMAT, array(__FILE__, '-show_format', '-print_format', 'json'), false),
|
||||||
|
array($stream, 'streams', array('-show_streams'), FFProbe::TYPE_STREAMS, array(__FILE__, '-show_streams'), true),
|
||||||
|
array($format, 'format', array('-show_format'), FFProbe::TYPE_FORMAT, array(__FILE__, '-show_format'), true),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider provideDataWhitoutCache
|
||||||
|
*/
|
||||||
|
public function testProbeWithoutCache($output, $method, $commands, $type, $caughtCommands, $isRaw)
|
||||||
|
{
|
||||||
|
$pathfile = __FILE__;
|
||||||
|
$data = array('key' => 'value');
|
||||||
|
$rawData = 'raw data';
|
||||||
|
|
||||||
|
$ffprobe = new FFProbe($this->getFFProbeDriverMock(), $this->getCacheMock());
|
||||||
|
|
||||||
|
$mapper = $this->getFFProbeMapperMock();
|
||||||
|
$mapper->expects($this->once())
|
||||||
|
->method('map')
|
||||||
|
->with($type, $data)
|
||||||
|
->will($this->returnValue($output));
|
||||||
|
|
||||||
|
$parser = $this->getFFProbeParserMock();
|
||||||
|
|
||||||
|
if ($isRaw) {
|
||||||
|
$parser->expects($this->once())
|
||||||
|
->method('parse')
|
||||||
|
->with($type, $rawData)
|
||||||
|
->will($this->returnValue($data));
|
||||||
|
} else {
|
||||||
|
$parser->expects($this->never())
|
||||||
|
->method('parse');
|
||||||
|
}
|
||||||
|
|
||||||
|
$tester = $this->getFFProbeOptionsTesterMockWithOptions($commands);
|
||||||
|
|
||||||
|
$cache = $this->getCacheMock();
|
||||||
|
$cache->expects($this->once())
|
||||||
|
->method('contains')
|
||||||
|
->will($this->returnValue(false));
|
||||||
|
$cache->expects($this->never())
|
||||||
|
->method('fetch');
|
||||||
|
$cache->expects($this->once())
|
||||||
|
->method('save')
|
||||||
|
->with($this->anything(), $output);
|
||||||
|
|
||||||
|
$driver = $this->getFFProbeDriverMock();
|
||||||
|
$driver->expects($this->once())
|
||||||
|
->method('command')
|
||||||
|
->with($caughtCommands)
|
||||||
|
->will($this->returnValue($isRaw ? $rawData : json_encode($data)));
|
||||||
|
|
||||||
|
$ffprobe->setOptionsTester($tester)
|
||||||
|
->setCache($cache)
|
||||||
|
->setMapper($mapper)
|
||||||
|
->setFFProbeDriver($driver)
|
||||||
|
->setParser($parser);
|
||||||
|
|
||||||
|
$this->assertEquals($output, call_user_func(array($ffprobe, $method), $pathfile));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideDataForInvalidJson()
|
||||||
|
{
|
||||||
|
$stream = $this->getStreamMock();
|
||||||
|
$format = $this->getFormatMock();
|
||||||
|
|
||||||
|
return array(
|
||||||
|
array($stream, 'streams', array('-show_streams', '-print_format'), FFProbe::TYPE_STREAMS, array(__FILE__, '-show_streams', '-print_format', 'json')),
|
||||||
|
array($format, 'format', array('-show_format', '-print_format'), FFProbe::TYPE_FORMAT, array(__FILE__, '-show_format', '-print_format', 'json')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider provideDataForInvalidJson
|
||||||
|
*/
|
||||||
|
public function testProbeWithWrongJson($output, $method, $commands, $type, $caughtCommands)
|
||||||
|
{
|
||||||
|
$pathfile = __FILE__;
|
||||||
|
$data = array('key' => 'value');
|
||||||
|
|
||||||
|
$ffprobe = new FFProbe($this->getFFProbeDriverMock(), $this->getCacheMock());
|
||||||
|
|
||||||
|
$mapper = $this->getFFProbeMapperMock();
|
||||||
|
$mapper->expects($this->once())
|
||||||
|
->method('map')
|
||||||
|
->with($this->isType('string'), 'good data parsed')
|
||||||
|
->will($this->returnValue($output));
|
||||||
|
|
||||||
|
$parser = $this->getFFProbeParserMock();
|
||||||
|
$parser->expects($this->once())
|
||||||
|
->method('parse')
|
||||||
|
->with($this->isType('string', json_encode($data) . 'lala'))
|
||||||
|
->will($this->returnValue('good data parsed'));
|
||||||
|
|
||||||
|
$tester = $this->getFFProbeOptionsTesterMockWithOptions($commands);
|
||||||
|
|
||||||
|
$cache = $this->getCacheMock();
|
||||||
|
$cache->expects($this->exactly(2))
|
||||||
|
->method('contains')
|
||||||
|
->will($this->returnValue(false));
|
||||||
|
$cache->expects($this->never())
|
||||||
|
->method('fetch');
|
||||||
|
|
||||||
|
$driver = $this->getFFProbeDriverMock();
|
||||||
|
$driver->expects($this->exactly(2))
|
||||||
|
->method('command')
|
||||||
|
->will($this->returnValue(json_encode($data) . 'lala'));
|
||||||
|
|
||||||
|
$ffprobe->setOptionsTester($tester)
|
||||||
|
->setCache($cache)
|
||||||
|
->setMapper($mapper)
|
||||||
|
->setFFProbeDriver($driver)
|
||||||
|
->setParser($parser);
|
||||||
|
|
||||||
|
$this->assertEquals($output, call_user_func(array($ffprobe, $method), $pathfile));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideProbingDataWithCache()
|
||||||
|
{
|
||||||
|
$stream = $this->getStreamMock();
|
||||||
|
$format = $this->getFormatMock();
|
||||||
|
|
||||||
|
return array(
|
||||||
|
array($stream, 'streams'),
|
||||||
|
array($format, 'format'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider provideProbingDataWithCache
|
||||||
|
*/
|
||||||
|
public function testProbeWithCache($output, $method)
|
||||||
|
{
|
||||||
|
$pathfile = __FILE__;
|
||||||
|
|
||||||
|
$ffprobe = new FFProbe($this->getFFProbeDriverMock(), $this->getCacheMock());
|
||||||
|
|
||||||
|
$mapper = $this->getFFProbeMapperMock();
|
||||||
|
$mapper->expects($this->never())
|
||||||
|
->method('map');
|
||||||
|
|
||||||
|
$tester = $this->getFFProbeOptionsTesterMock();
|
||||||
|
|
||||||
|
$cache = $this->getCacheMock();
|
||||||
|
$cache->expects($this->once())
|
||||||
|
->method('contains')
|
||||||
|
->will($this->returnValue(true));
|
||||||
|
$cache->expects($this->once())
|
||||||
|
->method('fetch')
|
||||||
|
->will($this->returnValue($output));
|
||||||
|
$cache->expects($this->never())
|
||||||
|
->method('save');
|
||||||
|
|
||||||
|
$driver = $this->getFFProbeDriverMock();
|
||||||
|
$driver->expects($this->never())
|
||||||
|
->method('command');
|
||||||
|
|
||||||
|
$ffprobe->setOptionsTester($tester)
|
||||||
|
->setCache($cache)
|
||||||
|
->setMapper($mapper)
|
||||||
|
->setFFProbeDriver($driver);
|
||||||
|
|
||||||
|
$this->assertEquals($output, call_user_func(array($ffprobe, $method), $pathfile));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException FFMpeg\Exception\InvalidArgumentException
|
||||||
|
* @dataProvider provideProbeMethod
|
||||||
|
*/
|
||||||
|
public function testProbeWithInvalidFile($method)
|
||||||
|
{
|
||||||
|
$pathfile = '/path/to/nofile';
|
||||||
|
|
||||||
|
$ffprobe = new FFProbe($this->getFFProbeDriverMock(), $this->getCacheMock());
|
||||||
|
call_user_func(array($ffprobe, $method), $pathfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideProbeMethod()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
array('streams'),
|
||||||
|
array('format'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException FFMpeg\Exception\RuntimeException
|
||||||
|
* @dataProvider provideProbeMethod
|
||||||
|
*/
|
||||||
|
public function testProbeWithoutShowStreamsAvailable($method)
|
||||||
|
{
|
||||||
|
$pathfile = __FILE__;
|
||||||
|
|
||||||
|
$ffprobe = new FFProbe($this->getFFProbeDriverMock(), $this->getCacheMock());
|
||||||
|
$ffprobe->setOptionsTester($this->getFFProbeOptionsTesterMock());
|
||||||
|
call_user_func(array($ffprobe, $method), $pathfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider provideCreateOptions
|
||||||
|
*/
|
||||||
|
public function testCreate($logger, $conf, $cache)
|
||||||
|
{
|
||||||
|
$finder = new ExecutableFinder();
|
||||||
|
|
||||||
|
$found = false;
|
||||||
|
foreach (array('avprobe', 'ffprobe') as $name) {
|
||||||
|
if (null !== $finder->find($name)) {
|
||||||
|
$found = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$found) {
|
||||||
|
$this->markTestSkipped("Unable to find avprobe or ffprobe on system");
|
||||||
|
}
|
||||||
|
|
||||||
|
$ffprobe = FFProbe::create();
|
||||||
|
$this->assertInstanceOf('FFMpeg\FFprobe', $ffprobe);
|
||||||
|
|
||||||
|
$ffprobe = FFProbe::create($conf, $logger, $cache);
|
||||||
|
$this->assertInstanceOf('FFMpeg\FFprobe', $ffprobe);
|
||||||
|
|
||||||
|
if (null !== $cache) {
|
||||||
|
$this->assertSame($cache, $ffprobe->getCache());
|
||||||
|
}
|
||||||
|
if (null !== $logger) {
|
||||||
|
$this->assertSame($logger, $ffprobe->getFFProbeDriver()->getProcessRunner()->getLogger());
|
||||||
|
}
|
||||||
|
if ($conf instanceof ConfigurationInterface) {
|
||||||
|
$this->assertSame($conf, $ffprobe->getFFProbeDriver()->getConfiguration());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideCreateOptions()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
array(null, array('key' => 'value'), null),
|
||||||
|
array($this->getLoggerMock(), array('key' => 'value'), null),
|
||||||
|
array(null, new Configuration(), null),
|
||||||
|
array(null, array('key' => 'value'), $this->getCacheMock()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
26
tests/FFMpeg/Tests/Filters/Audio/AudioFiltersTest.php
Normal file
26
tests/FFMpeg/Tests/Filters/Audio/AudioFiltersTest.php
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace FFMpeg\Tests\Filters\Audio;
|
||||||
|
|
||||||
|
use FFMpeg\Filters\Audio\AudioFilters;
|
||||||
|
use FFMpeg\Tests\TestCase;
|
||||||
|
|
||||||
|
class AudioFiltersTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testResample()
|
||||||
|
{
|
||||||
|
$capturedFilter = null;
|
||||||
|
|
||||||
|
$audio = $this->getAudioMock();
|
||||||
|
$audio->expects($this->once())
|
||||||
|
->method('addFilter')
|
||||||
|
->with($this->isInstanceOf('FFMpeg\Filters\Audio\AudioResamplableFilter'))
|
||||||
|
->will($this->returnCallback(function ($filter) use (&$capturedFilter) {
|
||||||
|
$capturedFilter = $filter;
|
||||||
|
}));
|
||||||
|
|
||||||
|
$filters = new AudioFilters($audio);
|
||||||
|
$filters->resample(8000);
|
||||||
|
$this->assertEquals(8000, $capturedFilter->getRate());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace FFMpeg\Tests\Filters\Audio;
|
||||||
|
|
||||||
|
use FFMpeg\Filters\Audio\AudioResamplableFilter;
|
||||||
|
use FFMpeg\Tests\TestCase;
|
||||||
|
|
||||||
|
class AudioResamplableFilterTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testGetRate()
|
||||||
|
{
|
||||||
|
$filter = new AudioResamplableFilter(500);
|
||||||
|
$this->assertEquals(500, $filter->getRate());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testApply()
|
||||||
|
{
|
||||||
|
$audio = $this->getAudioMock();
|
||||||
|
$format = $this->getMock('FFMpeg\Format\AudioInterface');
|
||||||
|
|
||||||
|
$filter = new AudioResamplableFilter(500);
|
||||||
|
$this->assertEquals(array('-ac', 2, '-ar', 500), $filter->apply($audio, $format));
|
||||||
|
}
|
||||||
|
}
|
||||||
30
tests/FFMpeg/Tests/Filters/FiltersCollectionTest.php
Normal file
30
tests/FFMpeg/Tests/Filters/FiltersCollectionTest.php
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace FFMpeg\Tests\Filters;
|
||||||
|
|
||||||
|
use FFMpeg\Filters\FiltersCollection;
|
||||||
|
|
||||||
|
class FiltersCollectionTest extends \PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
public function testCount()
|
||||||
|
{
|
||||||
|
$coll = new FiltersCollection();
|
||||||
|
$this->assertCount(0, $coll);
|
||||||
|
|
||||||
|
$coll->add($this->getMock('FFMpeg\Filters\FilterInterface'));
|
||||||
|
$this->assertCount(1, $coll);
|
||||||
|
|
||||||
|
$coll->add($this->getMock('FFMpeg\Filters\FilterInterface'));
|
||||||
|
$this->assertCount(2, $coll);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIterator()
|
||||||
|
{
|
||||||
|
$coll = new FiltersCollection();
|
||||||
|
$coll->add($this->getMock('FFMpeg\Filters\FilterInterface'));
|
||||||
|
$coll->add($this->getMock('FFMpeg\Filters\FilterInterface'));
|
||||||
|
|
||||||
|
$this->assertInstanceOf('\ArrayIterator', $coll->getIterator());
|
||||||
|
$this->assertCount(2, $coll->getIterator());
|
||||||
|
}
|
||||||
|
}
|
||||||
75
tests/FFMpeg/Tests/Filters/Video/ResizeFilterTest.php
Normal file
75
tests/FFMpeg/Tests/Filters/Video/ResizeFilterTest.php
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace FFMpeg\Tests\Filters\Video;
|
||||||
|
|
||||||
|
use FFMpeg\Filters\Video\ResizeFilter;
|
||||||
|
use FFMpeg\Tests\TestCase;
|
||||||
|
use FFMpeg\FFProbe\DataMapping\Stream;
|
||||||
|
use FFMpeg\FFProbe\DataMapping\StreamCollection;
|
||||||
|
use FFMpeg\Coordinate\Dimension;
|
||||||
|
|
||||||
|
class ResizeFilterTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @dataProvider provideDimensions
|
||||||
|
*/
|
||||||
|
public function testApply(Dimension $dimension, $mode, $width, $height, $modulus, $expected, $forceStandards = true)
|
||||||
|
{
|
||||||
|
$video = $this->getVideoMock();
|
||||||
|
$pathfile = '/path/to/file'.mt_rand();
|
||||||
|
|
||||||
|
$format = $this->getMock('FFMpeg\Format\VideoInterface');
|
||||||
|
$format->expects($this->any())
|
||||||
|
->method('getModulus')
|
||||||
|
->will($this->returnValue($modulus));
|
||||||
|
|
||||||
|
$streams = new StreamCollection(array(
|
||||||
|
new Stream(array(
|
||||||
|
'codec_type' => 'video',
|
||||||
|
'width' => $width,
|
||||||
|
'height' => $height,
|
||||||
|
))
|
||||||
|
));
|
||||||
|
|
||||||
|
$video->expects($this->once())
|
||||||
|
->method('getStreams')
|
||||||
|
->will($this->returnValue($streams));
|
||||||
|
|
||||||
|
$filter = new ResizeFilter($dimension, $mode, $forceStandards);
|
||||||
|
$this->assertEquals($expected, $filter->apply($video, $format));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideDimensions()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
array(new Dimension(320, 240), ResizeFilter::RESIZEMODE_FIT, 640, 480, 2, array('-s', '320x240')),
|
||||||
|
array(new Dimension(320, 240), ResizeFilter::RESIZEMODE_INSET, 640, 480, 2, array('-s', '320x240')),
|
||||||
|
array(new Dimension(320, 240), ResizeFilter::RESIZEMODE_SCALE_HEIGHT, 640, 480, 2, array('-s', '320x240')),
|
||||||
|
array(new Dimension(320, 240), ResizeFilter::RESIZEMODE_SCALE_WIDTH, 640, 480, 2, array('-s', '320x240')),
|
||||||
|
|
||||||
|
array(new Dimension(640, 480), ResizeFilter::RESIZEMODE_FIT, 320, 240, 2, array('-s', '640x480')),
|
||||||
|
array(new Dimension(640, 480), ResizeFilter::RESIZEMODE_INSET, 320, 240, 2, array('-s', '640x480')),
|
||||||
|
array(new Dimension(640, 480), ResizeFilter::RESIZEMODE_SCALE_HEIGHT, 320, 240, 2, array('-s', '640x480')),
|
||||||
|
array(new Dimension(640, 480), ResizeFilter::RESIZEMODE_SCALE_WIDTH, 320, 240, 2, array('-s', '640x480')),
|
||||||
|
|
||||||
|
array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_FIT, 1280, 720, 2, array('-s', '640x360')),
|
||||||
|
array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_INSET, 1280, 720, 2, array('-s', '640x360')),
|
||||||
|
array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_SCALE_HEIGHT, 1280, 720, 2, array('-s', '640x360')),
|
||||||
|
array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_SCALE_WIDTH, 1280, 720, 2, array('-s', '640x360')),
|
||||||
|
|
||||||
|
array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_FIT, 1280, 720, 2, array('-s', '640x360')),
|
||||||
|
array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_INSET, 1280, 720, 2, array('-s', '640x360')),
|
||||||
|
array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_SCALE_HEIGHT, 1280, 720, 2, array('-s', '640x360')),
|
||||||
|
array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_SCALE_WIDTH, 1280, 720, 2, array('-s', '640x360')),
|
||||||
|
|
||||||
|
// test non standard dimension
|
||||||
|
array(new Dimension(700, 150), ResizeFilter::RESIZEMODE_INSET, 123, 456, 2, array('-s', '62x150'), true),
|
||||||
|
array(new Dimension(700, 150), ResizeFilter::RESIZEMODE_INSET, 123, 456, 2, array('-s', '40x150'), false),
|
||||||
|
|
||||||
|
array(new Dimension(320, 320), ResizeFilter::RESIZEMODE_FIT, 640, 480, 2, array('-s', '320x320')),
|
||||||
|
array(new Dimension(320, 320), ResizeFilter::RESIZEMODE_INSET, 640, 480, 2, array('-s', '320x240')),
|
||||||
|
array(new Dimension(320, 320), ResizeFilter::RESIZEMODE_SCALE_HEIGHT, 640, 480, 2, array('-s', '320x240')),
|
||||||
|
array(new Dimension(320, 320), ResizeFilter::RESIZEMODE_SCALE_WIDTH, 640, 480, 2, array('-s', '426x320')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
58
tests/FFMpeg/Tests/Filters/Video/SynchronizeFilterTest.php
Normal file
58
tests/FFMpeg/Tests/Filters/Video/SynchronizeFilterTest.php
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace FFMpeg\Tests\Filters\Video;
|
||||||
|
|
||||||
|
use FFMpeg\Tests\TestCase;
|
||||||
|
use FFMpeg\FFProbe\DataMapping\StreamCollection;
|
||||||
|
use FFMpeg\FFProbe\DataMapping\Stream;
|
||||||
|
use FFMpeg\Filters\Video\SynchronizeFilter;
|
||||||
|
|
||||||
|
class SynchronizeFilterTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @dataProvider provideStreams
|
||||||
|
*/
|
||||||
|
public function testApply($streams, $expected)
|
||||||
|
{
|
||||||
|
$video = $this->getVideoMock();
|
||||||
|
$format = $this->getMock('FFMpeg\Format\VideoInterface');
|
||||||
|
$video->expects($this->once())
|
||||||
|
->method('getStreams')
|
||||||
|
->will($this->returnValue($streams));
|
||||||
|
$video->expects($this->any())
|
||||||
|
->method('getPathfile')
|
||||||
|
->will($this->returnValue(__FILE__));
|
||||||
|
|
||||||
|
$filter = new SynchronizeFilter();
|
||||||
|
$this->assertEquals($expected, $filter->apply($video, $format));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideStreams()
|
||||||
|
{
|
||||||
|
$audio = new StreamCollection(array(new Stream(array(
|
||||||
|
'index' => 0,
|
||||||
|
'codec_type' => 'audio',
|
||||||
|
))));
|
||||||
|
$synced = new StreamCollection(array(new Stream(array(
|
||||||
|
'index' => 0,
|
||||||
|
'codec_type' => 'video',
|
||||||
|
)), new Stream(array(
|
||||||
|
'index' => 1,
|
||||||
|
'codec_type' => 'audio',
|
||||||
|
))));
|
||||||
|
$video = new StreamCollection(array(new Stream(array(
|
||||||
|
'index' => 0,
|
||||||
|
'codec_type' => 'video',
|
||||||
|
'start_time' => '0.123456',
|
||||||
|
)), new Stream(array(
|
||||||
|
'index' => 1,
|
||||||
|
'codec_type' => 'audio',
|
||||||
|
))));
|
||||||
|
|
||||||
|
return array(
|
||||||
|
array($audio, array()),
|
||||||
|
array($synced, array()),
|
||||||
|
array($video, array('-itsoffset', '0.123456', '-i', __FILE__, '-map', '1:0', '-map', '0:1')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
79
tests/FFMpeg/Tests/Filters/Video/VideoFiltersTest.php
Normal file
79
tests/FFMpeg/Tests/Filters/Video/VideoFiltersTest.php
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace FFMpeg\Tests\Filters\Video;
|
||||||
|
|
||||||
|
use FFMpeg\Tests\TestCase;
|
||||||
|
use FFMpeg\Filters\Video\VideoFilters;
|
||||||
|
use FFMpeg\Filters\Video\ResizeFilter;
|
||||||
|
|
||||||
|
class VideoFiltersTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @dataProvider provideResizeOptions
|
||||||
|
*/
|
||||||
|
public function testResize($mode, $forceStandards)
|
||||||
|
{
|
||||||
|
$capturedFilter = null;
|
||||||
|
|
||||||
|
$video = $this->getVideoMock();
|
||||||
|
$filters = new VideoFilters($video);
|
||||||
|
$dimension = $this->getDimensionMock();
|
||||||
|
|
||||||
|
$video->expects($this->once())
|
||||||
|
->method('addFilter')
|
||||||
|
->with($this->isInstanceOf('FFMpeg\Filters\Video\ResizeFilter'))
|
||||||
|
->will($this->returnCallback(function ($filter) use (&$capturedFilter) {
|
||||||
|
$capturedFilter = $filter;
|
||||||
|
}));
|
||||||
|
|
||||||
|
$filters->resize($dimension, $mode, $forceStandards);
|
||||||
|
|
||||||
|
$this->assertSame($mode, $capturedFilter->getMode());
|
||||||
|
$this->assertSame($forceStandards, $capturedFilter->areStandardsForced());
|
||||||
|
$this->assertSame($dimension, $capturedFilter->getDimension());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideResizeOptions()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
array(ResizeFilter::RESIZEMODE_FIT, true),
|
||||||
|
array(ResizeFilter::RESIZEMODE_SCALE_WIDTH, true),
|
||||||
|
array(ResizeFilter::RESIZEMODE_SCALE_HEIGHT, false),
|
||||||
|
array(ResizeFilter::RESIZEMODE_INSET, false),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testResample()
|
||||||
|
{
|
||||||
|
$capturedFilter = null;
|
||||||
|
|
||||||
|
$video = $this->getVideoMock();
|
||||||
|
$filters = new VideoFilters($video);
|
||||||
|
$framerate = $this->getFramerateMock();
|
||||||
|
$gop = 42;
|
||||||
|
|
||||||
|
$video->expects($this->once())
|
||||||
|
->method('addFilter')
|
||||||
|
->with($this->isInstanceOf('FFMpeg\Filters\Video\VideoResampleFilter'))
|
||||||
|
->will($this->returnCallback(function ($filter) use (&$capturedFilter) {
|
||||||
|
$capturedFilter = $filter;
|
||||||
|
}));
|
||||||
|
|
||||||
|
$filters->resample($framerate, $gop);
|
||||||
|
|
||||||
|
$this->assertSame($framerate, $capturedFilter->getFramerate());
|
||||||
|
$this->assertSame($gop, $capturedFilter->getGOP());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSynchronize()
|
||||||
|
{
|
||||||
|
$video = $this->getVideoMock();
|
||||||
|
$filters = new VideoFilters($video);
|
||||||
|
|
||||||
|
$video->expects($this->once())
|
||||||
|
->method('addFilter')
|
||||||
|
->with($this->isInstanceOf('FFMpeg\Filters\Video\SynchronizeFilter'));
|
||||||
|
|
||||||
|
$filters->synchronize();
|
||||||
|
}
|
||||||
|
}
|
||||||
44
tests/FFMpeg/Tests/Filters/Video/VideoResampleFilterTest.php
Normal file
44
tests/FFMpeg/Tests/Filters/Video/VideoResampleFilterTest.php
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace FFMpeg\Tests\Filters\Video;
|
||||||
|
|
||||||
|
use FFMpeg\Filters\Video\VideoResampleFilter;
|
||||||
|
use FFMpeg\Tests\TestCase;
|
||||||
|
use FFMpeg\Coordinate\FrameRate;
|
||||||
|
|
||||||
|
class VideoResampleFilterTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testApplyWithAFormatThatSupportsBFrames()
|
||||||
|
{
|
||||||
|
$framerate = new FrameRate(54);
|
||||||
|
$gop = 42;
|
||||||
|
|
||||||
|
$video = $this->getVideoMock();
|
||||||
|
$format = $this->getMock('FFMpeg\Format\VideoInterface');
|
||||||
|
$format->expects($this->any())
|
||||||
|
->method('supportBFrames')
|
||||||
|
->will($this->returnValue(true));
|
||||||
|
|
||||||
|
$expected = array('-r', 54, '-b_strategy', '1', '-bf', '3', '-g', 42);
|
||||||
|
|
||||||
|
$filter = new VideoResampleFilter($framerate, $gop);
|
||||||
|
$this->assertEquals($expected, $filter->apply($video, $format));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testApplyWithAFormatThatDoesNotSupportsBFrames()
|
||||||
|
{
|
||||||
|
$framerate = new FrameRate(54);
|
||||||
|
$gop = 42;
|
||||||
|
|
||||||
|
$video = $this->getVideoMock();
|
||||||
|
$format = $this->getMock('FFMpeg\Format\VideoInterface');
|
||||||
|
$format->expects($this->any())
|
||||||
|
->method('supportBFrames')
|
||||||
|
->will($this->returnValue(false));
|
||||||
|
|
||||||
|
$expected = array('-r', 54);
|
||||||
|
|
||||||
|
$filter = new VideoResampleFilter($framerate, $gop);
|
||||||
|
$this->assertEquals($expected, $filter->apply($video, $format));
|
||||||
|
}
|
||||||
|
}
|
||||||
96
tests/FFMpeg/Tests/Format/Audio/AudioTestCase.php
Normal file
96
tests/FFMpeg/Tests/Format/Audio/AudioTestCase.php
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace FFMpeg\Tests\Format\Audio;
|
||||||
|
|
||||||
|
use FFMpeg\Tests\TestCase;
|
||||||
|
use FFMpeg\Format\Audio\DefaultAudio;
|
||||||
|
|
||||||
|
abstract class AudioTestCase extends TestCase
|
||||||
|
{
|
||||||
|
public function testExtraParams()
|
||||||
|
{
|
||||||
|
foreach ($this->getFormat()->getExtraParams() as $param) {
|
||||||
|
$this->assertScalar($param);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetAudioCodec()
|
||||||
|
{
|
||||||
|
$this->assertScalar($this->getFormat()->getAudioCodec());
|
||||||
|
$this->assertContains($this->getFormat()->getAudioCodec(), $this->getFormat()->getAvailableAudioCodecs());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSetAudioCodec()
|
||||||
|
{
|
||||||
|
$format = $this->getFormat();
|
||||||
|
|
||||||
|
foreach ($format->getAvailableAudioCodecs() as $codec) {
|
||||||
|
$format->setAudioCodec($codec);
|
||||||
|
$this->assertEquals($codec, $format->getAudioCodec());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException FFMpeg\Exception\InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function testSetInvalidAudioCodec()
|
||||||
|
{
|
||||||
|
$this->getFormat()->setAudioCodec('invalid-random-audio-codec');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetAvailableAudioCodecs()
|
||||||
|
{
|
||||||
|
$this->assertGreaterThan(0, count($this->getFormat()->getAvailableAudioCodecs()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetAudioKiloBitrate()
|
||||||
|
{
|
||||||
|
$this->assertInternalType('integer', $this->getFormat()->getAudioKiloBitrate());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSetAudioKiloBitrate()
|
||||||
|
{
|
||||||
|
$format = $this->getFormat();
|
||||||
|
$format->setAudioKiloBitrate(256);
|
||||||
|
$this->assertEquals(256, $format->getAudioKiloBitrate());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException FFMpeg\Exception\InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function testSetInvalidKiloBitrate()
|
||||||
|
{
|
||||||
|
$this->getFormat()->setAudioKiloBitrate(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException FFMpeg\Exception\InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function testSetNegativeKiloBitrate()
|
||||||
|
{
|
||||||
|
$this->getFormat()->setAudioKiloBitrate(-10);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCreateProgressListener()
|
||||||
|
{
|
||||||
|
$media = $this->getMock('FFMpeg\Media\MediaTypeInterface');
|
||||||
|
$media->expects($this->any())
|
||||||
|
->method('getPathfile')
|
||||||
|
->will($this->returnValue(__FILE__));
|
||||||
|
$format = $this->getFormat();
|
||||||
|
$ffprobe = $this->getFFProbeMock();
|
||||||
|
|
||||||
|
foreach ($format->createProgressListener($media, $ffprobe, 1, 3) as $listener) {
|
||||||
|
$this->assertInstanceOf('FFMpeg\Format\ProgressListener\AudioProgressListener', $listener);
|
||||||
|
$this->assertSame($ffprobe, $listener->getFFProbe());
|
||||||
|
$this->assertSame(__FILE__, $listener->getPathfile());
|
||||||
|
$this->assertSame(1, $listener->getCurrentPass());
|
||||||
|
$this->assertSame(3, $listener->getTotalPass());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return DefaultAudio
|
||||||
|
*/
|
||||||
|
abstract public function getFormat();
|
||||||
|
}
|
||||||
13
tests/FFMpeg/Tests/Format/Audio/FlacTest.php
Normal file
13
tests/FFMpeg/Tests/Format/Audio/FlacTest.php
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace FFMpeg\Tests\Format\Audio;
|
||||||
|
|
||||||
|
use FFMpeg\Format\Audio\Flac;
|
||||||
|
|
||||||
|
class FlacTest extends AudioTestCase
|
||||||
|
{
|
||||||
|
public function getFormat()
|
||||||
|
{
|
||||||
|
return new Flac();
|
||||||
|
}
|
||||||
|
}
|
||||||
13
tests/FFMpeg/Tests/Format/Audio/Mp3Test.php
Normal file
13
tests/FFMpeg/Tests/Format/Audio/Mp3Test.php
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace FFMpeg\Tests\Format\Audio;
|
||||||
|
|
||||||
|
use FFMpeg\Format\Audio\Mp3;
|
||||||
|
|
||||||
|
class Mp3Test extends AudioTestCase
|
||||||
|
{
|
||||||
|
public function getFormat()
|
||||||
|
{
|
||||||
|
return new Mp3();
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue