Adding lib no git submodule BT#6449

1.9.x
Julio Montoya 12 years ago
parent 386940ecd4
commit ff8c8ee7a4
  1. 1
      vendor/php-ffmpeg/php-ffmpeg
  2. 62
      vendor/php-ffmpeg/php-ffmpeg/CHANGELOG.md
  3. 21
      vendor/php-ffmpeg/php-ffmpeg/LICENSE
  4. 349
      vendor/php-ffmpeg/php-ffmpeg/README.md
  5. 44
      vendor/php-ffmpeg/php-ffmpeg/composer.json
  6. 27
      vendor/php-ffmpeg/php-ffmpeg/phpunit-functional.xml.dist
  7. 27
      vendor/php-ffmpeg/php-ffmpeg/phpunit.xml.dist
  8. 248
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/Coordinate/AspectRatio.php
  9. 71
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/Coordinate/Dimension.php
  10. 36
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/Coordinate/FrameRate.php
  11. 40
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/Coordinate/Point.php
  12. 92
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/Coordinate/TimeCode.php
  13. 57
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/Driver/FFMpegDriver.php
  14. 53
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/Driver/FFProbeDriver.php
  15. 16
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/Exception/ExceptionInterface.php
  16. 16
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/Exception/ExecutableNotFoundException.php
  17. 16
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/Exception/InvalidArgumentException.php
  18. 16
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/Exception/LogicException.php
  19. 16
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/Exception/RuntimeException.php
  20. 123
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/FFMpeg.php
  21. 66
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/FFMpegServiceProvider.php
  22. 269
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/FFProbe.php
  23. 80
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/FFProbe/DataMapping/AbstractData.php
  24. 16
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/FFProbe/DataMapping/Format.php
  25. 103
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/FFProbe/DataMapping/Stream.php
  26. 99
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/FFProbe/DataMapping/StreamCollection.php
  27. 54
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/FFProbe/Mapper.php
  28. 27
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/FFProbe/MapperInterface.php
  29. 70
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/FFProbe/OptionsTester.php
  30. 24
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/FFProbe/OptionsTesterInterface.php
  31. 125
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/FFProbe/OutputParser.php
  32. 27
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/FFProbe/OutputParserInterface.php
  33. 29
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/Filters/Audio/AudioFilterInterface.php
  34. 30
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/Filters/Audio/AudioFilters.php
  35. 54
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/Filters/Audio/AudioResamplableFilter.php
  36. 34
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/Filters/Audio/SimpleFilter.php
  37. 22
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/Filters/FilterInterface.php
  38. 60
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/Filters/FiltersCollection.php
  39. 58
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/Filters/Frame/DisplayRatioFixerFilter.php
  40. 20
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/Filters/Frame/FrameFilterInterface.php
  41. 39
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/Filters/Frame/FrameFilters.php
  42. 82
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/Filters/Video/FrameRateFilter.php
  43. 141
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/Filters/Video/ResizeFilter.php
  44. 44
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/Filters/Video/SynchronizeFilter.php
  45. 29
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/Filters/Video/VideoFilterInterface.php
  46. 83
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/Filters/Video/VideoFilters.php
  47. 106
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/Format/Audio/DefaultAudio.php
  48. 31
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/Format/Audio/Flac.php
  49. 31
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/Format/Audio/Mp3.php
  50. 39
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/Format/Audio/Vorbis.php
  51. 42
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/Format/AudioInterface.php
  52. 15
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/Format/FormatInterface.php
  53. 16
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/Format/FrameInterface.php
  54. 240
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/Format/ProgressListener/AbstractProgressListener.php
  55. 29
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/Format/ProgressListener/AudioProgressListener.php
  56. 29
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/Format/ProgressListener/VideoProgressListener.php
  57. 31
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/Format/ProgressableInterface.php
  58. 121
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/Format/Video/DefaultVideo.php
  59. 49
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/Format/Video/Ogg.php
  60. 49
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/Format/Video/WMV.php
  61. 49
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/Format/Video/WMV3.php
  62. 57
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/Format/Video/WebM.php
  63. 62
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/Format/Video/X264.php
  64. 64
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/Format/VideoInterface.php
  65. 126
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/Media/AbstractMediaType.php
  66. 34
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/Media/AbstractStreamableMedia.php
  67. 101
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/Media/Audio.php
  68. 125
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/Media/Frame.php
  69. 25
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/Media/MediaTypeInterface.php
  70. 171
      vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/Media/Video.php

@ -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
[![Build Status](https://secure.travis-ci.org/alchemy-fr/PHP-FFmpeg.png?branch=master)](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…
Cancel
Save