Merge branch 'master' into patch-1
This commit is contained in:
commit
d48cda2861
39 changed files with 1014 additions and 306 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -5,3 +5,4 @@ composer.phar
|
|||
composer.lock
|
||||
phpunit.xml
|
||||
sami.phar
|
||||
.idea/
|
||||
13
.travis.yml
13
.travis.yml
|
|
@ -5,6 +5,12 @@ dist: trusty
|
|||
branches:
|
||||
only:
|
||||
- master
|
||||
- v1.x
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.composer/cache
|
||||
- $HOME/.cache
|
||||
|
||||
php:
|
||||
- 5.4
|
||||
|
|
@ -12,11 +18,10 @@ php:
|
|||
- 5.6
|
||||
- 7.0
|
||||
- 7.1
|
||||
- hhvm
|
||||
- 7.2
|
||||
- 7.3
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- php: hhvm
|
||||
include:
|
||||
- php: 5.4
|
||||
env: COMPOSER_FLAGS="--prefer-lowest"
|
||||
|
|
@ -29,7 +34,7 @@ before_install:
|
|||
|
||||
install:
|
||||
- sudo apt-get install -y ffmpeg
|
||||
- composer update --prefer-source $COMPOSER_FLAGS
|
||||
- composer update --prefer-dist $COMPOSER_FLAGS
|
||||
|
||||
script:
|
||||
- vendor/bin/phpunit --verbose
|
||||
|
|
|
|||
113
README.md
113
README.md
|
|
@ -1,10 +1,10 @@
|
|||
# PHP FFmpeg
|
||||
# php-ffmpeg
|
||||
|
||||
[](http://travis-ci.org/PHP-FFMpeg/PHP-FFMpeg)
|
||||
|
||||
[](https://insight.sensiolabs.com/projects/607f3111-e2d7-44e8-8bcc-54dd64521983)
|
||||
|
||||
An Object Oriented library to convert video/audio files with FFmpeg / AVConv.
|
||||
An Object-Oriented library to convert video/audio files with FFmpeg / AVConv.
|
||||
|
||||
Check another amazing repo: [PHP FFMpeg extras](https://github.com/alchemy-fr/PHP-FFMpeg-Extras), you will find lots of Audio/Video formats there.
|
||||
|
||||
|
|
@ -16,7 +16,7 @@ This library requires a working FFMpeg install. You will need both FFMpeg and FF
|
|||
Be sure that these binaries can be located with system PATH to get the benefit of the binary detection,
|
||||
otherwise you should have to explicitly give the binaries path on load.
|
||||
|
||||
For Windows users : Please find the binaries at http://ffmpeg.zeranoe.com/builds/.
|
||||
For Windows users: Please find the binaries at http://ffmpeg.zeranoe.com/builds/.
|
||||
|
||||
### Known issues:
|
||||
|
||||
|
|
@ -35,6 +35,9 @@ $ composer require php-ffmpeg/php-ffmpeg
|
|||
## Basic Usage
|
||||
|
||||
```php
|
||||
|
||||
require 'vendor/autoload.php';
|
||||
|
||||
$ffmpeg = FFMpeg\FFMpeg::create();
|
||||
$video = $ffmpeg->open('video.mpg');
|
||||
$video
|
||||
|
|
@ -125,7 +128,7 @@ $video->save($format, 'video.avi');
|
|||
```
|
||||
|
||||
Transcoding progress can be monitored in realtime, see Format documentation
|
||||
below for more informations.
|
||||
below for more information.
|
||||
|
||||
##### Extracting image
|
||||
|
||||
|
|
@ -141,7 +144,7 @@ $frame = $video->frame(FFMpeg\Coordinate\TimeCode::fromSeconds(42));
|
|||
$frame->save('image.jpg');
|
||||
```
|
||||
|
||||
If you want to extract multiple images from your video, you can use the following filter:
|
||||
If you want to extract multiple images from the video, you can use the following filter:
|
||||
|
||||
```php
|
||||
$video
|
||||
|
|
@ -152,6 +155,38 @@ $video
|
|||
$video
|
||||
->save(new FFMpeg\Format\Video\X264(), '/path/to/new/file');
|
||||
```
|
||||
By default, this will save the frames as `jpg` images.
|
||||
|
||||
You are able to override this using `setFrameFileType` to save the frames in another format:
|
||||
```php
|
||||
$frameFileType = 'jpg'; // either 'jpg', 'jpeg' or 'png'
|
||||
$filter = new ExtractMultipleFramesFilter($frameRate, $destinationFolder);
|
||||
$filter->setFrameFileType($frameFileType);
|
||||
|
||||
$video->addFilter($filter);
|
||||
```
|
||||
|
||||
##### Clip
|
||||
|
||||
Cuts the video at a desired point. Use input seeking method. It is faster option than use filter clip.
|
||||
|
||||
```php
|
||||
$clip = $video->clip(FFMpeg\Coordinate\TimeCode::fromSeconds(30), FFMpeg\Coordinate\TimeCode::fromSeconds(15));
|
||||
$clip->save(new FFMpeg\Format\Video\X264(), 'video.avi');
|
||||
```
|
||||
|
||||
The clip filter takes two parameters:
|
||||
|
||||
- `$start`, an instance of `FFMpeg\Coordinate\TimeCode`, specifies the start point of the clip
|
||||
- `$duration`, optional, an instance of `FFMpeg\Coordinate\TimeCode`, specifies the duration of the clip
|
||||
|
||||
On clip you can apply same filters as on video. For example resizing filter.
|
||||
|
||||
```php
|
||||
$clip = $video->clip(FFMpeg\Coordinate\TimeCode::fromSeconds(30), FFMpeg\Coordinate\TimeCode::fromSeconds(15));
|
||||
$clip->filters()->resize(new FFMpeg\Coordinate\Dimension(320, 240), FFMpeg\Filters\Video\ResizeFilter::RESIZEMODE_INSET, true);
|
||||
$clip->save(new FFMpeg\Format\Video\X264(), 'video.avi');
|
||||
```
|
||||
|
||||
##### Generate a waveform
|
||||
|
||||
|
|
@ -159,13 +194,13 @@ You can generate a waveform of an audio file using the `FFMpeg\Media\Audio::wave
|
|||
method.
|
||||
|
||||
This code returns a `FFMpeg\Media\Waveform` instance.
|
||||
You can optionally pass dimensions as arguments, see dedicated
|
||||
You can optionally pass dimensions as the first two arguments and an array of hex string colors for ffmpeg to use for the waveform, see dedicated
|
||||
documentation below for more information.
|
||||
|
||||
The ouput file MUST use the PNG extension.
|
||||
The output file MUST use the PNG extension.
|
||||
|
||||
```php
|
||||
$waveform = $audio->waveform(640, 120);
|
||||
$waveform = $audio->waveform(640, 120, array('#00FF00'));
|
||||
$waveform->save('waveform.png');
|
||||
```
|
||||
|
||||
|
|
@ -178,8 +213,8 @@ $video = $ffmpeg->open( 'video.mp4' );
|
|||
// Set an audio format
|
||||
$audio_format = new FFMpeg\Format\Audio\Mp3();
|
||||
|
||||
// Extract the audio into a new file
|
||||
$video->save('audio.mp3');
|
||||
// Extract the audio into a new file as mp3
|
||||
$video->save($audio_format, 'audio.mp3');
|
||||
|
||||
// Set the audio file
|
||||
$audio = $ffmpeg->open( 'audio.mp3' );
|
||||
|
|
@ -298,7 +333,7 @@ The framerate filter takes two parameters:
|
|||
Synchronizes audio and video.
|
||||
|
||||
Some containers may use a delay that results in desynchronized outputs. This
|
||||
filters solves this issue.
|
||||
filter solves this issue.
|
||||
|
||||
```php
|
||||
$video->filters()->synchronize();
|
||||
|
|
@ -317,6 +352,18 @@ The clip filter takes two parameters:
|
|||
- `$start`, an instance of `FFMpeg\Coordinate\TimeCode`, specifies the start point of the clip
|
||||
- `$duration`, optional, an instance of `FFMpeg\Coordinate\TimeCode`, specifies the duration of the clip
|
||||
|
||||
###### Crop
|
||||
|
||||
Crops the video based on a width and height(a `Point`)
|
||||
|
||||
```php
|
||||
$video->filters()->crop(new FFMpeg\Coordinate\Point("t*100", 0, true), new FFMpeg\Coordinate\Dimension(200, 600));
|
||||
```
|
||||
|
||||
It takes two parameters:
|
||||
- `$point`, an instance of `FFMpeg\Coordinate\Point`, specifies the point to crop
|
||||
- `$dimension`, an instance of `FFMpeg\Coordinate\Dimension`, specifies the dimension of the output video
|
||||
|
||||
### Audio
|
||||
|
||||
`FFMpeg\Media\Audio` can be transcoded too, ie: change codec, isolate audio or
|
||||
|
|
@ -346,7 +393,7 @@ $audio->save($format, 'track.flac');
|
|||
```
|
||||
|
||||
Transcoding progress can be monitored in realtime, see Format documentation
|
||||
below for more informations.
|
||||
below for more information.
|
||||
|
||||
##### Filters
|
||||
|
||||
|
|
@ -399,7 +446,7 @@ The resample filter takes two parameters :
|
|||
|
||||
#### Frame
|
||||
|
||||
A frame is a image at a timecode of a video ; see documentation above about
|
||||
A frame is an image at a timecode of a video; see documentation above about
|
||||
frame extraction.
|
||||
|
||||
You can save frames using the `FFMpeg\Media\Frame::save` method.
|
||||
|
|
@ -409,7 +456,7 @@ $frame->save('target.jpg');
|
|||
```
|
||||
|
||||
This method has a second optional boolean parameter. Set it to true to get
|
||||
accurate images ; it takes more time to execute.
|
||||
accurate images; it takes more time to execute.
|
||||
|
||||
#### Gif
|
||||
|
||||
|
|
@ -447,7 +494,7 @@ To concatenate videos encoded with the same codec, do as follow:
|
|||
|
||||
```php
|
||||
// In order to instantiate the video object, you HAVE TO pass a path to a valid video file.
|
||||
// We recommand that you put there the path of any of the video you want to use in this concatenation.
|
||||
// We recommend that you put there the path of any of the video you want to use in this concatenation.
|
||||
$video = $ffmpeg->open( '/path/to/video' );
|
||||
$video
|
||||
->concat(array('/path/to/video1', '/path/to/video2'))
|
||||
|
|
@ -460,7 +507,7 @@ To concatenate videos encoded with the same codec, do as follow:
|
|||
|
||||
```php
|
||||
// In order to instantiate the video object, you HAVE TO pass a path to a valid video file.
|
||||
// We recommand that you put there the path of any of the video you want to use in this concatenation.
|
||||
// We recommend that you put there the path of any of the video you want to use in this concatenation.
|
||||
$video = $ffmpeg->open( '/path/to/video' );
|
||||
|
||||
$format = new FFMpeg\Format\Video\X264();
|
||||
|
|
@ -479,10 +526,10 @@ A format implements `FFMpeg\Format\FormatInterface`. To save to a video file,
|
|||
use `FFMpeg\Format\VideoInterface`, and `FFMpeg\Format\AudioInterface` for
|
||||
audio files.
|
||||
|
||||
Format can also extends `FFMpeg\Format\ProgressableInterface` to get realtime
|
||||
informations about the transcoding.
|
||||
A format can also extend `FFMpeg\Format\ProgressableInterface` to get realtime
|
||||
information about the transcoding.
|
||||
|
||||
Predefined formats already provide progress informations as events.
|
||||
Predefined formats already provide progress information as events.
|
||||
|
||||
```php
|
||||
$format = new FFMpeg\Format\Video\X264();
|
||||
|
|
@ -542,12 +589,12 @@ class CustomWMVFormat extends FFMpeg\Format\Video\DefaultVideo
|
|||
|
||||
#### Coordinates
|
||||
|
||||
FFMpeg use many units for time and space coordinates.
|
||||
FFMpeg uses many units for time and space coordinates.
|
||||
|
||||
- `FFMpeg\Coordinate\AspectRatio` represents an aspect ratio.
|
||||
- `FFMpeg\Coordinate\Dimension` represent a dimension.
|
||||
- `FFMpeg\Coordinate\FrameRate` represent a framerate.
|
||||
- `FFMpeg\Coordinate\Point` represent a point.
|
||||
- `FFMpeg\Coordinate\Point` represent a point. (Supports dynamic points since v0.10.0)
|
||||
- `FFMpeg\Coordinate\TimeCode` represent a timecode.
|
||||
|
||||
### FFProbe
|
||||
|
|
@ -571,9 +618,19 @@ $ffprobe
|
|||
->get('duration'); // returns the duration property
|
||||
```
|
||||
|
||||
### Validating media files
|
||||
|
||||
(since 0.10.0)
|
||||
You can validate media files using PHP-FFMpeg's FFProbe wrapper.
|
||||
|
||||
```php
|
||||
$ffprobe = FFMpeg\FFProbe::create();
|
||||
$ffprobe->isValid('/path/to/file/to/check'); // returns bool
|
||||
```
|
||||
|
||||
## Using with Silex Microframework
|
||||
|
||||
Service provider is easy to set up:
|
||||
The service provider is easy to set up:
|
||||
|
||||
```php
|
||||
$app = new Silex\Application();
|
||||
|
|
@ -597,10 +654,14 @@ $app->register(new FFMpeg\FFMpegServiceProvider(), array(
|
|||
));
|
||||
```
|
||||
|
||||
## API Browser
|
||||
|
||||
Browse the [API](https://ffmpeg-php.readthedocs.io/en/latest/_static/API/)
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the [MIT license](http://opensource.org/licenses/MIT).
|
||||
|
||||
Music: "Favorite Secrets" by Waylon Thornton
|
||||
From the Free Music Archive
|
||||
[CC BY NC SA](http://creativecommons.org/licenses/by-nc-sa/3.0/us/)
|
||||
|
||||
Music: "Siesta" by Jahzzar
|
||||
From the Free Music Archive
|
||||
[CC BY SA](https://creativecommons.org/licenses/by-sa/3.0/)
|
||||
|
|
|
|||
|
|
@ -24,11 +24,16 @@
|
|||
"name": "Romain Biard",
|
||||
"email": "romain.biard@gmail.com",
|
||||
"homepage": "https://www.strime.io/"
|
||||
},
|
||||
{
|
||||
"name": "Jens Hausdorf",
|
||||
"email": "hello@jens-hausdorf.de",
|
||||
"homepage": "https://jens-hausdorf.de"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^5.3.9 || ^7.0",
|
||||
"alchemy/binary-driver": "^1.5",
|
||||
"alchemy/binary-driver": "^1.5 || ~2.0.0 || ^5.0",
|
||||
"doctrine/cache": "^1.0",
|
||||
"evenement/evenement": "^3.0 || ^2.0 || ^1.0",
|
||||
"neutron/temporary-filesystem": "^2.1.1"
|
||||
|
|
@ -39,7 +44,7 @@
|
|||
"require-dev": {
|
||||
"sami/sami": "~1.0",
|
||||
"silex/silex": "~1.0",
|
||||
"phpunit/phpunit": "^4.8"
|
||||
"phpunit/phpunit": "^4.8.36"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,12 @@ class AspectRatio
|
|||
const AR_4_3 = '4/3';
|
||||
// named 16:9 or 1.77:1 HD video standard
|
||||
const AR_16_9 = '16/9';
|
||||
|
||||
// named 8:5 or 16:10 or 1.6:1
|
||||
const AR_8_5 = '8/5';
|
||||
|
||||
// named 25:16 or 1.56:1
|
||||
const AR_25_16 = '25/16';
|
||||
|
||||
// named 3:2 or 1.5:1 see http://en.wikipedia.org/wiki/135_film
|
||||
const AR_3_2 = '3/2';
|
||||
|
|
@ -160,6 +166,10 @@ class AspectRatio
|
|||
return 4 / 3;
|
||||
case static::AR_16_9:
|
||||
return 16 / 9;
|
||||
case static::AR_8_5:
|
||||
return 8 / 5;
|
||||
case static::AR_25_16:
|
||||
return 25 / 16;
|
||||
case static::AR_1_1:
|
||||
return 1 / 1;
|
||||
case static::AR_1_DOT_85_1:
|
||||
|
|
@ -207,6 +217,8 @@ class AspectRatio
|
|||
$availables = array(
|
||||
static::AR_4_3 => static::valueFromName(static::AR_4_3),
|
||||
static::AR_16_9 => static::valueFromName(static::AR_16_9),
|
||||
static::AR_8_5 => static::valueFromName(static::AR_8_5),
|
||||
static::AR_25_16 => static::valueFromName(static::AR_25_16),
|
||||
static::AR_1_1 => static::valueFromName(static::AR_1_1),
|
||||
static::AR_1_DOT_85_1 => static::valueFromName(static::AR_1_DOT_85_1),
|
||||
static::AR_2_DOT_39_1 => static::valueFromName(static::AR_2_DOT_39_1),
|
||||
|
|
|
|||
|
|
@ -16,10 +16,15 @@ class Point
|
|||
private $x;
|
||||
private $y;
|
||||
|
||||
public function __construct($x, $y)
|
||||
public function __construct($x, $y, $dynamic = false)
|
||||
{
|
||||
$this->x = (int) $x;
|
||||
$this->y = (int) $y;
|
||||
if ($dynamic) {
|
||||
$this->x = $x;
|
||||
$this->y = $y;
|
||||
} else {
|
||||
$this->x = (int)$x;
|
||||
$this->y = (int)$y;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -170,6 +170,25 @@ class FFProbe
|
|||
return $this->probe($pathfile, '-show_format', static::TYPE_FORMAT);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api
|
||||
*
|
||||
* Checks wether the given `$pathfile` is considered a valid media file.
|
||||
*
|
||||
* @param string $pathfile
|
||||
* @return bool
|
||||
* @since 0.10.0
|
||||
*/
|
||||
public function isValid($pathfile)
|
||||
{
|
||||
try {
|
||||
return $this->format($pathfile)->get('duration') > 0;
|
||||
} catch(\Exception $e) {
|
||||
// complete invalid data
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api
|
||||
*
|
||||
|
|
|
|||
|
|
@ -71,4 +71,18 @@ class AudioFilters
|
|||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a custom filter
|
||||
*
|
||||
* @param string $parameters
|
||||
*
|
||||
* @return AudioFilters
|
||||
*/
|
||||
public function custom($parameters)
|
||||
{
|
||||
$this->media->addFilter(new CustomFilter($parameters));
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
52
src/FFMpeg/Filters/Audio/CustomFilter.php
Normal file
52
src/FFMpeg/Filters/Audio/CustomFilter.php
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of PHP-FFmpeg.
|
||||
*
|
||||
* (c) Alchemy <dev.team@alchemy.fr>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
namespace FFMpeg\Filters\Audio;
|
||||
|
||||
use FFMpeg\Format\AudioInterface;
|
||||
use FFMpeg\Media\Audio;
|
||||
|
||||
class CustomFilter implements AudioFilterInterface
|
||||
{
|
||||
/** @var string */
|
||||
private $filter;
|
||||
/** @var integer */
|
||||
private $priority;
|
||||
|
||||
/**
|
||||
* A custom filter, useful if you want to build complex filters
|
||||
*
|
||||
* @param string $filter
|
||||
* @param int $priority
|
||||
*/
|
||||
public function __construct($filter, $priority = 0)
|
||||
{
|
||||
$this->filter = $filter;
|
||||
$this->priority = $priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getPriority()
|
||||
{
|
||||
return $this->priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function apply(Audio $audio, AudioInterface $format)
|
||||
{
|
||||
$commands = array('-af', $this->filter);
|
||||
|
||||
return $commands;
|
||||
}
|
||||
}
|
||||
54
src/FFMpeg/Filters/Frame/CustomFrameFilter.php
Normal file
54
src/FFMpeg/Filters/Frame/CustomFrameFilter.php
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of PHP-FFmpeg.
|
||||
*
|
||||
* (c) Alchemy <dev.team@alchemy.fr>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace FFMpeg\Filters\Frame;
|
||||
|
||||
use FFMpeg\Exception\RuntimeException;
|
||||
use FFMpeg\Media\Frame;
|
||||
|
||||
class CustomFrameFilter implements FrameFilterInterface
|
||||
{
|
||||
/** @var string */
|
||||
private $filter;
|
||||
/** @var integer */
|
||||
private $priority;
|
||||
|
||||
/**
|
||||
* A custom filter, useful if you want to build complex filters
|
||||
*
|
||||
* @param string $filter
|
||||
* @param int $priority
|
||||
*/
|
||||
public function __construct($filter, $priority = 0)
|
||||
{
|
||||
$this->filter = $filter;
|
||||
$this->priority = $priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getPriority()
|
||||
{
|
||||
return $this->priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function apply(Frame $frame)
|
||||
{
|
||||
$commands = array('-vf', $this->filter);
|
||||
|
||||
return $commands;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -36,4 +36,18 @@ class FrameFilters
|
|||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a custom filter: -vf foo bar
|
||||
*
|
||||
* @param string $parameters
|
||||
*
|
||||
* @return FrameFilters
|
||||
*/
|
||||
public function custom($parameters)
|
||||
{
|
||||
$this->frame->addFilter(new CustomFrameFilter($parameters));
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,10 @@ class ExtractMultipleFramesFilter implements VideoFilterInterface
|
|||
private $priority;
|
||||
private $frameRate;
|
||||
private $destinationFolder;
|
||||
private $frameFileType = 'jpg';
|
||||
|
||||
/** @var array */
|
||||
private static $supportedFrameFileTypes = ['jpg', 'jpeg', 'png'];
|
||||
|
||||
public function __construct($frameRate = self::FRAMERATE_EVERY_SEC, $destinationFolder = __DIR__, $priority = 0)
|
||||
{
|
||||
|
|
@ -49,6 +53,20 @@ class ExtractMultipleFramesFilter implements VideoFilterInterface
|
|||
$this->destinationFolder = $destinationFolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $frameFileType
|
||||
* @throws \FFMpeg\Exception\InvalidArgumentException
|
||||
* @return ExtractMultipleFramesFilter
|
||||
*/
|
||||
public function setFrameFileType($frameFileType) {
|
||||
if (in_array($frameFileType, self::$supportedFrameFileTypes)) {
|
||||
$this->frameFileType = $frameFileType;
|
||||
return $this;
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException('Invalid frame file type, use: ' . implode(',', self::$supportedFrameFileTypes));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
|
@ -117,7 +135,7 @@ class ExtractMultipleFramesFilter implements VideoFilterInterface
|
|||
// Set the parameters
|
||||
$commands[] = '-vf';
|
||||
$commands[] = 'fps=' . $this->frameRate;
|
||||
$commands[] = $this->destinationFolder . 'frame-%'.$nbDigitsInFileNames.'d.jpg';
|
||||
$commands[] = $this->destinationFolder . 'frame-%'.$nbDigitsInFileNames.'d.' . $this->frameFileType;
|
||||
}
|
||||
catch (RuntimeException $e) {
|
||||
throw new RuntimeException('An error occured while extracting the frames: ' . $e->getMessage() . '. The code: ' . $e->getCode());
|
||||
|
|
|
|||
|
|
@ -121,10 +121,10 @@ abstract class DefaultAudio extends EventEmitter implements AudioInterface, Prog
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createProgressListener(MediaTypeInterface $media, FFProbe $ffprobe, $pass, $total)
|
||||
public function createProgressListener(MediaTypeInterface $media, FFProbe $ffprobe, $pass, $total, $duration = 0)
|
||||
{
|
||||
$format = $this;
|
||||
$listener = new AudioProgressListener($ffprobe, $media->getPathfile(), $pass, $total);
|
||||
$listener = new AudioProgressListener($ffprobe, $media->getPathfile(), $pass, $total, $duration);
|
||||
$listener->on('progress', function () use ($media, $format) {
|
||||
$format->emit('progress', array_merge(array($media, $format), func_get_args()));
|
||||
});
|
||||
|
|
|
|||
|
|
@ -80,12 +80,13 @@ abstract class AbstractProgressListener extends EventEmitter implements Listener
|
|||
*
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function __construct(FFProbe $ffprobe, $pathfile, $currentPass, $totalPass)
|
||||
public function __construct(FFProbe $ffprobe, $pathfile, $currentPass, $totalPass, $duration = 0)
|
||||
{
|
||||
$this->ffprobe = $ffprobe;
|
||||
$this->pathfile = $pathfile;
|
||||
$this->currentPass = $currentPass;
|
||||
$this->totalPass = $totalPass;
|
||||
$this->duration = $duration;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -254,9 +255,8 @@ abstract class AbstractProgressListener extends EventEmitter implements Listener
|
|||
return;
|
||||
}
|
||||
|
||||
$this->totalSize = $format->get('size') / 1024;
|
||||
$this->duration = $format->get('duration');
|
||||
|
||||
$this->duration = (int) $this->duration > 0 ? $this->duration : $format->get('duration');
|
||||
$this->totalSize = $format->get('size') / 1024 * ($this->duration / $format->get('duration'));
|
||||
$this->initialized = true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,8 +24,9 @@ interface ProgressableInterface extends EventEmitterInterface
|
|||
* @param FFProbe $ffprobe
|
||||
* @param Integer $pass The current pas snumber
|
||||
* @param Integer $total The total pass number
|
||||
* @param Integer $duration The new video duration
|
||||
*
|
||||
* @return array An array of listeners
|
||||
*/
|
||||
public function createProgressListener(MediaTypeInterface $media, FFProbe $ffprobe, $pass, $total);
|
||||
public function createProgressListener(MediaTypeInterface $media, FFProbe $ffprobe, $pass, $total, $duration = 0);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -125,10 +125,10 @@ abstract class DefaultVideo extends DefaultAudio implements VideoInterface
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createProgressListener(MediaTypeInterface $media, FFProbe $ffprobe, $pass, $total)
|
||||
public function createProgressListener(MediaTypeInterface $media, FFProbe $ffprobe, $pass, $total, $duration = 0)
|
||||
{
|
||||
$format = $this;
|
||||
$listeners = array(new VideoProgressListener($ffprobe, $media->getPathfile(), $pass, $total));
|
||||
$listeners = array(new VideoProgressListener($ffprobe, $media->getPathfile(), $pass, $total, $duration));
|
||||
|
||||
foreach ($listeners as $listener) {
|
||||
$listener->on('progress', function () use ($format, $media) {
|
||||
|
|
|
|||
|
|
@ -52,6 +52,6 @@ class WebM extends DefaultVideo
|
|||
*/
|
||||
public function getAvailableVideoCodecs()
|
||||
{
|
||||
return array('libvpx');
|
||||
return array('libvpx', 'libvpx-vp9');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
292
src/FFMpeg/Media/AbstractVideo.php
Normal file
292
src/FFMpeg/Media/AbstractVideo.php
Normal file
|
|
@ -0,0 +1,292 @@
|
|||
<?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\SimpleFilter;
|
||||
use FFMpeg\Exception\InvalidArgumentException;
|
||||
use FFMpeg\Exception\RuntimeException;
|
||||
use FFMpeg\Filters\Video\VideoFilters;
|
||||
use FFMpeg\Filters\FilterInterface;
|
||||
use FFMpeg\Format\FormatInterface;
|
||||
use FFMpeg\Format\ProgressableInterface;
|
||||
use FFMpeg\Format\AudioInterface;
|
||||
use FFMpeg\Format\VideoInterface;
|
||||
use Neutron\TemporaryFilesystem\Manager as FsManager;
|
||||
use FFMpeg\Filters\Video\ClipFilter;
|
||||
|
||||
abstract class AbstractVideo extends Audio
|
||||
{
|
||||
|
||||
/**
|
||||
* FileSystem Manager instance
|
||||
* @var Manager
|
||||
*/
|
||||
protected $fs;
|
||||
|
||||
/**
|
||||
* FileSystem Manager ID
|
||||
* @var int
|
||||
*/
|
||||
protected $fsId;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @return VideoFilters
|
||||
*/
|
||||
public function filters()
|
||||
{
|
||||
return new VideoFilters($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @return Video
|
||||
*/
|
||||
public function addFilter(FilterInterface $filter)
|
||||
{
|
||||
$this->filters->add($filter);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports the video in the desired format, applies registered filters.
|
||||
*
|
||||
* @param FormatInterface $format
|
||||
* @param string $outputPathfile
|
||||
* @return Video
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function save(FormatInterface $format, $outputPathfile)
|
||||
{
|
||||
$passes = $this->buildCommand($format, $outputPathfile);
|
||||
|
||||
$failure = null;
|
||||
$totalPasses = $format->getPasses();
|
||||
|
||||
foreach ($passes as $pass => $passCommands) {
|
||||
try {
|
||||
/** add listeners here */
|
||||
$listeners = null;
|
||||
|
||||
if ($format instanceof ProgressableInterface) {
|
||||
$filters = clone $this->filters;
|
||||
$duration = 0;
|
||||
|
||||
// check the filters of the video, and if the video has the ClipFilter then
|
||||
// take the new video duration and send to the
|
||||
// FFMpeg\Format\ProgressListener\AbstractProgressListener class
|
||||
foreach ($filters as $filter) {
|
||||
if ($filter instanceof ClipFilter) {
|
||||
$duration = $filter->getDuration()->toSeconds();
|
||||
break;
|
||||
}
|
||||
}
|
||||
$listeners = $format->createProgressListener($this, $this->ffprobe, $pass + 1, $totalPasses, $duration);
|
||||
}
|
||||
|
||||
$this->driver->command($passCommands, false, $listeners);
|
||||
} catch (ExecutionFailureException $e) {
|
||||
$failure = $e;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$this->fs->clean($this->fsId);
|
||||
|
||||
if (null !== $failure) {
|
||||
throw new RuntimeException('Encoding failed', $failure->getCode(), $failure);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* NOTE: This method is different to the Audio's one, because Video is using passes.
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getFinalCommand(FormatInterface $format, $outputPathfile)
|
||||
{
|
||||
$finalCommands = array();
|
||||
|
||||
foreach ($this->buildCommand($format, $outputPathfile) as $pass => $passCommands) {
|
||||
$finalCommands[] = implode(' ', $passCommands);
|
||||
}
|
||||
|
||||
$this->fs->clean($this->fsId);
|
||||
|
||||
return $finalCommands;
|
||||
}
|
||||
|
||||
/**
|
||||
* **NOTE:** This creates passes instead of a single command!
|
||||
*
|
||||
* @inheritDoc
|
||||
* @return string[][]
|
||||
*/
|
||||
protected function buildCommand(FormatInterface $format, $outputPathfile)
|
||||
{
|
||||
$commands = $this->basePartOfCommand();
|
||||
|
||||
$filters = clone $this->filters;
|
||||
$filters->add(new SimpleFilter($format->getExtraParams(), 10));
|
||||
|
||||
if ($this->driver->getConfiguration()->has('ffmpeg.threads')) {
|
||||
$filters->add(new SimpleFilter(array('-threads', $this->driver->getConfiguration()->get('ffmpeg.threads'))));
|
||||
}
|
||||
if ($format instanceof VideoInterface) {
|
||||
if (null !== $format->getVideoCodec()) {
|
||||
$filters->add(new SimpleFilter(array('-vcodec', $format->getVideoCodec())));
|
||||
}
|
||||
}
|
||||
if ($format instanceof AudioInterface) {
|
||||
if (null !== $format->getAudioCodec()) {
|
||||
$filters->add(new SimpleFilter(array('-acodec', $format->getAudioCodec())));
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($filters as $filter) {
|
||||
$commands = array_merge($commands, $filter->apply($this, $format));
|
||||
}
|
||||
|
||||
if ($format instanceof VideoInterface) {
|
||||
$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';
|
||||
}
|
||||
|
||||
if ($format instanceof AudioInterface) {
|
||||
if (null !== $format->getAudioKiloBitrate()) {
|
||||
$commands[] = '-b:a';
|
||||
$commands[] = $format->getAudioKiloBitrate() . 'k';
|
||||
}
|
||||
if (null !== $format->getAudioChannels()) {
|
||||
$commands[] = '-ac';
|
||||
$commands[] = $format->getAudioChannels();
|
||||
}
|
||||
}
|
||||
|
||||
// If the user passed some additional parameters
|
||||
if ($format instanceof VideoInterface) {
|
||||
if (null !== $format->getAdditionalParameters()) {
|
||||
foreach ($format->getAdditionalParameters() as $additionalParameter) {
|
||||
$commands[] = $additionalParameter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Merge Filters into one command
|
||||
$videoFilterVars = $videoFilterProcesses = array();
|
||||
for ($i = 0; $i < count($commands); $i++) {
|
||||
$command = $commands[$i];
|
||||
if ($command === '-vf') {
|
||||
$commandSplits = explode(";", $commands[$i + 1]);
|
||||
if (count($commandSplits) == 1) {
|
||||
$commandSplit = $commandSplits[0];
|
||||
$command = trim($commandSplit);
|
||||
if (preg_match("/^\[in\](.*?)\[out\]$/is", $command, $match)) {
|
||||
$videoFilterProcesses[] = $match[1];
|
||||
} else {
|
||||
$videoFilterProcesses[] = $command;
|
||||
}
|
||||
} else {
|
||||
foreach ($commandSplits as $commandSplit) {
|
||||
$command = trim($commandSplit);
|
||||
if (preg_match("/^\[[^\]]+\](.*?)\[[^\]]+\]$/is", $command, $match)) {
|
||||
$videoFilterProcesses[] = $match[1];
|
||||
} else {
|
||||
$videoFilterVars[] = $command;
|
||||
}
|
||||
}
|
||||
}
|
||||
unset($commands[$i]);
|
||||
unset($commands[$i + 1]);
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
$videoFilterCommands = $videoFilterVars;
|
||||
$lastInput = 'in';
|
||||
foreach ($videoFilterProcesses as $i => $process) {
|
||||
$command = '[' . $lastInput . ']';
|
||||
$command .= $process;
|
||||
$lastInput = 'p' . $i;
|
||||
if ($i === (count($videoFilterProcesses) - 1)) {
|
||||
$command .= '[out]';
|
||||
} else {
|
||||
$command .= '[' . $lastInput . ']';
|
||||
}
|
||||
|
||||
$videoFilterCommands[] = $command;
|
||||
}
|
||||
$videoFilterCommand = implode(';', $videoFilterCommands);
|
||||
|
||||
if ($videoFilterCommand) {
|
||||
$commands[] = '-vf';
|
||||
$commands[] = $videoFilterCommand;
|
||||
}
|
||||
|
||||
$this->fs = FsManager::create();
|
||||
$this->fsId = uniqid('ffmpeg-passes');
|
||||
$passPrefix = $this->fs->createTemporaryDirectory(0777, 50, $this->fsId) . '/' . uniqid('pass-');
|
||||
$passes = array();
|
||||
$totalPasses = $format->getPasses();
|
||||
|
||||
if (!$totalPasses) {
|
||||
throw new InvalidArgumentException('Pass number should be a positive value.');
|
||||
}
|
||||
|
||||
for ($i = 1; $i <= $totalPasses; $i++) {
|
||||
$pass = $commands;
|
||||
|
||||
if ($totalPasses > 1) {
|
||||
$pass[] = '-pass';
|
||||
$pass[] = $i;
|
||||
$pass[] = '-passlogfile';
|
||||
$pass[] = $passPrefix;
|
||||
}
|
||||
|
||||
$pass[] = $outputPathfile;
|
||||
|
||||
$passes[] = $pass;
|
||||
}
|
||||
|
||||
return $passes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return base part of command.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function basePartOfCommand()
|
||||
{
|
||||
return array('-y', '-i', $this->pathfile);
|
||||
}
|
||||
}
|
||||
|
|
@ -52,11 +52,9 @@ class Audio extends AbstractStreamableMedia
|
|||
/**
|
||||
* Exports the audio in the desired format, applies registered filters.
|
||||
*
|
||||
* @param FormatInterface $format
|
||||
* @param string $outputPathfile
|
||||
*
|
||||
* @param FormatInterface $format
|
||||
* @param string $outputPathfile
|
||||
* @return Audio
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function save(FormatInterface $format, $outputPathfile)
|
||||
|
|
@ -64,9 +62,42 @@ class Audio extends AbstractStreamableMedia
|
|||
$listeners = null;
|
||||
|
||||
if ($format instanceof ProgressableInterface) {
|
||||
$listeners = $format->createProgressListener($this, $this->ffprobe, 1, 1);
|
||||
$listeners = $format->createProgressListener($this, $this->ffprobe, 1, 1, 0);
|
||||
}
|
||||
|
||||
$commands = $this->buildCommand($format, $outputPathfile);
|
||||
|
||||
try {
|
||||
$this->driver->command($commands, false, $listeners);
|
||||
} catch (ExecutionFailureException $e) {
|
||||
$this->cleanupTemporaryFile($outputPathfile);
|
||||
throw new RuntimeException('Encoding failed', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the final command as a string, useful for debugging purposes.
|
||||
*
|
||||
* @param FormatInterface $format
|
||||
* @param string $outputPathfile
|
||||
* @return string
|
||||
* @since 0.11.0
|
||||
*/
|
||||
public function getFinalCommand(FormatInterface $format, $outputPathfile) {
|
||||
return implode(' ', $this->buildCommand($format, $outputPathfile));
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the command which will be executed with the provided format
|
||||
*
|
||||
* @param FormatInterface $format
|
||||
* @param string $outputPathfile
|
||||
* @return string[] An array which are the components of the command
|
||||
* @since 0.11.0
|
||||
*/
|
||||
protected function buildCommand(FormatInterface $format, $outputPathfile) {
|
||||
$commands = array('-y', '-i', $this->pathfile);
|
||||
|
||||
$filters = clone $this->filters;
|
||||
|
|
@ -93,14 +124,7 @@ class Audio extends AbstractStreamableMedia
|
|||
}
|
||||
$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;
|
||||
return $commands;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -108,10 +132,22 @@ class Audio extends AbstractStreamableMedia
|
|||
*
|
||||
* @param integer $width
|
||||
* @param integer $height
|
||||
* @param array $colors Array of colors for ffmpeg to use. Color format is #000000 (RGB hex string with #)
|
||||
* @return Waveform
|
||||
*/
|
||||
public function waveform($width = 640, $height = 120)
|
||||
public function waveform($width = 640, $height = 120, $colors = array(Waveform::DEFAULT_COLOR))
|
||||
{
|
||||
return new Waveform($this, $this->driver, $this->ffprobe, $width, $height);
|
||||
return new Waveform($this, $this->driver, $this->ffprobe, $width, $height, $colors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Concatenates a list of audio files into one unique audio file.
|
||||
*
|
||||
* @param array $sources
|
||||
* @return Concat
|
||||
*/
|
||||
public function concat($sources)
|
||||
{
|
||||
return new Concat($sources, $this->driver, $this->ffprobe);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
60
src/FFMpeg/Media/Clip.php
Normal file
60
src/FFMpeg/Media/Clip.php
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
namespace FFMpeg\Media;
|
||||
|
||||
use FFMpeg\Driver\FFMpegDriver;
|
||||
use FFMpeg\FFProbe;
|
||||
use FFMpeg\Coordinate\TimeCode;
|
||||
|
||||
/**
|
||||
* Video clip.
|
||||
*
|
||||
* Use input seeking, see http://trac.ffmpeg.org/wiki/Seeking
|
||||
*/
|
||||
class Clip extends Video
|
||||
{
|
||||
|
||||
/** @var TimeCode Start time */
|
||||
private $start;
|
||||
|
||||
/** @var TimeCode Duration */
|
||||
private $duration;
|
||||
|
||||
/** @var Video Parrent video */
|
||||
private $video;
|
||||
|
||||
public function __construct(Video $video, FFMpegDriver $driver, FFProbe $ffprobe, TimeCode $start, TimeCode $duration = null)
|
||||
{
|
||||
$this->start = $start;
|
||||
$this->duration = $duration;
|
||||
$this->video = $video;
|
||||
|
||||
parent::__construct($video->getPathfile(), $driver, $ffprobe);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the video related to the frame.
|
||||
*
|
||||
* @return Video
|
||||
*/
|
||||
public function getVideo()
|
||||
{
|
||||
return $this->video;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return base part of command.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function basePartOfCommand()
|
||||
{
|
||||
$arr = array('-y', '-ss', (string) $this->start, '-i', $this->pathfile);
|
||||
|
||||
if (is_null($this->duration) === false) {
|
||||
$arr[] = '-t';
|
||||
$arr[] = (string) $this->duration;
|
||||
}
|
||||
|
||||
return $arr;
|
||||
}
|
||||
}
|
||||
|
|
@ -72,8 +72,8 @@ class Concat extends AbstractMediaType
|
|||
/**
|
||||
* Saves the concatenated video in the given array, considering that the sources videos are all encoded with the same codec.
|
||||
*
|
||||
* @param array $outputPathfile
|
||||
* @param string $streamCopy
|
||||
* @param string $outputPathfile
|
||||
* @param bool $streamCopy
|
||||
*
|
||||
* @return Concat
|
||||
*
|
||||
|
|
@ -105,7 +105,7 @@ class Concat extends AbstractMediaType
|
|||
if($count_videos != 0)
|
||||
$line .= "\n";
|
||||
|
||||
$line .= "file ".$videoPath;
|
||||
$line .= "file " . addcslashes($videoPath, '\'"\\\0 ');
|
||||
|
||||
fwrite($fileStream, $line);
|
||||
|
||||
|
|
@ -144,6 +144,7 @@ class Concat extends AbstractMediaType
|
|||
$this->driver->command($commands);
|
||||
} catch (ExecutionFailureException $e) {
|
||||
$this->cleanupTemporaryFile($outputPathfile);
|
||||
// TODO@v1: paste this line into an `finally` block.
|
||||
$this->cleanupTemporaryFile($sourcesFile);
|
||||
throw new RuntimeException('Unable to save concatenated video', $e->getCode(), $e);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -115,7 +115,9 @@ class Frame extends AbstractMediaType
|
|||
$commands = array_merge($commands, $filter->apply($this));
|
||||
}
|
||||
|
||||
$commands = array_merge($commands, array($pathfile));
|
||||
if (!$returnBase64) {
|
||||
$commands = array_merge($commands, array($pathfile));
|
||||
}
|
||||
|
||||
try {
|
||||
if(!$returnBase64) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of PHP-FFmpeg.
|
||||
*
|
||||
|
|
@ -8,228 +7,13 @@
|
|||
* 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\Coordinate\Dimension;
|
||||
use FFMpeg\Filters\Audio\SimpleFilter;
|
||||
use FFMpeg\Exception\InvalidArgumentException;
|
||||
use FFMpeg\Exception\RuntimeException;
|
||||
use FFMpeg\Filters\Video\VideoFilters;
|
||||
use FFMpeg\Filters\FilterInterface;
|
||||
use FFMpeg\Format\FormatInterface;
|
||||
use FFMpeg\Format\ProgressableInterface;
|
||||
use FFMpeg\Format\AudioInterface;
|
||||
use FFMpeg\Format\VideoInterface;
|
||||
use Neutron\TemporaryFilesystem\Manager as FsManager;
|
||||
|
||||
class Video extends Audio
|
||||
class Video extends AbstractVideo
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return VideoFilters
|
||||
*/
|
||||
public function filters()
|
||||
{
|
||||
return new VideoFilters($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return Video
|
||||
*/
|
||||
public function addFilter(FilterInterface $filter)
|
||||
{
|
||||
$this->filters->add($filter);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports the video in the desired format, applies registered filters.
|
||||
*
|
||||
* @param FormatInterface $format
|
||||
* @param string $outputPathfile
|
||||
*
|
||||
* @return Video
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function save(FormatInterface $format, $outputPathfile)
|
||||
{
|
||||
$commands = array('-y', '-i', $this->pathfile);
|
||||
|
||||
$filters = clone $this->filters;
|
||||
$filters->add(new SimpleFilter($format->getExtraParams(), 10));
|
||||
|
||||
if ($this->driver->getConfiguration()->has('ffmpeg.threads')) {
|
||||
$filters->add(new SimpleFilter(array('-threads', $this->driver->getConfiguration()->get('ffmpeg.threads'))));
|
||||
}
|
||||
if ($format instanceof VideoInterface) {
|
||||
if (null !== $format->getVideoCodec()) {
|
||||
$filters->add(new SimpleFilter(array('-vcodec', $format->getVideoCodec())));
|
||||
}
|
||||
}
|
||||
if ($format instanceof AudioInterface) {
|
||||
if (null !== $format->getAudioCodec()) {
|
||||
$filters->add(new SimpleFilter(array('-acodec', $format->getAudioCodec())));
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($filters as $filter) {
|
||||
$commands = array_merge($commands, $filter->apply($this, $format));
|
||||
}
|
||||
|
||||
if ($format instanceof VideoInterface) {
|
||||
$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';
|
||||
}
|
||||
|
||||
if ($format instanceof AudioInterface) {
|
||||
if (null !== $format->getAudioKiloBitrate()) {
|
||||
$commands[] = '-b:a';
|
||||
$commands[] = $format->getAudioKiloBitrate() . 'k';
|
||||
}
|
||||
if (null !== $format->getAudioChannels()) {
|
||||
$commands[] = '-ac';
|
||||
$commands[] = $format->getAudioChannels();
|
||||
}
|
||||
}
|
||||
|
||||
// If the user passed some additional parameters
|
||||
if ($format instanceof VideoInterface) {
|
||||
if (null !== $format->getAdditionalParameters()) {
|
||||
foreach ($format->getAdditionalParameters() as $additionalParameter) {
|
||||
$commands[] = $additionalParameter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Merge Filters into one command
|
||||
$videoFilterVars = $videoFilterProcesses = [];
|
||||
for($i=0;$i<count($commands);$i++) {
|
||||
$command = $commands[$i];
|
||||
if ( $command == '-vf' ) {
|
||||
$commandSplits = explode(";", $commands[$i + 1]);
|
||||
if ( count($commandSplits) == 1 ) {
|
||||
$commandSplit = $commandSplits[0];
|
||||
$command = trim($commandSplit);
|
||||
if ( preg_match("/^\[in\](.*?)\[out\]$/is", $command, $match) ) {
|
||||
$videoFilterProcesses[] = $match[1];
|
||||
} else {
|
||||
$videoFilterProcesses[] = $command;
|
||||
}
|
||||
} else {
|
||||
foreach($commandSplits as $commandSplit) {
|
||||
$command = trim($commandSplit);
|
||||
if ( preg_match("/^\[[^\]]+\](.*?)\[[^\]]+\]$/is", $command, $match) ) {
|
||||
$videoFilterProcesses[] = $match[1];
|
||||
} else {
|
||||
$videoFilterVars[] = $command;
|
||||
}
|
||||
}
|
||||
}
|
||||
unset($commands[$i]);
|
||||
unset($commands[$i + 1]);
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
$videoFilterCommands = $videoFilterVars;
|
||||
$lastInput = 'in';
|
||||
foreach($videoFilterProcesses as $i => $process) {
|
||||
$command = '[' . $lastInput .']';
|
||||
$command .= $process;
|
||||
$lastInput = 'p' . $i;
|
||||
if ( $i == count($videoFilterProcesses) - 1 ) {
|
||||
$command .= '[out]';
|
||||
} else {
|
||||
$command .= '[' . $lastInput . ']';
|
||||
}
|
||||
|
||||
$videoFilterCommands[] = $command;
|
||||
}
|
||||
$videoFilterCommand = implode(";", $videoFilterCommands);
|
||||
|
||||
if ( $videoFilterCommand ) {
|
||||
$commands[] = '-vf';
|
||||
$commands[] = $videoFilterCommand;
|
||||
}
|
||||
|
||||
$fs = FsManager::create();
|
||||
$fsId = uniqid('ffmpeg-passes');
|
||||
$passPrefix = $fs->createTemporaryDirectory(0777, 50, $fsId) . '/' . uniqid('pass-');
|
||||
$passes = array();
|
||||
$totalPasses = $format->getPasses();
|
||||
|
||||
if (1 > $totalPasses) {
|
||||
throw new InvalidArgumentException('Pass number should be a positive value.');
|
||||
}
|
||||
|
||||
for ($i = 1; $i <= $totalPasses; $i++) {
|
||||
$pass = $commands;
|
||||
|
||||
if ($totalPasses > 1) {
|
||||
$pass[] = '-pass';
|
||||
$pass[] = $i;
|
||||
$pass[] = '-passlogfile';
|
||||
$pass[] = $passPrefix;
|
||||
}
|
||||
|
||||
$pass[] = $outputPathfile;
|
||||
|
||||
$passes[] = $pass;
|
||||
}
|
||||
|
||||
$failure = null;
|
||||
|
||||
foreach ($passes as $pass => $passCommands) {
|
||||
try {
|
||||
/** add listeners here */
|
||||
$listeners = null;
|
||||
|
||||
if ($format instanceof ProgressableInterface) {
|
||||
$listeners = $format->createProgressListener($this, $this->ffprobe, $pass + 1, $totalPasses);
|
||||
}
|
||||
|
||||
$this->driver->command($passCommands, false, $listeners);
|
||||
} catch (ExecutionFailureException $e) {
|
||||
$failure = $e;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$fs->clean($fsId);
|
||||
|
||||
if (null !== $failure) {
|
||||
throw new RuntimeException('Encoding failed', $failure->getCode(), $failure);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the frame at timecode.
|
||||
|
|
@ -265,4 +49,16 @@ class Video extends Audio
|
|||
{
|
||||
return new Concat($sources, $this->driver, $this->ffprobe);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clips the video at the given time(s).
|
||||
*
|
||||
* @param TimeCode $start Start time
|
||||
* @param TimeCode $duration Duration
|
||||
* @return \FFMpeg\Media\Clip
|
||||
*/
|
||||
public function clip(TimeCode $start, TimeCode $duration = null)
|
||||
{
|
||||
return new Clip($this, $this->driver, $this->ffprobe, $start, $duration);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
namespace FFMpeg\Media;
|
||||
|
||||
use Alchemy\BinaryDriver\Exception\ExecutionFailureException;
|
||||
use FFMpeg\Exception\InvalidArgumentException;
|
||||
use FFMpeg\Filters\Waveform\WaveformFilterInterface;
|
||||
use FFMpeg\Filters\Waveform\WaveformFilters;
|
||||
use FFMpeg\Driver\FFMpegDriver;
|
||||
|
|
@ -20,17 +21,26 @@ use FFMpeg\Exception\RuntimeException;
|
|||
|
||||
class Waveform extends AbstractMediaType
|
||||
{
|
||||
/** @var Video */
|
||||
private $audio;
|
||||
private $width;
|
||||
private $height;
|
||||
const DEFAULT_COLOR = '#000000';
|
||||
|
||||
public function __construct(Audio $audio, FFMpegDriver $driver, FFProbe $ffprobe, $width, $height)
|
||||
/** @var Video */
|
||||
protected $audio;
|
||||
protected $width;
|
||||
protected $height;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $colors;
|
||||
|
||||
public function __construct(Audio $audio, FFMpegDriver $driver, FFProbe $ffprobe, $width, $height, $colors = array(self::DEFAULT_COLOR))
|
||||
{
|
||||
parent::__construct($audio->getPathfile(), $driver, $ffprobe);
|
||||
$this->audio = $audio;
|
||||
$this->width = $width;
|
||||
$this->height = $height;
|
||||
|
||||
$this->setColors($colors);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -65,6 +75,55 @@ class Waveform extends AbstractMediaType
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parameter should be an array containing at least one valid color represented as a HTML color string. For
|
||||
* example #FFFFFF or #000000. By default the color is set to black. Keep in mind that if you save the waveform
|
||||
* as jpg file, it will appear completely black and to avoid this you can set the waveform color to white (#FFFFFF).
|
||||
* Saving waveforms to png is strongly suggested.
|
||||
*
|
||||
* @param array $colors
|
||||
*/
|
||||
public function setColors(array $colors)
|
||||
{
|
||||
foreach ($colors as $row => $value)
|
||||
{
|
||||
if (!preg_match('/^#(?:[0-9a-fA-F]{6})$/', $value))
|
||||
{
|
||||
//invalid color
|
||||
//unset($colors[$row]);
|
||||
|
||||
throw new InvalidArgumentException("The provided color '$value' is invalid");
|
||||
}
|
||||
}
|
||||
|
||||
if (count($colors))
|
||||
{
|
||||
$this->colors = $colors;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of colors that will be passed to ffmpeg to use for waveform generation. Colors are applied ONLY
|
||||
* to the waveform. Background cannot be controlled that easily and it is probably easier to save the waveform
|
||||
* as a transparent png file and then add background of choice.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getColors()
|
||||
{
|
||||
return $this->colors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles the selected colors into a string, using a pipe separator.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function compileColors()
|
||||
{
|
||||
return implode('|', $this->colors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the waveform in the given filename.
|
||||
*
|
||||
|
|
@ -81,8 +140,8 @@ class Waveform extends AbstractMediaType
|
|||
* @see http://ffmpeg.org/ffmpeg.html#Main-options
|
||||
*/
|
||||
$commands = array(
|
||||
'-i', $this->pathfile, '-filter_complex',
|
||||
'showwavespic=s='.$this->width.'x'.$this->height,
|
||||
'-y', '-i', $this->pathfile, '-filter_complex',
|
||||
'showwavespic=colors='.$this->compileColors().':s='.$this->width.'x'.$this->height,
|
||||
'-frames:v', '1'
|
||||
);
|
||||
|
||||
|
|
|
|||
31
tests/Functional/AudioConcatenationTest.php
Normal file
31
tests/Functional/AudioConcatenationTest.php
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\FFMpeg\Functional;
|
||||
|
||||
use FFMpeg\Format\Audio\Mp3;
|
||||
use FFMpeg\Media\Audio;
|
||||
|
||||
class AudioConcatenationTest extends FunctionalTestCase
|
||||
{
|
||||
public function testSimpleAudioFileConcatTest()
|
||||
{
|
||||
$ffmpeg = $this->getFFMpeg();
|
||||
|
||||
$files = [
|
||||
__DIR__ . '/../files/Jahzzar_-_05_-_Siesta.mp3',
|
||||
__DIR__ . '/../files/02_-_Favorite_Secrets.mp3',
|
||||
];
|
||||
|
||||
$audio = $ffmpeg->open(reset($files));
|
||||
|
||||
$this->assertInstanceOf('FFMpeg\Media\Audio', $audio);
|
||||
|
||||
clearstatcache();
|
||||
$filename = __DIR__ . '/output/concat-output.mp3';
|
||||
|
||||
$audio->concat($files)->saveFromSameCodecs($filename, TRUE);
|
||||
|
||||
$this->assertFileExists($filename);
|
||||
unlink($filename);
|
||||
}
|
||||
}
|
||||
|
|
@ -12,10 +12,23 @@ class FFProbeTest extends FunctionalTestCase
|
|||
$this->assertGreaterThan(0, count($ffprobe->streams(__DIR__ . '/../files/Audio.mp3')));
|
||||
}
|
||||
|
||||
public function testValidateExistingFile()
|
||||
{
|
||||
$ffprobe = FFProbe::create();
|
||||
$this->assertTrue($ffprobe->isValid(__DIR__ . '/../files/sample.3gp'));
|
||||
}
|
||||
|
||||
|
||||
public function testValidateNonExistingFile()
|
||||
{
|
||||
$ffprobe = FFProbe::create();
|
||||
$this->assertFalse($ffprobe->isValid(__DIR__ . '/../files/WrongFile.mp4'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException FFMpeg\Exception\RuntimeException
|
||||
*/
|
||||
public function testProbeOnUnexistantFile()
|
||||
public function testProbeOnNonExistantFile()
|
||||
{
|
||||
$ffprobe = FFProbe::create();
|
||||
$ffprobe->streams('/path/to/no/file');
|
||||
|
|
|
|||
|
|
@ -3,8 +3,9 @@
|
|||
namespace Tests\FFMpeg\Functional;
|
||||
|
||||
use FFMpeg\FFMpeg;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
abstract class FunctionalTestCase extends \PHPUnit_Framework_TestCase
|
||||
abstract class FunctionalTestCase extends TestCase
|
||||
{
|
||||
/**
|
||||
* @return FFMpeg
|
||||
|
|
|
|||
|
|
@ -13,4 +13,11 @@ class PointTest extends TestCase
|
|||
$this->assertEquals(4, $point->getX());
|
||||
$this->assertEquals(25, $point->getY());
|
||||
}
|
||||
|
||||
public function testDynamicPointGetters()
|
||||
{
|
||||
$point = new Point("t*100", "t", true);
|
||||
$this->assertEquals("t*100", $point->getX());
|
||||
$this->assertEquals("t", $point->getY());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,9 @@ namespace Tests\FFMpeg\Unit;
|
|||
|
||||
use FFMpeg\FFMpegServiceProvider;
|
||||
use Silex\Application;
|
||||
use PHPUnit\Framework\TestCase as BaseTestCase;
|
||||
|
||||
class FFMpegServiceProviderTest extends \PHPUnit_Framework_TestCase
|
||||
class FFMpegServiceProviderTest extends BaseTestCase
|
||||
{
|
||||
public function testWithConfig()
|
||||
{
|
||||
|
|
|
|||
20
tests/Unit/Filters/Audio/CustomFilterTest.php
Normal file
20
tests/Unit/Filters/Audio/CustomFilterTest.php
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\FFMpeg\Unit\Filters\Audio;
|
||||
|
||||
use FFMpeg\Filters\Audio\CustomFilter;
|
||||
use FFMpeg\Filters\Audio\FrameRateFilter;
|
||||
use Tests\FFMpeg\Unit\TestCase;
|
||||
use FFMpeg\Coordinate\FrameRate;
|
||||
|
||||
class CustomFilterTest extends TestCase
|
||||
{
|
||||
public function testApplyCustomFilter()
|
||||
{
|
||||
$audio = $this->getAudioMock();
|
||||
$format = $this->getMock('FFMpeg\Format\AudioInterface');
|
||||
|
||||
$filter = new CustomFilter('whatever i put would end up as a filter');
|
||||
$this->assertEquals(array('-af', 'whatever i put would end up as a filter'), $filter->apply($audio, $format));
|
||||
}
|
||||
}
|
||||
17
tests/Unit/Filters/Frame/CustomFrameFilterTest.php
Normal file
17
tests/Unit/Filters/Frame/CustomFrameFilterTest.php
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\FFMpeg\Unit\Filters\Frame;
|
||||
|
||||
use FFMpeg\Filters\Frame\CustomFrameFilter;
|
||||
use Tests\FFMpeg\Unit\TestCase;
|
||||
|
||||
class CustomFrameFilterTest extends TestCase
|
||||
{
|
||||
public function testApplyCustomFrameFilter()
|
||||
{
|
||||
$frame = $this->getFrameMock();
|
||||
|
||||
$filter = new CustomFrameFilter('whatever i put would end up as a filter');
|
||||
$this->assertEquals(array('-vf', 'whatever i put would end up as a filter'), $filter->apply($frame));
|
||||
}
|
||||
}
|
||||
|
|
@ -12,7 +12,7 @@ class ExtractMultipleFramesFilterTest extends TestCase
|
|||
/**
|
||||
* @dataProvider provideFrameRates
|
||||
*/
|
||||
public function testApply($frameRate, $destinationFolder, $duration, $modulus, $expected)
|
||||
public function testApply($frameRate, $frameFileType,$destinationFolder, $duration, $modulus, $expected)
|
||||
{
|
||||
$video = $this->getVideoMock();
|
||||
$pathfile = '/path/to/file'.mt_rand();
|
||||
|
|
@ -34,18 +34,41 @@ class ExtractMultipleFramesFilterTest extends TestCase
|
|||
->will($this->returnValue($streams));
|
||||
|
||||
$filter = new ExtractMultipleFramesFilter($frameRate, $destinationFolder);
|
||||
$filter->setFrameFileType($frameFileType);
|
||||
$this->assertEquals($expected, $filter->apply($video, $format));
|
||||
}
|
||||
|
||||
public function provideFrameRates()
|
||||
{
|
||||
return array(
|
||||
array(ExtractMultipleFramesFilter::FRAMERATE_EVERY_SEC, '/', 100, 2, array('-vf', 'fps=1/1', '/frame-%03d.jpg')),
|
||||
array(ExtractMultipleFramesFilter::FRAMERATE_EVERY_2SEC, '/', 100, 2, array('-vf', 'fps=1/2', '/frame-%02d.jpg')),
|
||||
array(ExtractMultipleFramesFilter::FRAMERATE_EVERY_5SEC, '/', 100, 2, array('-vf', 'fps=1/5', '/frame-%02d.jpg')),
|
||||
array(ExtractMultipleFramesFilter::FRAMERATE_EVERY_10SEC, '/', 100, 2, array('-vf', 'fps=1/10', '/frame-%02d.jpg')),
|
||||
array(ExtractMultipleFramesFilter::FRAMERATE_EVERY_30SEC, '/', 100, 2, array('-vf', 'fps=1/30', '/frame-%02d.jpg')),
|
||||
array(ExtractMultipleFramesFilter::FRAMERATE_EVERY_60SEC, '/', 100, 2, array('-vf', 'fps=1/60', '/frame-%02d.jpg')),
|
||||
array(ExtractMultipleFramesFilter::FRAMERATE_EVERY_SEC, 'jpg', '/', 100, 2, array('-vf', 'fps=1/1', '/frame-%03d.jpg')),
|
||||
array(ExtractMultipleFramesFilter::FRAMERATE_EVERY_2SEC, 'jpg', '/', 100, 2, array('-vf', 'fps=1/2', '/frame-%02d.jpg')),
|
||||
array(ExtractMultipleFramesFilter::FRAMERATE_EVERY_5SEC, 'jpg', '/', 100, 2, array('-vf', 'fps=1/5', '/frame-%02d.jpg')),
|
||||
array(ExtractMultipleFramesFilter::FRAMERATE_EVERY_10SEC, 'jpg', '/', 100, 2, array('-vf', 'fps=1/10', '/frame-%02d.jpg')),
|
||||
array(ExtractMultipleFramesFilter::FRAMERATE_EVERY_30SEC, 'jpg', '/', 100, 2, array('-vf', 'fps=1/30', '/frame-%02d.jpg')),
|
||||
array(ExtractMultipleFramesFilter::FRAMERATE_EVERY_60SEC, 'jpg', '/', 100, 2, array('-vf', 'fps=1/60', '/frame-%02d.jpg')),
|
||||
|
||||
array(ExtractMultipleFramesFilter::FRAMERATE_EVERY_SEC, 'jpeg', '/', 100, 2, array('-vf', 'fps=1/1', '/frame-%03d.jpeg')),
|
||||
array(ExtractMultipleFramesFilter::FRAMERATE_EVERY_2SEC, 'jpeg', '/', 100, 2, array('-vf', 'fps=1/2', '/frame-%02d.jpeg')),
|
||||
array(ExtractMultipleFramesFilter::FRAMERATE_EVERY_5SEC, 'jpeg', '/', 100, 2, array('-vf', 'fps=1/5', '/frame-%02d.jpeg')),
|
||||
array(ExtractMultipleFramesFilter::FRAMERATE_EVERY_10SEC, 'jpeg', '/', 100, 2, array('-vf', 'fps=1/10', '/frame-%02d.jpeg')),
|
||||
array(ExtractMultipleFramesFilter::FRAMERATE_EVERY_30SEC, 'jpeg', '/', 100, 2, array('-vf', 'fps=1/30', '/frame-%02d.jpeg')),
|
||||
array(ExtractMultipleFramesFilter::FRAMERATE_EVERY_60SEC, 'jpeg', '/', 100, 2, array('-vf', 'fps=1/60', '/frame-%02d.jpeg')),
|
||||
|
||||
array(ExtractMultipleFramesFilter::FRAMERATE_EVERY_SEC, 'png', '/', 100, 2, array('-vf', 'fps=1/1', '/frame-%03d.png')),
|
||||
array(ExtractMultipleFramesFilter::FRAMERATE_EVERY_2SEC, 'png', '/', 100, 2, array('-vf', 'fps=1/2', '/frame-%02d.png')),
|
||||
array(ExtractMultipleFramesFilter::FRAMERATE_EVERY_5SEC, 'png', '/', 100, 2, array('-vf', 'fps=1/5', '/frame-%02d.png')),
|
||||
array(ExtractMultipleFramesFilter::FRAMERATE_EVERY_10SEC, 'png', '/', 100, 2, array('-vf', 'fps=1/10', '/frame-%02d.png')),
|
||||
array(ExtractMultipleFramesFilter::FRAMERATE_EVERY_30SEC, 'png', '/', 100, 2, array('-vf', 'fps=1/30', '/frame-%02d.png')),
|
||||
array(ExtractMultipleFramesFilter::FRAMERATE_EVERY_60SEC, 'png', '/', 100, 2, array('-vf', 'fps=1/60', '/frame-%02d.png')),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \FFMpeg\Exception\InvalidArgumentException
|
||||
*/
|
||||
public function testInvalidFrameFileType() {
|
||||
$filter = new ExtractMultipleFramesFilter('1/1', '/');
|
||||
$filter->setFrameFileType('webm');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ class VideoProgressListenerTest extends TestCase
|
|||
/**
|
||||
* @dataProvider provideData
|
||||
*/
|
||||
public function testHandle($size, $duration,
|
||||
public function testHandle($size, $duration, $newVideoDuration,
|
||||
$data, $expectedPercent, $expectedRemaining, $expectedRate,
|
||||
$data2, $expectedPercent2, $expectedRemaining2, $expectedRate2,
|
||||
$currentPass, $totalPass
|
||||
|
|
@ -26,7 +26,7 @@ class VideoProgressListenerTest extends TestCase
|
|||
'duration' => $duration,
|
||||
))));
|
||||
|
||||
$listener = new VideoProgressListener($ffprobe, __FILE__, $currentPass, $totalPass);
|
||||
$listener = new VideoProgressListener($ffprobe, __FILE__, $currentPass, $totalPass, $newVideoDuration);
|
||||
$phpunit = $this;
|
||||
$n = 0;
|
||||
$listener->on('progress', function ($percent, $remaining, $rate) use (&$n, $phpunit, $expectedPercent, $expectedRemaining, $expectedRate, $expectedPercent2, $expectedRemaining2, $expectedRate2) {
|
||||
|
|
@ -57,6 +57,7 @@ class VideoProgressListenerTest extends TestCase
|
|||
array(
|
||||
147073958,
|
||||
281.147533,
|
||||
281.147533,
|
||||
'frame= 206 fps=202 q=10.0 size= 571kB time=00:00:07.12 bitrate= 656.8kbits/s dup=9 drop=0',
|
||||
2,
|
||||
0,
|
||||
|
|
@ -71,6 +72,7 @@ class VideoProgressListenerTest extends TestCase
|
|||
array(
|
||||
147073958,
|
||||
281.147533,
|
||||
281.147533,
|
||||
'frame= 206 fps=202 q=10.0 size= 571kB time=00:00:07.12 bitrate= 656.8kbits/s dup=9 drop=0',
|
||||
1,
|
||||
0,
|
||||
|
|
@ -81,6 +83,21 @@ class VideoProgressListenerTest extends TestCase
|
|||
3868,
|
||||
1,
|
||||
2
|
||||
),
|
||||
array(
|
||||
147073958,
|
||||
281.147533,
|
||||
35,
|
||||
'frame= 206 fps=202 q=10.0 size= 571kB time=00:00:07.12 bitrate= 656.8kbits/s dup=9 drop=0',
|
||||
60,
|
||||
0,
|
||||
0,
|
||||
'frame= 854 fps=113 q=20.0 size= 4430kB time=00:00:33.04 bitrate=1098.5kbits/s dup=36 drop=0',
|
||||
97,
|
||||
0,
|
||||
3868,
|
||||
2,
|
||||
2
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
|||
67
tests/Unit/Media/ClipTest.php
Normal file
67
tests/Unit/Media/ClipTest.php
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
namespace Tests\FFMpeg\Unit\Media;
|
||||
|
||||
use FFMpeg\Media\Clip;
|
||||
|
||||
class ClipTest extends AbstractMediaTestCase
|
||||
{
|
||||
|
||||
/**
|
||||
* @dataProvider provideBuildOptions
|
||||
*/
|
||||
public function testBuildCommand($startValue, $durationValue, $commands)
|
||||
{
|
||||
$configuration = $this->getConfigurationMock();
|
||||
|
||||
$driver = $this->getFFMpegDriverMock();
|
||||
$driver->expects($this->any())
|
||||
->method('getConfiguration')
|
||||
->will($this->returnValue($configuration));
|
||||
|
||||
$ffprobe = $this->getFFProbeMock();
|
||||
|
||||
$start = $this->getTimeCodeMock();
|
||||
$start->expects($this->once())
|
||||
->method('__toString')
|
||||
->will($this->returnValue($startValue));
|
||||
|
||||
$duration = null;
|
||||
if (null !== $durationValue) {
|
||||
$duration = $this->getTimeCodeMock();
|
||||
$duration->expects($this->once())
|
||||
->method('__toString')
|
||||
->will($this->returnValue($durationValue));
|
||||
}
|
||||
|
||||
$outputPathfile = '/target/file';
|
||||
|
||||
$format = $this->getMock('FFMpeg\Format\VideoInterface');
|
||||
$format->expects($this->any())
|
||||
->method('getPasses')
|
||||
->will($this->returnValue(1));
|
||||
$format->expects($this->any())
|
||||
->method('getExtraParams')
|
||||
->will($this->returnValue(array()));
|
||||
|
||||
$clip = new Clip($this->getVideoMock(__FILE__), $driver, $ffprobe, $start, $duration);
|
||||
$fc = $clip->getFinalCommand($format, $outputPathfile);
|
||||
|
||||
$this->assertCount(1, $fc);
|
||||
$this->assertStringStartsWith(implode(' ', $commands), $fc[0]);
|
||||
}
|
||||
|
||||
public function provideBuildOptions()
|
||||
{
|
||||
return array(
|
||||
array('SS01', null, array(
|
||||
'-y', '-ss', 'SS01',
|
||||
'-i', __FILE__)
|
||||
),
|
||||
array('SS02', 'D02', array(
|
||||
'-y', '-ss', 'SS02',
|
||||
'-i', __FILE__,
|
||||
'-t', 'D02')
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -61,7 +61,9 @@ class FrameTest extends AbstractMediaTestCase
|
|||
|
||||
$pathfile = '/target/destination';
|
||||
|
||||
array_push($commands, $pathfile);
|
||||
if (!$base64) {
|
||||
array_push($commands, $pathfile);
|
||||
}
|
||||
|
||||
$driver->expects($this->once())
|
||||
->method('command')
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ class WaveformTest extends AbstractMediaTestCase
|
|||
->method('command')
|
||||
->with($commands);
|
||||
|
||||
$waveform = new Waveform($this->getAudioMock(__FILE__), $driver, $ffprobe, 640, 120);
|
||||
$waveform = new Waveform($this->getAudioMock(__FILE__), $driver, $ffprobe, 640, 120, ['#FFFFFF']);
|
||||
$this->assertSame($waveform, $waveform->save($pathfile));
|
||||
}
|
||||
|
||||
|
|
@ -61,8 +61,8 @@ class WaveformTest extends AbstractMediaTestCase
|
|||
return array(
|
||||
array(
|
||||
array(
|
||||
'-i', NULL, '-filter_complex',
|
||||
'showwavespic=s=640x120',
|
||||
'-y', '-i', NULL, '-filter_complex',
|
||||
'showwavespic=colors=#FFFFFF:s=640x120',
|
||||
'-frames:v', '1',
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@
|
|||
|
||||
namespace Tests\FFMpeg\Unit;
|
||||
|
||||
class TestCase extends \PHPUnit_Framework_TestCase
|
||||
use PHPUnit\Framework\TestCase as BaseTestCase;
|
||||
|
||||
class TestCase extends BaseTestCase
|
||||
{
|
||||
public function assertScalar($value)
|
||||
{
|
||||
|
|
|
|||
BIN
tests/files/02_-_Favorite_Secrets.mp3
Normal file
BIN
tests/files/02_-_Favorite_Secrets.mp3
Normal file
Binary file not shown.
BIN
tests/files/Jahzzar_-_05_-_Siesta.mp3
Normal file
BIN
tests/files/Jahzzar_-_05_-_Siesta.mp3
Normal file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue