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