parent
386940ecd4
commit
ff8c8ee7a4
@ -1 +0,0 @@ |
|||||||
Subproject commit 9fcb485d497872e674cb14eb3df1386dbda9169b |
|
@ -0,0 +1,62 @@ |
|||||||
|
CHANGELOG |
||||||
|
--------- |
||||||
|
|
||||||
|
* 0.3.5 (10-21-2013) |
||||||
|
|
||||||
|
* Add vorbis audio format (@jacobbudin). |
||||||
|
* Fix #66 : Allow single pass encodings. |
||||||
|
|
||||||
|
* 0.3.4 (09-05-2013) |
||||||
|
|
||||||
|
* Fix Invalid ratio computing. |
||||||
|
|
||||||
|
* 0.3.3 (09-05-2013) |
||||||
|
|
||||||
|
* Add convenient Stream::getDimensions method to extract video dimension. |
||||||
|
* Add DisplayRatioFixer Frame filter. |
||||||
|
|
||||||
|
* 0.3.2 (08-08-2013) |
||||||
|
|
||||||
|
* Fix A/V synchronization over flash and HTML5 players. |
||||||
|
|
||||||
|
* 0.3.1 (08-06-2013) |
||||||
|
|
||||||
|
* Allow use of FFProbe on remote URIs. |
||||||
|
* Fix #47 : MediaTypeInterface::save adds filters depending on the codec. |
||||||
|
* Save frame to target file without prompt. |
||||||
|
|
||||||
|
* 0.3.0 (07-04-2013) |
||||||
|
|
||||||
|
* Complete rewrite of the library, lots of BC breaks, check the doc. |
||||||
|
|
||||||
|
* 0.2.4 (05-10-2013) |
||||||
|
|
||||||
|
* Add Video\ResizableInterface::getModulus method for better output scaling (@retrojunk) |
||||||
|
* Fix timeout setting on audio/video encoding (@xammep-ua) |
||||||
|
|
||||||
|
* 0.2.3 (04-21-2013) |
||||||
|
|
||||||
|
* Add timeout getter and setter on FFMpeg and FFProbe |
||||||
|
* Add timeout setting via second argument on FFMpeg::load and FFProbe::load |
||||||
|
|
||||||
|
* 0.2.2 (02-11-2013) |
||||||
|
|
||||||
|
* Add compatibility with FFMpeg 1.1 |
||||||
|
* Upgrade deprecated options (`-ab`, `-qscale` and `-b`) |
||||||
|
* Use of a custom stat file for each multi-pass encoding (fix #20) |
||||||
|
* Use larger version range for dependencies |
||||||
|
|
||||||
|
* 0.2.1 (02-04-2013) |
||||||
|
|
||||||
|
* Parse the output of FFProbe using correct EOL sequences (@ak76) |
||||||
|
* Add process timeout customization (@pulse00) |
||||||
|
* Fix `accurate` option (`FFMpeg::extractImage`) |
||||||
|
|
||||||
|
* 0.2.0 (12-13-2012) |
||||||
|
|
||||||
|
* Add HelperInterface and support for realtime progress ( @pulse00 ). |
||||||
|
* Add `accurate` option to `FFMpeg::extractImage` method. |
||||||
|
|
||||||
|
* 0.1.0 (10-30-2012) |
||||||
|
|
||||||
|
* First stable version. |
@ -0,0 +1,21 @@ |
|||||||
|
PHP-FFmpeg is released with MIT License : |
||||||
|
|
||||||
|
Copyright (c) 2012 Alchemy |
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||||
|
of this software and associated documentation files (the "Software"), |
||||||
|
to deal in the Software without restriction, including without limitation |
||||||
|
the rights to use, copy, modify, merge, publish, distribute, sublicense, |
||||||
|
and/or sell copies of the Software, and to permit persons to whom the |
||||||
|
Software is furnished to do so, subject to the following conditions: |
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all |
||||||
|
copies or substantial portions of the Software. |
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
||||||
|
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
||||||
|
IN THE SOFTWARE. |
@ -0,0 +1,349 @@ |
|||||||
|
#PHP FFmpeg |
||||||
|
|
||||||
|
[](http://travis-ci.org/alchemy-fr/PHP-FFmpeg) |
||||||
|
|
||||||
|
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. |
||||||
|
|
||||||
|
## Your attention please |
||||||
|
|
||||||
|
This library requires a working FFMpeg install. You will need both FFMpeg and FFProbe binaries to use it. |
||||||
|
Be sure that these binaries can be located with system PATH to get the benefit of the binary detection, |
||||||
|
otherwise you should have to explicitely give the binaries path on load. |
||||||
|
|
||||||
|
For Windows users : Please find the binaries at http://ffmpeg.zeranoe.com/builds/. |
||||||
|
|
||||||
|
## Installation |
||||||
|
|
||||||
|
The recommended way to install PHP-FFMpeg is through [Composer](https://getcomposer.org). |
||||||
|
|
||||||
|
```json |
||||||
|
{ |
||||||
|
"require": { |
||||||
|
"php-ffmpeg/php-ffmpeg": "0.3.x-dev@dev" |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
## Basic Usage |
||||||
|
|
||||||
|
```php |
||||||
|
$ffmpeg = FFMpeg\FFMpeg::create(); |
||||||
|
$video = $ffmpeg->open('video.mpg'); |
||||||
|
$video |
||||||
|
->filters() |
||||||
|
->resize(new FFMpeg\Coordinate\Dimension(320, 240)) |
||||||
|
->synchronize(); |
||||||
|
$video |
||||||
|
->frame(FFMpeg\Coordinate\TimeCode::fromSeconds(10)) |
||||||
|
->save('frame.jpg'); |
||||||
|
$video |
||||||
|
->save(new FFMpeg\Format\Video\X264(), 'export-x264.mp4') |
||||||
|
->save(new FFMpeg\Format\Video\WMV(), 'export-wmv.wmv') |
||||||
|
->save(new FFMpeg\Format\Video\WebM(), 'export-webm.webm'); |
||||||
|
``` |
||||||
|
|
||||||
|
## Documentation |
||||||
|
|
||||||
|
This documentation is an introduction to discover the API. It's recommended |
||||||
|
to browse the source code as it is self-documented. |
||||||
|
|
||||||
|
### FFMpeg |
||||||
|
|
||||||
|
`FFMpeg\FFMpeg` is the main object to use to manipulate medias. To build it, |
||||||
|
use the static `FFMpeg\FFMpeg::create` : |
||||||
|
|
||||||
|
```php |
||||||
|
$ffmpeg = FFMpeg\FFMpeg::create(); |
||||||
|
``` |
||||||
|
|
||||||
|
FFMpeg will autodetect ffmpeg and ffprobe binaries. If you want to give binary |
||||||
|
paths explicitely, you can pass an array as configuration. A `Psr\Logger\LoggerInterface` |
||||||
|
can also be passed to log binary executions. |
||||||
|
|
||||||
|
```php |
||||||
|
$ffmpeg = FFMpeg\FFMpeg::create(array( |
||||||
|
'ffmpeg.binaries' => '/opt/local/ffmpeg/bin/ffmpeg', |
||||||
|
'ffprobe.binaries' => '/opt/local/ffmpeg/bin/ffprobe', |
||||||
|
'timeout' => 3600, // The timeout for the underlying process |
||||||
|
'ffmpeg.threads' => 12, // The number of threads that FFMpeg should use |
||||||
|
), $logger); |
||||||
|
``` |
||||||
|
|
||||||
|
### Manipulate media |
||||||
|
|
||||||
|
`FFMpeg\FFMpeg` creates media based on file paths. To open a file path, use the |
||||||
|
`FFMpeg\FFMpeg::open` method. |
||||||
|
|
||||||
|
```php |
||||||
|
$ffmpeg->open('video.mpeg'); |
||||||
|
``` |
||||||
|
|
||||||
|
Two types of media can be resolved : `FFMpeg\Media\Audio` and `FFMpeg\Media\Video`. |
||||||
|
A third type, `FFMpeg\Media\Frame`, is available through videos. |
||||||
|
|
||||||
|
#### Video |
||||||
|
|
||||||
|
`FFMpeg\Media\Video` can be transcoded, ie : change codec, isolate audio or |
||||||
|
video. Frames can be extracted. |
||||||
|
|
||||||
|
##### Transcoding |
||||||
|
|
||||||
|
You can transcode videos using the `FFMpeg\Media\Video:save` method. You will |
||||||
|
pass a `FFMpeg\Format\FormatInterface` for that. |
||||||
|
|
||||||
|
```php |
||||||
|
$format = new Format\Video\X264(); |
||||||
|
$format->on('progress', function ($video, $format, $percentage) { |
||||||
|
echo "$percentage % transcoded"; |
||||||
|
}); |
||||||
|
|
||||||
|
$video->save($format, 'video.avi'); |
||||||
|
``` |
||||||
|
|
||||||
|
Transcoding progress can be monitored in realtime, see Format documentation |
||||||
|
below for more informations. |
||||||
|
|
||||||
|
##### Extracting image |
||||||
|
|
||||||
|
You can extract a frame at any timecode using the `FFMpeg\Media\Video::frame` |
||||||
|
method. |
||||||
|
|
||||||
|
This code return a `FFMpeg\Media\Frame` instance corresponding to the second 42. |
||||||
|
You can pass any `FFMpeg\Coordinate\TimeCode` as argument, see dedicated |
||||||
|
documentation below for more information. |
||||||
|
|
||||||
|
```php |
||||||
|
$frame = $video->frame(FFMpeg\Coordinate\TimeCode::fromSeconds(42)); |
||||||
|
$frame->save('image.jpg'); |
||||||
|
``` |
||||||
|
|
||||||
|
##### Filters |
||||||
|
|
||||||
|
You can apply filters on `FFMpeg\Media\Video` with the `FFMpeg\Media\Video::addFilter` |
||||||
|
method. Video accepts Audio and Video filters. |
||||||
|
|
||||||
|
You can build your own filters and some are bundled in PHP-FFMpeg - they are |
||||||
|
accessible through the `FFMpeg\Media\Video::filters` method. |
||||||
|
|
||||||
|
Filters are chainable |
||||||
|
|
||||||
|
```php |
||||||
|
$video |
||||||
|
->filters() |
||||||
|
->resize($dimension, $mode, $useStandards) |
||||||
|
->framerate($framerate, $gop) |
||||||
|
->synchronize(); |
||||||
|
``` |
||||||
|
|
||||||
|
###### Resize |
||||||
|
|
||||||
|
Resizes a video to a given size. |
||||||
|
|
||||||
|
```php |
||||||
|
$video->filters()->resize($dimension, $mode, $useStandards); |
||||||
|
``` |
||||||
|
|
||||||
|
The resize filter takes three parameters : |
||||||
|
|
||||||
|
- `$dimension`, an instance of `FFMpeg\Coordinate\Dimension` |
||||||
|
- `$mode`, one of the constants `FFMpeg\Filters\Video\ResizeFilter::RESIZEMODE_*` constants |
||||||
|
- `$useStandards`, a boolean to force the use of the nearest aspect ratio standard. |
||||||
|
|
||||||
|
###### Framerate |
||||||
|
|
||||||
|
Changes the frame rate of the video. |
||||||
|
|
||||||
|
```php |
||||||
|
$video->filters()->framerate($framerate, $gop); |
||||||
|
``` |
||||||
|
|
||||||
|
The framerate filter takes two parameters : |
||||||
|
|
||||||
|
- `$framerate`, an instance of `FFMpeg\Coordinate\Framerate` |
||||||
|
- `$gop`, a [GOP](https://wikipedia.org/wiki/Group_of_pictures) value (integer) |
||||||
|
|
||||||
|
###### Synchronize |
||||||
|
|
||||||
|
Synchronizes audio and video. |
||||||
|
|
||||||
|
Some containers may use a delay that results in desynchronized outputs. This |
||||||
|
filters solves this issue. |
||||||
|
|
||||||
|
```php |
||||||
|
$video->filters()->synchronize(); |
||||||
|
``` |
||||||
|
|
||||||
|
#### Audio |
||||||
|
|
||||||
|
`FFMpeg\Media\Audio` can be transcoded, ie : change codec, isolate audio or |
||||||
|
video. Frames can be extracted. |
||||||
|
|
||||||
|
##### Transcoding |
||||||
|
|
||||||
|
You can transcode audios using the `FFMpeg\Media\Audio:save` method. You will |
||||||
|
pass a `FFMpeg\Format\FormatInterface` for that. |
||||||
|
|
||||||
|
```php |
||||||
|
$format = new Format\Audio\Flac(); |
||||||
|
$format->on('progress', function ($$audio, $format, $percentage) { |
||||||
|
echo "$percentage % transcoded"; |
||||||
|
}); |
||||||
|
|
||||||
|
$audio->save($format, 'track.flac'); |
||||||
|
``` |
||||||
|
|
||||||
|
Transcoding progress can be monitored in realtime, see Format documentation |
||||||
|
below for more informations. |
||||||
|
|
||||||
|
##### Filters |
||||||
|
|
||||||
|
You can apply filters on `FFMpeg\Media\Audio` with the `FFMpeg\Media\Audio::addFilter` |
||||||
|
method. It only accepts audio filters. |
||||||
|
|
||||||
|
You can build your own filters and some are bundled in PHP-FFMpeg - they are |
||||||
|
accessible through the `FFMpeg\Media\Audio::filters` method. |
||||||
|
|
||||||
|
###### Resample |
||||||
|
|
||||||
|
Resamples an audio file. |
||||||
|
|
||||||
|
```php |
||||||
|
$audio->filters()->resample($rate); |
||||||
|
``` |
||||||
|
|
||||||
|
The resample filter takes two parameters : |
||||||
|
|
||||||
|
- `$rate`, a valid audio sample rate value (integer) |
||||||
|
|
||||||
|
#### Frame |
||||||
|
|
||||||
|
A frame is a image at a timecode of a video ; see documentation above about |
||||||
|
frame extraction. |
||||||
|
|
||||||
|
You can save frames using the `FFMpeg\Media\Frame::save` method. |
||||||
|
|
||||||
|
```php |
||||||
|
$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. |
||||||
|
|
||||||
|
#### Formats |
||||||
|
|
||||||
|
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. |
||||||
|
|
||||||
|
Predefined formats already provide progress informations as events. |
||||||
|
|
||||||
|
```php |
||||||
|
$format = new Format\Video\X264(); |
||||||
|
$format->on('progress', function ($video, $format, $percentage) { |
||||||
|
echo "$percentage % transcoded"; |
||||||
|
}); |
||||||
|
|
||||||
|
$video->save($format, 'video.avi'); |
||||||
|
``` |
||||||
|
|
||||||
|
The callback provided for the event can be any callable. |
||||||
|
|
||||||
|
##### Create your own format |
||||||
|
|
||||||
|
The easiest way to create a format is to extend the abstract |
||||||
|
`FFMpeg\Format\Video\DefaultVideo` and `FFMpeg\Format\Audio\DefaultAudio`. |
||||||
|
and implement the following methods. |
||||||
|
|
||||||
|
```php |
||||||
|
class CustomWMVFormat extends FFMpeg\Format\Video\DefaultVideo |
||||||
|
{ |
||||||
|
public function __construct($audioCodec = 'wmav2', $videoCodec = 'wmv2') |
||||||
|
{ |
||||||
|
$this |
||||||
|
->setAudioCodec($audioCodec) |
||||||
|
->setVideoCodec($videoCodec); |
||||||
|
} |
||||||
|
|
||||||
|
public function supportBFrames() |
||||||
|
{ |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
public function getAvailableAudioCodecs() |
||||||
|
{ |
||||||
|
return array('wmav2'); |
||||||
|
} |
||||||
|
|
||||||
|
public function getAvailableVideoCodecs() |
||||||
|
{ |
||||||
|
return array('wmv2'); |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
#### Coordinates |
||||||
|
|
||||||
|
FFMpeg use 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\TimeCode` represent a timecode. |
||||||
|
|
||||||
|
### FFProbe |
||||||
|
|
||||||
|
`FFMpeg\FFProbe` is used internally by `FFMpeg\FFMpeg` to probe medias. You can |
||||||
|
also use it to extract media metadata. |
||||||
|
|
||||||
|
```php |
||||||
|
$ffprobe = FFMpeg\FFProbe::create(); |
||||||
|
$ffprobe |
||||||
|
->streams('/path/to/video/mp4') // extracts streams informations |
||||||
|
->videos() // filters video streams |
||||||
|
->first() // returns the first video stream |
||||||
|
->get('duration'); // returns the duration property |
||||||
|
``` |
||||||
|
|
||||||
|
##Using with Silex Microframework |
||||||
|
|
||||||
|
Service provider is easy to set up : |
||||||
|
|
||||||
|
```php |
||||||
|
$app = new Silex\Application(); |
||||||
|
$app->register(new FFMpeg\FFMpegServiceProvider()); |
||||||
|
|
||||||
|
$video = $app['ffmpeg']->open('video.mpeg'); |
||||||
|
``` |
||||||
|
|
||||||
|
Available options are as follow : |
||||||
|
|
||||||
|
```php |
||||||
|
$app->register(new FFMpeg\FFMpegServiceProvider(), array( |
||||||
|
'ffmpeg.configuration' => array( |
||||||
|
'ffmpeg.threads' => 4, |
||||||
|
'ffmpeg.timeout' => 300, |
||||||
|
'ffmpeg.binaries' => '/opt/local/ffmpeg/bin/ffmpeg', |
||||||
|
'ffprobe.timeout' => 30, |
||||||
|
'ffprobe.binaries' => '/opt/local/ffmpeg/bin/ffprobe', |
||||||
|
), |
||||||
|
'ffmpeg.logger' => $logger, |
||||||
|
)); |
||||||
|
``` |
||||||
|
|
||||||
|
## API Browser |
||||||
|
|
||||||
|
Browse the [API](http://readthedocs.org/docs/ffmpeg-php/en/latest/_static/API/) |
||||||
|
|
||||||
|
## License |
||||||
|
|
||||||
|
This project is licensed under the [MIT license](http://opensource.org/licenses/MIT). |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,44 @@ |
|||||||
|
{ |
||||||
|
"name": "php-ffmpeg/php-ffmpeg", |
||||||
|
"type": "library", |
||||||
|
"description": "FFMpeg PHP, an Object Oriented library to communicate with AVconv / ffmpeg", |
||||||
|
"keywords": ["video processing", "video", "audio processing", "audio", "avconv", "ffmpeg", "avprobe", "ffprobe"], |
||||||
|
"license": "MIT", |
||||||
|
"authors": [ |
||||||
|
{ |
||||||
|
"name": "Romain Neutron", |
||||||
|
"email": "imprec@gmail.com", |
||||||
|
"homepage": "http://www.lickmychip.com/" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "Phraseanet Team", |
||||||
|
"email": "info@alchemy.fr", |
||||||
|
"homepage": "http://www.phraseanet.com/" |
||||||
|
} |
||||||
|
], |
||||||
|
"require": { |
||||||
|
"php" : ">=5.3.3", |
||||||
|
"alchemy/binary-driver" : "~1.5", |
||||||
|
"doctrine/cache" : "~1.0", |
||||||
|
"evenement/evenement" : "~1.0", |
||||||
|
"neutron/temporary-filesystem" : "~2.1, >=2.1.1" |
||||||
|
}, |
||||||
|
"suggest": { |
||||||
|
"php-ffmpeg/extras" : "A compilation of common audio & video drivers for PHP-FFMpeg" |
||||||
|
}, |
||||||
|
"require-dev": { |
||||||
|
"sami/sami" : "~1.0", |
||||||
|
"silex/silex" : "~1.0", |
||||||
|
"phpunit/phpunit" : "~3.7" |
||||||
|
}, |
||||||
|
"autoload": { |
||||||
|
"psr-0": { |
||||||
|
"FFMpeg": "src" |
||||||
|
} |
||||||
|
}, |
||||||
|
"extra": { |
||||||
|
"branch-alias": { |
||||||
|
"dev-master": "0.4-dev" |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,27 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<phpunit backupGlobals="false" |
||||||
|
backupStaticAttributes="false" |
||||||
|
colors="true" |
||||||
|
convertErrorsToExceptions="true" |
||||||
|
convertNoticesToExceptions="true" |
||||||
|
convertWarningsToExceptions="true" |
||||||
|
processIsolation="false" |
||||||
|
stopOnFailure="false" |
||||||
|
syntaxCheck="true" |
||||||
|
verbose="false" |
||||||
|
bootstrap="tests/bootstrap.php" |
||||||
|
> |
||||||
|
<testsuites> |
||||||
|
<testsuite name="FFMpeg Tests Suite"> |
||||||
|
<directory>tests/FFMpeg/Functional</directory> |
||||||
|
</testsuite> |
||||||
|
</testsuites> |
||||||
|
<filter> |
||||||
|
<blacklist> |
||||||
|
<directory>vendor</directory> |
||||||
|
<directory>tests</directory> |
||||||
|
</blacklist> |
||||||
|
</filter> |
||||||
|
|
||||||
|
</phpunit> |
||||||
|
|
@ -0,0 +1,27 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<phpunit backupGlobals="false" |
||||||
|
backupStaticAttributes="false" |
||||||
|
colors="true" |
||||||
|
convertErrorsToExceptions="true" |
||||||
|
convertNoticesToExceptions="true" |
||||||
|
convertWarningsToExceptions="true" |
||||||
|
processIsolation="false" |
||||||
|
stopOnFailure="false" |
||||||
|
syntaxCheck="true" |
||||||
|
verbose="false" |
||||||
|
bootstrap="tests/bootstrap.php" |
||||||
|
> |
||||||
|
<testsuites> |
||||||
|
<testsuite name="FFMpeg Tests Suite"> |
||||||
|
<directory>tests/FFMpeg/Tests</directory> |
||||||
|
</testsuite> |
||||||
|
</testsuites> |
||||||
|
<filter> |
||||||
|
<blacklist> |
||||||
|
<directory>vendor</directory> |
||||||
|
<directory>tests</directory> |
||||||
|
</blacklist> |
||||||
|
</filter> |
||||||
|
|
||||||
|
</phpunit> |
||||||
|
|
@ -0,0 +1,248 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <dev.team@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\Coordinate; |
||||||
|
|
||||||
|
use FFMpeg\Exception\InvalidArgumentException; |
||||||
|
|
||||||
|
// see http://en.wikipedia.org/wiki/List_of_common_resolutions |
||||||
|
class AspectRatio |
||||||
|
{ |
||||||
|
// named 4:3 or 1.33:1 Traditional TV |
||||||
|
const AR_4_3 = '4/3'; |
||||||
|
// named 16:9 or 1.77:1 HD video standard |
||||||
|
const AR_16_9 = '16/9'; |
||||||
|
|
||||||
|
// named 3:2 or 1.5:1 see http://en.wikipedia.org/wiki/135_film |
||||||
|
const AR_3_2 = '3/2'; |
||||||
|
// named 5:3 or 1.66:1 see http://en.wikipedia.org/wiki/Super_16_mm |
||||||
|
const AR_5_3 = '5/3'; |
||||||
|
|
||||||
|
// mostly used in Photography |
||||||
|
const AR_5_4 = '5/4'; |
||||||
|
const AR_1_1 = '1/1'; |
||||||
|
|
||||||
|
// 1.85:1 US widescreen cinema standard see http://en.wikipedia.org/wiki/Widescreen#Film |
||||||
|
const AR_1_DOT_85_1 = '1.85:1'; |
||||||
|
// 2.39:1 or 2.40:1 Current widescreen cinema standard see http://en.wikipedia.org/wiki/Anamorphic_format |
||||||
|
const AR_2_DOT_39_1 = '2.39:1'; |
||||||
|
|
||||||
|
// Rotated constants |
||||||
|
|
||||||
|
// Rotated 4:3 |
||||||
|
const AR_ROTATED_3_4 = '3/4'; |
||||||
|
// Rotated 16:9 |
||||||
|
const AR_ROTATED_9_16 = '9/16'; |
||||||
|
|
||||||
|
// Rotated 3:2 |
||||||
|
const AR_ROTATED_2_3 = '2/3'; |
||||||
|
// Rotated 5:3 |
||||||
|
const AR_ROTATED_3_5 = '3/5'; |
||||||
|
|
||||||
|
// Rotated 5:4 |
||||||
|
const AR_ROTATED_4_5 = '4/5'; |
||||||
|
|
||||||
|
// Rotated 1.85 |
||||||
|
const AR_ROTATED_1_DOT_85 = '1/1.85'; |
||||||
|
// Rotated 2.39 |
||||||
|
const AR_ROTATED_2_DOT_39 = '1/2.39'; |
||||||
|
|
||||||
|
/** @var float */ |
||||||
|
private $ratio; |
||||||
|
|
||||||
|
public function __construct($ratio) |
||||||
|
{ |
||||||
|
$this->ratio = $ratio; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the value of the ratio. |
||||||
|
* |
||||||
|
* @return float |
||||||
|
*/ |
||||||
|
public function getValue() |
||||||
|
{ |
||||||
|
return $this->ratio; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Computes the best width for given height and modulus. |
||||||
|
* |
||||||
|
* @param Integer $height |
||||||
|
* @param Integer $modulus |
||||||
|
* |
||||||
|
* @return Integer |
||||||
|
*/ |
||||||
|
public function calculateWidth($height, $modulus = 1) |
||||||
|
{ |
||||||
|
$maxPossibleWidth = $this->getMultipleUp(ceil($this->ratio * $height), $modulus); |
||||||
|
$minPossibleWidth = $this->getMultipleDown(floor($this->ratio * $height), $modulus); |
||||||
|
|
||||||
|
$maxRatioDiff = abs($this->ratio - ($maxPossibleWidth / $height)); |
||||||
|
$minRatioDiff = abs($this->ratio - ($minPossibleWidth / $height)); |
||||||
|
|
||||||
|
return $maxRatioDiff < $minRatioDiff ? $maxPossibleWidth : $minPossibleWidth; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Computes the best height for given width and modulus. |
||||||
|
* |
||||||
|
* @param Integer $width |
||||||
|
* @param Integer $modulus |
||||||
|
* |
||||||
|
* @return Integer |
||||||
|
*/ |
||||||
|
public function calculateHeight($width, $modulus = 1) |
||||||
|
{ |
||||||
|
$maxPossibleHeight = $this->getMultipleUp(ceil($width / $this->ratio), $modulus); |
||||||
|
$minPossibleHeight = $this->getMultipleDown(floor($width / $this->ratio), $modulus); |
||||||
|
|
||||||
|
$maxRatioDiff = abs($this->ratio - ($width / $maxPossibleHeight)); |
||||||
|
$minRatioDiff = abs($this->ratio - ($width / $minPossibleHeight)); |
||||||
|
|
||||||
|
return $maxRatioDiff < $minRatioDiff ? $maxPossibleHeight : $minPossibleHeight; |
||||||
|
} |
||||||
|
|
||||||
|
private function getMultipleUp($value, $multiple) |
||||||
|
{ |
||||||
|
while (0 !== $value % $multiple) { |
||||||
|
$value++; |
||||||
|
} |
||||||
|
|
||||||
|
return $value; |
||||||
|
} |
||||||
|
|
||||||
|
private function getMultipleDown($value, $multiple) |
||||||
|
{ |
||||||
|
while (0 !== $value % $multiple) { |
||||||
|
$value--; |
||||||
|
} |
||||||
|
|
||||||
|
return $value; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a ratio based on Dimension. |
||||||
|
* |
||||||
|
* The strategy parameter forces by default to use standardized ratios. If |
||||||
|
* custom ratio need to be used, disable it. |
||||||
|
* |
||||||
|
* @param Dimension $dimension |
||||||
|
* @param Boolean $forceStandards Whether to force or not standard ratios |
||||||
|
* |
||||||
|
* @return AspectRatio |
||||||
|
* |
||||||
|
* @throws InvalidArgumentException |
||||||
|
*/ |
||||||
|
public static function create(Dimension $dimension, $forceStandards = true) |
||||||
|
{ |
||||||
|
$incoming = $dimension->getWidth() / $dimension->getHeight(); |
||||||
|
|
||||||
|
if ($forceStandards) { |
||||||
|
return new static(static::nearestStrategy($incoming)); |
||||||
|
} else { |
||||||
|
return new static(static::customStrategy($incoming)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static function valueFromName($name) |
||||||
|
{ |
||||||
|
switch ($name) { |
||||||
|
case static::AR_4_3: |
||||||
|
return 4 / 3; |
||||||
|
case static::AR_16_9: |
||||||
|
return 16 / 9; |
||||||
|
case static::AR_1_1: |
||||||
|
return 1 / 1; |
||||||
|
case static::AR_1_DOT_85_1: |
||||||
|
return 1.85; |
||||||
|
case static::AR_2_DOT_39_1: |
||||||
|
return 2.39; |
||||||
|
case static::AR_3_2: |
||||||
|
return 3 / 2; |
||||||
|
case static::AR_5_3: |
||||||
|
return 5 / 3; |
||||||
|
case static::AR_5_4: |
||||||
|
return 5 / 4; |
||||||
|
case static::AR_ROTATED_3_4: |
||||||
|
return 3 / 4; |
||||||
|
case static::AR_ROTATED_9_16: |
||||||
|
return 9 / 16; |
||||||
|
case static::AR_ROTATED_2_3: |
||||||
|
return 2 / 3; |
||||||
|
case static::AR_ROTATED_3_5: |
||||||
|
return 3 / 5; |
||||||
|
case static::AR_ROTATED_4_5: |
||||||
|
return 4 / 5; |
||||||
|
case static::AR_ROTATED_1_DOT_85: |
||||||
|
return 1 / 1.85; |
||||||
|
case static::AR_ROTATED_2_DOT_39: |
||||||
|
return 1 / 2.39; |
||||||
|
default: |
||||||
|
throw new InvalidArgumentException(sprintf('Unable to find value for %s', $name)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static function customStrategy($incoming) |
||||||
|
{ |
||||||
|
$try = static::nearestStrategy($incoming); |
||||||
|
|
||||||
|
if (abs($try - $incoming) < $try * 0.05) { |
||||||
|
return $try; |
||||||
|
} |
||||||
|
|
||||||
|
return $incoming; |
||||||
|
} |
||||||
|
|
||||||
|
private static function nearestStrategy($incoming) |
||||||
|
{ |
||||||
|
$availables = array( |
||||||
|
static::AR_4_3 => static::valueFromName(static::AR_4_3), |
||||||
|
static::AR_16_9 => static::valueFromName(static::AR_16_9), |
||||||
|
static::AR_1_1 => static::valueFromName(static::AR_1_1), |
||||||
|
static::AR_1_DOT_85_1 => static::valueFromName(static::AR_1_DOT_85_1), |
||||||
|
static::AR_2_DOT_39_1 => static::valueFromName(static::AR_2_DOT_39_1), |
||||||
|
static::AR_3_2 => static::valueFromName(static::AR_3_2), |
||||||
|
static::AR_5_3 => static::valueFromName(static::AR_5_3), |
||||||
|
static::AR_5_4 => static::valueFromName(static::AR_5_4), |
||||||
|
|
||||||
|
// Rotated |
||||||
|
static::AR_ROTATED_4_5 => static::valueFromName(static::AR_ROTATED_4_5), |
||||||
|
static::AR_ROTATED_9_16 => static::valueFromName(static::AR_ROTATED_9_16), |
||||||
|
static::AR_ROTATED_2_3 => static::valueFromName(static::AR_ROTATED_2_3), |
||||||
|
static::AR_ROTATED_3_5 => static::valueFromName(static::AR_ROTATED_3_5), |
||||||
|
static::AR_ROTATED_3_4 => static::valueFromName(static::AR_ROTATED_3_4), |
||||||
|
static::AR_ROTATED_1_DOT_85 => static::valueFromName(static::AR_ROTATED_1_DOT_85), |
||||||
|
static::AR_ROTATED_2_DOT_39 => static::valueFromName(static::AR_ROTATED_2_DOT_39), |
||||||
|
); |
||||||
|
asort($availables); |
||||||
|
|
||||||
|
$previous = $current = null; |
||||||
|
|
||||||
|
foreach ($availables as $name => $value) { |
||||||
|
$current = $value; |
||||||
|
if ($incoming <= $value) { |
||||||
|
break; |
||||||
|
} |
||||||
|
$previous = $value; |
||||||
|
} |
||||||
|
|
||||||
|
if (null === $previous) { |
||||||
|
return $current; |
||||||
|
} |
||||||
|
|
||||||
|
if (($current - $incoming) < ($incoming - $previous)) { |
||||||
|
return $current; |
||||||
|
} |
||||||
|
|
||||||
|
return $previous; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,71 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <info@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\Coordinate; |
||||||
|
|
||||||
|
use FFMpeg\Exception\InvalidArgumentException; |
||||||
|
|
||||||
|
/** |
||||||
|
* Dimension object, used for manipulating width and height couples |
||||||
|
*/ |
||||||
|
class Dimension |
||||||
|
{ |
||||||
|
private $width; |
||||||
|
private $height; |
||||||
|
|
||||||
|
/** |
||||||
|
* @param integer $width |
||||||
|
* @param integer $height |
||||||
|
* |
||||||
|
* @throws InvalidArgumentException when one of the parameteres is invalid |
||||||
|
*/ |
||||||
|
public function __construct($width, $height) |
||||||
|
{ |
||||||
|
if ($width <= 0 || $height <= 0) { |
||||||
|
throw new InvalidArgumentException('Width and height should be positive integer'); |
||||||
|
} |
||||||
|
|
||||||
|
$this->width = (int) $width; |
||||||
|
$this->height = (int) $height; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns width. |
||||||
|
* |
||||||
|
* @return width |
||||||
|
*/ |
||||||
|
public function getWidth() |
||||||
|
{ |
||||||
|
return $this->width; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns height. |
||||||
|
* |
||||||
|
* @return integer |
||||||
|
*/ |
||||||
|
public function getHeight() |
||||||
|
{ |
||||||
|
return $this->height; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the ratio. |
||||||
|
* |
||||||
|
* @param type $forceStandards Whether or not force the use of standards ratios; |
||||||
|
* |
||||||
|
* @return AspectRatio |
||||||
|
*/ |
||||||
|
public function getRatio($forceStandards = true) |
||||||
|
{ |
||||||
|
return AspectRatio::create($this, $forceStandards); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,36 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <info@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\Coordinate; |
||||||
|
|
||||||
|
use FFMpeg\Exception\InvalidArgumentException; |
||||||
|
|
||||||
|
class FrameRate |
||||||
|
{ |
||||||
|
private $value; |
||||||
|
|
||||||
|
public function __construct($value) |
||||||
|
{ |
||||||
|
if ($value <= 0) { |
||||||
|
throw new InvalidArgumentException('Invalid frame rate, must be positive value.'); |
||||||
|
} |
||||||
|
|
||||||
|
$this->value = $value; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return float |
||||||
|
*/ |
||||||
|
public function getValue() |
||||||
|
{ |
||||||
|
return $this->value; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,40 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <info@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\Coordinate; |
||||||
|
|
||||||
|
class Point |
||||||
|
{ |
||||||
|
private $x; |
||||||
|
private $y; |
||||||
|
|
||||||
|
public function __construct($x, $y) |
||||||
|
{ |
||||||
|
$this->x = (int) $x; |
||||||
|
$this->y = (int) $y; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return integer |
||||||
|
*/ |
||||||
|
public function getX() |
||||||
|
{ |
||||||
|
return $this->x; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return integer |
||||||
|
*/ |
||||||
|
public function getY() |
||||||
|
{ |
||||||
|
return $this->y; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,92 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <info@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\Coordinate; |
||||||
|
|
||||||
|
use FFMpeg\Exception\InvalidArgumentException; |
||||||
|
|
||||||
|
class TimeCode |
||||||
|
{ |
||||||
|
//see http://www.dropframetimecode.org/ |
||||||
|
private $hours; |
||||||
|
private $minutes; |
||||||
|
private $seconds; |
||||||
|
private $frames; |
||||||
|
|
||||||
|
public function __construct($hours, $minutes, $seconds, $frames) |
||||||
|
{ |
||||||
|
$this->hours = $hours; |
||||||
|
$this->minutes = $minutes; |
||||||
|
$this->seconds = $seconds; |
||||||
|
$this->frames = $frames; |
||||||
|
} |
||||||
|
|
||||||
|
public function __toString() |
||||||
|
{ |
||||||
|
return sprintf('%02d:%02d:%02d.%02d', $this->hours, $this->minutes, $this->seconds, $this->frames); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates timecode from string. |
||||||
|
* |
||||||
|
* @param string $timecode |
||||||
|
* |
||||||
|
* @return TimeCode |
||||||
|
* |
||||||
|
* @throws InvalidArgumentException In case an invalid timecode is supplied |
||||||
|
*/ |
||||||
|
public static function fromString($timecode) |
||||||
|
{ |
||||||
|
$days = 0; |
||||||
|
|
||||||
|
if (preg_match('/^[0-9]+:[0-9]+:[0-9]+:[0-9]+\.[0-9]+$/', $timecode)) { |
||||||
|
list($days, $hours, $minutes, $seconds, $frames) = sscanf($timecode, '%d:%d:%d:%d.%d'); |
||||||
|
} elseif (preg_match('/^[0-9]+:[0-9]+:[0-9]+:[0-9]+:[0-9]+$/', $timecode)) { |
||||||
|
list($days, $hours, $minutes, $seconds, $frames) = sscanf($timecode, '%d:%d:%d:%d:%d'); |
||||||
|
} elseif (preg_match('/^[0-9]+:[0-9]+:[0-9]+\.[0-9]+$/', $timecode)) { |
||||||
|
list($hours, $minutes, $seconds, $frames) = sscanf($timecode, '%d:%d:%d.%s'); |
||||||
|
} elseif (preg_match('/^[0-9]+:[0-9]+:[0-9]+:[0-9]+$/', $timecode)) { |
||||||
|
list($hours, $minutes, $seconds, $frames) = sscanf($timecode, '%d:%d:%d:%s'); |
||||||
|
} else { |
||||||
|
throw new InvalidArgumentException(sprintf('Unable to parse timecode %s', $timecode)); |
||||||
|
} |
||||||
|
|
||||||
|
$hours += $days * 24; |
||||||
|
|
||||||
|
return new static($hours, $minutes, $seconds, $frames); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates timecode from number of seconds. |
||||||
|
* |
||||||
|
* @param float $quantity |
||||||
|
* |
||||||
|
* @return TimeCode |
||||||
|
*/ |
||||||
|
public static function fromSeconds($quantity) |
||||||
|
{ |
||||||
|
$minutes = $hours = $frames = 0; |
||||||
|
|
||||||
|
$frames = round(100 * ($quantity - floor($quantity))); |
||||||
|
$seconds = floor($quantity); |
||||||
|
|
||||||
|
if ($seconds > 59) { |
||||||
|
$minutes = floor($seconds / 60); |
||||||
|
$seconds = $seconds % 60; |
||||||
|
} |
||||||
|
if ($minutes > 59) { |
||||||
|
$hours = floor($minutes / 60); |
||||||
|
$minutes = $minutes % 60; |
||||||
|
} |
||||||
|
|
||||||
|
return new static($hours, $minutes, $seconds, $frames); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,57 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <info@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\Driver; |
||||||
|
|
||||||
|
use Alchemy\BinaryDriver\AbstractBinary; |
||||||
|
use Alchemy\BinaryDriver\Configuration; |
||||||
|
use Alchemy\BinaryDriver\ConfigurationInterface; |
||||||
|
use Alchemy\BinaryDriver\Exception\ExecutableNotFoundException as BinaryDriverExecutableNotFound; |
||||||
|
use FFMpeg\Exception\ExecutableNotFoundException; |
||||||
|
use Psr\Log\LoggerInterface; |
||||||
|
|
||||||
|
class FFMpegDriver extends AbstractBinary |
||||||
|
{ |
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function getName() |
||||||
|
{ |
||||||
|
return 'ffmpeg'; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates an FFMpegDriver. |
||||||
|
* |
||||||
|
* @param LoggerInterface $logger |
||||||
|
* @param array|Configuration $configuration |
||||||
|
* |
||||||
|
* @return FFMpegDriver |
||||||
|
*/ |
||||||
|
public static function create(LoggerInterface $logger = null, $configuration = array()) |
||||||
|
{ |
||||||
|
if (!$configuration instanceof ConfigurationInterface) { |
||||||
|
$configuration = new Configuration($configuration); |
||||||
|
} |
||||||
|
|
||||||
|
$binaries = $configuration->get('ffmpeg.binaries', array('avconv', 'ffmpeg')); |
||||||
|
|
||||||
|
if (!$configuration->has('timeout')) { |
||||||
|
$configuration->set('timeout', 300); |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
return static::load($binaries, $logger, $configuration); |
||||||
|
} catch (BinaryDriverExecutableNotFound $e) { |
||||||
|
throw new ExecutableNotFoundException('Unable to load FFMpeg', $e->getCode(), $e); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,53 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <info@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\Driver; |
||||||
|
|
||||||
|
use Alchemy\BinaryDriver\AbstractBinary; |
||||||
|
use Alchemy\BinaryDriver\Configuration; |
||||||
|
use Alchemy\BinaryDriver\ConfigurationInterface; |
||||||
|
use Alchemy\BinaryDriver\Exception\ExecutableNotFoundException as BinaryDriverExecutableNotFound; |
||||||
|
use FFMpeg\Exception\ExecutableNotFoundException; |
||||||
|
use Psr\Log\LoggerInterface; |
||||||
|
|
||||||
|
class FFProbeDriver extends AbstractBinary |
||||||
|
{ |
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function getName() |
||||||
|
{ |
||||||
|
return 'ffprobe'; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates an FFProbeDriver. |
||||||
|
* |
||||||
|
* @param array|ConfigurationInterface $configuration |
||||||
|
* @param LoggerInterface $logger |
||||||
|
* |
||||||
|
* @return FFProbeDriver |
||||||
|
*/ |
||||||
|
public static function create($configuration, LoggerInterface $logger = null) |
||||||
|
{ |
||||||
|
if (!$configuration instanceof ConfigurationInterface) { |
||||||
|
$configuration = new Configuration($configuration); |
||||||
|
} |
||||||
|
|
||||||
|
$binaries = $configuration->get('ffprobe.binaries', array('avprobe', 'ffprobe')); |
||||||
|
|
||||||
|
try { |
||||||
|
return static::load($binaries, $logger, $configuration); |
||||||
|
} catch (BinaryDriverExecutableNotFound $e) { |
||||||
|
throw new ExecutableNotFoundException('Unable to load FFProbe', $e->getCode(), $e); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,16 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <info@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\Exception; |
||||||
|
|
||||||
|
interface ExceptionInterface |
||||||
|
{ |
||||||
|
} |
@ -0,0 +1,16 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <info@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\Exception; |
||||||
|
|
||||||
|
class ExecutableNotFoundException extends RuntimeException |
||||||
|
{ |
||||||
|
} |
@ -0,0 +1,16 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <info@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\Exception; |
||||||
|
|
||||||
|
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface |
||||||
|
{ |
||||||
|
} |
@ -0,0 +1,16 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <info@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\Exception; |
||||||
|
|
||||||
|
class LogicException extends \LogicException implements ExceptionInterface |
||||||
|
{ |
||||||
|
} |
@ -0,0 +1,16 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <info@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\Exception; |
||||||
|
|
||||||
|
class RuntimeException extends \RuntimeException implements ExceptionInterface |
||||||
|
{ |
||||||
|
} |
@ -0,0 +1,123 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <info@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg; |
||||||
|
|
||||||
|
use Alchemy\BinaryDriver\ConfigurationInterface; |
||||||
|
use FFMpeg\Driver\FFMpegDriver; |
||||||
|
use FFMpeg\Exception\InvalidArgumentException; |
||||||
|
use FFMpeg\Media\Audio; |
||||||
|
use FFMpeg\Media\Video; |
||||||
|
use Psr\Log\LoggerInterface; |
||||||
|
|
||||||
|
class FFMpeg |
||||||
|
{ |
||||||
|
/** @var FFMpegDriver */ |
||||||
|
private $driver; |
||||||
|
/** @var FFProbe */ |
||||||
|
private $ffprobe; |
||||||
|
|
||||||
|
public function __construct(FFMpegDriver $ffmpeg, FFProbe $ffprobe) |
||||||
|
{ |
||||||
|
$this->driver = $ffmpeg; |
||||||
|
$this->ffprobe = $ffprobe; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets FFProbe. |
||||||
|
* |
||||||
|
* @param FFProbe |
||||||
|
* |
||||||
|
* @return FFMpeg |
||||||
|
*/ |
||||||
|
public function setFFProbe(FFProbe $ffprobe) |
||||||
|
{ |
||||||
|
$this->ffprobe = $ffprobe; |
||||||
|
|
||||||
|
return $this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Gets FFProbe. |
||||||
|
* |
||||||
|
* @return FFProbe |
||||||
|
*/ |
||||||
|
public function getFFProbe() |
||||||
|
{ |
||||||
|
return $this->ffprobe; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets the ffmpeg driver. |
||||||
|
* |
||||||
|
* @return FFMpeg |
||||||
|
*/ |
||||||
|
public function setFFMpegDriver(FFMpegDriver $ffmpeg) |
||||||
|
{ |
||||||
|
$this->driver = $ffmpeg; |
||||||
|
|
||||||
|
return $this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Gets the ffmpeg driver. |
||||||
|
* |
||||||
|
* @return FFMpegDriver |
||||||
|
*/ |
||||||
|
public function getFFMpegDriver() |
||||||
|
{ |
||||||
|
return $this->driver; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Opens a file in order to be processed. |
||||||
|
* |
||||||
|
* @param string $pathfile A pathfile |
||||||
|
* |
||||||
|
* @return Audio|Video |
||||||
|
* |
||||||
|
* @throws InvalidArgumentException |
||||||
|
*/ |
||||||
|
public function open($pathfile) |
||||||
|
{ |
||||||
|
if (!file_exists($pathfile)) { |
||||||
|
throw new InvalidArgumentException(sprintf('File %s does not exists', $pathfile)); |
||||||
|
} |
||||||
|
|
||||||
|
$streams = $this->ffprobe->streams($pathfile); |
||||||
|
|
||||||
|
if (0 < count($streams->videos())) { |
||||||
|
return new Video($pathfile, $this->driver, $this->ffprobe); |
||||||
|
} elseif (0 < count($streams->audios())) { |
||||||
|
return new Audio($pathfile, $this->driver, $this->ffprobe); |
||||||
|
} |
||||||
|
|
||||||
|
throw new InvalidArgumentException('Unable to detect file format, only audio and video supported'); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a new FFMpeg instance. |
||||||
|
* |
||||||
|
* @param array|ConfigurationInterface $configuration |
||||||
|
* @param LoggerInterface $logger |
||||||
|
* @param FFProbe $probe |
||||||
|
* |
||||||
|
* @return FFMpeg |
||||||
|
*/ |
||||||
|
public static function create($configuration = array(), LoggerInterface $logger = null, FFProbe $probe = null) |
||||||
|
{ |
||||||
|
if (null === $probe) { |
||||||
|
$probe = FFProbe::create($configuration, $logger, null); |
||||||
|
} |
||||||
|
|
||||||
|
return new static(FFMpegDriver::create($logger, $configuration), $probe); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,66 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <info@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg; |
||||||
|
|
||||||
|
use Doctrine\Common\Cache\ArrayCache; |
||||||
|
use FFMpeg\FFMpeg; |
||||||
|
use FFMpeg\FFProbe; |
||||||
|
use Silex\Application; |
||||||
|
use Silex\ServiceProviderInterface; |
||||||
|
|
||||||
|
class FFMpegServiceProvider implements ServiceProviderInterface |
||||||
|
{ |
||||||
|
public function register(Application $app) |
||||||
|
{ |
||||||
|
$app['ffmpeg.configuration'] = array(); |
||||||
|
$app['ffmpeg.default.configuration'] = array( |
||||||
|
'ffmpeg.threads' => 4, |
||||||
|
'ffmpeg.timeout' => 300, |
||||||
|
'ffmpeg.binaries' => array('avconv', 'ffmpeg'), |
||||||
|
'ffprobe.timeout' => 30, |
||||||
|
'ffprobe.binaries' => array('avprobe', 'ffprobe'), |
||||||
|
); |
||||||
|
$app['ffmpeg.logger'] = null; |
||||||
|
|
||||||
|
$app['ffmpeg.configuration.build'] = $app->share(function (Application $app) { |
||||||
|
return array_replace($app['ffmpeg.default.configuration'], $app['ffmpeg.configuration']); |
||||||
|
}); |
||||||
|
|
||||||
|
$app['ffmpeg'] = $app['ffmpeg.ffmpeg'] = $app->share(function(Application $app) { |
||||||
|
$configuration = $app['ffmpeg.configuration.build']; |
||||||
|
|
||||||
|
if (isset($configuration['ffmpeg.timeout'])) { |
||||||
|
$configuration['timeout'] = $configuration['ffmpeg.timeout']; |
||||||
|
} |
||||||
|
|
||||||
|
return FFMpeg::create($configuration, $app['ffmpeg.logger'], $app['ffmpeg.ffprobe']); |
||||||
|
}); |
||||||
|
|
||||||
|
$app['ffprobe.cache'] = $app->share(function () { |
||||||
|
return new ArrayCache(); |
||||||
|
}); |
||||||
|
|
||||||
|
$app['ffmpeg.ffprobe'] = $app->share(function(Application $app) { |
||||||
|
$configuration = $app['ffmpeg.configuration.build']; |
||||||
|
|
||||||
|
if (isset($configuration['ffmpeg.timeout'])) { |
||||||
|
$configuration['timeout'] = $configuration['ffprobe.timeout']; |
||||||
|
} |
||||||
|
|
||||||
|
return FFProbe::create($configuration, $app['ffmpeg.logger'], $app['ffprobe.cache']); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
public function boot(Application $app) |
||||||
|
{ |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,269 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <info@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg; |
||||||
|
|
||||||
|
use Alchemy\BinaryDriver\ConfigurationInterface; |
||||||
|
use Alchemy\BinaryDriver\Exception\ExecutionFailureException; |
||||||
|
use Doctrine\Common\Cache\ArrayCache; |
||||||
|
use Doctrine\Common\Cache\Cache; |
||||||
|
use FFMpeg\Driver\FFProbeDriver; |
||||||
|
use FFMpeg\FFProbe\DataMapping\Format; |
||||||
|
use FFMpeg\FFProbe\Mapper; |
||||||
|
use FFMpeg\FFProbe\MapperInterface; |
||||||
|
use FFMpeg\FFProbe\OptionsTester; |
||||||
|
use FFMpeg\FFProbe\OptionsTesterInterface; |
||||||
|
use FFMpeg\FFProbe\OutputParser; |
||||||
|
use FFMpeg\FFProbe\OutputParserInterface; |
||||||
|
use FFMpeg\Exception\InvalidArgumentException; |
||||||
|
use FFMpeg\Exception\RuntimeException; |
||||||
|
use Psr\Log\LoggerInterface; |
||||||
|
|
||||||
|
class FFProbe |
||||||
|
{ |
||||||
|
const TYPE_STREAMS = 'streams'; |
||||||
|
const TYPE_FORMAT = 'format'; |
||||||
|
|
||||||
|
/** @var Cache */ |
||||||
|
private $cache; |
||||||
|
/** @var OptionsTesterInterface */ |
||||||
|
private $optionsTester; |
||||||
|
/** @var OutputParserInterface */ |
||||||
|
private $parser; |
||||||
|
/** @var FFProbeDriver */ |
||||||
|
private $ffprobe; |
||||||
|
/** @var MapperInterface */ |
||||||
|
private $mapper; |
||||||
|
|
||||||
|
public function __construct(FFProbeDriver $ffprobe, Cache $cache) |
||||||
|
{ |
||||||
|
$this->ffprobe = $ffprobe; |
||||||
|
$this->optionsTester = new OptionsTester($ffprobe, $cache); |
||||||
|
$this->parser = new OutputParser(); |
||||||
|
$this->mapper = new Mapper(); |
||||||
|
$this->cache = $cache; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return OutputParserInterface |
||||||
|
*/ |
||||||
|
public function getParser() |
||||||
|
{ |
||||||
|
return $this->parser; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param OutputParserInterface $parser |
||||||
|
* |
||||||
|
* @return FFProbe |
||||||
|
*/ |
||||||
|
public function setParser(OutputParserInterface $parser) |
||||||
|
{ |
||||||
|
$this->parser = $parser; |
||||||
|
|
||||||
|
return $this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return FFProbeDriver |
||||||
|
*/ |
||||||
|
public function getFFProbeDriver() |
||||||
|
{ |
||||||
|
return $this->ffprobe; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param FFProbeDriver $ffprobe |
||||||
|
* |
||||||
|
* @return FFProbe |
||||||
|
*/ |
||||||
|
public function setFFProbeDriver(FFProbeDriver $ffprobe) |
||||||
|
{ |
||||||
|
$this->ffprobe = $ffprobe; |
||||||
|
|
||||||
|
return $this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param OptionsTesterInterface $tester |
||||||
|
* |
||||||
|
* @return FFProbe |
||||||
|
*/ |
||||||
|
public function setOptionsTester(OptionsTesterInterface $tester) |
||||||
|
{ |
||||||
|
$this->optionsTester = $tester; |
||||||
|
|
||||||
|
return $this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return OptionsTesterInterface |
||||||
|
*/ |
||||||
|
public function getOptionsTester() |
||||||
|
{ |
||||||
|
return $this->optionsTester; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param Cache $cache |
||||||
|
* |
||||||
|
* @return FFProbe |
||||||
|
*/ |
||||||
|
public function setCache(Cache $cache) |
||||||
|
{ |
||||||
|
$this->cache = $cache; |
||||||
|
|
||||||
|
return $this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return Cache |
||||||
|
*/ |
||||||
|
public function getCache() |
||||||
|
{ |
||||||
|
return $this->cache; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return MapperInterface |
||||||
|
*/ |
||||||
|
public function getMapper() |
||||||
|
{ |
||||||
|
return $this->mapper; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param MapperInterface $mapper |
||||||
|
* |
||||||
|
* @return FFProbe |
||||||
|
*/ |
||||||
|
public function setMapper(MapperInterface $mapper) |
||||||
|
{ |
||||||
|
$this->mapper = $mapper; |
||||||
|
|
||||||
|
return $this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @api |
||||||
|
* |
||||||
|
* Probes the format of a given file. |
||||||
|
* |
||||||
|
* @param string $pathfile |
||||||
|
* |
||||||
|
* @return Format A Format object |
||||||
|
* |
||||||
|
* @throws InvalidArgumentException |
||||||
|
* @throws RuntimeException |
||||||
|
*/ |
||||||
|
public function format($pathfile) |
||||||
|
{ |
||||||
|
return $this->probe($pathfile, '-show_format', static::TYPE_FORMAT); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @api |
||||||
|
* |
||||||
|
* Probes the streams contained in a given file. |
||||||
|
* |
||||||
|
* @param string $pathfile |
||||||
|
* |
||||||
|
* @return StreamCollection A collection of streams |
||||||
|
* |
||||||
|
* @throws InvalidArgumentException |
||||||
|
* @throws RuntimeException |
||||||
|
*/ |
||||||
|
public function streams($pathfile) |
||||||
|
{ |
||||||
|
return $this->probe($pathfile, '-show_streams', static::TYPE_STREAMS); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @api |
||||||
|
* |
||||||
|
* Creates an FFProbe. |
||||||
|
* |
||||||
|
* @param array|ConfigurationInterface $configuration |
||||||
|
* @param LoggerInterface $logger |
||||||
|
* @param Cache $cache |
||||||
|
* |
||||||
|
* @return FFProbe |
||||||
|
*/ |
||||||
|
public static function create($configuration = array(), LoggerInterface $logger = null, Cache $cache = null) |
||||||
|
{ |
||||||
|
if (null === $cache) { |
||||||
|
$cache = new ArrayCache(); |
||||||
|
} |
||||||
|
|
||||||
|
return new static(FFProbeDriver::create($configuration, $logger), $cache); |
||||||
|
} |
||||||
|
|
||||||
|
private function probe($pathfile, $command, $type, $allowJson = true) |
||||||
|
{ |
||||||
|
$id = sprintf('%s-%s', $command, $pathfile); |
||||||
|
|
||||||
|
if ($this->cache->contains($id)) { |
||||||
|
return $this->cache->fetch($id); |
||||||
|
} |
||||||
|
|
||||||
|
if (!$this->optionsTester->has($command)) { |
||||||
|
throw new RuntimeException(sprintf( |
||||||
|
'This version of ffprobe is too old and ' |
||||||
|
. 'does not support `%s` option, please upgrade', $command |
||||||
|
)); |
||||||
|
} |
||||||
|
|
||||||
|
$commands = array($pathfile, $command); |
||||||
|
|
||||||
|
$parseIsToDo = false; |
||||||
|
|
||||||
|
if ($allowJson && $this->optionsTester->has('-print_format')) { |
||||||
|
$commands[] = '-print_format'; |
||||||
|
$commands[] = 'json'; |
||||||
|
} else { |
||||||
|
$parseIsToDo = true; |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
$output = $this->ffprobe->command($commands); |
||||||
|
} catch (ExecutionFailureException $e) { |
||||||
|
throw new RuntimeException(sprintf('Unable to probe %s', $pathfile), $e->getCode(), $e); |
||||||
|
} |
||||||
|
|
||||||
|
if ($parseIsToDo) { |
||||||
|
$data = $this->parser->parse($type, $output); |
||||||
|
} else { |
||||||
|
try { |
||||||
|
// Malformed json may be retrieved |
||||||
|
$data = $this->parseJson($output); |
||||||
|
} catch (RuntimeException $e) { |
||||||
|
return $this->probe($pathfile, $command, $type, false); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
$ret = $this->mapper->map($type, $data); |
||||||
|
|
||||||
|
$this->cache->save($id, $ret); |
||||||
|
|
||||||
|
return $ret; |
||||||
|
} |
||||||
|
|
||||||
|
private function parseJson($data) |
||||||
|
{ |
||||||
|
$ret = @json_decode($data, true); |
||||||
|
|
||||||
|
if (JSON_ERROR_NONE !== json_last_error()) { |
||||||
|
throw new RuntimeException(sprintf('Unable to parse json %s', $ret)); |
||||||
|
} |
||||||
|
|
||||||
|
return $ret; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,80 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <info@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\FFProbe\DataMapping; |
||||||
|
|
||||||
|
use FFMpeg\Exception\InvalidArgumentException; |
||||||
|
|
||||||
|
abstract class AbstractData implements \Countable |
||||||
|
{ |
||||||
|
private $properties; |
||||||
|
|
||||||
|
public function __construct(array $properties) |
||||||
|
{ |
||||||
|
$this->properties = $properties; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns true if data has property. |
||||||
|
* |
||||||
|
* @param string $property |
||||||
|
* @return Boolean |
||||||
|
*/ |
||||||
|
public function has($property) |
||||||
|
{ |
||||||
|
return isset($this->properties[$property]); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the property value given its name. |
||||||
|
* |
||||||
|
* @param string $property |
||||||
|
* @return mixed |
||||||
|
* |
||||||
|
* @throws InvalidArgumentException In case the data does not have the property |
||||||
|
*/ |
||||||
|
public function get($property) |
||||||
|
{ |
||||||
|
if (!isset($this->properties[$property])) { |
||||||
|
throw new InvalidArgumentException(sprintf('Invalid property `%s`.', $property)); |
||||||
|
} |
||||||
|
|
||||||
|
return $this->properties[$property]; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns all property names. |
||||||
|
* |
||||||
|
* @return array |
||||||
|
*/ |
||||||
|
public function keys() |
||||||
|
{ |
||||||
|
return array_keys($this->properties); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns all properties and their values. |
||||||
|
* |
||||||
|
* @return array |
||||||
|
*/ |
||||||
|
public function all() |
||||||
|
{ |
||||||
|
return $this->properties; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function count() |
||||||
|
{ |
||||||
|
return count($this->properties); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,16 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <info@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\FFProbe\DataMapping; |
||||||
|
|
||||||
|
class Format extends AbstractData |
||||||
|
{ |
||||||
|
} |
@ -0,0 +1,103 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <info@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\FFProbe\DataMapping; |
||||||
|
|
||||||
|
use FFMpeg\Exception\LogicException; |
||||||
|
use FFMpeg\Exception\RuntimeException; |
||||||
|
use FFMpeg\Coordinate\Dimension; |
||||||
|
|
||||||
|
class Stream extends AbstractData |
||||||
|
{ |
||||||
|
/** |
||||||
|
* Returns true if the stream is an audio stream. |
||||||
|
* |
||||||
|
* @return Boolean |
||||||
|
*/ |
||||||
|
public function isAudio() |
||||||
|
{ |
||||||
|
return $this->has('codec_type') ? 'audio' === $this->get('codec_type') : false; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns true if the stream is a video stream. |
||||||
|
* |
||||||
|
* @return Boolean |
||||||
|
*/ |
||||||
|
public function isVideo() |
||||||
|
{ |
||||||
|
return $this->has('codec_type') ? 'video' === $this->get('codec_type') : false; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the dimension of the video stream. |
||||||
|
* |
||||||
|
* @return Dimension |
||||||
|
* |
||||||
|
* @throws LogicException In case the stream is not a video stream. |
||||||
|
* @throws RuntimeException In case the dimensions can not be extracted. |
||||||
|
*/ |
||||||
|
public function getDimensions() |
||||||
|
{ |
||||||
|
if (!$this->isVideo()) { |
||||||
|
throw new LogicException('Dimensions can only be retrieved from video streams.'); |
||||||
|
} |
||||||
|
|
||||||
|
$width = $height = $sampleRatio = $displayRatio = null; |
||||||
|
|
||||||
|
if ($this->has('width')) { |
||||||
|
$width = $this->get('width'); |
||||||
|
} |
||||||
|
if ($this->has('height')) { |
||||||
|
$height = $this->get('height'); |
||||||
|
} |
||||||
|
if (null !== $ratio = $this->extractRatio($this, 'sample_aspect_ratio')) { |
||||||
|
$sampleRatio = $ratio; |
||||||
|
} |
||||||
|
if (null !== $ratio = $this->extractRatio($this, 'display_aspect_ratio')) { |
||||||
|
$displayRatio = $ratio; |
||||||
|
} |
||||||
|
|
||||||
|
if (null === $height || null === $width) { |
||||||
|
throw new RuntimeException('Unable to extract dimensions.'); |
||||||
|
} |
||||||
|
|
||||||
|
if (null !== $displayRatio && null !== $sampleRatio) { |
||||||
|
$width = round($width / $sampleRatio[0] * $sampleRatio[1] * $displayRatio[0] / $displayRatio[1]); |
||||||
|
} |
||||||
|
|
||||||
|
return new Dimension($width, $height); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Extracts a ratio from a string in a \d+:\d+ format given a key name. |
||||||
|
* |
||||||
|
* @param Stream $stream The stream where to look for the ratio. |
||||||
|
* @param string $name the name of the key. |
||||||
|
* @return null|array An array containing the width and the height, null if not found. |
||||||
|
*/ |
||||||
|
private function extractRatio(Stream $stream, $name) |
||||||
|
{ |
||||||
|
if ($stream->has($name)) { |
||||||
|
$ratio = $stream->get($name); |
||||||
|
if (preg_match('/\d+:\d+/', $ratio)) { |
||||||
|
$data = array_filter(explode(':', $ratio), function ($int) { |
||||||
|
return $int > 0; |
||||||
|
}); |
||||||
|
if (2 === count($data)) { |
||||||
|
return array_map(function ($int) { return (int) $int; }, $data); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,99 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <info@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\FFProbe\DataMapping; |
||||||
|
|
||||||
|
class StreamCollection implements \Countable, \IteratorAggregate |
||||||
|
{ |
||||||
|
private $streams; |
||||||
|
|
||||||
|
public function __construct(array $streams = array()) |
||||||
|
{ |
||||||
|
$this->streams = array_values($streams); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the first stream of the collection, null if the collection is |
||||||
|
* empty. |
||||||
|
* |
||||||
|
* @return null|Stream |
||||||
|
*/ |
||||||
|
public function first() |
||||||
|
{ |
||||||
|
$stream = reset($this->streams); |
||||||
|
|
||||||
|
return $stream ?: null; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Adds a stream to the collection. |
||||||
|
* |
||||||
|
* @param Stream $stream |
||||||
|
* |
||||||
|
* @return StreamCollection |
||||||
|
*/ |
||||||
|
public function add(Stream $stream) |
||||||
|
{ |
||||||
|
$this->streams[] = $stream; |
||||||
|
|
||||||
|
return $this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns a new StreamCollection with only video streams. |
||||||
|
* |
||||||
|
* @return StreamCollection |
||||||
|
*/ |
||||||
|
public function videos() |
||||||
|
{ |
||||||
|
return new static(array_filter($this->streams, function (Stream $stream) { |
||||||
|
return $stream->isVideo(); |
||||||
|
})); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns a new StreamCollection with only audio streams. |
||||||
|
* |
||||||
|
* @return StreamCollection |
||||||
|
*/ |
||||||
|
public function audios() |
||||||
|
{ |
||||||
|
return new static(array_filter($this->streams, function (Stream $stream) { |
||||||
|
return $stream->isAudio(); |
||||||
|
})); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function count() |
||||||
|
{ |
||||||
|
return count($this->streams); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the array of contained streams. |
||||||
|
* |
||||||
|
* @return array |
||||||
|
*/ |
||||||
|
public function all() |
||||||
|
{ |
||||||
|
return $this->streams; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function getIterator() |
||||||
|
{ |
||||||
|
return new \ArrayIterator($this->streams); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,54 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <info@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\FFProbe; |
||||||
|
|
||||||
|
use FFMpeg\FFProbe; |
||||||
|
use FFMpeg\FFProbe\DataMapping\Format; |
||||||
|
use FFMpeg\FFProbe\DataMapping\StreamCollection; |
||||||
|
use FFMpeg\FFProbe\DataMapping\Stream; |
||||||
|
use FFMpeg\Exception\InvalidArgumentException; |
||||||
|
|
||||||
|
class Mapper implements MapperInterface |
||||||
|
{ |
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function map($type, $data) |
||||||
|
{ |
||||||
|
switch ($type) { |
||||||
|
case FFProbe::TYPE_FORMAT: |
||||||
|
return $this->mapFormat($data); |
||||||
|
case FFProbe::TYPE_STREAMS: |
||||||
|
return $this->mapStreams($data); |
||||||
|
default: |
||||||
|
throw new InvalidArgumentException(sprintf( |
||||||
|
'Invalid type `%s`.', $type |
||||||
|
)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private function mapFormat($data) |
||||||
|
{ |
||||||
|
return new Format($data['format']); |
||||||
|
} |
||||||
|
|
||||||
|
private function mapStreams($data) |
||||||
|
{ |
||||||
|
$streams = new StreamCollection(); |
||||||
|
|
||||||
|
foreach ($data['streams'] as $properties) { |
||||||
|
$streams->add(new Stream($properties)); |
||||||
|
} |
||||||
|
|
||||||
|
return $streams; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,27 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <info@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\FFProbe; |
||||||
|
|
||||||
|
interface MapperInterface |
||||||
|
{ |
||||||
|
/** |
||||||
|
* Maps data given its type. |
||||||
|
* |
||||||
|
* @param string $type One of FFProbe::TYPE_* constant |
||||||
|
* @param string $data The data |
||||||
|
* |
||||||
|
* @return Format|Stream |
||||||
|
* |
||||||
|
* @throws InvalidArgumentException In case the type is not supported |
||||||
|
*/ |
||||||
|
public function map($type, $data); |
||||||
|
} |
@ -0,0 +1,70 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <info@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\FFProbe; |
||||||
|
|
||||||
|
use Alchemy\BinaryDriver\Exception\ExecutionFailureException; |
||||||
|
use Doctrine\Common\Cache\Cache; |
||||||
|
use FFMpeg\Driver\FFProbeDriver; |
||||||
|
use FFMpeg\Exception\RuntimeException; |
||||||
|
|
||||||
|
class OptionsTester implements OptionsTesterInterface |
||||||
|
{ |
||||||
|
/** @var FFProbeDriver */ |
||||||
|
private $ffprobe; |
||||||
|
/** @var Cache */ |
||||||
|
private $cache; |
||||||
|
|
||||||
|
public function __construct(FFProbeDriver $ffprobe, Cache $cache) |
||||||
|
{ |
||||||
|
$this->ffprobe = $ffprobe; |
||||||
|
$this->cache = $cache; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function has($name) |
||||||
|
{ |
||||||
|
$id = sprintf('option-%s', $name); |
||||||
|
|
||||||
|
if ($this->cache->contains($id)) { |
||||||
|
return $this->cache->fetch($id); |
||||||
|
} |
||||||
|
|
||||||
|
$output = $this->retrieveHelpOutput(); |
||||||
|
|
||||||
|
$ret = (Boolean) preg_match('/^'.$name.'/m', $output); |
||||||
|
|
||||||
|
$this->cache->save($id, $ret); |
||||||
|
|
||||||
|
return $ret; |
||||||
|
} |
||||||
|
|
||||||
|
private function retrieveHelpOutput() |
||||||
|
{ |
||||||
|
$id = 'help'; |
||||||
|
|
||||||
|
if ($this->cache->contains($id)) { |
||||||
|
return $this->cache->fetch($id); |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
$output = $this->ffprobe->command(array('-help', '-loglevel', 'quiet')); |
||||||
|
} catch (ExecutionFailureException $e) { |
||||||
|
throw new RuntimeException('Your FFProbe version is too old and does not support `-help` option, please upgrade.', $e->getCode(), $e); |
||||||
|
} |
||||||
|
|
||||||
|
$this->cache->save($id, $output); |
||||||
|
|
||||||
|
return $output; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,24 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <info@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\FFProbe; |
||||||
|
|
||||||
|
interface OptionsTesterInterface |
||||||
|
{ |
||||||
|
/** |
||||||
|
* Tells if the given option is supported by ffprobe. |
||||||
|
* |
||||||
|
* @param string $name |
||||||
|
* |
||||||
|
* @return Boolean |
||||||
|
*/ |
||||||
|
public function has($name); |
||||||
|
} |
@ -0,0 +1,125 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <info@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\FFProbe; |
||||||
|
|
||||||
|
use FFMpeg\FFProbe; |
||||||
|
use FFMpeg\Exception\InvalidArgumentException; |
||||||
|
|
||||||
|
class OutputParser implements OutputParserInterface |
||||||
|
{ |
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function parse($type, $data) |
||||||
|
{ |
||||||
|
switch ($type) { |
||||||
|
case FFProbe::TYPE_FORMAT: |
||||||
|
return $this->parseFormat($data); |
||||||
|
break; |
||||||
|
case FFProbe::TYPE_STREAMS: |
||||||
|
return $this->parseStreams($data); |
||||||
|
break; |
||||||
|
default: |
||||||
|
throw new InvalidArgumentException(sprintf('Unknown data type %s', $type)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private function parseFormat($data) |
||||||
|
{ |
||||||
|
$ret = array(); |
||||||
|
|
||||||
|
foreach (explode(PHP_EOL, $data) as $line) { |
||||||
|
|
||||||
|
if (in_array($line, array('[FORMAT]', '[/FORMAT]'))) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
$chunks = explode('=', $line); |
||||||
|
$key = array_shift($chunks); |
||||||
|
|
||||||
|
if ('' === trim($key)) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
$value = trim(implode('=', $chunks)); |
||||||
|
|
||||||
|
if ('nb_streams' === $key) { |
||||||
|
$value = (int) $value; |
||||||
|
} |
||||||
|
|
||||||
|
if (0 === strpos($key, 'TAG:')) { |
||||||
|
if (!isset($ret['tags'])) { |
||||||
|
$ret['tags'] = array(); |
||||||
|
} |
||||||
|
$ret['tags'][substr($key, 4)] = $value; |
||||||
|
} else { |
||||||
|
$ret[$key] = $value; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return array('format' => $ret); |
||||||
|
} |
||||||
|
|
||||||
|
private function parseStreams($data) |
||||||
|
{ |
||||||
|
$ret = array(); |
||||||
|
$n = -1; |
||||||
|
|
||||||
|
foreach (explode(PHP_EOL, $data) as $line) { |
||||||
|
|
||||||
|
if ($line == '[STREAM]') { |
||||||
|
$n ++; |
||||||
|
$ret[$n] = array(); |
||||||
|
continue; |
||||||
|
} |
||||||
|
if ($line == '[/STREAM]') { |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
$chunks = explode('=', $line); |
||||||
|
$key = array_shift($chunks); |
||||||
|
|
||||||
|
if ('' === trim($key)) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
$value = trim(implode('=', $chunks)); |
||||||
|
|
||||||
|
if ('N/A' === $value) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
if ('profile' === $key && 'unknown' === $value) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
if (in_array($key, array('index', 'width', 'height', 'channels', 'bits_per_sample', 'has_b_frames', 'level', 'start_pts', 'duration_ts'))) { |
||||||
|
$value = (int) $value; |
||||||
|
} |
||||||
|
|
||||||
|
if (0 === strpos($key, 'TAG:')) { |
||||||
|
if (!isset($ret[$n]['tags'])) { |
||||||
|
$ret[$n]['tags'] = array(); |
||||||
|
} |
||||||
|
$ret[$n]['tags'][substr($key, 4)] = $value; |
||||||
|
} elseif (0 === strpos($key, 'DISPOSITION:')) { |
||||||
|
if (!isset($ret[$n]['disposition'])) { |
||||||
|
$ret[$n]['disposition'] = array(); |
||||||
|
} |
||||||
|
$ret[$n]['disposition'][substr($key, 12)] = $value; |
||||||
|
} else { |
||||||
|
$ret[$n][$key] = $value; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return array('streams' => $ret); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,27 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <info@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\FFProbe; |
||||||
|
|
||||||
|
interface OutputParserInterface |
||||||
|
{ |
||||||
|
/** |
||||||
|
* Parses ffprobe raw output. |
||||||
|
* |
||||||
|
* @param string $type One of FFProbe::TYPE_* constant |
||||||
|
* @param string $data The data |
||||||
|
* |
||||||
|
* @return array |
||||||
|
* |
||||||
|
* @throws InvalidArgumentException In case the type is not supported |
||||||
|
*/ |
||||||
|
public function parse($type, $data); |
||||||
|
} |
@ -0,0 +1,29 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <dev.team@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\Filters\Audio; |
||||||
|
|
||||||
|
use FFMpeg\Filters\FilterInterface; |
||||||
|
use FFMpeg\Format\AudioInterface; |
||||||
|
use FFMpeg\Media\Audio; |
||||||
|
|
||||||
|
interface AudioFilterInterface extends FilterInterface |
||||||
|
{ |
||||||
|
/** |
||||||
|
* Applies the filter on the the Audio media given an format. |
||||||
|
* |
||||||
|
* @param Audio $audio |
||||||
|
* @param AudioInterface $format |
||||||
|
* |
||||||
|
* @return array An array of arguments |
||||||
|
*/ |
||||||
|
public function apply(Audio $audio, AudioInterface $format); |
||||||
|
} |
@ -0,0 +1,30 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace FFMpeg\Filters\Audio; |
||||||
|
|
||||||
|
use FFMpeg\Media\Audio; |
||||||
|
use FFMpeg\Filters\Audio\AudioResamplableFilter; |
||||||
|
|
||||||
|
class AudioFilters |
||||||
|
{ |
||||||
|
protected $media; |
||||||
|
|
||||||
|
public function __construct(Audio $media) |
||||||
|
{ |
||||||
|
$this->media = $media; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Resamples the audio file. |
||||||
|
* |
||||||
|
* @param Integer $rate |
||||||
|
* |
||||||
|
* @return AudioFilters |
||||||
|
*/ |
||||||
|
public function resample($rate) |
||||||
|
{ |
||||||
|
$this->media->addFilter(new AudioResamplableFilter($rate)); |
||||||
|
|
||||||
|
return $this; |
||||||
|
} |
||||||
|
} |
@ -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\Audio; |
||||||
|
|
||||||
|
use FFMpeg\Format\AudioInterface; |
||||||
|
use FFMpeg\Media\Audio; |
||||||
|
|
||||||
|
class AudioResamplableFilter implements AudioFilterInterface |
||||||
|
{ |
||||||
|
/** @var string */ |
||||||
|
private $rate; |
||||||
|
/** @var integer */ |
||||||
|
private $priority; |
||||||
|
|
||||||
|
public function __construct($rate, $priority = 0) |
||||||
|
{ |
||||||
|
$this->rate = $rate; |
||||||
|
$this->priority = $priority; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function getPriority() |
||||||
|
{ |
||||||
|
return $this->priority; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* |
||||||
|
* @return Integer |
||||||
|
*/ |
||||||
|
public function getRate() |
||||||
|
{ |
||||||
|
return $this->rate; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function apply(Audio $audio, AudioInterface $format) |
||||||
|
{ |
||||||
|
return array('-ac', 2, '-ar', $this->rate); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,34 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace FFMpeg\Filters\Audio; |
||||||
|
|
||||||
|
use FFMpeg\Media\Audio; |
||||||
|
use FFMpeg\Format\AudioInterface; |
||||||
|
|
||||||
|
class SimpleFilter implements AudioFilterInterface |
||||||
|
{ |
||||||
|
private $params; |
||||||
|
private $priority; |
||||||
|
|
||||||
|
public function __construct(array $params, $priority = 0) |
||||||
|
{ |
||||||
|
$this->params = $params; |
||||||
|
$this->priority = $priority; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function getPriority() |
||||||
|
{ |
||||||
|
return $this->priority; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function apply(Audio $audio, AudioInterface $format) |
||||||
|
{ |
||||||
|
return $this->params; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,22 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <dev.team@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\Filters; |
||||||
|
|
||||||
|
interface FilterInterface |
||||||
|
{ |
||||||
|
/** |
||||||
|
* Returns the priority of the filter. |
||||||
|
* |
||||||
|
* @return integer |
||||||
|
*/ |
||||||
|
public function getPriority(); |
||||||
|
} |
@ -0,0 +1,60 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <dev.team@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\Filters; |
||||||
|
|
||||||
|
class FiltersCollection implements \Countable, \IteratorAggregate |
||||||
|
{ |
||||||
|
private $sorted; |
||||||
|
private $filters = array(); |
||||||
|
|
||||||
|
/** |
||||||
|
* @param FilterInterface $filter |
||||||
|
* |
||||||
|
* @return FiltersCollection |
||||||
|
*/ |
||||||
|
public function add(FilterInterface $filter) |
||||||
|
{ |
||||||
|
$this->filters[$filter->getPriority()][] = $filter; |
||||||
|
$this->sorted = null; |
||||||
|
|
||||||
|
return $this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function count() |
||||||
|
{ |
||||||
|
if (0 === count($this->filters)) { |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
return count(call_user_func_array('array_merge', $this->filters)); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function getIterator() |
||||||
|
{ |
||||||
|
if (null === $this->sorted) { |
||||||
|
if (0 === count($this->filters)) { |
||||||
|
$this->sorted = $this->filters; |
||||||
|
} else { |
||||||
|
krsort($this->filters); |
||||||
|
$this->sorted = call_user_func_array('array_merge', $this->filters); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return new \ArrayIterator($this->sorted); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,58 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <dev.team@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\Filters\Frame; |
||||||
|
|
||||||
|
use FFMpeg\Exception\RuntimeException; |
||||||
|
use FFMpeg\Media\Frame; |
||||||
|
|
||||||
|
class DisplayRatioFixerFilter implements FrameFilterInterface |
||||||
|
{ |
||||||
|
/** @var integer */ |
||||||
|
private $priority; |
||||||
|
|
||||||
|
public function __construct($priority = 0) |
||||||
|
{ |
||||||
|
$this->priority = $priority; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function getPriority() |
||||||
|
{ |
||||||
|
return $this->priority; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function apply(Frame $frame) |
||||||
|
{ |
||||||
|
$dimensions = null; |
||||||
|
$commands = array(); |
||||||
|
|
||||||
|
foreach ($frame->getVideo()->getStreams() as $stream) { |
||||||
|
if ($stream->isVideo()) { |
||||||
|
try { |
||||||
|
$dimensions = $stream->getDimensions(); |
||||||
|
$commands[] = '-s'; |
||||||
|
$commands[] = $dimensions->getWidth() . 'x' . $dimensions->getHeight(); |
||||||
|
break; |
||||||
|
} catch (RuntimeException $e) { |
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return $commands; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,20 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <dev.team@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\Filters\Frame; |
||||||
|
|
||||||
|
use FFMpeg\Filters\FilterInterface; |
||||||
|
use FFMpeg\Media\Frame; |
||||||
|
|
||||||
|
interface FrameFilterInterface extends FilterInterface |
||||||
|
{ |
||||||
|
public function apply(Frame $frame); |
||||||
|
} |
@ -0,0 +1,39 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <dev.team@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\Filters\Frame; |
||||||
|
|
||||||
|
use FFMpeg\Media\Frame; |
||||||
|
|
||||||
|
class FrameFilters |
||||||
|
{ |
||||||
|
private $frame; |
||||||
|
|
||||||
|
public function __construct(Frame $frame) |
||||||
|
{ |
||||||
|
$this->frame = $frame; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Fixes the display ratio of the output frame. |
||||||
|
* |
||||||
|
* In case the sample ratio and display ratio are different, image may be |
||||||
|
* anamorphozed. This filter fixes this by specifying the output size. |
||||||
|
* |
||||||
|
* @return FrameFilters |
||||||
|
*/ |
||||||
|
public function fixDisplayRatio() |
||||||
|
{ |
||||||
|
$this->frame->addFilter(new DisplayRatioFixerFilter()); |
||||||
|
|
||||||
|
return $this; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,82 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <dev.team@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\Filters\Video; |
||||||
|
|
||||||
|
use FFMpeg\Coordinate\FrameRate; |
||||||
|
use FFMpeg\Media\Video; |
||||||
|
use FFMpeg\Format\VideoInterface; |
||||||
|
|
||||||
|
class FrameRateFilter implements VideoFilterInterface |
||||||
|
{ |
||||||
|
private $rate; |
||||||
|
private $gop; |
||||||
|
private $priority; |
||||||
|
|
||||||
|
public function __construct(FrameRate $rate, $gop, $priority = 0) |
||||||
|
{ |
||||||
|
$this->rate = $rate; |
||||||
|
$this->gop = $gop; |
||||||
|
$this->priority = $priority; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function getPriority() |
||||||
|
{ |
||||||
|
return $this->priority; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the frame rate. |
||||||
|
* |
||||||
|
* @return FrameRate |
||||||
|
*/ |
||||||
|
public function getFrameRate() |
||||||
|
{ |
||||||
|
return $this->rate; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the GOP size. |
||||||
|
* |
||||||
|
* @see https://wikipedia.org/wiki/Group_of_pictures |
||||||
|
* |
||||||
|
* @return Integer |
||||||
|
*/ |
||||||
|
public function getGOP() |
||||||
|
{ |
||||||
|
return $this->gop; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function apply(Video $video, VideoInterface $format) |
||||||
|
{ |
||||||
|
$commands = array('-r', $this->rate->getValue()); |
||||||
|
|
||||||
|
/** |
||||||
|
* @see http://sites.google.com/site/linuxencoding/x264-ffmpeg-mapping |
||||||
|
*/ |
||||||
|
if ($format->supportBFrames()) { |
||||||
|
$commands[] = '-b_strategy'; |
||||||
|
$commands[] = '1'; |
||||||
|
$commands[] = '-bf'; |
||||||
|
$commands[] = '3'; |
||||||
|
$commands[] = '-g'; |
||||||
|
$commands[] = $this->gop; |
||||||
|
} |
||||||
|
|
||||||
|
return $commands; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,141 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <dev.team@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\Filters\Video; |
||||||
|
|
||||||
|
use FFMpeg\Coordinate\Dimension; |
||||||
|
use FFMpeg\Exception\RuntimeException; |
||||||
|
use FFMpeg\Media\Video; |
||||||
|
use FFMpeg\Format\VideoInterface; |
||||||
|
|
||||||
|
class ResizeFilter implements VideoFilterInterface |
||||||
|
{ |
||||||
|
/** fits to the dimensions, might introduce anamorphosis */ |
||||||
|
const RESIZEMODE_FIT = 'fit'; |
||||||
|
/** resizes the video inside the given dimension, no anamorphosis */ |
||||||
|
const RESIZEMODE_INSET = 'inset'; |
||||||
|
/** resizes the video to fit the dimension width, no anamorphosis */ |
||||||
|
const RESIZEMODE_SCALE_WIDTH = 'width'; |
||||||
|
/** resizes the video to fit the dimension height, no anamorphosis */ |
||||||
|
const RESIZEMODE_SCALE_HEIGHT = 'height'; |
||||||
|
|
||||||
|
/** @var Dimension */ |
||||||
|
private $dimension; |
||||||
|
/** @var string */ |
||||||
|
private $mode; |
||||||
|
/** @var Boolean */ |
||||||
|
private $forceStandards; |
||||||
|
/** @var integer */ |
||||||
|
private $priority; |
||||||
|
|
||||||
|
public function __construct(Dimension $dimension, $mode = self::RESIZEMODE_FIT, $forceStandards = true, $priority = 0) |
||||||
|
{ |
||||||
|
$this->dimension = $dimension; |
||||||
|
$this->mode = $mode; |
||||||
|
$this->forceStandards = $forceStandards; |
||||||
|
$this->priority = $priority; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function getPriority() |
||||||
|
{ |
||||||
|
return $this->priority; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return Dimension |
||||||
|
*/ |
||||||
|
public function getDimension() |
||||||
|
{ |
||||||
|
return $this->dimension; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return string |
||||||
|
*/ |
||||||
|
public function getMode() |
||||||
|
{ |
||||||
|
return $this->mode; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return Boolean |
||||||
|
*/ |
||||||
|
public function areStandardsForced() |
||||||
|
{ |
||||||
|
return $this->forceStandards; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function apply(Video $video, VideoInterface $format) |
||||||
|
{ |
||||||
|
$dimensions = null; |
||||||
|
$commands = array(); |
||||||
|
|
||||||
|
foreach ($video->getStreams() as $stream) { |
||||||
|
if ($stream->isVideo()) { |
||||||
|
try { |
||||||
|
$dimensions = $stream->getDimensions(); |
||||||
|
break; |
||||||
|
} catch (RuntimeException $e) { |
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (null !== $dimensions) { |
||||||
|
$dimensions = $this->getComputedDimensions($dimensions, $format->getModulus()); |
||||||
|
|
||||||
|
$commands[] = '-s'; |
||||||
|
$commands[] = $dimensions->getWidth() . 'x' . $dimensions->getHeight(); |
||||||
|
} |
||||||
|
|
||||||
|
return $commands; |
||||||
|
} |
||||||
|
|
||||||
|
private function getComputedDimensions(Dimension $dimension, $modulus) |
||||||
|
{ |
||||||
|
$originalRatio = $dimension->getRatio($this->forceStandards); |
||||||
|
|
||||||
|
switch ($this->mode) { |
||||||
|
case self::RESIZEMODE_SCALE_WIDTH: |
||||||
|
$height = $this->dimension->getHeight(); |
||||||
|
$width = $originalRatio->calculateWidth($height, $modulus); |
||||||
|
break; |
||||||
|
case self::RESIZEMODE_SCALE_HEIGHT: |
||||||
|
$width = $this->dimension->getWidth(); |
||||||
|
$height = $originalRatio->calculateHeight($width, $modulus); |
||||||
|
break; |
||||||
|
case self::RESIZEMODE_INSET: |
||||||
|
$targetRatio = $this->dimension->getRatio($this->forceStandards); |
||||||
|
|
||||||
|
if ($targetRatio->getValue() > $originalRatio->getValue()) { |
||||||
|
$height = $this->dimension->getHeight(); |
||||||
|
$width = $originalRatio->calculateWidth($height, $modulus); |
||||||
|
} else { |
||||||
|
$width = $this->dimension->getWidth(); |
||||||
|
$height = $originalRatio->calculateHeight($width, $modulus); |
||||||
|
} |
||||||
|
break; |
||||||
|
case self::RESIZEMODE_FIT: |
||||||
|
default: |
||||||
|
$width = $this->dimension->getWidth(); |
||||||
|
$height = $this->dimension->getHeight(); |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
return new Dimension($width, $height); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,44 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <dev.team@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\Filters\Video; |
||||||
|
|
||||||
|
use FFMpeg\Format\VideoInterface; |
||||||
|
use FFMpeg\Media\Video; |
||||||
|
|
||||||
|
/** |
||||||
|
* Synchronizes audio and video in case of desynchronized movies. |
||||||
|
*/ |
||||||
|
class SynchronizeFilter implements VideoFilterInterface |
||||||
|
{ |
||||||
|
private $priority; |
||||||
|
|
||||||
|
public function __construct($priority = 12) |
||||||
|
{ |
||||||
|
$this->priority = $priority; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function getPriority() |
||||||
|
{ |
||||||
|
return $this->priority; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function apply(Video $video, VideoInterface $format) |
||||||
|
{ |
||||||
|
return array('-async', '1'); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,29 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <dev.team@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\Filters\Video; |
||||||
|
|
||||||
|
use FFMpeg\Filters\FilterInterface; |
||||||
|
use FFMpeg\Format\VideoInterface; |
||||||
|
use FFMpeg\Media\Video; |
||||||
|
|
||||||
|
interface VideoFilterInterface extends FilterInterface |
||||||
|
{ |
||||||
|
/** |
||||||
|
* Applies the filter on the the Video media given an format. |
||||||
|
* |
||||||
|
* @param Video $video |
||||||
|
* @param VideoInterface $format |
||||||
|
* |
||||||
|
* @return array An array of arguments |
||||||
|
*/ |
||||||
|
public function apply(Video $video, VideoInterface $format); |
||||||
|
} |
@ -0,0 +1,83 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <dev.team@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\Filters\Video; |
||||||
|
|
||||||
|
use FFMpeg\Media\Video; |
||||||
|
use FFMpeg\Coordinate\Dimension; |
||||||
|
use FFMpeg\Coordinate\FrameRate; |
||||||
|
use FFMpeg\Filters\Audio\AudioResamplableFilter; |
||||||
|
use FFMpeg\Filters\Audio\AudioFilters; |
||||||
|
|
||||||
|
class VideoFilters extends AudioFilters |
||||||
|
{ |
||||||
|
public function __construct(Video $media) |
||||||
|
{ |
||||||
|
parent::__construct($media); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Resizes a video to a given dimension. |
||||||
|
* |
||||||
|
* @param Dimension $dimension |
||||||
|
* @param string $mode |
||||||
|
* @param Boolean $forceStandards |
||||||
|
* |
||||||
|
* @return VideoFilters |
||||||
|
*/ |
||||||
|
public function resize(Dimension $dimension, $mode = ResizeFilter::RESIZEMODE_FIT, $forceStandards = true) |
||||||
|
{ |
||||||
|
$this->media->addFilter(new ResizeFilter($dimension, $mode, $forceStandards)); |
||||||
|
|
||||||
|
return $this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Changes the video framerate. |
||||||
|
* |
||||||
|
* @param FrameRate $framerate |
||||||
|
* @param type $gop |
||||||
|
* |
||||||
|
* @return VideoFilters |
||||||
|
*/ |
||||||
|
public function framerate(FrameRate $framerate, $gop) |
||||||
|
{ |
||||||
|
$this->media->addFilter(new FrameRateFilter($framerate, $gop)); |
||||||
|
|
||||||
|
return $this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Synchronizes audio and video. |
||||||
|
* |
||||||
|
* @return VideoFilters |
||||||
|
*/ |
||||||
|
public function synchronize() |
||||||
|
{ |
||||||
|
$this->media->addFilter(new SynchronizeFilter()); |
||||||
|
|
||||||
|
return $this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Resamples the audio file. |
||||||
|
* |
||||||
|
* @param Integer $rate |
||||||
|
* |
||||||
|
* @return AudioFilters |
||||||
|
*/ |
||||||
|
public function audioResample($rate) |
||||||
|
{ |
||||||
|
$this->media->addFilter(new AudioResamplableFilter($rate)); |
||||||
|
|
||||||
|
return $this; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,106 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <info@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\Format\Audio; |
||||||
|
|
||||||
|
use Evenement\EventEmitter; |
||||||
|
use FFMpeg\Exception\InvalidArgumentException; |
||||||
|
use FFMpeg\Format\AudioInterface; |
||||||
|
use FFMpeg\Media\MediaTypeInterface; |
||||||
|
use FFMpeg\Format\ProgressableInterface; |
||||||
|
use FFMpeg\Format\ProgressListener\AudioProgressListener; |
||||||
|
use FFMpeg\FFProbe; |
||||||
|
|
||||||
|
abstract class DefaultAudio extends EventEmitter implements AudioInterface, ProgressableInterface |
||||||
|
{ |
||||||
|
/** @var string */ |
||||||
|
protected $audioCodec; |
||||||
|
|
||||||
|
/** @var integer */ |
||||||
|
protected $audioKiloBitrate = 128; |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function getExtraParams() |
||||||
|
{ |
||||||
|
return array(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function getAudioCodec() |
||||||
|
{ |
||||||
|
return $this->audioCodec; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets the audio codec, Should be in the available ones, otherwise an |
||||||
|
* exception is thrown. |
||||||
|
* |
||||||
|
* @param string $audioCodec |
||||||
|
* |
||||||
|
* @throws InvalidArgumentException |
||||||
|
*/ |
||||||
|
public function setAudioCodec($audioCodec) |
||||||
|
{ |
||||||
|
if ( ! in_array($audioCodec, $this->getAvailableAudioCodecs())) { |
||||||
|
throw new InvalidArgumentException(sprintf( |
||||||
|
'Wrong audiocodec value for %s, available formats are %s' |
||||||
|
, $audioCodec, implode(', ', $this->getAvailableAudioCodecs()) |
||||||
|
)); |
||||||
|
} |
||||||
|
|
||||||
|
$this->audioCodec = $audioCodec; |
||||||
|
|
||||||
|
return $this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function getAudioKiloBitrate() |
||||||
|
{ |
||||||
|
return $this->audioKiloBitrate; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets the kiloBitrate value. |
||||||
|
* |
||||||
|
* @param integer $kiloBitrate |
||||||
|
* @throws InvalidArgumentException |
||||||
|
*/ |
||||||
|
public function setAudioKiloBitrate($kiloBitrate) |
||||||
|
{ |
||||||
|
if ($kiloBitrate < 1) { |
||||||
|
throw new InvalidArgumentException('Wrong kiloBitrate value'); |
||||||
|
} |
||||||
|
|
||||||
|
$this->audioKiloBitrate = (int) $kiloBitrate; |
||||||
|
|
||||||
|
return $this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function createProgressListener(MediaTypeInterface $media, FFProbe $ffprobe, $pass, $total) |
||||||
|
{ |
||||||
|
$format = $this; |
||||||
|
$listener = new AudioProgressListener($ffprobe, $media->getPathfile(), $pass, $total); |
||||||
|
$listener->on('progress', function () use ($media, $format) { |
||||||
|
$format->emit('progress', array_merge(array($media, $format), func_get_args())); |
||||||
|
}); |
||||||
|
|
||||||
|
return array($listener); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,31 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <info@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\Format\Audio; |
||||||
|
|
||||||
|
/** |
||||||
|
* The Flac audio format |
||||||
|
*/ |
||||||
|
class Flac extends DefaultAudio |
||||||
|
{ |
||||||
|
public function __construct() |
||||||
|
{ |
||||||
|
$this->audioCodec = 'flac'; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritDoc} |
||||||
|
*/ |
||||||
|
public function getAvailableAudioCodecs() |
||||||
|
{ |
||||||
|
return array('flac'); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,31 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <info@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\Format\Audio; |
||||||
|
|
||||||
|
/** |
||||||
|
* The MP3 audio format |
||||||
|
*/ |
||||||
|
class Mp3 extends DefaultAudio |
||||||
|
{ |
||||||
|
public function __construct() |
||||||
|
{ |
||||||
|
$this->audioCodec = 'libmp3lame'; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritDoc} |
||||||
|
*/ |
||||||
|
public function getAvailableAudioCodecs() |
||||||
|
{ |
||||||
|
return array('libmp3lame'); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,39 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <info@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\Format\Audio; |
||||||
|
|
||||||
|
/** |
||||||
|
* The Vorbis audio format |
||||||
|
*/ |
||||||
|
class Vorbis extends DefaultAudio |
||||||
|
{ |
||||||
|
public function __construct() |
||||||
|
{ |
||||||
|
$this->audioCodec = 'vorbis'; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function getExtraParams() |
||||||
|
{ |
||||||
|
return array('-strict', '-2'); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritDoc} |
||||||
|
*/ |
||||||
|
public function getAvailableAudioCodecs() |
||||||
|
{ |
||||||
|
return array('vorbis'); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,42 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <info@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
namespace FFMpeg\Format; |
||||||
|
|
||||||
|
interface AudioInterface extends FormatInterface |
||||||
|
{ |
||||||
|
/** |
||||||
|
* Gets the audio kiloBitrate value. |
||||||
|
* |
||||||
|
* @return integer |
||||||
|
*/ |
||||||
|
public function getAudioKiloBitrate(); |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns an array of extra parameters to add to ffmpeg commandline. |
||||||
|
* |
||||||
|
* @return array() |
||||||
|
*/ |
||||||
|
public function getExtraParams(); |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the audio codec. |
||||||
|
* |
||||||
|
* @return string |
||||||
|
*/ |
||||||
|
public function getAudioCodec(); |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the list of available audio codecs for this format. |
||||||
|
* |
||||||
|
* @return array |
||||||
|
*/ |
||||||
|
public function getAvailableAudioCodecs(); |
||||||
|
} |
@ -0,0 +1,15 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <info@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
namespace FFMpeg\Format; |
||||||
|
|
||||||
|
interface FormatInterface |
||||||
|
{ |
||||||
|
} |
@ -0,0 +1,16 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <dev.team@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\Format; |
||||||
|
|
||||||
|
interface FrameInterface extends FormatInterface |
||||||
|
{ |
||||||
|
} |
@ -0,0 +1,240 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <info@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\Format\ProgressListener; |
||||||
|
|
||||||
|
use Alchemy\BinaryDriver\Listeners\ListenerInterface; |
||||||
|
use Evenement\EventEmitter; |
||||||
|
use FFMpeg\FFProbe; |
||||||
|
use FFMpeg\Exception\RuntimeException; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Robert Gruendler <r.gruendler@gmail.com> |
||||||
|
*/ |
||||||
|
abstract class AbstractProgressListener extends EventEmitter implements ListenerInterface |
||||||
|
{ |
||||||
|
/** @var integer */ |
||||||
|
private $duration; |
||||||
|
|
||||||
|
/** @var integer */ |
||||||
|
private $totalSize; |
||||||
|
|
||||||
|
/** @var integer */ |
||||||
|
private $currentSize; |
||||||
|
|
||||||
|
/** @var integer */ |
||||||
|
private $currentTime; |
||||||
|
|
||||||
|
/** @var double */ |
||||||
|
private $lastOutput = null; |
||||||
|
|
||||||
|
/** @var FFProbe */ |
||||||
|
private $ffprobe; |
||||||
|
|
||||||
|
/** @var string */ |
||||||
|
private $pathfile; |
||||||
|
|
||||||
|
/** @var Boolean */ |
||||||
|
private $initialized = false; |
||||||
|
|
||||||
|
/** @var integer */ |
||||||
|
private $currentPass; |
||||||
|
|
||||||
|
/** @var integer */ |
||||||
|
private $totalPass; |
||||||
|
|
||||||
|
/** |
||||||
|
* Transcoding rate in kb/s |
||||||
|
* |
||||||
|
* @var integer |
||||||
|
*/ |
||||||
|
private $rate; |
||||||
|
|
||||||
|
/** |
||||||
|
* Percentage of transcoding progress (0 - 100) |
||||||
|
* |
||||||
|
* @var integer |
||||||
|
*/ |
||||||
|
private $percent = 0; |
||||||
|
|
||||||
|
/** |
||||||
|
* Time remaining (seconds) |
||||||
|
* |
||||||
|
* @var integer |
||||||
|
*/ |
||||||
|
private $remaining = null; |
||||||
|
|
||||||
|
/** |
||||||
|
* @param FFProbe $ffprobe |
||||||
|
* @param string $pathfile |
||||||
|
* @param integer $currentPass The cureent pass number |
||||||
|
* @param integer $totalPass The total number of passes |
||||||
|
* |
||||||
|
* @throws RuntimeException |
||||||
|
*/ |
||||||
|
public function __construct(FFProbe $ffprobe, $pathfile, $currentPass, $totalPass) |
||||||
|
{ |
||||||
|
$this->ffprobe = $ffprobe; |
||||||
|
$this->pathfile = $pathfile; |
||||||
|
$this->currentPass = $currentPass; |
||||||
|
$this->totalPass = $totalPass; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return FFProbe |
||||||
|
*/ |
||||||
|
public function getFFProbe() |
||||||
|
{ |
||||||
|
return $this->ffprobe; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return string |
||||||
|
*/ |
||||||
|
public function getPathfile() |
||||||
|
{ |
||||||
|
return $this->pathfile; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return integer |
||||||
|
*/ |
||||||
|
public function getCurrentPass() |
||||||
|
{ |
||||||
|
return $this->currentPass; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return integer |
||||||
|
*/ |
||||||
|
public function getTotalPass() |
||||||
|
{ |
||||||
|
return $this->totalPass; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function handle($type, $data) |
||||||
|
{ |
||||||
|
if (null !== $progress = $this->parseProgress($data)) { |
||||||
|
$this->emit('progress', array_values($progress)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function forwardedEvents() |
||||||
|
{ |
||||||
|
return array(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Get the regex pattern to match a ffmpeg stderr status line |
||||||
|
*/ |
||||||
|
abstract protected function getPattern(); |
||||||
|
|
||||||
|
/** |
||||||
|
* @param string $progress A ffmpeg stderr progress output |
||||||
|
* |
||||||
|
* @return array the progressinfo array or null if there's no progress available yet. |
||||||
|
*/ |
||||||
|
private function parseProgress($progress) |
||||||
|
{ |
||||||
|
if (!$this->initialized) { |
||||||
|
$this->initialize(); |
||||||
|
} |
||||||
|
|
||||||
|
$matches = array(); |
||||||
|
|
||||||
|
if (preg_match($this->getPattern(), $progress, $matches) !== 1) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
$currentDuration = $this->convertDuration($matches[2]); |
||||||
|
$currentTime = microtime(true); |
||||||
|
$currentSize = trim(str_replace('kb', '', strtolower(($matches[1])))); |
||||||
|
$percent = max(0, min(1, $currentDuration / $this->duration)); |
||||||
|
|
||||||
|
if ($this->lastOutput !== null) { |
||||||
|
$delta = $currentTime - $this->lastOutput; |
||||||
|
$deltaSize = $currentSize - $this->currentSize; |
||||||
|
$rate = $deltaSize * $delta; |
||||||
|
if ($rate > 0) { |
||||||
|
$totalDuration = $this->totalSize / $rate; |
||||||
|
$this->remaining = floor($totalDuration - ($totalDuration * $percent)); |
||||||
|
$this->rate = floor($rate); |
||||||
|
} else { |
||||||
|
$this->remaining = 0; |
||||||
|
$this->rate = 0; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
$percent = $percent / $this->totalPass + ($this->currentPass - 1) / $this->totalPass; |
||||||
|
|
||||||
|
$this->percent = floor($percent * 100); |
||||||
|
$this->lastOutput = $currentTime; |
||||||
|
$this->currentSize = (int) $currentSize; |
||||||
|
$this->currentTime = $currentDuration; |
||||||
|
|
||||||
|
return $this->getProgressInfo(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* |
||||||
|
* @param string $rawDuration in the format 00:00:00.00 |
||||||
|
* @return number |
||||||
|
*/ |
||||||
|
private function convertDuration($rawDuration) |
||||||
|
{ |
||||||
|
$ar = array_reverse(explode(":", $rawDuration)); |
||||||
|
$duration = floatval($ar[0]); |
||||||
|
if (!empty($ar[1])) { |
||||||
|
$duration += intval($ar[1]) * 60; |
||||||
|
} |
||||||
|
if (!empty($ar[2])) { |
||||||
|
$duration += intval($ar[2]) * 60 * 60; |
||||||
|
} |
||||||
|
|
||||||
|
return $duration; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return array |
||||||
|
*/ |
||||||
|
private function getProgressInfo() |
||||||
|
{ |
||||||
|
if ($this->remaining === null) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
return array( |
||||||
|
'percent' => $this->percent, |
||||||
|
'remaining' => $this->remaining, |
||||||
|
'rate' => $this->rate |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
private function initialize() |
||||||
|
{ |
||||||
|
$format = $this->ffprobe->format($this->pathfile); |
||||||
|
|
||||||
|
if (false === $format->has('size') || false === $format->has('duration')) { |
||||||
|
throw new RuntimeException(sprintf('Unable to probe format for %s', $this->pathfile)); |
||||||
|
} |
||||||
|
|
||||||
|
$this->totalSize = $format->get('size') / 1024; |
||||||
|
$this->duration = $format->get('duration'); |
||||||
|
|
||||||
|
$this->initialized = true; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,29 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <info@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\Format\ProgressListener; |
||||||
|
|
||||||
|
/** |
||||||
|
* Parses ffmpeg stderr progress information. An example: |
||||||
|
* |
||||||
|
* <pre> |
||||||
|
* size= 3552kB time=00:03:47.29 bitrate= 128.0kbits/s |
||||||
|
* </pre> |
||||||
|
* |
||||||
|
* @author Robert Gruendler <r.gruendler@gmail.com> |
||||||
|
*/ |
||||||
|
class AudioProgressListener extends AbstractProgressListener |
||||||
|
{ |
||||||
|
public function getPattern() |
||||||
|
{ |
||||||
|
return '/size=(.*?) time=(.*?) /'; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,29 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <info@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\Format\ProgressListener; |
||||||
|
|
||||||
|
/** |
||||||
|
* Parses ffmpeg stderr progress information for video files. An example: |
||||||
|
* |
||||||
|
* <pre> |
||||||
|
* frame= 171 fps=0.0 q=10.0 size= 18kB time=00:00:05.72 bitrate= 26.4kbits/s dup=8 drop=0 |
||||||
|
* </pre> |
||||||
|
* |
||||||
|
* @author Robert Gruendler <r.gruendler@gmail.com> |
||||||
|
*/ |
||||||
|
class VideoProgressListener extends AbstractProgressListener |
||||||
|
{ |
||||||
|
public function getPattern() |
||||||
|
{ |
||||||
|
return '/size=(.*?) time=(.*?) /'; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,31 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <dev.team@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\Format; |
||||||
|
|
||||||
|
use Evenement\EventEmitterInterface; |
||||||
|
use FFMpeg\FFProbe; |
||||||
|
use FFMpeg\Media\MediaTypeInterface; |
||||||
|
|
||||||
|
interface ProgressableInterface extends EventEmitterInterface |
||||||
|
{ |
||||||
|
/** |
||||||
|
* Creates the progress listener. |
||||||
|
* |
||||||
|
* @param MediaTypeInterface $media |
||||||
|
* @param FFProbe $ffprobe |
||||||
|
* @param Integer $pass The current pas snumber |
||||||
|
* @param Integer $total The total pass number |
||||||
|
* |
||||||
|
* @return array An array of listeners |
||||||
|
*/ |
||||||
|
public function createProgressListener(MediaTypeInterface $media, FFProbe $ffprobe, $pass, $total); |
||||||
|
} |
@ -0,0 +1,121 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <info@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\Format\Video; |
||||||
|
|
||||||
|
use FFMpeg\FFProbe; |
||||||
|
use FFMpeg\Exception\InvalidArgumentException; |
||||||
|
use FFMpeg\Format\Audio\DefaultAudio; |
||||||
|
use FFMpeg\Format\VideoInterface; |
||||||
|
use FFMpeg\Media\MediaTypeInterface; |
||||||
|
use FFMpeg\Format\ProgressListener\VideoProgressListener; |
||||||
|
|
||||||
|
/** |
||||||
|
* The abstract default Video format |
||||||
|
*/ |
||||||
|
abstract class DefaultVideo extends DefaultAudio implements VideoInterface |
||||||
|
{ |
||||||
|
/** @var string */ |
||||||
|
protected $videoCodec; |
||||||
|
|
||||||
|
/** @var Integer */ |
||||||
|
protected $kiloBitrate = 1000; |
||||||
|
|
||||||
|
/** @var Integer */ |
||||||
|
protected $modulus = 16; |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function getKiloBitrate() |
||||||
|
{ |
||||||
|
return $this->kiloBitrate; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets the kiloBitrate value. |
||||||
|
* |
||||||
|
* @param integer $kiloBitrate |
||||||
|
* @throws InvalidArgumentException |
||||||
|
*/ |
||||||
|
public function setKiloBitrate($kiloBitrate) |
||||||
|
{ |
||||||
|
if ($kiloBitrate < 1) { |
||||||
|
throw new InvalidArgumentException('Wrong kiloBitrate value'); |
||||||
|
} |
||||||
|
|
||||||
|
$this->kiloBitrate = (int) $kiloBitrate; |
||||||
|
|
||||||
|
return $this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function getVideoCodec() |
||||||
|
{ |
||||||
|
return $this->videoCodec; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets the video codec, Should be in the available ones, otherwise an |
||||||
|
* exception is thrown. |
||||||
|
* |
||||||
|
* @param string $videoCodec |
||||||
|
* @throws InvalidArgumentException |
||||||
|
*/ |
||||||
|
public function setVideoCodec($videoCodec) |
||||||
|
{ |
||||||
|
if ( ! in_array($videoCodec, $this->getAvailableVideoCodecs())) { |
||||||
|
throw new InvalidArgumentException(sprintf( |
||||||
|
'Wrong videocodec value for %s, available formats are %s' |
||||||
|
, $videoCodec, implode(', ', $this->getAvailableVideoCodecs()) |
||||||
|
)); |
||||||
|
} |
||||||
|
|
||||||
|
$this->videoCodec = $videoCodec; |
||||||
|
|
||||||
|
return $this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritDoc} |
||||||
|
*/ |
||||||
|
public function getPasses() |
||||||
|
{ |
||||||
|
return 1; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return integer |
||||||
|
*/ |
||||||
|
public function getModulus() |
||||||
|
{ |
||||||
|
return $this->modulus; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function createProgressListener(MediaTypeInterface $media, FFProbe $ffprobe, $pass, $total) |
||||||
|
{ |
||||||
|
$format = $this; |
||||||
|
$listeners = array(new VideoProgressListener($ffprobe, $media->getPathfile(), $pass, $total)); |
||||||
|
|
||||||
|
foreach ($listeners as $listener) { |
||||||
|
$listener->on('progress', function () use ($format, $media) { |
||||||
|
$format->emit('progress', array_merge(array($media, $format), func_get_args())); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
return $listeners; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,49 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <info@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\Format\Video; |
||||||
|
|
||||||
|
/** |
||||||
|
* The Ogg video format |
||||||
|
*/ |
||||||
|
class Ogg extends DefaultVideo |
||||||
|
{ |
||||||
|
public function __construct($audioCodec = 'libvorbis', $videoCodec = 'libtheora') |
||||||
|
{ |
||||||
|
$this |
||||||
|
->setAudioCodec($audioCodec) |
||||||
|
->setVideoCodec($videoCodec); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritDoc} |
||||||
|
*/ |
||||||
|
public function supportBFrames() |
||||||
|
{ |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritDoc} |
||||||
|
*/ |
||||||
|
public function getAvailableAudioCodecs() |
||||||
|
{ |
||||||
|
return array('libvorbis'); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritDoc} |
||||||
|
*/ |
||||||
|
public function getAvailableVideoCodecs() |
||||||
|
{ |
||||||
|
return array('libtheora'); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,49 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <info@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\Format\Video; |
||||||
|
|
||||||
|
/** |
||||||
|
* The WMV video format |
||||||
|
*/ |
||||||
|
class WMV extends DefaultVideo |
||||||
|
{ |
||||||
|
public function __construct($audioCodec = 'wmav2', $videoCodec = 'wmv2') |
||||||
|
{ |
||||||
|
$this |
||||||
|
->setAudioCodec($audioCodec) |
||||||
|
->setVideoCodec($videoCodec); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritDoc} |
||||||
|
*/ |
||||||
|
public function supportBFrames() |
||||||
|
{ |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritDoc} |
||||||
|
*/ |
||||||
|
public function getAvailableAudioCodecs() |
||||||
|
{ |
||||||
|
return array('wmav2'); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritDoc} |
||||||
|
*/ |
||||||
|
public function getAvailableVideoCodecs() |
||||||
|
{ |
||||||
|
return array('wmv2'); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,49 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <info@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\Format\Video; |
||||||
|
|
||||||
|
/** |
||||||
|
* The WMV video format |
||||||
|
*/ |
||||||
|
class WMV3 extends DefaultVideo |
||||||
|
{ |
||||||
|
public function __construct($audioCodec = 'wmav3', $videoCodec = 'wmv3') |
||||||
|
{ |
||||||
|
$this |
||||||
|
->setAudioCodec($audioCodec) |
||||||
|
->setVideoCodec($videoCodec); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritDoc} |
||||||
|
*/ |
||||||
|
public function supportBFrames() |
||||||
|
{ |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritDoc} |
||||||
|
*/ |
||||||
|
public function getAvailableAudioCodecs() |
||||||
|
{ |
||||||
|
return array('wmav3'); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritDoc} |
||||||
|
*/ |
||||||
|
public function getAvailableVideoCodecs() |
||||||
|
{ |
||||||
|
return array('wmv3'); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,57 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <info@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\Format\Video; |
||||||
|
|
||||||
|
/** |
||||||
|
* The WebM video format |
||||||
|
*/ |
||||||
|
class WebM extends DefaultVideo |
||||||
|
{ |
||||||
|
public function __construct($audioCodec = 'libvorbis', $videoCodec = 'libvpx') |
||||||
|
{ |
||||||
|
$this |
||||||
|
->setAudioCodec($audioCodec) |
||||||
|
->setVideoCodec($videoCodec); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritDoc} |
||||||
|
*/ |
||||||
|
public function supportBFrames() |
||||||
|
{ |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritDoc} |
||||||
|
*/ |
||||||
|
public function getExtraParams() |
||||||
|
{ |
||||||
|
return array('-f', 'webm'); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritDoc} |
||||||
|
*/ |
||||||
|
public function getAvailableAudioCodecs() |
||||||
|
{ |
||||||
|
return array('libvorbis'); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritDoc} |
||||||
|
*/ |
||||||
|
public function getAvailableVideoCodecs() |
||||||
|
{ |
||||||
|
return array('libvpx'); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,62 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <info@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\Format\Video; |
||||||
|
|
||||||
|
/** |
||||||
|
* The X264 video format |
||||||
|
*/ |
||||||
|
class X264 extends DefaultVideo |
||||||
|
{ |
||||||
|
public function __construct($audioCodec = 'libfaac', $videoCodec = 'libx264') |
||||||
|
{ |
||||||
|
$this |
||||||
|
->setAudioCodec($audioCodec) |
||||||
|
->setVideoCodec($videoCodec); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritDoc} |
||||||
|
*/ |
||||||
|
public function supportBFrames() |
||||||
|
{ |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritDoc} |
||||||
|
*/ |
||||||
|
public function getAvailableAudioCodecs() |
||||||
|
{ |
||||||
|
return array('libvo_aacenc', 'libfaac', 'libmp3lame'); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritDoc} |
||||||
|
*/ |
||||||
|
public function getAvailableVideoCodecs() |
||||||
|
{ |
||||||
|
return array('libx264'); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritDoc} |
||||||
|
*/ |
||||||
|
public function getPasses() |
||||||
|
{ |
||||||
|
return 2; |
||||||
|
} |
||||||
|
|
||||||
|
public function getModulus() |
||||||
|
{ |
||||||
|
return 2; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,64 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <info@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\Format; |
||||||
|
|
||||||
|
interface VideoInterface extends AudioInterface |
||||||
|
{ |
||||||
|
/** |
||||||
|
* Gets the kiloBitrate value. |
||||||
|
* |
||||||
|
* @return integer |
||||||
|
*/ |
||||||
|
public function getKiloBitrate(); |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the number of passes. |
||||||
|
* |
||||||
|
* @return string |
||||||
|
*/ |
||||||
|
public function getPasses(); |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the modulus used by the Resizable video. |
||||||
|
* |
||||||
|
* This used to calculate the target dimensions while maintaining the best |
||||||
|
* aspect ratio. |
||||||
|
* |
||||||
|
* @see http://www.undeadborn.net/tools/rescalculator.php |
||||||
|
* |
||||||
|
* @return integer |
||||||
|
*/ |
||||||
|
public function getModulus(); |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the video codec. |
||||||
|
* |
||||||
|
* @return string |
||||||
|
*/ |
||||||
|
public function getVideoCodec(); |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns true if the current format supports B-Frames. |
||||||
|
* |
||||||
|
* @see https://wikipedia.org/wiki/Video_compression_picture_types |
||||||
|
* |
||||||
|
* @return Boolean |
||||||
|
*/ |
||||||
|
public function supportBFrames(); |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the list of available video codecs for this format. |
||||||
|
* |
||||||
|
* @return array |
||||||
|
*/ |
||||||
|
public function getAvailableVideoCodecs(); |
||||||
|
} |
@ -0,0 +1,126 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <dev.team@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\Media; |
||||||
|
|
||||||
|
use FFMpeg\Driver\FFMpegDriver; |
||||||
|
use FFMpeg\Exception\InvalidArgumentException; |
||||||
|
use FFMpeg\FFProbe; |
||||||
|
use FFMpeg\Filters\FiltersCollection; |
||||||
|
use FFMpeg\Media\MediaTypeInterface; |
||||||
|
|
||||||
|
abstract class AbstractMediaType implements MediaTypeInterface |
||||||
|
{ |
||||||
|
/** @var string */ |
||||||
|
protected $pathfile; |
||||||
|
/** @var FFMpegDriver */ |
||||||
|
protected $driver; |
||||||
|
/** @var FFProbe */ |
||||||
|
protected $ffprobe; |
||||||
|
/** @var FiltersCollection */ |
||||||
|
protected $filters; |
||||||
|
|
||||||
|
public function __construct($pathfile, FFMpegDriver $driver, FFProbe $ffprobe) |
||||||
|
{ |
||||||
|
$this->ensureFileIsPresent($pathfile); |
||||||
|
|
||||||
|
$this->pathfile = $pathfile; |
||||||
|
$this->driver = $driver; |
||||||
|
$this->ffprobe = $ffprobe; |
||||||
|
$this->filters = new FiltersCollection(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return FFMpegDriver |
||||||
|
*/ |
||||||
|
public function getFFMpegDriver() |
||||||
|
{ |
||||||
|
return $this->driver; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param FFMpegDriver $driver |
||||||
|
* |
||||||
|
* @return MediaTypeInterface |
||||||
|
*/ |
||||||
|
public function setFFMpegDriver(FFMpegDriver $driver) |
||||||
|
{ |
||||||
|
$this->driver = $driver; |
||||||
|
|
||||||
|
return $this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return FFProbe |
||||||
|
*/ |
||||||
|
public function getFFProbe() |
||||||
|
{ |
||||||
|
return $this->ffprobe; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param FFProbe $ffprobe |
||||||
|
* |
||||||
|
* @return MediaTypeInterface |
||||||
|
*/ |
||||||
|
public function setFFProbe(FFProbe $ffprobe) |
||||||
|
{ |
||||||
|
$this->ffprobe = $ffprobe; |
||||||
|
|
||||||
|
return $this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return string |
||||||
|
*/ |
||||||
|
public function getPathfile() |
||||||
|
{ |
||||||
|
return $this->pathfile; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param FiltersCollection $filters |
||||||
|
* |
||||||
|
* @return MediaTypeInterface |
||||||
|
*/ |
||||||
|
public function setFiltersCollection(FiltersCollection $filters) |
||||||
|
{ |
||||||
|
$this->filters = $filters; |
||||||
|
|
||||||
|
return $this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return MediaTypeInterface |
||||||
|
*/ |
||||||
|
public function getFiltersCollection() |
||||||
|
{ |
||||||
|
return $this->filters; |
||||||
|
} |
||||||
|
|
||||||
|
protected function ensureFileIsPresent($filename) |
||||||
|
{ |
||||||
|
if (!is_file($filename) || !is_readable($filename)) { |
||||||
|
throw new InvalidArgumentException(sprintf( |
||||||
|
'%s is not present or not readable', $filename |
||||||
|
)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
protected function cleanupTemporaryFile($filename) |
||||||
|
{ |
||||||
|
if (file_exists($filename) && is_writable($filename)) { |
||||||
|
unlink($filename); |
||||||
|
} |
||||||
|
|
||||||
|
return $this; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,34 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <info@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\Media; |
||||||
|
|
||||||
|
use FFMpeg\FFProbe\DataMapping\Stream; |
||||||
|
use FFMpeg\FFProbe\DataMapping\StreamCollection; |
||||||
|
|
||||||
|
abstract class AbstractStreamableMedia extends AbstractMediaType |
||||||
|
{ |
||||||
|
/** |
||||||
|
* @return StreamCollection |
||||||
|
*/ |
||||||
|
public function getStreams() |
||||||
|
{ |
||||||
|
return $this->ffprobe->streams($this->pathfile); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return Stream |
||||||
|
*/ |
||||||
|
public function getFormat() |
||||||
|
{ |
||||||
|
return $this->ffprobe->format($this->pathfile); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,101 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <info@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\Media; |
||||||
|
|
||||||
|
use Alchemy\BinaryDriver\Exception\ExecutionFailureException; |
||||||
|
use FFMpeg\Filters\Audio\AudioFilters; |
||||||
|
use FFMpeg\Format\FormatInterface; |
||||||
|
use FFMpeg\Filters\Audio\SimpleFilter; |
||||||
|
use FFMpeg\Exception\RuntimeException; |
||||||
|
use FFMpeg\Exception\InvalidArgumentException; |
||||||
|
use FFMpeg\Filters\Audio\AudioFilterInterface; |
||||||
|
use FFMpeg\Filters\FilterInterface; |
||||||
|
use FFMpeg\Format\ProgressableInterface; |
||||||
|
|
||||||
|
class Audio extends AbstractStreamableMedia |
||||||
|
{ |
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
* |
||||||
|
* @return AudioFilters |
||||||
|
*/ |
||||||
|
public function filters() |
||||||
|
{ |
||||||
|
return new AudioFilters($this); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
* |
||||||
|
* @return Audio |
||||||
|
*/ |
||||||
|
public function addFilter(FilterInterface $filter) |
||||||
|
{ |
||||||
|
if (!$filter instanceof AudioFilterInterface) { |
||||||
|
throw new InvalidArgumentException('Audio only accepts AudioFilterInterface filters'); |
||||||
|
} |
||||||
|
|
||||||
|
$this->filters->add($filter); |
||||||
|
|
||||||
|
return $this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Exports the audio in the desired format, applies registered filters. |
||||||
|
* |
||||||
|
* @param FormatInterface $format |
||||||
|
* @param string $outputPathfile |
||||||
|
* |
||||||
|
* @return Audio |
||||||
|
* |
||||||
|
* @throws RuntimeException |
||||||
|
*/ |
||||||
|
public function save(FormatInterface $format, $outputPathfile) |
||||||
|
{ |
||||||
|
$listeners = null; |
||||||
|
|
||||||
|
if ($format instanceof ProgressableInterface) { |
||||||
|
$listeners = $format->createProgressListener($this, $this->ffprobe, 1, 1); |
||||||
|
} |
||||||
|
|
||||||
|
$commands = array('-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 (null !== $format->getAudioCodec()) { |
||||||
|
$filters->add(new SimpleFilter(array('-acodec', $format->getAudioCodec()))); |
||||||
|
} |
||||||
|
|
||||||
|
foreach ($filters as $filter) { |
||||||
|
$commands = array_merge($commands, $filter->apply($this, $format)); |
||||||
|
} |
||||||
|
|
||||||
|
if (null !== $format->getAudioKiloBitrate()) { |
||||||
|
$commands[] = '-b:a'; |
||||||
|
$commands[] = $format->getAudioKiloBitrate() . 'k'; |
||||||
|
} |
||||||
|
$commands[] = $outputPathfile; |
||||||
|
|
||||||
|
try { |
||||||
|
$this->driver->command($commands, false, $listeners); |
||||||
|
} catch (ExecutionFailureException $e) { |
||||||
|
$this->cleanupTemporaryFile($outputPathfile); |
||||||
|
throw new RuntimeException('Encoding failed', $e->getCode(), $e); |
||||||
|
} |
||||||
|
|
||||||
|
return $this; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,125 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <info@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\Media; |
||||||
|
|
||||||
|
use Alchemy\BinaryDriver\Exception\ExecutionFailureException; |
||||||
|
use FFMpeg\Filters\Frame\FrameFilterInterface; |
||||||
|
use FFMpeg\Filters\Frame\FrameFilters; |
||||||
|
use FFMpeg\Driver\FFMpegDriver; |
||||||
|
use FFMpeg\FFProbe; |
||||||
|
use FFMpeg\Exception\RuntimeException; |
||||||
|
use FFMpeg\Coordinate\TimeCode; |
||||||
|
use FFMpeg\Media\Video; |
||||||
|
|
||||||
|
class Frame extends AbstractMediaType |
||||||
|
{ |
||||||
|
/** @var TimeCode */ |
||||||
|
private $timecode; |
||||||
|
/** @var Video */ |
||||||
|
private $video; |
||||||
|
|
||||||
|
public function __construct(Video $video, FFMpegDriver $driver, FFProbe $ffprobe, TimeCode $timecode) |
||||||
|
{ |
||||||
|
parent::__construct($video->getPathfile(), $driver, $ffprobe); |
||||||
|
$this->timecode = $timecode; |
||||||
|
$this->video = $video; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the video related to the frame. |
||||||
|
* |
||||||
|
* @return Video |
||||||
|
*/ |
||||||
|
public function getVideo() |
||||||
|
{ |
||||||
|
return $this->video; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
* |
||||||
|
* @return FrameFilters |
||||||
|
*/ |
||||||
|
public function filters() |
||||||
|
{ |
||||||
|
return new FrameFilters($this); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
* |
||||||
|
* @return Frame |
||||||
|
*/ |
||||||
|
public function addFilter(FrameFilterInterface $filter) |
||||||
|
{ |
||||||
|
$this->filters->add($filter); |
||||||
|
|
||||||
|
return $this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return TimeCode |
||||||
|
*/ |
||||||
|
public function getTimeCode() |
||||||
|
{ |
||||||
|
return $this->timecode; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Saves the frame in the given filename. |
||||||
|
* |
||||||
|
* Uses the `unaccurate method by default.` |
||||||
|
* |
||||||
|
* @param string $pathfile |
||||||
|
* @param Boolean $accurate |
||||||
|
* |
||||||
|
* @return Frame |
||||||
|
* |
||||||
|
* @throws RuntimeException |
||||||
|
*/ |
||||||
|
public function save($pathfile, $accurate = false) |
||||||
|
{ |
||||||
|
/** |
||||||
|
* might be optimized with http://ffmpeg.org/trac/ffmpeg/wiki/Seeking%20with%20FFmpeg |
||||||
|
* @see http://ffmpeg.org/ffmpeg.html#Main-options |
||||||
|
*/ |
||||||
|
if (!$accurate) { |
||||||
|
$commands = array( |
||||||
|
'-y', '-ss', (string) $this->timecode, |
||||||
|
'-i', $this->pathfile, |
||||||
|
'-vframes', '1', |
||||||
|
'-f', 'image2' |
||||||
|
); |
||||||
|
} else { |
||||||
|
$commands = array( |
||||||
|
'-y', '-i', $this->pathfile, |
||||||
|
'-vframes', '1', '-ss', (string) $this->timecode, |
||||||
|
'-f', 'image2' |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
foreach ($this->filters as $filter) { |
||||||
|
$commands = array_merge($commands, $filter->apply($this)); |
||||||
|
} |
||||||
|
|
||||||
|
$commands = array_merge($commands, array($pathfile)); |
||||||
|
|
||||||
|
try { |
||||||
|
$this->driver->command($commands); |
||||||
|
} catch (ExecutionFailureException $e) { |
||||||
|
$this->cleanupTemporaryFile($pathfile); |
||||||
|
throw new RuntimeException('Unable to save frame', $e->getCode(), $e); |
||||||
|
} |
||||||
|
|
||||||
|
return $this; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,25 @@ |
|||||||
|
<?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; |
||||||
|
|
||||||
|
interface MediaTypeInterface |
||||||
|
{ |
||||||
|
/** |
||||||
|
* Returns the available filters. |
||||||
|
*/ |
||||||
|
public function filters(); |
||||||
|
|
||||||
|
/** |
||||||
|
* @return string |
||||||
|
*/ |
||||||
|
public function getPathfile(); |
||||||
|
} |
@ -0,0 +1,171 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/* |
||||||
|
* This file is part of PHP-FFmpeg. |
||||||
|
* |
||||||
|
* (c) Alchemy <info@alchemy.fr> |
||||||
|
* |
||||||
|
* For the full copyright and license information, please view the LICENSE |
||||||
|
* file that was distributed with this source code. |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace FFMpeg\Media; |
||||||
|
|
||||||
|
use Alchemy\BinaryDriver\Exception\ExecutionFailureException; |
||||||
|
use FFMpeg\Coordinate\TimeCode; |
||||||
|
use FFMpeg\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\Media\Frame; |
||||||
|
use Neutron\TemporaryFilesystem\Manager as FsManager; |
||||||
|
|
||||||
|
class Video extends Audio |
||||||
|
{ |
||||||
|
/** |
||||||
|
* {@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 (null !== $format->getVideoCodec()) { |
||||||
|
$filters->add(new SimpleFilter(array('-vcodec', $format->getVideoCodec()))); |
||||||
|
} |
||||||
|
if (null !== $format->getAudioCodec()) { |
||||||
|
$filters->add(new SimpleFilter(array('-acodec', $format->getAudioCodec()))); |
||||||
|
} |
||||||
|
|
||||||
|
foreach ($filters as $filter) { |
||||||
|
$commands = array_merge($commands, $filter->apply($this, $format)); |
||||||
|
} |
||||||
|
|
||||||
|
$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 (null !== $format->getAudioKiloBitrate()) { |
||||||
|
$commands[] = '-b:a'; |
||||||
|
$commands[] = $format->getAudioKiloBitrate() . 'k'; |
||||||
|
} |
||||||
|
|
||||||
|
$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. |
||||||
|
* |
||||||
|
* @param TimeCode $at |
||||||
|
* @return Frame |
||||||
|
*/ |
||||||
|
public function frame(TimeCode $at) |
||||||
|
{ |
||||||
|
return new Frame($this, $this->driver, $this->ffprobe, $at); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue