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": {
|
||||
"php" : ">=5.3.3",
|
||||
"symfony/process" : "~2.1",
|
||||
"monolog/monolog" : "~1.0"
|
||||
"php" : ">=5.3.3",
|
||||
"alchemy/binary-driver" : "~1.5",
|
||||
"doctrine/cache" : "~1.0",
|
||||
"evenement/evenement" : "~1.0",
|
||||
"symfony/process" : "~2.0"
|
||||
},
|
||||
"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": {
|
||||
"sami/sami" : "~1.0",
|
||||
"silex/silex" : "~1.0"
|
||||
"sami/sami" : "dev-master@dev",
|
||||
"silex/silex" : "~1.0",
|
||||
"phpunit/phpunit" : "~3.7"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"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"
|
||||
syntaxCheck="true"
|
||||
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>
|
||||
<testsuite name="FFMpeg Tests Suite">
|
||||
<directory>tests</directory>
|
||||
<directory>tests/FFMpeg/Tests</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<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.
|
||||
*/
|
||||
|
||||
namespace FFMpeg\Format;
|
||||
namespace FFMpeg\Coordinate;
|
||||
|
||||
use FFMpeg\Exception\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Dimension object, used for manipulating width and height couples
|
||||
*
|
||||
* @author Romain Neutron imprec@gmail.com
|
||||
*/
|
||||
class Dimension
|
||||
{
|
||||
protected $width;
|
||||
protected $height;
|
||||
private $width;
|
||||
private $height;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
|
@ -59,4 +57,16 @@ class Dimension
|
|||
{
|
||||
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
|
||||
{
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,5 +13,4 @@ namespace FFMpeg\Exception;
|
|||
|
||||
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
|
||||
{
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,5 +13,4 @@ namespace FFMpeg\Exception;
|
|||
|
||||
class RuntimeException extends \RuntimeException implements ExceptionInterface
|
||||
{
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,466 +11,126 @@
|
|||
|
||||
namespace FFMpeg;
|
||||
|
||||
use Alchemy\BinaryDriver\ConfigurationInterface;
|
||||
use FFMpeg\Driver\FFMpegDriver;
|
||||
use FFMpeg\Exception\InvalidArgumentException;
|
||||
use FFMpeg\Exception\LogicException;
|
||||
use FFMpeg\Exception\RuntimeException;
|
||||
use FFMpeg\Format\AudioInterface;
|
||||
use FFMpeg\Format\VideoInterface;
|
||||
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;
|
||||
use FFMpeg\Media\Audio;
|
||||
use FFMpeg\Media\Video;
|
||||
use Alchemy\BinaryDriver\Configuration;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* FFMpeg driver
|
||||
*
|
||||
* @author Romain Neutron imprec@gmail.com
|
||||
*/
|
||||
class FFMpeg extends Binary
|
||||
class FFMpeg
|
||||
{
|
||||
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;
|
||||
protected $threads = 1;
|
||||
|
||||
/**
|
||||
* @var HelperInterface[]
|
||||
*/
|
||||
protected $helpers = array();
|
||||
|
||||
/**
|
||||
* Destructor
|
||||
*/
|
||||
public function __destruct()
|
||||
public function setFFProbe(FFProbe $ffprobe)
|
||||
{
|
||||
$this->prober = null;
|
||||
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);
|
||||
}
|
||||
$this->ffprobe = $ffprobe;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setThreads($threads)
|
||||
/**
|
||||
* Gets FFProbe
|
||||
*
|
||||
* @return FFProbe
|
||||
*/
|
||||
public function getFFProbe()
|
||||
{
|
||||
if ($threads > 64 || $threads < 1) {
|
||||
throw new InvalidArgumentException('Invalid `threads` value ; threads must fit in range 1 - 64');
|
||||
}
|
||||
return $this->ffprobe;
|
||||
}
|
||||
|
||||
$this->threads = (int) $threads;
|
||||
/**
|
||||
* Sets ffmpeg driver
|
||||
*
|
||||
* @return FFMpeg
|
||||
*/
|
||||
public function setFFMpegDriver(FFMpegDriver $ffmpeg)
|
||||
{
|
||||
$this->driver = $ffmpeg;
|
||||
|
||||
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
|
||||
*
|
||||
* @param string $pathfile A pathfile
|
||||
* @return \FFMpeg\FFMpeg
|
||||
* @param string $pathfile A pathfile
|
||||
*
|
||||
* @return Audio|Video
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function open($pathfile)
|
||||
{
|
||||
if (!file_exists($pathfile)) {
|
||||
$this->logger->addError(sprintf('FFmpeg failed to open %s', $pathfile));
|
||||
|
||||
throw new InvalidArgumentException(sprintf('File %s does not exist', $pathfile));
|
||||
throw new InvalidArgumentException(sprintf('File %s does not exists', $pathfile));
|
||||
}
|
||||
|
||||
$this->logger->addInfo(sprintf('FFmpeg opens %s', $pathfile));
|
||||
$this->pathfile = $pathfile;
|
||||
$streams = $this->ffprobe->streams($pathfile);
|
||||
|
||||
foreach ($this->helpers as $helper) {
|
||||
$helper->open($pathfile);
|
||||
if (0 < count($streams->videos())) {
|
||||
return new Video($pathfile, $this->driver, $this->ffprobe);
|
||||
} elseif (0 < count($streams->audios())) {
|
||||
return new Audio($pathfile, $this->driver, $this->ffprobe);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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');
|
||||
if (!$configuration instanceof ConfigurationInterface) {
|
||||
$configuration = new Configuration($configuration);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see http://ffmpeg.org/ffmpeg.html#Main-options
|
||||
*/
|
||||
if (!$accurate) {
|
||||
$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
|
||||
);
|
||||
$binaries = $configuration->get('ffmpeg.binaries', array('avconv', 'ffmpeg'));
|
||||
|
||||
if (!$configuration->has('timeout')) {
|
||||
$configuration->set('timeout', 300);
|
||||
}
|
||||
|
||||
$builder = ProcessBuilder::create($options);
|
||||
$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) {
|
||||
$driver = FFMpegDriver::load($binaries, $logger, $configuration);
|
||||
|
||||
if (null === $probe) {
|
||||
$probe = FFProbe::create($configuration, $logger, null);
|
||||
}
|
||||
|
||||
if (!$process->isSuccessful()) {
|
||||
$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');
|
||||
return new static($driver, $probe);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,49 +11,52 @@
|
|||
|
||||
namespace FFMpeg;
|
||||
|
||||
use Doctrine\Common\Cache\ArrayCache;
|
||||
use FFMpeg\FFMpeg;
|
||||
use FFMpeg\FFProbe;
|
||||
use Monolog\Logger;
|
||||
use Monolog\Handler\NullHandler;
|
||||
use Silex\Application;
|
||||
use Silex\ServiceProviderInterface;
|
||||
|
||||
class FFMpegServiceProvider implements ServiceProviderInterface
|
||||
{
|
||||
|
||||
public function register(Application $app)
|
||||
{
|
||||
if (isset($app['monolog'])) {
|
||||
$app['ffmpeg.logger'] = function() use ($app) {
|
||||
return $app['monolog'];
|
||||
};
|
||||
} else {
|
||||
$app['ffmpeg.logger'] = $app->share(function(Application $app) {
|
||||
$logger = new Logger('FFMpeg logger');
|
||||
$logger->pushHandler(new NullHandler());
|
||||
$app['ffmpeg.configuration'] = array();
|
||||
$app['ffmpeg.default.configuration'] = array(
|
||||
'ffmpeg.threads' => 4,
|
||||
'ffmpeg.timeout' => 300,
|
||||
'ffmpeg.binaries' => array('avconv', 'ffmpeg'),
|
||||
'ffprobe.timeout' => 30,
|
||||
'ffprobe.binaries' => array('avprobe', 'ffprobe'),
|
||||
);
|
||||
$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) {
|
||||
if (isset($app['ffmpeg.ffmpeg.binary'])) {
|
||||
$ffmpeg = new FFMpeg($app['ffmpeg.ffmpeg.binary'], $app['ffmpeg.logger']);
|
||||
} else {
|
||||
$ffmpeg = FFMpeg::load($app['ffmpeg.logger']);
|
||||
$app['ffmpeg'] = $app['ffmpeg.ffmpeg'] = $app->share(function(Application $app) {
|
||||
$configuration = $app['ffmpeg.configuration.build'];
|
||||
|
||||
if (isset($configuration['ffmpeg.timeout'])) {
|
||||
$configuration['timeout'] = $configuration['ffmpeg.timeout'];
|
||||
}
|
||||
|
||||
return $ffmpeg
|
||||
->setProber($app['ffmpeg.ffprobe'])
|
||||
->setThreads(isset($app['ffmpeg.threads']) ? $app['ffmpeg.threads'] : 1);
|
||||
return FFMpeg::create($configuration, $app['ffmpeg.logger'], $app['ffmpeg.ffprobe']);
|
||||
});
|
||||
|
||||
$app['ffprobe.cache'] = $app->share(function () {
|
||||
return new ArrayCache();
|
||||
});
|
||||
|
||||
$app['ffmpeg.ffprobe'] = $app->share(function(Application $app) {
|
||||
if (isset($app['ffmpeg.ffprobe.binary'])) {
|
||||
return new FFProbe($app['ffmpeg.ffprobe.binary'], $app['ffmpeg.logger']);
|
||||
} else {
|
||||
return FFProbe::load($app['ffmpeg.logger']);
|
||||
$configuration = $app['ffmpeg.configuration.build'];
|
||||
|
||||
if (isset($configuration['ffmpeg.timeout'])) {
|
||||
$configuration['timeout'] = $configuration['ffprobe.timeout'];
|
||||
}
|
||||
|
||||
return FFProbe::create($configuration, $app['ffmpeg.logger'], $app['ffprobe.cache']);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,163 +11,258 @@
|
|||
|
||||
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\RuntimeException;
|
||||
use Symfony\Component\Process\Process;
|
||||
use Symfony\Component\Process\ProcessBuilder;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* FFProbe driver
|
||||
*
|
||||
* @author Romain Neutron imprec@gmail.com
|
||||
*/
|
||||
class FFProbe extends Binary
|
||||
class FFProbe
|
||||
{
|
||||
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
|
||||
*
|
||||
* @param string $pathfile
|
||||
* @return string A Json object containing the key/values of the probe output
|
||||
* @param string $pathfile
|
||||
*
|
||||
* @return Format A Format object
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function probeFormat($pathfile)
|
||||
public function format($pathfile)
|
||||
{
|
||||
if ( ! is_file($pathfile)) {
|
||||
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);
|
||||
return $this->probe($pathfile, '-show_format', static::TYPE_FORMAT);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api
|
||||
*
|
||||
* Probe the streams contained in a given file
|
||||
*
|
||||
* @param string $pathfile
|
||||
* @return array An array of streams array
|
||||
* @param string $pathfile
|
||||
*
|
||||
* @return StreamCollection A collection of streams
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function probeStreams($pathfile)
|
||||
public function streams($pathfile)
|
||||
{
|
||||
if ( ! is_file($pathfile)) {
|
||||
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));
|
||||
return $this->probe($pathfile, '-show_streams', static::TYPE_STREAMS);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api
|
||||
*
|
||||
* @param Process $process
|
||||
* @return string
|
||||
* @throws RuntimeException
|
||||
* @param array|ConfigurationInterface $configuration
|
||||
* @param LoggerInterface $logger
|
||||
* @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()));
|
||||
|
||||
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 (null === $cache) {
|
||||
$cache = new ArrayCache();
|
||||
}
|
||||
|
||||
if ( ! $process->isSuccessful()) {
|
||||
$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();
|
||||
return new static(FFProbeDriver::create($configuration, $logger), $cache);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function getBinaryName()
|
||||
private function probe($pathfile, $command, $type, $allowJson = true)
|
||||
{
|
||||
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.
|
||||
*/
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
namespace FFMpeg;
|
||||
namespace FFMpeg\FFProbe;
|
||||
|
||||
use Monolog\Logger;
|
||||
|
||||
/**
|
||||
* FFMpeg Adapter interface
|
||||
*
|
||||
* @author Romain Neutron imprec@gmail.com
|
||||
*/
|
||||
interface AdapterInterface
|
||||
interface OptionsTesterInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use FFMpeg\Exception\InvalidArgumentException;
|
||||
use FFMpeg\Format\AudioInterface;
|
||||
use FFMpeg\Media\MediaTypeInterface;
|
||||
use FFMpeg\Format\ProgressableInterface;
|
||||
use FFMpeg\Format\ProgressListener\AudioProgressListener;
|
||||
use FFMpeg\FFProbe;
|
||||
|
||||
/**
|
||||
* The abstract default Audio format
|
||||
*
|
||||
* @author Romain Neutron imprec@gmail.com
|
||||
*/
|
||||
abstract class DefaultAudio implements Resamplable, Interactive
|
||||
abstract class DefaultAudio extends EventEmitter implements AudioInterface, ProgressableInterface
|
||||
{
|
||||
/** @var string */
|
||||
protected $audioCodec;
|
||||
protected $audioSampleRate = 44100;
|
||||
protected $kiloBitrate = 128;
|
||||
|
||||
/** @var integer */
|
||||
protected $audioKiloBitrate = 128;
|
||||
|
||||
/**
|
||||
* Returns extra parameters for the encoding
|
||||
*
|
||||
* @return string
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
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
|
||||
*
|
||||
* @param string $audioCodec
|
||||
* @throws \InvalidArgumentException
|
||||
* @param string $audioCodec
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function setAudioCodec($audioCodec)
|
||||
{
|
||||
|
|
@ -66,24 +68,24 @@ abstract class DefaultAudio implements Resamplable, Interactive
|
|||
/**
|
||||
* {@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
|
||||
* @throws \InvalidArgumentException
|
||||
* @param integer $kiloBitrate
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function setAudioSampleRate($audioSampleRate)
|
||||
public function setAudioKiloBitrate($kiloBitrate)
|
||||
{
|
||||
if ($audioSampleRate < 1) {
|
||||
throw new InvalidArgumentException('Wrong audio sample rate value');
|
||||
if ($kiloBitrate < 1) {
|
||||
throw new InvalidArgumentException('Wrong kiloBitrate value');
|
||||
}
|
||||
|
||||
$this->audioSampleRate = (int) $audioSampleRate;
|
||||
$this->audioKiloBitrate = (int) $kiloBitrate;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
|
@ -91,25 +93,14 @@ abstract class DefaultAudio implements Resamplable, Interactive
|
|||
/**
|
||||
* {@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()));
|
||||
});
|
||||
|
||||
/**
|
||||
* 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;
|
||||
return array($listener);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,12 +13,13 @@ namespace FFMpeg\Format\Audio;
|
|||
|
||||
/**
|
||||
* The Flac audio format
|
||||
*
|
||||
* @author Romain Neutron imprec@gmail.com
|
||||
*/
|
||||
class Flac extends DefaultAudio
|
||||
{
|
||||
protected $audioCodec = 'flac';
|
||||
public function __construct()
|
||||
{
|
||||
$this->audioCodec = 'flac';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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
|
||||
*
|
||||
* @author Romain Neutron imprec@gmail.com
|
||||
*/
|
||||
class Mp3 extends DefaultAudio
|
||||
{
|
||||
protected $audioCodec = 'libmp3lame';
|
||||
public function __construct()
|
||||
{
|
||||
$this->audioCodec = 'libmp3lame';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace FFMpeg\Format;
|
||||
|
||||
/**
|
||||
* The base audio interface
|
||||
*
|
||||
* @author Romain Neutron imprec@gmail.com
|
||||
*/
|
||||
interface AudioInterface
|
||||
interface AudioInterface extends FormatInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Get the kiloBitrate value
|
||||
* Get the audio kiloBitrate value
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getKiloBitrate();
|
||||
public function getAudioKiloBitrate();
|
||||
|
||||
/**
|
||||
* Return an array of extra parameters to add to ffmpeg commandline
|
||||
|
|
@ -33,4 +26,17 @@ interface AudioInterface
|
|||
*/
|
||||
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
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
namespace FFMpeg\Format;
|
||||
|
||||
namespace FFMpeg\Exception;
|
||||
|
||||
class BinaryNotFoundException extends \Exception implements ExceptionInterface
|
||||
interface FormatInterface
|
||||
{
|
||||
|
||||
}
|
||||
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.
|
||||
*/
|
||||
|
||||
namespace FFMpeg\Helper;
|
||||
namespace FFMpeg\Format\ProgressListener;
|
||||
|
||||
/**
|
||||
* Parses ffmpeg stderr progress information. An example:
|
||||
|
|
@ -20,7 +20,7 @@ namespace FFMpeg\Helper;
|
|||
*
|
||||
* @author Robert Gruendler <r.gruendler@gmail.com>
|
||||
*/
|
||||
class AudioProgressHelper extends ProgressHelper
|
||||
class AudioProgressListener extends AbstractProgressListener
|
||||
{
|
||||
public function getPattern()
|
||||
{
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
* 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:
|
||||
|
|
@ -20,7 +20,7 @@ namespace FFMpeg\Helper;
|
|||
*
|
||||
* @author Robert Gruendler <r.gruendler@gmail.com>
|
||||
*/
|
||||
class VideoProgressHelper extends ProgressHelper
|
||||
class VideoProgressListener extends AbstractProgressListener
|
||||
{
|
||||
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;
|
||||
|
||||
use FFMpeg\Format\Audio\DefaultAudio;
|
||||
use FFMpeg\Format\Dimension;
|
||||
use FFMpeg\FFProbe;
|
||||
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
|
||||
*
|
||||
* @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';
|
||||
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;
|
||||
/** @var string */
|
||||
protected $videoCodec;
|
||||
protected $GOPsize = 25;
|
||||
|
||||
/** @var Integer */
|
||||
protected $kiloBitrate = 1000;
|
||||
|
||||
/** @var Integer */
|
||||
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}
|
||||
*/
|
||||
public function getFrameRate()
|
||||
public function getKiloBitrate()
|
||||
{
|
||||
return $this->frameRate;
|
||||
return $this->kiloBitrate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the framerate
|
||||
* Sets the kiloBitrate value
|
||||
*
|
||||
* @param integer $frameRate
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* @param integer $kiloBitrate
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function setFrameRate($frameRate)
|
||||
public function setKiloBitrate($kiloBitrate)
|
||||
{
|
||||
if ($frameRate < 1) {
|
||||
throw new InvalidArgumentException('Wrong framerate value');
|
||||
if ($kiloBitrate < 1) {
|
||||
throw new InvalidArgumentException('Wrong kiloBitrate value');
|
||||
}
|
||||
|
||||
$this->frameRate = (int) $frameRate;
|
||||
$this->kiloBitrate = (int) $kiloBitrate;
|
||||
|
||||
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
|
||||
*
|
||||
* @param string $videoCodec
|
||||
* @throws \InvalidArgumentException
|
||||
* @param string $videoCodec
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function setVideoCodec($videoCodec)
|
||||
{
|
||||
|
|
@ -209,32 +86,6 @@ abstract class DefaultVideo extends DefaultAudio implements Interactive, Resampl
|
|||
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}
|
||||
*/
|
||||
|
|
@ -244,24 +95,27 @@ abstract class DefaultVideo extends DefaultAudio implements Interactive, Resampl
|
|||
}
|
||||
|
||||
/**
|
||||
* Used to determine what resolutions sizes are valid.
|
||||
*
|
||||
* @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
|
||||
* @return integer
|
||||
*/
|
||||
public function getModulus()
|
||||
{
|
||||
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
|
||||
*
|
||||
* @author Romain Neutron imprec@gmail.com
|
||||
*/
|
||||
class Ogg extends DefaultVideo
|
||||
{
|
||||
protected $audioCodec = 'libvorbis';
|
||||
protected $videoCodec = 'libtheora';
|
||||
public function __construct($audioCodec = 'libvorbis', $videoCodec = 'libtheora')
|
||||
{
|
||||
$this
|
||||
->setAudioCodec($audioCodec)
|
||||
->setVideoCodec($videoCodec);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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
|
||||
*
|
||||
* @author Romain Neutron imprec@gmail.com
|
||||
*/
|
||||
class WMV extends DefaultVideo
|
||||
{
|
||||
protected $audioCodec = 'wmav2';
|
||||
protected $videoCodec = 'wmv2';
|
||||
public function __construct($audioCodec = 'wmav2', $videoCodec = 'wmv2')
|
||||
{
|
||||
$this
|
||||
->setAudioCodec($audioCodec)
|
||||
->setVideoCodec($videoCodec);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
|
|
|
|||
|
|
@ -13,13 +13,15 @@ namespace FFMpeg\Format\Video;
|
|||
|
||||
/**
|
||||
* The WMV video format
|
||||
*
|
||||
* @author Romain Neutron imprec@gmail.com
|
||||
*/
|
||||
class WMV3 extends DefaultVideo
|
||||
{
|
||||
protected $audioCodec = 'wmav3';
|
||||
protected $videoCodec = 'wmv3';
|
||||
public function __construct($audioCodec = 'wmav3', $videoCodec = 'wmv3')
|
||||
{
|
||||
$this
|
||||
->setAudioCodec($audioCodec)
|
||||
->setVideoCodec($videoCodec);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
|
|
|
|||
|
|
@ -13,13 +13,15 @@ namespace FFMpeg\Format\Video;
|
|||
|
||||
/**
|
||||
* The WebM video format
|
||||
*
|
||||
* @author Romain Neutron imprec@gmail.com
|
||||
*/
|
||||
class WebM extends DefaultVideo
|
||||
{
|
||||
protected $audioCodec = 'libvorbis';
|
||||
protected $videoCodec = 'libvpx';
|
||||
public function __construct($audioCodec = 'libvorbis', $videoCodec = 'libvpx')
|
||||
{
|
||||
$this
|
||||
->setAudioCodec($audioCodec)
|
||||
->setVideoCodec($videoCodec);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
|
|
|
|||
|
|
@ -13,13 +13,15 @@ namespace FFMpeg\Format\Video;
|
|||
|
||||
/**
|
||||
* The X264 video format
|
||||
*
|
||||
* @author Romain Neutron imprec@gmail.com
|
||||
*/
|
||||
class X264 extends DefaultVideo
|
||||
{
|
||||
protected $audioCodec = 'libmp3lame';
|
||||
protected $videoCodec = 'libx264';
|
||||
public function __construct($audioCodec = 'libfaac', $videoCodec = 'libx264')
|
||||
{
|
||||
$this
|
||||
->setAudioCodec($audioCodec)
|
||||
->setVideoCodec($videoCodec);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
|
|
|
|||
|
|
@ -11,17 +11,54 @@
|
|||
|
||||
namespace FFMpeg\Format;
|
||||
|
||||
/**
|
||||
* The base video interface
|
||||
*
|
||||
* @author Romain Neutron imprec@gmail.com
|
||||
*/
|
||||
interface VideoInterface extends AudioInterface
|
||||
{
|
||||
/**
|
||||
* Get the kiloBitrate value
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getKiloBitrate();
|
||||
|
||||
/**
|
||||
* Returns the number of passes
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
|
||||
namespace FFMpeg\Format\Audio;
|
||||
namespace FFMpeg\Media;
|
||||
|
||||
use FFMpeg\Format\AudioInterface;
|
||||
|
||||
/**
|
||||
* @author Romain Neutron imprec@gmail.com
|
||||
*/
|
||||
interface Transcodable extends AudioInterface
|
||||
interface MediaTypeInterface
|
||||
{
|
||||
/**
|
||||
* Returns the available filters
|
||||
*/
|
||||
public function filters();
|
||||
|
||||
/**
|
||||
* Returns the audio codec
|
||||
*
|
||||
* @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