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
|
composer.lock
|
||||||
phpunit.xml
|
phpunit.xml
|
||||||
sami.phar
|
sami.phar
|
||||||
|
.idea/
|
||||||
13
.travis.yml
13
.travis.yml
|
|
@ -5,6 +5,12 @@ dist: trusty
|
||||||
branches:
|
branches:
|
||||||
only:
|
only:
|
||||||
- master
|
- master
|
||||||
|
- v1.x
|
||||||
|
|
||||||
|
cache:
|
||||||
|
directories:
|
||||||
|
- $HOME/.composer/cache
|
||||||
|
- $HOME/.cache
|
||||||
|
|
||||||
php:
|
php:
|
||||||
- 5.4
|
- 5.4
|
||||||
|
|
@ -12,11 +18,10 @@ php:
|
||||||
- 5.6
|
- 5.6
|
||||||
- 7.0
|
- 7.0
|
||||||
- 7.1
|
- 7.1
|
||||||
- hhvm
|
- 7.2
|
||||||
|
- 7.3
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
allow_failures:
|
|
||||||
- php: hhvm
|
|
||||||
include:
|
include:
|
||||||
- php: 5.4
|
- php: 5.4
|
||||||
env: COMPOSER_FLAGS="--prefer-lowest"
|
env: COMPOSER_FLAGS="--prefer-lowest"
|
||||||
|
|
@ -29,7 +34,7 @@ before_install:
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- sudo apt-get install -y ffmpeg
|
- sudo apt-get install -y ffmpeg
|
||||||
- composer update --prefer-source $COMPOSER_FLAGS
|
- composer update --prefer-dist $COMPOSER_FLAGS
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- vendor/bin/phpunit --verbose
|
- 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)
|
[](http://travis-ci.org/PHP-FFMpeg/PHP-FFMpeg)
|
||||||
|
|
||||||
[](https://insight.sensiolabs.com/projects/607f3111-e2d7-44e8-8bcc-54dd64521983)
|
[](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.
|
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,
|
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.
|
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:
|
### Known issues:
|
||||||
|
|
||||||
|
|
@ -35,6 +35,9 @@ $ composer require php-ffmpeg/php-ffmpeg
|
||||||
## Basic Usage
|
## Basic Usage
|
||||||
|
|
||||||
```php
|
```php
|
||||||
|
|
||||||
|
require 'vendor/autoload.php';
|
||||||
|
|
||||||
$ffmpeg = FFMpeg\FFMpeg::create();
|
$ffmpeg = FFMpeg\FFMpeg::create();
|
||||||
$video = $ffmpeg->open('video.mpg');
|
$video = $ffmpeg->open('video.mpg');
|
||||||
$video
|
$video
|
||||||
|
|
@ -125,7 +128,7 @@ $video->save($format, 'video.avi');
|
||||||
```
|
```
|
||||||
|
|
||||||
Transcoding progress can be monitored in realtime, see Format documentation
|
Transcoding progress can be monitored in realtime, see Format documentation
|
||||||
below for more informations.
|
below for more information.
|
||||||
|
|
||||||
##### Extracting image
|
##### Extracting image
|
||||||
|
|
||||||
|
|
@ -141,7 +144,7 @@ $frame = $video->frame(FFMpeg\Coordinate\TimeCode::fromSeconds(42));
|
||||||
$frame->save('image.jpg');
|
$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
|
```php
|
||||||
$video
|
$video
|
||||||
|
|
@ -152,6 +155,38 @@ $video
|
||||||
$video
|
$video
|
||||||
->save(new FFMpeg\Format\Video\X264(), '/path/to/new/file');
|
->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
|
##### Generate a waveform
|
||||||
|
|
||||||
|
|
@ -159,13 +194,13 @@ You can generate a waveform of an audio file using the `FFMpeg\Media\Audio::wave
|
||||||
method.
|
method.
|
||||||
|
|
||||||
This code returns a `FFMpeg\Media\Waveform` instance.
|
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.
|
documentation below for more information.
|
||||||
|
|
||||||
The ouput file MUST use the PNG extension.
|
The output file MUST use the PNG extension.
|
||||||
|
|
||||||
```php
|
```php
|
||||||
$waveform = $audio->waveform(640, 120);
|
$waveform = $audio->waveform(640, 120, array('#00FF00'));
|
||||||
$waveform->save('waveform.png');
|
$waveform->save('waveform.png');
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -178,8 +213,8 @@ $video = $ffmpeg->open( 'video.mp4' );
|
||||||
// Set an audio format
|
// Set an audio format
|
||||||
$audio_format = new FFMpeg\Format\Audio\Mp3();
|
$audio_format = new FFMpeg\Format\Audio\Mp3();
|
||||||
|
|
||||||
// Extract the audio into a new file
|
// Extract the audio into a new file as mp3
|
||||||
$video->save('audio.mp3');
|
$video->save($audio_format, 'audio.mp3');
|
||||||
|
|
||||||
// Set the audio file
|
// Set the audio file
|
||||||
$audio = $ffmpeg->open( 'audio.mp3' );
|
$audio = $ffmpeg->open( 'audio.mp3' );
|
||||||
|
|
@ -298,7 +333,7 @@ The framerate filter takes two parameters:
|
||||||
Synchronizes audio and video.
|
Synchronizes audio and video.
|
||||||
|
|
||||||
Some containers may use a delay that results in desynchronized outputs. This
|
Some containers may use a delay that results in desynchronized outputs. This
|
||||||
filters solves this issue.
|
filter solves this issue.
|
||||||
|
|
||||||
```php
|
```php
|
||||||
$video->filters()->synchronize();
|
$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
|
- `$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
|
- `$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
|
### Audio
|
||||||
|
|
||||||
`FFMpeg\Media\Audio` can be transcoded too, ie: change codec, isolate audio or
|
`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
|
Transcoding progress can be monitored in realtime, see Format documentation
|
||||||
below for more informations.
|
below for more information.
|
||||||
|
|
||||||
##### Filters
|
##### Filters
|
||||||
|
|
||||||
|
|
@ -399,7 +446,7 @@ The resample filter takes two parameters :
|
||||||
|
|
||||||
#### Frame
|
#### 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.
|
frame extraction.
|
||||||
|
|
||||||
You can save frames using the `FFMpeg\Media\Frame::save` method.
|
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
|
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
|
#### Gif
|
||||||
|
|
||||||
|
|
@ -447,7 +494,7 @@ To concatenate videos encoded with the same codec, do as follow:
|
||||||
|
|
||||||
```php
|
```php
|
||||||
// In order to instantiate the video object, you HAVE TO pass a path to a valid video file.
|
// 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 = $ffmpeg->open( '/path/to/video' );
|
||||||
$video
|
$video
|
||||||
->concat(array('/path/to/video1', '/path/to/video2'))
|
->concat(array('/path/to/video1', '/path/to/video2'))
|
||||||
|
|
@ -460,7 +507,7 @@ To concatenate videos encoded with the same codec, do as follow:
|
||||||
|
|
||||||
```php
|
```php
|
||||||
// In order to instantiate the video object, you HAVE TO pass a path to a valid video file.
|
// 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 = $ffmpeg->open( '/path/to/video' );
|
||||||
|
|
||||||
$format = new FFMpeg\Format\Video\X264();
|
$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
|
use `FFMpeg\Format\VideoInterface`, and `FFMpeg\Format\AudioInterface` for
|
||||||
audio files.
|
audio files.
|
||||||
|
|
||||||
Format can also extends `FFMpeg\Format\ProgressableInterface` to get realtime
|
A format can also extend `FFMpeg\Format\ProgressableInterface` to get realtime
|
||||||
informations about the transcoding.
|
information about the transcoding.
|
||||||
|
|
||||||
Predefined formats already provide progress informations as events.
|
Predefined formats already provide progress information as events.
|
||||||
|
|
||||||
```php
|
```php
|
||||||
$format = new FFMpeg\Format\Video\X264();
|
$format = new FFMpeg\Format\Video\X264();
|
||||||
|
|
@ -542,12 +589,12 @@ class CustomWMVFormat extends FFMpeg\Format\Video\DefaultVideo
|
||||||
|
|
||||||
#### Coordinates
|
#### 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\AspectRatio` represents an aspect ratio.
|
||||||
- `FFMpeg\Coordinate\Dimension` represent a dimension.
|
- `FFMpeg\Coordinate\Dimension` represent a dimension.
|
||||||
- `FFMpeg\Coordinate\FrameRate` represent a framerate.
|
- `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.
|
- `FFMpeg\Coordinate\TimeCode` represent a timecode.
|
||||||
|
|
||||||
### FFProbe
|
### FFProbe
|
||||||
|
|
@ -571,9 +618,19 @@ $ffprobe
|
||||||
->get('duration'); // returns the duration property
|
->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
|
## Using with Silex Microframework
|
||||||
|
|
||||||
Service provider is easy to set up:
|
The service provider is easy to set up:
|
||||||
|
|
||||||
```php
|
```php
|
||||||
$app = new Silex\Application();
|
$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
|
## License
|
||||||
|
|
||||||
This project is licensed under the [MIT license](http://opensource.org/licenses/MIT).
|
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",
|
"name": "Romain Biard",
|
||||||
"email": "romain.biard@gmail.com",
|
"email": "romain.biard@gmail.com",
|
||||||
"homepage": "https://www.strime.io/"
|
"homepage": "https://www.strime.io/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Jens Hausdorf",
|
||||||
|
"email": "hello@jens-hausdorf.de",
|
||||||
|
"homepage": "https://jens-hausdorf.de"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^5.3.9 || ^7.0",
|
"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",
|
"doctrine/cache": "^1.0",
|
||||||
"evenement/evenement": "^3.0 || ^2.0 || ^1.0",
|
"evenement/evenement": "^3.0 || ^2.0 || ^1.0",
|
||||||
"neutron/temporary-filesystem": "^2.1.1"
|
"neutron/temporary-filesystem": "^2.1.1"
|
||||||
|
|
@ -39,7 +44,7 @@
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"sami/sami": "~1.0",
|
"sami/sami": "~1.0",
|
||||||
"silex/silex": "~1.0",
|
"silex/silex": "~1.0",
|
||||||
"phpunit/phpunit": "^4.8"
|
"phpunit/phpunit": "^4.8.36"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-0": {
|
"psr-0": {
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,12 @@ class AspectRatio
|
||||||
// named 16:9 or 1.77:1 HD video standard
|
// named 16:9 or 1.77:1 HD video standard
|
||||||
const AR_16_9 = '16/9';
|
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
|
// named 3:2 or 1.5:1 see http://en.wikipedia.org/wiki/135_film
|
||||||
const AR_3_2 = '3/2';
|
const AR_3_2 = '3/2';
|
||||||
// named 5:3 or 1.66:1 see http://en.wikipedia.org/wiki/Super_16_mm
|
// named 5:3 or 1.66:1 see http://en.wikipedia.org/wiki/Super_16_mm
|
||||||
|
|
@ -160,6 +166,10 @@ class AspectRatio
|
||||||
return 4 / 3;
|
return 4 / 3;
|
||||||
case static::AR_16_9:
|
case static::AR_16_9:
|
||||||
return 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:
|
case static::AR_1_1:
|
||||||
return 1 / 1;
|
return 1 / 1;
|
||||||
case static::AR_1_DOT_85_1:
|
case static::AR_1_DOT_85_1:
|
||||||
|
|
@ -207,6 +217,8 @@ class AspectRatio
|
||||||
$availables = array(
|
$availables = array(
|
||||||
static::AR_4_3 => static::valueFromName(static::AR_4_3),
|
static::AR_4_3 => static::valueFromName(static::AR_4_3),
|
||||||
static::AR_16_9 => static::valueFromName(static::AR_16_9),
|
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_1 => static::valueFromName(static::AR_1_1),
|
||||||
static::AR_1_DOT_85_1 => static::valueFromName(static::AR_1_DOT_85_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_2_DOT_39_1 => static::valueFromName(static::AR_2_DOT_39_1),
|
||||||
|
|
|
||||||
|
|
@ -16,10 +16,15 @@ class Point
|
||||||
private $x;
|
private $x;
|
||||||
private $y;
|
private $y;
|
||||||
|
|
||||||
public function __construct($x, $y)
|
public function __construct($x, $y, $dynamic = false)
|
||||||
{
|
{
|
||||||
$this->x = (int) $x;
|
if ($dynamic) {
|
||||||
$this->y = (int) $y;
|
$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);
|
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
|
* @api
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -71,4 +71,18 @@ class AudioFilters
|
||||||
|
|
||||||
return $this;
|
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;
|
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 $priority;
|
||||||
private $frameRate;
|
private $frameRate;
|
||||||
private $destinationFolder;
|
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)
|
public function __construct($frameRate = self::FRAMERATE_EVERY_SEC, $destinationFolder = __DIR__, $priority = 0)
|
||||||
{
|
{
|
||||||
|
|
@ -49,6 +53,20 @@ class ExtractMultipleFramesFilter implements VideoFilterInterface
|
||||||
$this->destinationFolder = $destinationFolder;
|
$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}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
|
|
@ -117,7 +135,7 @@ class ExtractMultipleFramesFilter implements VideoFilterInterface
|
||||||
// Set the parameters
|
// Set the parameters
|
||||||
$commands[] = '-vf';
|
$commands[] = '-vf';
|
||||||
$commands[] = 'fps=' . $this->frameRate;
|
$commands[] = 'fps=' . $this->frameRate;
|
||||||
$commands[] = $this->destinationFolder . 'frame-%'.$nbDigitsInFileNames.'d.jpg';
|
$commands[] = $this->destinationFolder . 'frame-%'.$nbDigitsInFileNames.'d.' . $this->frameFileType;
|
||||||
}
|
}
|
||||||
catch (RuntimeException $e) {
|
catch (RuntimeException $e) {
|
||||||
throw new RuntimeException('An error occured while extracting the frames: ' . $e->getMessage() . '. The code: ' . $e->getCode());
|
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}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function createProgressListener(MediaTypeInterface $media, FFProbe $ffprobe, $pass, $total)
|
public function createProgressListener(MediaTypeInterface $media, FFProbe $ffprobe, $pass, $total, $duration = 0)
|
||||||
{
|
{
|
||||||
$format = $this;
|
$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) {
|
$listener->on('progress', function () use ($media, $format) {
|
||||||
$format->emit('progress', array_merge(array($media, $format), func_get_args()));
|
$format->emit('progress', array_merge(array($media, $format), func_get_args()));
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -80,12 +80,13 @@ abstract class AbstractProgressListener extends EventEmitter implements Listener
|
||||||
*
|
*
|
||||||
* @throws RuntimeException
|
* @throws RuntimeException
|
||||||
*/
|
*/
|
||||||
public function __construct(FFProbe $ffprobe, $pathfile, $currentPass, $totalPass)
|
public function __construct(FFProbe $ffprobe, $pathfile, $currentPass, $totalPass, $duration = 0)
|
||||||
{
|
{
|
||||||
$this->ffprobe = $ffprobe;
|
$this->ffprobe = $ffprobe;
|
||||||
$this->pathfile = $pathfile;
|
$this->pathfile = $pathfile;
|
||||||
$this->currentPass = $currentPass;
|
$this->currentPass = $currentPass;
|
||||||
$this->totalPass = $totalPass;
|
$this->totalPass = $totalPass;
|
||||||
|
$this->duration = $duration;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -254,9 +255,8 @@ abstract class AbstractProgressListener extends EventEmitter implements Listener
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->totalSize = $format->get('size') / 1024;
|
$this->duration = (int) $this->duration > 0 ? $this->duration : $format->get('duration');
|
||||||
$this->duration = $format->get('duration');
|
$this->totalSize = $format->get('size') / 1024 * ($this->duration / $format->get('duration'));
|
||||||
|
|
||||||
$this->initialized = true;
|
$this->initialized = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,9 @@ interface ProgressableInterface extends EventEmitterInterface
|
||||||
* @param FFProbe $ffprobe
|
* @param FFProbe $ffprobe
|
||||||
* @param Integer $pass The current pas snumber
|
* @param Integer $pass The current pas snumber
|
||||||
* @param Integer $total The total pass number
|
* @param Integer $total The total pass number
|
||||||
|
* @param Integer $duration The new video duration
|
||||||
*
|
*
|
||||||
* @return array An array of listeners
|
* @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}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function createProgressListener(MediaTypeInterface $media, FFProbe $ffprobe, $pass, $total)
|
public function createProgressListener(MediaTypeInterface $media, FFProbe $ffprobe, $pass, $total, $duration = 0)
|
||||||
{
|
{
|
||||||
$format = $this;
|
$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) {
|
foreach ($listeners as $listener) {
|
||||||
$listener->on('progress', function () use ($format, $media) {
|
$listener->on('progress', function () use ($format, $media) {
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,6 @@ class WebM extends DefaultVideo
|
||||||
*/
|
*/
|
||||||
public function getAvailableVideoCodecs()
|
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.
|
* Exports the audio in the desired format, applies registered filters.
|
||||||
*
|
*
|
||||||
* @param FormatInterface $format
|
* @param FormatInterface $format
|
||||||
* @param string $outputPathfile
|
* @param string $outputPathfile
|
||||||
*
|
|
||||||
* @return Audio
|
* @return Audio
|
||||||
*
|
|
||||||
* @throws RuntimeException
|
* @throws RuntimeException
|
||||||
*/
|
*/
|
||||||
public function save(FormatInterface $format, $outputPathfile)
|
public function save(FormatInterface $format, $outputPathfile)
|
||||||
|
|
@ -64,9 +62,42 @@ class Audio extends AbstractStreamableMedia
|
||||||
$listeners = null;
|
$listeners = null;
|
||||||
|
|
||||||
if ($format instanceof ProgressableInterface) {
|
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);
|
$commands = array('-y', '-i', $this->pathfile);
|
||||||
|
|
||||||
$filters = clone $this->filters;
|
$filters = clone $this->filters;
|
||||||
|
|
@ -93,14 +124,7 @@ class Audio extends AbstractStreamableMedia
|
||||||
}
|
}
|
||||||
$commands[] = $outputPathfile;
|
$commands[] = $outputPathfile;
|
||||||
|
|
||||||
try {
|
return $commands;
|
||||||
$this->driver->command($commands, false, $listeners);
|
|
||||||
} catch (ExecutionFailureException $e) {
|
|
||||||
$this->cleanupTemporaryFile($outputPathfile);
|
|
||||||
throw new RuntimeException('Encoding failed', $e->getCode(), $e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -108,10 +132,22 @@ class Audio extends AbstractStreamableMedia
|
||||||
*
|
*
|
||||||
* @param integer $width
|
* @param integer $width
|
||||||
* @param integer $height
|
* @param integer $height
|
||||||
|
* @param array $colors Array of colors for ffmpeg to use. Color format is #000000 (RGB hex string with #)
|
||||||
* @return Waveform
|
* @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.
|
* 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 $outputPathfile
|
||||||
* @param string $streamCopy
|
* @param bool $streamCopy
|
||||||
*
|
*
|
||||||
* @return Concat
|
* @return Concat
|
||||||
*
|
*
|
||||||
|
|
@ -105,7 +105,7 @@ class Concat extends AbstractMediaType
|
||||||
if($count_videos != 0)
|
if($count_videos != 0)
|
||||||
$line .= "\n";
|
$line .= "\n";
|
||||||
|
|
||||||
$line .= "file ".$videoPath;
|
$line .= "file " . addcslashes($videoPath, '\'"\\\0 ');
|
||||||
|
|
||||||
fwrite($fileStream, $line);
|
fwrite($fileStream, $line);
|
||||||
|
|
||||||
|
|
@ -144,6 +144,7 @@ class Concat extends AbstractMediaType
|
||||||
$this->driver->command($commands);
|
$this->driver->command($commands);
|
||||||
} catch (ExecutionFailureException $e) {
|
} catch (ExecutionFailureException $e) {
|
||||||
$this->cleanupTemporaryFile($outputPathfile);
|
$this->cleanupTemporaryFile($outputPathfile);
|
||||||
|
// TODO@v1: paste this line into an `finally` block.
|
||||||
$this->cleanupTemporaryFile($sourcesFile);
|
$this->cleanupTemporaryFile($sourcesFile);
|
||||||
throw new RuntimeException('Unable to save concatenated video', $e->getCode(), $e);
|
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, $filter->apply($this));
|
||||||
}
|
}
|
||||||
|
|
||||||
$commands = array_merge($commands, array($pathfile));
|
if (!$returnBase64) {
|
||||||
|
$commands = array_merge($commands, array($pathfile));
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if(!$returnBase64) {
|
if(!$returnBase64) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This file is part of PHP-FFmpeg.
|
* This file is part of PHP-FFmpeg.
|
||||||
*
|
*
|
||||||
|
|
@ -8,228 +7,13 @@
|
||||||
* For the full copyright and license information, please view the LICENSE
|
* For the full copyright and license information, please view the LICENSE
|
||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace FFMpeg\Media;
|
namespace FFMpeg\Media;
|
||||||
|
|
||||||
use Alchemy\BinaryDriver\Exception\ExecutionFailureException;
|
|
||||||
use FFMpeg\Coordinate\TimeCode;
|
use FFMpeg\Coordinate\TimeCode;
|
||||||
use FFMpeg\Coordinate\Dimension;
|
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.
|
* Gets the frame at timecode.
|
||||||
|
|
@ -265,4 +49,16 @@ class Video extends Audio
|
||||||
{
|
{
|
||||||
return new Concat($sources, $this->driver, $this->ffprobe);
|
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;
|
namespace FFMpeg\Media;
|
||||||
|
|
||||||
use Alchemy\BinaryDriver\Exception\ExecutionFailureException;
|
use Alchemy\BinaryDriver\Exception\ExecutionFailureException;
|
||||||
|
use FFMpeg\Exception\InvalidArgumentException;
|
||||||
use FFMpeg\Filters\Waveform\WaveformFilterInterface;
|
use FFMpeg\Filters\Waveform\WaveformFilterInterface;
|
||||||
use FFMpeg\Filters\Waveform\WaveformFilters;
|
use FFMpeg\Filters\Waveform\WaveformFilters;
|
||||||
use FFMpeg\Driver\FFMpegDriver;
|
use FFMpeg\Driver\FFMpegDriver;
|
||||||
|
|
@ -20,17 +21,26 @@ use FFMpeg\Exception\RuntimeException;
|
||||||
|
|
||||||
class Waveform extends AbstractMediaType
|
class Waveform extends AbstractMediaType
|
||||||
{
|
{
|
||||||
/** @var Video */
|
const DEFAULT_COLOR = '#000000';
|
||||||
private $audio;
|
|
||||||
private $width;
|
|
||||||
private $height;
|
|
||||||
|
|
||||||
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);
|
parent::__construct($audio->getPathfile(), $driver, $ffprobe);
|
||||||
$this->audio = $audio;
|
$this->audio = $audio;
|
||||||
$this->width = $width;
|
$this->width = $width;
|
||||||
$this->height = $height;
|
$this->height = $height;
|
||||||
|
|
||||||
|
$this->setColors($colors);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -65,6 +75,55 @@ class Waveform extends AbstractMediaType
|
||||||
return $this;
|
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.
|
* Saves the waveform in the given filename.
|
||||||
*
|
*
|
||||||
|
|
@ -81,8 +140,8 @@ class Waveform extends AbstractMediaType
|
||||||
* @see http://ffmpeg.org/ffmpeg.html#Main-options
|
* @see http://ffmpeg.org/ffmpeg.html#Main-options
|
||||||
*/
|
*/
|
||||||
$commands = array(
|
$commands = array(
|
||||||
'-i', $this->pathfile, '-filter_complex',
|
'-y', '-i', $this->pathfile, '-filter_complex',
|
||||||
'showwavespic=s='.$this->width.'x'.$this->height,
|
'showwavespic=colors='.$this->compileColors().':s='.$this->width.'x'.$this->height,
|
||||||
'-frames:v', '1'
|
'-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')));
|
$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
|
* @expectedException FFMpeg\Exception\RuntimeException
|
||||||
*/
|
*/
|
||||||
public function testProbeOnUnexistantFile()
|
public function testProbeOnNonExistantFile()
|
||||||
{
|
{
|
||||||
$ffprobe = FFProbe::create();
|
$ffprobe = FFProbe::create();
|
||||||
$ffprobe->streams('/path/to/no/file');
|
$ffprobe->streams('/path/to/no/file');
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,9 @@
|
||||||
namespace Tests\FFMpeg\Functional;
|
namespace Tests\FFMpeg\Functional;
|
||||||
|
|
||||||
use FFMpeg\FFMpeg;
|
use FFMpeg\FFMpeg;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
abstract class FunctionalTestCase extends \PHPUnit_Framework_TestCase
|
abstract class FunctionalTestCase extends TestCase
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @return FFMpeg
|
* @return FFMpeg
|
||||||
|
|
|
||||||
|
|
@ -13,4 +13,11 @@ class PointTest extends TestCase
|
||||||
$this->assertEquals(4, $point->getX());
|
$this->assertEquals(4, $point->getX());
|
||||||
$this->assertEquals(25, $point->getY());
|
$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 FFMpeg\FFMpegServiceProvider;
|
||||||
use Silex\Application;
|
use Silex\Application;
|
||||||
|
use PHPUnit\Framework\TestCase as BaseTestCase;
|
||||||
|
|
||||||
class FFMpegServiceProviderTest extends \PHPUnit_Framework_TestCase
|
class FFMpegServiceProviderTest extends BaseTestCase
|
||||||
{
|
{
|
||||||
public function testWithConfig()
|
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
|
* @dataProvider provideFrameRates
|
||||||
*/
|
*/
|
||||||
public function testApply($frameRate, $destinationFolder, $duration, $modulus, $expected)
|
public function testApply($frameRate, $frameFileType,$destinationFolder, $duration, $modulus, $expected)
|
||||||
{
|
{
|
||||||
$video = $this->getVideoMock();
|
$video = $this->getVideoMock();
|
||||||
$pathfile = '/path/to/file'.mt_rand();
|
$pathfile = '/path/to/file'.mt_rand();
|
||||||
|
|
@ -34,18 +34,41 @@ class ExtractMultipleFramesFilterTest extends TestCase
|
||||||
->will($this->returnValue($streams));
|
->will($this->returnValue($streams));
|
||||||
|
|
||||||
$filter = new ExtractMultipleFramesFilter($frameRate, $destinationFolder);
|
$filter = new ExtractMultipleFramesFilter($frameRate, $destinationFolder);
|
||||||
|
$filter->setFrameFileType($frameFileType);
|
||||||
$this->assertEquals($expected, $filter->apply($video, $format));
|
$this->assertEquals($expected, $filter->apply($video, $format));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function provideFrameRates()
|
public function provideFrameRates()
|
||||||
{
|
{
|
||||||
return array(
|
return array(
|
||||||
array(ExtractMultipleFramesFilter::FRAMERATE_EVERY_SEC, '/', 100, 2, array('-vf', 'fps=1/1', '/frame-%03d.jpg')),
|
array(ExtractMultipleFramesFilter::FRAMERATE_EVERY_SEC, 'jpg', '/', 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_2SEC, 'jpg', '/', 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_5SEC, 'jpg', '/', 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_10SEC, 'jpg', '/', 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_30SEC, 'jpg', '/', 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_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
|
* @dataProvider provideData
|
||||||
*/
|
*/
|
||||||
public function testHandle($size, $duration,
|
public function testHandle($size, $duration, $newVideoDuration,
|
||||||
$data, $expectedPercent, $expectedRemaining, $expectedRate,
|
$data, $expectedPercent, $expectedRemaining, $expectedRate,
|
||||||
$data2, $expectedPercent2, $expectedRemaining2, $expectedRate2,
|
$data2, $expectedPercent2, $expectedRemaining2, $expectedRate2,
|
||||||
$currentPass, $totalPass
|
$currentPass, $totalPass
|
||||||
|
|
@ -26,7 +26,7 @@ class VideoProgressListenerTest extends TestCase
|
||||||
'duration' => $duration,
|
'duration' => $duration,
|
||||||
))));
|
))));
|
||||||
|
|
||||||
$listener = new VideoProgressListener($ffprobe, __FILE__, $currentPass, $totalPass);
|
$listener = new VideoProgressListener($ffprobe, __FILE__, $currentPass, $totalPass, $newVideoDuration);
|
||||||
$phpunit = $this;
|
$phpunit = $this;
|
||||||
$n = 0;
|
$n = 0;
|
||||||
$listener->on('progress', function ($percent, $remaining, $rate) use (&$n, $phpunit, $expectedPercent, $expectedRemaining, $expectedRate, $expectedPercent2, $expectedRemaining2, $expectedRate2) {
|
$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(
|
array(
|
||||||
147073958,
|
147073958,
|
||||||
281.147533,
|
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',
|
'frame= 206 fps=202 q=10.0 size= 571kB time=00:00:07.12 bitrate= 656.8kbits/s dup=9 drop=0',
|
||||||
2,
|
2,
|
||||||
0,
|
0,
|
||||||
|
|
@ -71,6 +72,7 @@ class VideoProgressListenerTest extends TestCase
|
||||||
array(
|
array(
|
||||||
147073958,
|
147073958,
|
||||||
281.147533,
|
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',
|
'frame= 206 fps=202 q=10.0 size= 571kB time=00:00:07.12 bitrate= 656.8kbits/s dup=9 drop=0',
|
||||||
1,
|
1,
|
||||||
0,
|
0,
|
||||||
|
|
@ -81,6 +83,21 @@ class VideoProgressListenerTest extends TestCase
|
||||||
3868,
|
3868,
|
||||||
1,
|
1,
|
||||||
2
|
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';
|
$pathfile = '/target/destination';
|
||||||
|
|
||||||
array_push($commands, $pathfile);
|
if (!$base64) {
|
||||||
|
array_push($commands, $pathfile);
|
||||||
|
}
|
||||||
|
|
||||||
$driver->expects($this->once())
|
$driver->expects($this->once())
|
||||||
->method('command')
|
->method('command')
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ class WaveformTest extends AbstractMediaTestCase
|
||||||
->method('command')
|
->method('command')
|
||||||
->with($commands);
|
->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));
|
$this->assertSame($waveform, $waveform->save($pathfile));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -61,8 +61,8 @@ class WaveformTest extends AbstractMediaTestCase
|
||||||
return array(
|
return array(
|
||||||
array(
|
array(
|
||||||
array(
|
array(
|
||||||
'-i', NULL, '-filter_complex',
|
'-y', '-i', NULL, '-filter_complex',
|
||||||
'showwavespic=s=640x120',
|
'showwavespic=colors=#FFFFFF:s=640x120',
|
||||||
'-frames:v', '1',
|
'-frames:v', '1',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,9 @@
|
||||||
|
|
||||||
namespace Tests\FFMpeg\Unit;
|
namespace Tests\FFMpeg\Unit;
|
||||||
|
|
||||||
class TestCase extends \PHPUnit_Framework_TestCase
|
use PHPUnit\Framework\TestCase as BaseTestCase;
|
||||||
|
|
||||||
|
class TestCase extends BaseTestCase
|
||||||
{
|
{
|
||||||
public function assertScalar($value)
|
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