Merge branch 'master' into patch-1

This commit is contained in:
CDNRocket 2018-03-01 21:13:47 +01:00 committed by GitHub
commit b60a6c9922
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
112 changed files with 3790 additions and 421 deletions

25
.github/ISSUE_TEMPLATE.md vendored Normal file
View file

@ -0,0 +1,25 @@
| Q | A
| -------------- | ---
| Bug? | no
| New Feature? | no
| Version Used | Specific tag or commit sha
| FFmpeg Version | FFmpeg or AVConv and version
| OS | Your OS and version
#### Actual Behavior
How does PHP-FFMpeg behave at the moment?
#### Expected Behavior
What is the behavior you expect?
#### Steps to Reproduce
What are the steps to reproduce this bug? Please add code examples,
screenshots or links to GitHub repositories that reproduce the problem.
#### Possible Solutions
If you have already ideas how to solve the issue, add them here.
Otherwise remove this section.

36
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View file

@ -0,0 +1,36 @@
| Q | A
| ------------------ | ---
| Bug fix? | no
| New feature? | no
| BC breaks? | no
| Deprecations? | no
| Fixed tickets | fixes #issuenum
| Related issues/PRs | #issuenum
| License | MIT
#### What's in this PR?
Explain the contents of the PR.
#### Why?
Which problem does the PR fix?
#### Example Usage
```php
$foo = new Foo();
// Now we can do
$foo->doSomething();
// Remove this section if not needed
~~~
#### BC Breaks/Deprecations
Describe BC breaks/deprecations here (Remove this section if not needed).
#### To Do
- [ ] Create tests

6
.gitignore vendored
View file

@ -1,6 +1,8 @@
/nbproject/ #/nbproject/
/vendor/ /vendor/
/docs/build /docs/build
composer.phar composer.phar
composer.lock
phpunit.xml phpunit.xml
sami.phar
.idea/

View file

@ -1,18 +1,39 @@
language: php language: php
before_script: dist: trusty
- sudo apt-get update
- sudo apt-get install -y ffmpeg libavcodec-extra-53 branches:
- composer self-update only:
- composer install --no-interaction --prefer-source --dev - master
- v1.x
cache:
directories:
- $HOME/.composer/cache
- $HOME/.cache
php: php:
- 5.3.3
- 5.3
- 5.4 - 5.4
- 5.5 - 5.5
- 5.6 - 5.6
- 7.0
- 7.1
- 7.2
matrix:
include:
- php: 5.4
env: COMPOSER_FLAGS="--prefer-lowest"
before_install:
- sudo add-apt-repository ppa:mc3man/trusty-media -y
- sudo apt-get update -q
- composer self-update
- if [ "$COMPOSER_FLAGS" == "--prefer-lowest" ]; then composer require "roave/security-advisories" dev-master --no-update; fi;
install:
- sudo apt-get install -y ffmpeg
- composer update --prefer-dist $COMPOSER_FLAGS
script: script:
- vendor/bin/phpunit --verbose - vendor/bin/phpunit --verbose
- vendor/bin/phpunit --verbose -c phpunit-functional.xml.dist

View file

@ -1,101 +1,173 @@
CHANGELOG CHANGELOG
--------- =========
* 0.6.0 (xx-xx-2015) All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
* AbstractData::get no longer throws exceptions (@sujayjaju). [Unreleased]
* Add crop filter (@cangelis). ------------
* Fix watermark (@sujayjaju).
* 0.5.1 (08-26-2014) ### Added
* Fix video aspect ratio calculation (@nlegoff). - Add pull request and issue templates.
- Usage of new codec "aac" of ffmpeg 3
* 0.5.0 (08-12-2014) ### Changed
* Add support for Wav and AAC audio formats (@MrHash). - Updated changelog to follow [keepachangelog.com](http://keepachangelog.com/)
* Add watermark filter (@sylvainv). style you see now here.
* Add configuration for audio channels (@SimonSimCity).
* 0.4.4 (12-17-2013) [0.7.0] - 2016-12-15
--------------------
* Fix width / height dimensions extraction. - Add support for FFMpeg 3 aac codec (@Nek-)
- Add a waveform filter to extract audio waveform images (@Romain)
* 0.4.3 (12-02-2013) [0.6.1] - 2016-03-08
--------------------
* Fix using rotate and resize filters at the same time (#78) - Support PHP 7 and test against
- Unused code cleanup (@haphan)
- Composer and tests cleanup (PSR-4 autoloading)
- Allow usage of evenement v2.0
* 0.4.2 (11-29-2013) [0.6.0] - 2016-01-30
--------------------
* Add Rotate filter. - AbstractData::get no longer throws exceptions (@sujayjaju).
* Remove time_start metadata when using synchronize filter - Add crop filter (@cangelis).
* Remove restriction on filesystem resources. - Fix watermark (@sujayjaju).
* 0.4.1 (11-26-2013) [0.5.1] - 2016-08-26
--------------------
* Add Clip filter (@guimeira) - Fix video aspect ratio calculation (@nlegoff).
* 0.4.0 (10-21-2013) [0.5.0] - 2014-08-12
--------------------
* Add support for video to audio transcoding - Add support for Wav and AAC audio formats (@MrHash).
* BC Break : Add FormatInterface::getPasses and FormatInterface::getExtraParams - Add watermark filter (@sylvainv).
- Add configuration for audio channels (@SimonSimCity).
* 0.3.5 (10-21-2013) [0.4.4] - 2016-12-17
--------------------
* Add vorbis audio format (@jacobbudin). - Fix width / height dimensions extraction.
* Fix #66 : Allow single pass encodings.
* 0.3.4 (09-05-2013) [0.4.3] - 2013-02-12
--------------------
* Fix Invalid ratio computing. - Fix using rotate and resize filters at the same time (#78)
* 0.3.3 (09-05-2013) [0.4.2] - 2013-11-29
--------------------
* Add convenient Stream::getDimensions method to extract video dimension. - Add Rotate filter.
* Add DisplayRatioFixer Frame filter. - Remove time_start metadata when using synchronize filter
- Remove restriction on filesystem resources.
* 0.3.2 (08-08-2013) [0.4.1] - 2013-11-26
--------------------
* Fix A/V synchronization over flash and HTML5 players. - Add Clip filter (@guimeira)
* 0.3.1 (08-06-2013) [0.4.0] - 2013-10-21
--------------------
* Allow use of FFProbe on remote URIs. - Add support for video to audio transcoding
* Fix #47 : MediaTypeInterface::save adds filters depending on the codec. - BC Break : Add FormatInterface::getPasses and FormatInterface::getExtraParams
* Save frame to target file without prompt.
* 0.3.0 (07-04-2013) [0.3.5] - 2013-10-21
--------------------
* Complete rewrite of the library, lots of BC breaks, check the doc. - Add vorbis audio format (@jacobbudin).
- Fix #66 : Allow single pass encodings.
* 0.2.4 (05-10-2013) [0.3.4] - 2013-09-05
--------------------
* Add Video\ResizableInterface::getModulus method for better output scaling (@retrojunk) - Fix Invalid ratio computing.
* Fix timeout setting on audio/video encoding (@xammep-ua)
* 0.2.3 (04-21-2013) [0.3.3] - 2013-09-05
--------------------
* Add timeout getter and setter on FFMpeg and FFProbe - Add convenient Stream::getDimensions method to extract video dimension.
* Add timeout setting via second argument on FFMpeg::load and FFProbe::load - Add DisplayRatioFixer Frame filter.
* 0.2.2 (02-11-2013) [0.3.2] - 2013-08-08
--------------------
* Add compatibility with FFMpeg 1.1 - Fix A/V synchronization over flash and HTML5 players.
* 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) [0.3.1] - 2013-08-06
--------------------
* Parse the output of FFProbe using correct EOL sequences (@ak76) - Allow use of FFProbe on remote URIs.
* Add process timeout customization (@pulse00) - Fix #47 : MediaTypeInterface::save adds filters depending on the codec.
* Fix `accurate` option (`FFMpeg::extractImage`) - Save frame to target file without prompt.
* 0.2.0 (12-13-2012) [0.3.0] - 2013-07-04
--------------------
* Add HelperInterface and support for realtime progress ( @pulse00 ). - Complete rewrite of the library, lots of BC breaks, check the doc.
* Add `accurate` option to `FFMpeg::extractImage` method.
* 0.1.0 (10-30-2012) [0.2.4] - 2013-05-10
--------------------
* First stable version. - Add Video\ResizableInterface::getModulus method for better output scaling (@retrojunk)
- Fix timeout setting on audio/video encoding (@xammep-ua)
[0.2.3] - 2013-04-21
--------------------
- Add timeout getter and setter on FFMpeg and FFProbe
- Add timeout setting via second argument on FFMpeg::load and FFProbe::load
[0.2.2] - 2013-02-11
--------------------
- 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] - 2013-02-04
--------------------
- Parse the output of FFProbe using correct EOL sequences (@ak76)
- Add process timeout customization (@pulse00)
- Fix `accurate` option (`FFMpeg::extractImage`)
[0.2.0] - 2012-12-13
--------------------
- Add HelperInterface and support for realtime progress ( @pulse00 ).
- Add `accurate` option to `FFMpeg::extractImage` method.
0.1.0 - 2012-10-30
--------------------
- First stable version.
[Unreleased]: https://github.com/PHP-FFMpeg/PHP-FFMpeg/compare/0.6.1...HEAD
[0.6.1]: https://github.com/PHP-FFMpeg/PHP-FFMpeg/compare/0.6.0...0.6.1
[0.6.0]: https://github.com/PHP-FFMpeg/PHP-FFMpeg/compare/0.5.1...0.6.0
[0.5.1]: https://github.com/PHP-FFMpeg/PHP-FFMpeg/compare/0.5.0...0.5.1
[0.5.0]: https://github.com/PHP-FFMpeg/PHP-FFMpeg/compare/0.4.4...0.5.0
[0.4.4]: https://github.com/PHP-FFMpeg/PHP-FFMpeg/compare/0.4.3...0.4.4
[0.4.3]: https://github.com/PHP-FFMpeg/PHP-FFMpeg/compare/0.4.2...0.4.3
[0.4.2]: https://github.com/PHP-FFMpeg/PHP-FFMpeg/compare/0.4.1...0.4.2
[0.4.1]: https://github.com/PHP-FFMpeg/PHP-FFMpeg/compare/0.4.0...0.4.1
[0.4.0]: https://github.com/PHP-FFMpeg/PHP-FFMpeg/compare/0.3.5...0.4.0
[0.3.5]: https://github.com/PHP-FFMpeg/PHP-FFMpeg/compare/0.3.4...0.3.5
[0.3.4]: https://github.com/PHP-FFMpeg/PHP-FFMpeg/compare/0.3.3...0.3.4
[0.3.3]: https://github.com/PHP-FFMpeg/PHP-FFMpeg/compare/0.3.2...0.3.3
[0.3.2]: https://github.com/PHP-FFMpeg/PHP-FFMpeg/compare/0.3.1...0.3.2
[0.3.1]: https://github.com/PHP-FFMpeg/PHP-FFMpeg/compare/0.3.0...0.3.1
[0.3.0]: https://github.com/PHP-FFMpeg/PHP-FFMpeg/compare/0.2.4...0.3.0
[0.2.4]: https://github.com/PHP-FFMpeg/PHP-FFMpeg/compare/0.2.3...0.2.4
[0.2.3]: https://github.com/PHP-FFMpeg/PHP-FFMpeg/compare/0.2.2...0.2.3
[0.2.2]: https://github.com/PHP-FFMpeg/PHP-FFMpeg/compare/0.2.1...0.2.2
[0.2.1]: https://github.com/PHP-FFMpeg/PHP-FFMpeg/compare/0.2.0...0.2.1
[0.2.0]: https://github.com/PHP-FFMpeg/PHP-FFMpeg/compare/0.1.0...0.2.0

255
README.md
View file

@ -14,7 +14,7 @@ Check another amazing repo : [PHP FFMpeg extras](https://github.com/alchemy-fr/P
This library requires a working FFMpeg install. You will need both FFMpeg and FFProbe binaries to use it. 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, 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. otherwise you should have to explicitly give the binaries path on load.
For Windows users : Please find the binaries at http://ffmpeg.zeranoe.com/builds/. For Windows users : Please find the binaries at http://ffmpeg.zeranoe.com/builds/.
@ -28,17 +28,16 @@ appear in latest ffmpeg version.
The recommended way to install PHP-FFMpeg is through [Composer](https://getcomposer.org). The recommended way to install PHP-FFMpeg is through [Composer](https://getcomposer.org).
```json ```bash
{ $ composer require php-ffmpeg/php-ffmpeg
"require": {
"php-ffmpeg/php-ffmpeg": "~0.5"
}
}
``` ```
## Basic Usage ## Basic Usage
```php ```php
require 'vendor/autoload.php';
$ffmpeg = FFMpeg\FFMpeg::create(); $ffmpeg = FFMpeg\FFMpeg::create();
$video = $ffmpeg->open('video.mpg'); $video = $ffmpeg->open('video.mpg');
$video $video
@ -69,7 +68,7 @@ $ffmpeg = FFMpeg\FFMpeg::create();
``` ```
FFMpeg will autodetect ffmpeg and ffprobe binaries. If you want to give binary 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` paths explicitly, you can pass an array as configuration. A `Psr\Logger\LoggerInterface`
can also be passed to log binary executions. can also be passed to log binary executions.
```php ```php
@ -102,7 +101,7 @@ $ffmpeg->open('video.mpeg');
Two types of media can be resolved: `FFMpeg\Media\Audio` and `FFMpeg\Media\Video`. Two types of media can be resolved: `FFMpeg\Media\Audio` and `FFMpeg\Media\Video`.
A third type, `FFMpeg\Media\Frame`, is available through videos. A third type, `FFMpeg\Media\Frame`, is available through videos.
#### Video ### Video
`FFMpeg\Media\Video` can be transcoded, ie: change codec, isolate audio or `FFMpeg\Media\Video` can be transcoded, ie: change codec, isolate audio or
video. Frames can be extracted. video. Frames can be extracted.
@ -115,7 +114,7 @@ pass a `FFMpeg\Format\FormatInterface` for that.
Please note that audio and video bitrate are set on the format. Please note that audio and video bitrate are set on the format.
```php ```php
$format = new Format\Video\X264(); $format = new FFMpeg\Format\Video\X264();
$format->on('progress', function ($video, $format, $percentage) { $format->on('progress', function ($video, $format, $percentage) {
echo "$percentage % transcoded"; echo "$percentage % transcoded";
}); });
@ -136,7 +135,7 @@ below for more informations.
You can extract a frame at any timecode using the `FFMpeg\Media\Video::frame` You can extract a frame at any timecode using the `FFMpeg\Media\Video::frame`
method. method.
This code return a `FFMpeg\Media\Frame` instance corresponding to the second 42. This code returns a `FFMpeg\Media\Frame` instance corresponding to the second 42.
You can pass any `FFMpeg\Coordinate\TimeCode` as argument, see dedicated You can pass any `FFMpeg\Coordinate\TimeCode` as argument, see dedicated
documentation below for more information. documentation below for more information.
@ -145,6 +144,54 @@ $frame = $video->frame(FFMpeg\Coordinate\TimeCode::fromSeconds(42));
$frame->save('image.jpg'); $frame->save('image.jpg');
``` ```
If you want to extract multiple images from your video, you can use the following filter:
```php
$video
->filters()
->extractMultipleFrames(FFMpeg\Filters\Video\ExtractMultipleFramesFilter::FRAMERATE_EVERY_10SEC, '/path/to/destination/folder/')
->synchronize();
$video
->save(new FFMpeg\Format\Video\X264(), '/path/to/new/file');
```
##### Generate a waveform
You can generate a waveform of an audio file using the `FFMpeg\Media\Audio::waveform`
method.
This code returns a `FFMpeg\Media\Waveform` instance.
You can optionally pass dimensions as the first two arguments and an array of hex string colors for ffmpeg to use for the waveform, see dedicated
documentation below for more information.
The ouput file MUST use the PNG extension.
```php
$waveform = $audio->waveform(640, 120, array('#00FF00'));
$waveform->save('waveform.png');
```
If you want to get a waveform from a video, convert it in an audio file first.
```php
// Open your video file
$video = $ffmpeg->open( 'video.mp4' );
// Set an audio format
$audio_format = new FFMpeg\Format\Audio\Mp3();
// Extract the audio into a new file as mp3
$video->save($audio_format, 'audio.mp3');
// Set the audio file
$audio = $ffmpeg->open( 'audio.mp3' );
// Create the waveform
$waveform = $audio->waveform();
$waveform->save( 'waveform.png' );
```
##### Filters ##### Filters
You can apply filters on `FFMpeg\Media\Video` with the `FFMpeg\Media\Video::addFilter` You can apply filters on `FFMpeg\Media\Video` with the `FFMpeg\Media\Video::addFilter`
@ -191,6 +238,51 @@ The resize filter takes three parameters :
- `$mode`, one of the constants `FFMpeg\Filters\Video\ResizeFilter::RESIZEMODE_*` constants - `$mode`, one of the constants `FFMpeg\Filters\Video\ResizeFilter::RESIZEMODE_*` constants
- `$useStandards`, a boolean to force the use of the nearest aspect ratio standard. - `$useStandards`, a boolean to force the use of the nearest aspect ratio standard.
If you want a video in a non-standard ratio, you can use the padding filter to resize your video in the desired size, and wrap it into black bars.
```php
$video->filters()->pad($dimension);
```
The pad filter takes one parameter:
- `$dimension`, an instance of `FFMpeg\Coordinate\Dimension`
Don't forget to save it afterwards.
```php
$video->save(new FFMpeg\Format\Video\X264(), $new_file);
```
###### Watermark
Watermark a video with a given image.
```php
$video
->filters()
->watermark($watermarkPath, array(
'position' => 'relative',
'bottom' => 50,
'right' => 50,
));
```
The watermark filter takes two parameters:
`$watermarkPath`, the path to your watermark file.
`$coordinates`, an array defining how you want your watermark positioned. You can use relative positioning as demonstrated above or absolute as such:
```php
$video
->filters()
->watermark($watermarkPath, array(
'position' => 'absolute',
'x' => 1180,
'y' => 620,
));
```
###### Framerate ###### Framerate
Changes the frame rate of the video. Changes the frame rate of the video.
@ -228,9 +320,21 @@ The clip filter takes two parameters:
- `$start`, an instance of `FFMpeg\Coordinate\TimeCode`, specifies the start point of the clip - `$start`, an instance of `FFMpeg\Coordinate\TimeCode`, specifies the start point of the clip
- `$duration`, optional, an instance of `FFMpeg\Coordinate\TimeCode`, specifies the duration of the clip - `$duration`, optional, an instance of `FFMpeg\Coordinate\TimeCode`, specifies the duration of the clip
#### Audio ###### Crop
`FFMpeg\Media\Audio` can be transcoded, ie : change codec, isolate audio or Crops the video based on a width and height(a `Point`)
```php
$video->filters()->crop(new FFMpeg\Coordinate\Point("t*100", 0, true), new FFMpeg\Coordinate\Dimension(200, 600));
```
It takes two parameters:
- `$point`, an instance of `FFMpeg\Coordinate\Point`, specifies the point to crop
- `$dimension`, an instance of `FFMpeg\Coordinate\Dimension`, specifies the dimension of the output video
### Audio
`FFMpeg\Media\Audio` can be transcoded too, ie: change codec, isolate audio or
video. Frames can be extracted. video. Frames can be extracted.
##### Transcoding ##### Transcoding
@ -267,6 +371,35 @@ method. It only accepts audio filters.
You can build your own filters and some are bundled in PHP-FFMpeg - they are You can build your own filters and some are bundled in PHP-FFMpeg - they are
accessible through the `FFMpeg\Media\Audio::filters` method. accessible through the `FFMpeg\Media\Audio::filters` method.
##### Clipping
Cuts the audio at a desired point.
```php
$audio->filters()->clip(FFMpeg\Coordinate\TimeCode::fromSeconds(30), FFMpeg\Coordinate\TimeCode::fromSeconds(15));
```
###### Metadata
Add metadata to audio files. Just pass an array of key=value pairs of all
metadata you would like to add. If no arguments are passed into the filter
all metadata will be removed from input file. Currently supported data is
title, artist, album, artist, composer, track, year, description, artwork
```php
$audio->filters()->addMetadata(["title" => "Some Title", "track" => 1]);
//remove all metadata and video streams from audio file
$audio->filters()->addMetadata();
```
Add artwork to the audio file
```php
$audio->filters()->addMetadata(["artwork" => "/path/to/image/file.jpg"]);
```
NOTE: at present ffmpeg (version 3.2.2) only supports artwork output for .mp3
files
###### Resample ###### Resample
Resamples an audio file. Resamples an audio file.
@ -293,6 +426,68 @@ $frame->save('target.jpg');
This method has a second optional boolean parameter. Set it to true to get This method has a second optional boolean parameter. Set it to true to get
accurate images ; it takes more time to execute. accurate images ; it takes more time to execute.
#### Gif
A gif is an animated image extracted from a sequence of the video.
You can save gif files using the `FFMpeg\Media\Gif::save` method.
```php
$video = $ffmpeg->open( '/path/to/video' );
$video
->gif(FFMpeg\Coordinate\TimeCode::fromSeconds(2), new FFMpeg\Coordinate\Dimension(640, 480), 3)
->save($new_file);
```
This method has a third optional boolean parameter, which is the duration of the animation.
If you don't set it, you will get a fixed gif image.
#### Concatenation
This feature allows you to generate one audio or video file, based on multiple sources.
There are two ways to concatenate videos, depending on the codecs of the sources.
If your sources have all been encoded with the same codec, you will want to use the `FFMpeg\Media\Concatenate::saveFromSameCodecs` which has way better performances.
If your sources have been encoded with different codecs, you will want to use the `FFMpeg\Media\Concatenate::saveFromDifferentCodecs`.
The first function will use the initial codec as the one for the generated file.
With the second function, you will be able to choose which codec you want for the generated file.
You also need to pay attention to the fact that, when using the saveFromDifferentCodecs method,
your files MUST have video and audio streams.
In both cases, you will have to provide an array of files.
To concatenate videos encoded with the same codec, do as follow:
```php
// In order to instantiate the video object, you HAVE TO pass a path to a valid video file.
// We recommand that you put there the path of any of the video you want to use in this concatenation.
$video = $ffmpeg->open( '/path/to/video' );
$video
->concat(array('/path/to/video1', '/path/to/video2'))
->saveFromSameCodecs('/path/to/new_file', TRUE);
```
The boolean parameter of the save function allows you to use the copy parameter which accelerates drastically the generation of the encoded file.
To concatenate videos encoded with the same codec, do as follow:
```php
// In order to instantiate the video object, you HAVE TO pass a path to a valid video file.
// We recommand that you put there the path of any of the video you want to use in this concatenation.
$video = $ffmpeg->open( '/path/to/video' );
$format = new FFMpeg\Format\Video\X264();
$format->setAudioCodec("libmp3lame");
$video
->concat(array('/path/to/video1', '/path/to/video2'))
->saveFromDifferentCodecs($format, '/path/to/new_file');
```
More details about concatenation in FFMPEG can be found [here](https://trac.ffmpeg.org/wiki/Concatenate), [here](https://ffmpeg.org/ffmpeg-formats.html#concat-1) and [here](https://ffmpeg.org/ffmpeg.html#Stream-copy).
#### Formats #### Formats
A format implements `FFMpeg\Format\FormatInterface`. To save to a video file, A format implements `FFMpeg\Format\FormatInterface`. To save to a video file,
@ -305,7 +500,7 @@ informations about the transcoding.
Predefined formats already provide progress informations as events. Predefined formats already provide progress informations as events.
```php ```php
$format = new Format\Video\X264(); $format = new FFMpeg\Format\Video\X264();
$format->on('progress', function ($video, $format, $percentage) { $format->on('progress', function ($video, $format, $percentage) {
echo "$percentage % transcoded"; echo "$percentage % transcoded";
}); });
@ -315,6 +510,18 @@ $video->save($format, 'video.avi');
The callback provided for the event can be any callable. The callback provided for the event can be any callable.
##### Add additional parameters
You can add additional parameters to your encoding requests based on your video format.
The argument of the setAdditionalParameters method is an array.
```php
$format = new FFMpeg\Format\Video\X264();
$format->setAdditionalParameters(array('foo', 'bar'));
$video->save($format, 'video.avi');
```
##### Create your own format ##### Create your own format
The easiest way to create a format is to extend the abstract The easiest way to create a format is to extend the abstract
@ -355,7 +562,7 @@ FFMpeg use many units for time and space coordinates.
- `FFMpeg\Coordinate\AspectRatio` represents an aspect ratio. - `FFMpeg\Coordinate\AspectRatio` represents an aspect ratio.
- `FFMpeg\Coordinate\Dimension` represent a dimension. - `FFMpeg\Coordinate\Dimension` represent a dimension.
- `FFMpeg\Coordinate\FrameRate` represent a framerate. - `FFMpeg\Coordinate\FrameRate` represent a framerate.
- `FFMpeg\Coordinate\Point` represent a point. - `FFMpeg\Coordinate\Point` represent a point. (Supports dynamic points since v0.10.0)
- `FFMpeg\Coordinate\TimeCode` represent a timecode. - `FFMpeg\Coordinate\TimeCode` represent a timecode.
### FFProbe ### FFProbe
@ -379,6 +586,16 @@ $ffprobe
->get('duration'); // returns the duration property ->get('duration'); // returns the duration property
``` ```
### Validating media files
(since 0.10.0)
You can validate media files using PHP-FFMpeg's FFProbe wrapper.
```php
$ffprobe = FFMpeg\FFProbe::create();
$ffprobe->isValid('/path/to/file/to/check'); // returns bool
```
## Using with Silex Microframework ## Using with Silex Microframework
Service provider is easy to set up: Service provider is easy to set up:
@ -405,14 +622,6 @@ $app->register(new FFMpeg\FFMpegServiceProvider(), array(
)); ));
``` ```
## API Browser
Browse the [API](http://readthedocs.org/docs/ffmpeg-php/en/latest/_static/API/)
## License ## License
This project is licensed under the [MIT license](http://opensource.org/licenses/MIT). This project is licensed under the [MIT license](http://opensource.org/licenses/MIT).

View file

@ -14,14 +14,29 @@
"name": "Phraseanet Team", "name": "Phraseanet Team",
"email": "info@alchemy.fr", "email": "info@alchemy.fr",
"homepage": "http://www.phraseanet.com/" "homepage": "http://www.phraseanet.com/"
},
{
"name": "Patrik Karisch",
"email": "patrik@karisch.guru",
"homepage": "http://www.karisch.guru"
},
{
"name": "Romain Biard",
"email": "romain.biard@gmail.com",
"homepage": "https://www.strime.io/"
},
{
"name": "Jens Hausdorf",
"email": "hello@jens-hausdorf.de",
"homepage": "https://jens-hausdorf.de"
} }
], ],
"require": { "require": {
"php" : ">=5.3.3", "php": "^5.3.9 || ^7.0",
"alchemy/binary-driver" : "~1.5", "alchemy/binary-driver": "^1.5",
"doctrine/cache" : "~1.0", "doctrine/cache": "^1.0",
"evenement/evenement" : "~1.0", "evenement/evenement": "^2.0 || ^1.0",
"neutron/temporary-filesystem" : "~2.1, >=2.1.1" "neutron/temporary-filesystem": "^2.1.1"
}, },
"suggest": { "suggest": {
"php-ffmpeg/extras": "A compilation of common audio & video drivers for PHP-FFMpeg" "php-ffmpeg/extras": "A compilation of common audio & video drivers for PHP-FFMpeg"
@ -29,16 +44,21 @@
"require-dev": { "require-dev": {
"sami/sami": "~1.0", "sami/sami": "~1.0",
"silex/silex": "~1.0", "silex/silex": "~1.0",
"phpunit/phpunit" : "~3.7" "phpunit/phpunit": "^4.8.36"
}, },
"autoload": { "autoload": {
"psr-0": { "psr-0": {
"FFMpeg": "src" "FFMpeg": "src"
} }
}, },
"autoload-dev": {
"psr-4": {
"Tests\\FFMpeg\\": "tests"
}
},
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "0.5-dev" "dev-master": "0.7-dev"
} }
} }
} }

View file

@ -119,7 +119,17 @@
</tr> </tr>
<tr> <tr>
<td class="type"> <td class="type">
string integer
</td>
<td class="last">
<a href="#method_setPasses">setPasses</a>(integer $passes)
<p>Sets the number of passes.</p>
</td>
<td></td>
</tr>
<tr>
<td class="type">
integer
</td> </td>
<td class="last"> <td class="last">
<a href="#method_getPasses">getPasses</a>() <a href="#method_getPasses">getPasses</a>()
@ -499,12 +509,37 @@
</div>
</div>
<h3 id="method_setPasses">
<div class="location">in <a href="../../../FFMpeg/Format/Video/X264.html#method_setPasses"><abbr title="FFMpeg\Format\Video\X264">X264</abbr></a> at line 68</div>
<code> public integer
<strong>setPasses</strong>(integer $passes)</code>
</h3>
<div class="details">
<p>Sets the number of passes.</p>
<p>
</p>
<div class="tags">
<h4>Parameters</h4>
<table>
<tr>
<td>integer</td>
<td>$passes</td>
</tr>
</table>
</div> </div>
</div> </div>
<h3 id="method_getPasses"> <h3 id="method_getPasses">
<div class="location">at line 68</div> <div class="location">in <a href="../../../FFMpeg/Format/Video/X264.html#method_getPasses"><abbr title="FFMpeg\Format\Video\X264">X264</abbr></a> at line 79</div>
<code> public string <code> public integer
<strong>getPasses</strong>()</code> <strong>getPasses</strong>()</code>
</h3> </h3>
<div class="details"> <div class="details">
@ -517,7 +552,7 @@
<table> <table>
<tr> <tr>
<td>string</td> <td>integer</td>
<td> <td>
</td> </td>
</tr> </tr>

View file

@ -171,6 +171,16 @@
<p>Exports the audio in the desired format, applies registered filters.</p> <p>Exports the audio in the desired format, applies registered filters.</p>
</td> </td>
<td></td> <td></td>
</tr>
<tr>
<td class="type">
<a href="../../FFMpeg/Media/Waveform.html"><abbr title="FFMpeg\Media\Audio">Audio</abbr></a>
</td>
<td class="last">
<a href="#method_waveform">waveform</a>(integer $width, integer $height)
<p>Generates an image file representing the waveform of the audio file.</p>
</td>
<td></td>
</tr> </tr>
</table> </table>
@ -609,6 +619,59 @@
</div> </div>
</div> </div>
<h3 id="method_waveform">
<div class="location">at line 113</div>
<code> public <a href="../../FFMpeg/Media/Waveform.html"><abbr title="FFMpeg\Media\Audio">Audio</abbr></a>
<strong>save</strong>(<a href="../../FFMpeg/Format/FormatInterface.html"><abbr title="FFMpeg\Format\FormatInterface">FormatInterface</abbr></a> $format, string $outputPathfile)</code>
</h3>
<div class="details">
<p>Exports the audio in the desired format, applies registered filters.</p>
<p>
</p>
<div class="tags">
<h4>Parameters</h4>
<table>
<tr>
<td><a href="../../FFMpeg/Format/FormatInterface.html"><abbr title="FFMpeg\Format\FormatInterface">FormatInterface</abbr></a></td>
<td>$format</td>
<td>
</td>
</tr>
<tr>
<td>string</td>
<td>$outputPathfile</td>
<td>
</td>
</tr>
</table>
<h4>Return Value</h4>
<table>
<tr>
<td><a href="../../FFMpeg/Media/Audio.html"><abbr title="FFMpeg\Media\Audio">Audio</abbr></a></td>
<td>
</td>
</tr>
</table>
<h4>Exceptions</h4>
<table>
<tr>
<td><a href="http://php.net/RuntimeException"><abbr title="RuntimeException">RuntimeException</abbr></a></td>
<td>
</td>
</tr>
</table>
</div>
</div>
</div> </div>
<div id="footer"> <div id="footer">

View file

@ -0,0 +1,621 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="robots" content="index, follow, all" />
<title>FFMpeg\Media\Frame | PHP-FFMpeg API</title>
<link rel="stylesheet" type="text/css" href="../../stylesheet.css">
</head>
<body id="class">
<div class="header">
<ul>
<li><a href="../../classes.html">Classes</a></li>
<li><a href="../../namespaces.html">Namespaces</a></li>
<li><a href="../../interfaces.html">Interfaces</a></li>
<li><a href="../../traits.html">Traits</a></li>
<li><a href="../../doc-index.html">Index</a></li>
</ul>
<div id="title">PHP-FFMpeg API</div>
<div class="type">Class</div>
<h1><a href="../../FFMpeg/Media.html">FFMpeg\Media</a>\Frame</h1>
</div>
<div class="content">
<p> class
<strong>Frame</strong> extends <a href="../../FFMpeg/Media/AbstractMediaType.html"><abbr title="FFMpeg\Media\AbstractMediaType">AbstractMediaType</abbr></a></p>
<h2>Methods</h2>
<table>
<tr>
<td class="type">
</td>
<td class="last">
<a href="#method___construct">__construct</a>(<a href="../../FFMpeg/Media/Video.html"><abbr title="FFMpeg\Media\Video">Video</abbr></a> $video, <a href="../../FFMpeg/Driver/FFMpegDriver.html"><abbr title="FFMpeg\Driver\FFMpegDriver">FFMpegDriver</abbr></a> $driver, <a href="../../FFMpeg/FFProbe.html"><abbr title="FFMpeg\FFProbe">FFProbe</abbr></a> $ffprobe, <a href="../../FFMpeg/Coordinate/TimeCode.html"><abbr title="FFMpeg\Coordinate\TimeCode">TimeCode</abbr></a> $timecode)
<p>
</p>
</td>
<td></td>
</tr>
<tr>
<td class="type">
<a href="../../FFMpeg/Driver/FFMpegDriver.html"><abbr title="FFMpeg\Driver\FFMpegDriver">FFMpegDriver</abbr></a>
</td>
<td class="last">
<a href="#method_getFFMpegDriver">getFFMpegDriver</a>()
<p>
</p>
</td>
<td><small>from&nbsp;<a href="../../FFMpeg/Media/AbstractMediaType.html#method_getFFMpegDriver"><abbr title="FFMpeg\Media\AbstractMediaType">AbstractMediaType</abbr></a></small></td>
</tr>
<tr>
<td class="type">
<a href="../../FFMpeg/Media/MediaTypeInterface.html"><abbr title="FFMpeg\Media\MediaTypeInterface">MediaTypeInterface</abbr></a>
</td>
<td class="last">
<a href="#method_setFFMpegDriver">setFFMpegDriver</a>(<a href="../../FFMpeg/Driver/FFMpegDriver.html"><abbr title="FFMpeg\Driver\FFMpegDriver">FFMpegDriver</abbr></a> $driver)
<p>
</p>
</td>
<td><small>from&nbsp;<a href="../../FFMpeg/Media/AbstractMediaType.html#method_setFFMpegDriver"><abbr title="FFMpeg\Media\AbstractMediaType">AbstractMediaType</abbr></a></small></td>
</tr>
<tr>
<td class="type">
<a href="../../FFMpeg/FFProbe.html"><abbr title="FFMpeg\FFProbe">FFProbe</abbr></a>
</td>
<td class="last">
<a href="#method_getFFProbe">getFFProbe</a>()
<p>
</p>
</td>
<td><small>from&nbsp;<a href="../../FFMpeg/Media/AbstractMediaType.html#method_getFFProbe"><abbr title="FFMpeg\Media\AbstractMediaType">AbstractMediaType</abbr></a></small></td>
</tr>
<tr>
<td class="type">
<a href="../../FFMpeg/Media/MediaTypeInterface.html"><abbr title="FFMpeg\Media\MediaTypeInterface">MediaTypeInterface</abbr></a>
</td>
<td class="last">
<a href="#method_setFFProbe">setFFProbe</a>(<a href="../../FFMpeg/FFProbe.html"><abbr title="FFMpeg\FFProbe">FFProbe</abbr></a> $ffprobe)
<p>
</p>
</td>
<td><small>from&nbsp;<a href="../../FFMpeg/Media/AbstractMediaType.html#method_setFFProbe"><abbr title="FFMpeg\Media\AbstractMediaType">AbstractMediaType</abbr></a></small></td>
</tr>
<tr>
<td class="type">
string
</td>
<td class="last">
<a href="#method_getPathfile">getPathfile</a>()
<p>
</p>
</td>
<td><small>from&nbsp;<a href="../../FFMpeg/Media/AbstractMediaType.html#method_getPathfile"><abbr title="FFMpeg\Media\AbstractMediaType">AbstractMediaType</abbr></a></small></td>
</tr>
<tr>
<td class="type">
<a href="../../FFMpeg/Media/MediaTypeInterface.html"><abbr title="FFMpeg\Media\MediaTypeInterface">MediaTypeInterface</abbr></a>
</td>
<td class="last">
<a href="#method_setFiltersCollection">setFiltersCollection</a>(<a href="../../FFMpeg/Filters/FiltersCollection.html"><abbr title="FFMpeg\Filters\FiltersCollection">FiltersCollection</abbr></a> $filters)
<p>
</p>
</td>
<td><small>from&nbsp;<a href="../../FFMpeg/Media/AbstractMediaType.html#method_setFiltersCollection"><abbr title="FFMpeg\Media\AbstractMediaType">AbstractMediaType</abbr></a></small></td>
</tr>
<tr>
<td class="type">
<a href="../../FFMpeg/Media/MediaTypeInterface.html"><abbr title="FFMpeg\Media\MediaTypeInterface">MediaTypeInterface</abbr></a>
</td>
<td class="last">
<a href="#method_getFiltersCollection">getFiltersCollection</a>()
<p>
</p>
</td>
<td><small>from&nbsp;<a href="../../FFMpeg/Media/AbstractMediaType.html#method_getFiltersCollection"><abbr title="FFMpeg\Media\AbstractMediaType">AbstractMediaType</abbr></a></small></td>
</tr>
<tr>
<td class="type">
<a href="../../FFMpeg/Media/Video.html"><abbr title="FFMpeg\Media\Video">Video</abbr></a>
</td>
<td class="last">
<a href="#method_getVideo">getVideo</a>()
<p>Returns the video related to the frame.</p>
</td>
<td></td>
</tr>
<tr>
<td class="type">
<a href="../../FFMpeg/Filters/Frame/FrameFilters.html"><abbr title="FFMpeg\Filters\Frame\FrameFilters">FrameFilters</abbr></a>
</td>
<td class="last">
<a href="#method_filters">filters</a>()
<p>Returns the available filters.</p>
</td>
<td></td>
</tr>
<tr>
<td class="type">
<a href="../../FFMpeg/Media/Frame.html"><abbr title="FFMpeg\Media\Frame">Frame</abbr></a>
</td>
<td class="last">
<a href="#method_addFilter">addFilter</a>(<a href="../../FFMpeg/Filters/Frame/FrameFilterInterface.html"><abbr title="FFMpeg\Filters\Frame\FrameFilterInterface">FrameFilterInterface</abbr></a> $filter)
<p>{@inheritdoc}</p>
</td>
<td></td>
</tr>
<tr>
<td class="type">
<a href="../../FFMpeg/Coordinate/TimeCode.html"><abbr title="FFMpeg\Coordinate\TimeCode">TimeCode</abbr></a>
</td>
<td class="last">
<a href="#method_getTimeCode">getTimeCode</a>()
<p>
</p>
</td>
<td></td>
</tr>
<tr>
<td class="type">
<a href="../../FFMpeg/Media/Frame.html"><abbr title="FFMpeg\Media\Frame">Frame</abbr></a>
</td>
<td class="last">
<a href="#method_save">save</a>(string $pathfile, Boolean $accurate = false)
<p>Saves the frame in the given filename.</p>
</td>
<td></td>
</tr>
</table>
<h2>Details</h2>
<h3 id="method___construct">
<div class="location">at line 29</div>
<code> public
<strong>__construct</strong>(<a href="../../FFMpeg/Media/Video.html"><abbr title="FFMpeg\Media\Video">Video</abbr></a> $video, <a href="../../FFMpeg/Driver/FFMpegDriver.html"><abbr title="FFMpeg\Driver\FFMpegDriver">FFMpegDriver</abbr></a> $driver, <a href="../../FFMpeg/FFProbe.html"><abbr title="FFMpeg\FFProbe">FFProbe</abbr></a> $ffprobe, <a href="../../FFMpeg/Coordinate/TimeCode.html"><abbr title="FFMpeg\Coordinate\TimeCode">TimeCode</abbr></a> $timecode)</code>
</h3>
<div class="details">
<p>
</p>
<p>
</p>
<div class="tags">
<h4>Parameters</h4>
<table>
<tr>
<td><a href="../../FFMpeg/Media/Video.html"><abbr title="FFMpeg\Media\Video">Video</abbr></a></td>
<td>$video</td>
<td>
</td>
</tr>
<tr>
<td><a href="../../FFMpeg/Driver/FFMpegDriver.html"><abbr title="FFMpeg\Driver\FFMpegDriver">FFMpegDriver</abbr></a></td>
<td>$driver</td>
<td>
</td>
</tr>
<tr>
<td><a href="../../FFMpeg/FFProbe.html"><abbr title="FFMpeg\FFProbe">FFProbe</abbr></a></td>
<td>$ffprobe</td>
<td>
</td>
</tr>
<tr>
<td><a href="../../FFMpeg/Coordinate/TimeCode.html"><abbr title="FFMpeg\Coordinate\TimeCode">TimeCode</abbr></a></td>
<td>$timecode</td>
<td>
</td>
</tr>
</table>
</div>
</div>
<h3 id="method_getFFMpegDriver">
<div class="location">in <a href="../../FFMpeg/Media/AbstractMediaType.html#method_getFFMpegDriver"><abbr title="FFMpeg\Media\AbstractMediaType">AbstractMediaType</abbr></a> at line 40</div>
<code> public <a href="../../FFMpeg/Driver/FFMpegDriver.html"><abbr title="FFMpeg\Driver\FFMpegDriver">FFMpegDriver</abbr></a>
<strong>getFFMpegDriver</strong>()</code>
</h3>
<div class="details">
<p>
</p>
<p>
</p>
<div class="tags">
<h4>Return Value</h4>
<table>
<tr>
<td><a href="../../FFMpeg/Driver/FFMpegDriver.html"><abbr title="FFMpeg\Driver\FFMpegDriver">FFMpegDriver</abbr></a></td>
<td>
</td>
</tr>
</table>
</div>
</div>
<h3 id="method_setFFMpegDriver">
<div class="location">in <a href="../../FFMpeg/Media/AbstractMediaType.html#method_setFFMpegDriver"><abbr title="FFMpeg\Media\AbstractMediaType">AbstractMediaType</abbr></a> at line 50</div>
<code> public <a href="../../FFMpeg/Media/MediaTypeInterface.html"><abbr title="FFMpeg\Media\MediaTypeInterface">MediaTypeInterface</abbr></a>
<strong>setFFMpegDriver</strong>(<a href="../../FFMpeg/Driver/FFMpegDriver.html"><abbr title="FFMpeg\Driver\FFMpegDriver">FFMpegDriver</abbr></a> $driver)</code>
</h3>
<div class="details">
<p>
</p>
<p>
</p>
<div class="tags">
<h4>Parameters</h4>
<table>
<tr>
<td><a href="../../FFMpeg/Driver/FFMpegDriver.html"><abbr title="FFMpeg\Driver\FFMpegDriver">FFMpegDriver</abbr></a></td>
<td>$driver</td>
<td>
</td>
</tr>
</table>
<h4>Return Value</h4>
<table>
<tr>
<td><a href="../../FFMpeg/Media/MediaTypeInterface.html"><abbr title="FFMpeg\Media\MediaTypeInterface">MediaTypeInterface</abbr></a></td>
<td>
</td>
</tr>
</table>
</div>
</div>
<h3 id="method_getFFProbe">
<div class="location">in <a href="../../FFMpeg/Media/AbstractMediaType.html#method_getFFProbe"><abbr title="FFMpeg\Media\AbstractMediaType">AbstractMediaType</abbr></a> at line 60</div>
<code> public <a href="../../FFMpeg/FFProbe.html"><abbr title="FFMpeg\FFProbe">FFProbe</abbr></a>
<strong>getFFProbe</strong>()</code>
</h3>
<div class="details">
<p>
</p>
<p>
</p>
<div class="tags">
<h4>Return Value</h4>
<table>
<tr>
<td><a href="../../FFMpeg/FFProbe.html"><abbr title="FFMpeg\FFProbe">FFProbe</abbr></a></td>
<td>
</td>
</tr>
</table>
</div>
</div>
<h3 id="method_setFFProbe">
<div class="location">in <a href="../../FFMpeg/Media/AbstractMediaType.html#method_setFFProbe"><abbr title="FFMpeg\Media\AbstractMediaType">AbstractMediaType</abbr></a> at line 70</div>
<code> public <a href="../../FFMpeg/Media/MediaTypeInterface.html"><abbr title="FFMpeg\Media\MediaTypeInterface">MediaTypeInterface</abbr></a>
<strong>setFFProbe</strong>(<a href="../../FFMpeg/FFProbe.html"><abbr title="FFMpeg\FFProbe">FFProbe</abbr></a> $ffprobe)</code>
</h3>
<div class="details">
<p>
</p>
<p>
</p>
<div class="tags">
<h4>Parameters</h4>
<table>
<tr>
<td><a href="../../FFMpeg/FFProbe.html"><abbr title="FFMpeg\FFProbe">FFProbe</abbr></a></td>
<td>$ffprobe</td>
<td>
</td>
</tr>
</table>
<h4>Return Value</h4>
<table>
<tr>
<td><a href="../../FFMpeg/Media/MediaTypeInterface.html"><abbr title="FFMpeg\Media\MediaTypeInterface">MediaTypeInterface</abbr></a></td>
<td>
</td>
</tr>
</table>
</div>
</div>
<h3 id="method_getPathfile">
<div class="location">in <a href="../../FFMpeg/Media/AbstractMediaType.html#method_getPathfile"><abbr title="FFMpeg\Media\AbstractMediaType">AbstractMediaType</abbr></a> at line 80</div>
<code> public string
<strong>getPathfile</strong>()</code>
</h3>
<div class="details">
<p>
</p>
<p>
</p>
<div class="tags">
<h4>Return Value</h4>
<table>
<tr>
<td>string</td>
<td>
</td>
</tr>
</table>
</div>
</div>
<h3 id="method_setFiltersCollection">
<div class="location">in <a href="../../FFMpeg/Media/AbstractMediaType.html#method_setFiltersCollection"><abbr title="FFMpeg\Media\AbstractMediaType">AbstractMediaType</abbr></a> at line 90</div>
<code> public <a href="../../FFMpeg/Media/MediaTypeInterface.html"><abbr title="FFMpeg\Media\MediaTypeInterface">MediaTypeInterface</abbr></a>
<strong>setFiltersCollection</strong>(<a href="../../FFMpeg/Filters/FiltersCollection.html"><abbr title="FFMpeg\Filters\FiltersCollection">FiltersCollection</abbr></a> $filters)</code>
</h3>
<div class="details">
<p>
</p>
<p>
</p>
<div class="tags">
<h4>Parameters</h4>
<table>
<tr>
<td><a href="../../FFMpeg/Filters/FiltersCollection.html"><abbr title="FFMpeg\Filters\FiltersCollection">FiltersCollection</abbr></a></td>
<td>$filters</td>
<td>
</td>
</tr>
</table>
<h4>Return Value</h4>
<table>
<tr>
<td><a href="../../FFMpeg/Media/MediaTypeInterface.html"><abbr title="FFMpeg\Media\MediaTypeInterface">MediaTypeInterface</abbr></a></td>
<td>
</td>
</tr>
</table>
</div>
</div>
<h3 id="method_getFiltersCollection">
<div class="location">in <a href="../../FFMpeg/Media/AbstractMediaType.html#method_getFiltersCollection"><abbr title="FFMpeg\Media\AbstractMediaType">AbstractMediaType</abbr></a> at line 100</div>
<code> public <a href="../../FFMpeg/Media/MediaTypeInterface.html"><abbr title="FFMpeg\Media\MediaTypeInterface">MediaTypeInterface</abbr></a>
<strong>getFiltersCollection</strong>()</code>
</h3>
<div class="details">
<p>
</p>
<p>
</p>
<div class="tags">
<h4>Return Value</h4>
<table>
<tr>
<td><a href="../../FFMpeg/Media/MediaTypeInterface.html"><abbr title="FFMpeg\Media\MediaTypeInterface">MediaTypeInterface</abbr></a></td>
<td>
</td>
</tr>
</table>
</div>
</div>
<h3 id="method_getVideo">
<div class="location">at line 41</div>
<code> public <a href="../../FFMpeg/Media/Video.html"><abbr title="FFMpeg\Media\Video">Video</abbr></a>
<strong>getVideo</strong>()</code>
</h3>
<div class="details">
<p>Returns the video related to the frame.</p>
<p>
</p>
<div class="tags">
<h4>Return Value</h4>
<table>
<tr>
<td><a href="../../FFMpeg/Media/Video.html"><abbr title="FFMpeg\Media\Video">Video</abbr></a></td>
<td>
</td>
</tr>
</table>
</div>
</div>
<h3 id="method_filters">
<div class="location">at line 51</div>
<code> public <a href="../../FFMpeg/Filters/Frame/FrameFilters.html"><abbr title="FFMpeg\Filters\Frame\FrameFilters">FrameFilters</abbr></a>
<strong>filters</strong>()</code>
</h3>
<div class="details">
<p>Returns the available filters.</p>
<p>
</p>
<div class="tags">
<h4>Return Value</h4>
<table>
<tr>
<td><a href="../../FFMpeg/Filters/Frame/FrameFilters.html"><abbr title="FFMpeg\Filters\Frame\FrameFilters">FrameFilters</abbr></a></td>
<td>
</td>
</tr>
</table>
</div>
</div>
<h3 id="method_addFilter">
<div class="location">at line 61</div>
<code> public <a href="../../FFMpeg/Media/Frame.html"><abbr title="FFMpeg\Media\Frame">Frame</abbr></a>
<strong>addFilter</strong>(<a href="../../FFMpeg/Filters/Frame/FrameFilterInterface.html"><abbr title="FFMpeg\Filters\Frame\FrameFilterInterface">FrameFilterInterface</abbr></a> $filter)</code>
</h3>
<div class="details">
<p>{@inheritdoc}</p>
<p>
</p>
<div class="tags">
<h4>Parameters</h4>
<table>
<tr>
<td><a href="../../FFMpeg/Filters/Frame/FrameFilterInterface.html"><abbr title="FFMpeg\Filters\Frame\FrameFilterInterface">FrameFilterInterface</abbr></a></td>
<td>$filter</td>
<td>
</td>
</tr>
</table>
<h4>Return Value</h4>
<table>
<tr>
<td><a href="../../FFMpeg/Media/Frame.html"><abbr title="FFMpeg\Media\Frame">Frame</abbr></a></td>
<td>
</td>
</tr>
</table>
</div>
</div>
<h3 id="method_getTimeCode">
<div class="location">at line 71</div>
<code> public <a href="../../FFMpeg/Coordinate/TimeCode.html"><abbr title="FFMpeg\Coordinate\TimeCode">TimeCode</abbr></a>
<strong>getTimeCode</strong>()</code>
</h3>
<div class="details">
<p>
</p>
<p>
</p>
<div class="tags">
<h4>Return Value</h4>
<table>
<tr>
<td><a href="../../FFMpeg/Coordinate/TimeCode.html"><abbr title="FFMpeg\Coordinate\TimeCode">TimeCode</abbr></a></td>
<td>
</td>
</tr>
</table>
</div>
</div>
<h3 id="method_save">
<div class="location">at line 88</div>
<code> public <a href="../../FFMpeg/Media/Frame.html"><abbr title="FFMpeg\Media\Frame">Frame</abbr></a>
<strong>save</strong>(string $pathfile, Boolean $accurate = false)</code>
</h3>
<div class="details">
<p>Saves the frame in the given filename.</p>
<p>Uses the <code>unaccurate method by default.</code></p>
<div class="tags">
<h4>Parameters</h4>
<table>
<tr>
<td>string</td>
<td>$pathfile</td>
<td>
</td>
</tr>
<tr>
<td>Boolean</td>
<td>$accurate</td>
<td>
</td>
</tr>
</table>
<h4>Return Value</h4>
<table>
<tr>
<td><a href="../../FFMpeg/Media/Frame.html"><abbr title="FFMpeg\Media\Frame">Frame</abbr></a></td>
<td>
</td>
</tr>
</table>
<h4>Exceptions</h4>
<table>
<tr>
<td><a href="http://php.net/RuntimeException"><abbr title="RuntimeException">RuntimeException</abbr></a></td>
<td>
</td>
</tr>
</table>
</div>
</div>
</div>
<div id="footer">
Generated by <a href="http://sami.sensiolabs.org/" target="_top">Sami, the API Documentation Generator</a>.
</div>
</body>
</html>

View file

@ -1,27 +0,0 @@
<?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>

View file

@ -12,16 +12,19 @@
bootstrap="tests/bootstrap.php" bootstrap="tests/bootstrap.php"
> >
<testsuites> <testsuites>
<testsuite name="FFMpeg Tests Suite"> <testsuite name="unit">
<directory>tests/FFMpeg/Tests</directory> <directory>tests/Unit</directory>
</testsuite>
<testsuite name="functional">
<directory>tests/Functional</directory>
</testsuite> </testsuite>
</testsuites> </testsuites>
<filter> <filter>
<blacklist> <blacklist>
<directory>vendor</directory> <directory>vendor</directory>
<directory>tests</directory> <directory>tests</directory>
</blacklist> </blacklist>
</filter> </filter>
</phpunit> </phpunit>

View file

@ -16,11 +16,16 @@ class Point
private $x; private $x;
private $y; private $y;
public function __construct($x, $y) public function __construct($x, $y, $dynamic = false)
{ {
if ($dynamic) {
$this->x = $x;
$this->y = $y;
} else {
$this->x = (int)$x; $this->x = (int)$x;
$this->y = (int)$y; $this->y = (int)$y;
} }
}
/** /**
* @return integer * @return integer

View file

@ -89,4 +89,32 @@ class TimeCode
return new static($hours, $minutes, $seconds, $frames); return new static($hours, $minutes, $seconds, $frames);
} }
/**
* Returns this timecode in seconds
* @return int
*/
public function toSeconds() {
$seconds = 0;
$seconds += $this->hours * 60 * 60;
$seconds += $this->minutes * 60;
$seconds += $this->seconds;
// TODO: Handle frames?
return (int) $seconds;
}
/**
* Helper function wether `$timecode` is after this one
*
* @param TimeCode $timecode The Timecode to compare
* @return bool
*/
public function isAfter(TimeCode $timecode) {
// convert everything to seconds and compare
return ($this->toSeconds() > $timecode->toSeconds());
}
} }

View file

@ -170,6 +170,25 @@ class FFProbe
return $this->probe($pathfile, '-show_format', static::TYPE_FORMAT); return $this->probe($pathfile, '-show_format', static::TYPE_FORMAT);
} }
/**
* @api
*
* Checks wether the given `$pathfile` is considered a valid media file.
*
* @param string $pathfile
* @return bool
* @since 0.10.0
*/
public function isValid($pathfile)
{
try {
return $this->format($pathfile)->get('duration') > 0;
} catch(\Exception $e) {
// complete invalid data
return false;
}
}
/** /**
* @api * @api
* *

View file

@ -11,8 +11,6 @@
namespace FFMpeg\FFProbe\DataMapping; namespace FFMpeg\FFProbe\DataMapping;
use FFMpeg\Exception\InvalidArgumentException;
abstract class AbstractData implements \Countable abstract class AbstractData implements \Countable
{ {
private $properties; private $properties;

View file

@ -0,0 +1,58 @@
<?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\Filters\Audio;
use FFMpeg\Filters\Audio\AudioFilterInterface;
use FFMpeg\Format\AudioInterface;
use FFMpeg\Media\Audio;
class AddMetadataFilter implements AudioFilterInterface
{
/** @var Array */
private $metaArr;
/** @var Integer */
private $priority;
function __construct($metaArr = null, $priority = 9)
{
$this->metaArr = $metaArr;
$this->priority = $priority;
}
public function getPriority()
{
//must be of high priority in case theres a second input stream (artwork) to register with audio
return $this->priority;
}
public function apply(Audio $audio, AudioInterface $format)
{
$meta = $this->metaArr;
if (is_null($meta)) {
return ['-map_metadata', '-1', '-vn'];
}
$metadata = [];
if (array_key_exists("artwork", $meta)) {
array_push($metadata, "-i", $meta['artwork'], "-map", "0", "-map", "1");
unset($meta['artwork']);
}
foreach ($meta as $k => $v) {
array_push($metadata, "-metadata", "$k=$v");
}
return $metadata;
}
}

View file

@ -0,0 +1,84 @@
<?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\Filters\Audio;
use FFMpeg\Coordinate\TimeCode;
use FFMpeg\Format\AudioInterface;
use FFMpeg\Media\Audio;
class AudioClipFilter implements AudioFilterInterface {
/**
* @var TimeCode
*/
private $start;
/**
* @var TimeCode
*/
private $duration;
/**
* @var int
*/
private $priority;
public function __construct(TimeCode $start, TimeCode $duration = null, $priority = 0) {
$this->start = $start;
$this->duration = $duration;
$this->priority = $priority;
}
/**
* @inheritDoc
*/
public function getPriority() {
return $this->priority;
}
/**
* Returns the start position the audio is being cutted
*
* @return TimeCode
*/
public function getStart() {
return $this->start;
}
/**
* Returns how long the audio is being cutted. Returns null when the duration is infinite,
*
* @return TimeCode|null
*/
public function getDuration() {
return $this->duration;
}
/**
* @inheritDoc
*/
public function apply(Audio $audio, AudioInterface $format) {
$commands = array('-ss', (string) $this->start);
if ($this->duration !== null) {
$commands[] = '-t';
$commands[] = (string) $this->duration;
}
$commands[] = '-acodec';
$commands[] = 'copy';
return $commands;
}
}

View file

@ -1,8 +1,19 @@
<?php <?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\Filters\Audio; namespace FFMpeg\Filters\Audio;
use FFMpeg\Filters\Audio\AddMetadataFilter;
use FFMpeg\Media\Audio; use FFMpeg\Media\Audio;
use FFMpeg\Coordinate\TimeCode;
class AudioFilters class AudioFilters
{ {
@ -26,4 +37,38 @@ class AudioFilters
return $this; return $this;
} }
/**
* Add metadata to an audio file. If no arguments are given then filter
* will remove all metadata from the audio file
* @param Array|Null $data If array must contain one of these key/value pairs:
* - "title": Title metadata
* - "artist": Artist metadata
* - "composer": Composer metadata
* - "album": Album metadata
* - "track": Track metadata
* - "artwork": Song artwork. String of file path
* - "year": Year metadata
* - "genre": Genre metadata
* - "description": Description metadata
*/
public function addMetadata($data = null)
{
$this->media->addFilter(new AddMetadataFilter($data));
return $this;
}
/**
* Cuts the audio at `$start`, optionally define the end
*
* @param TimeCode $start Where the clipping starts(seek to time)
* @param TimeCode $duration How long the clipped audio should be
* @return AudioFilters
*/
public function clip($start, $duration = null) {
$this->media->addFilter(new AudioClipFilter($start, $duration));
return $this;
}
} }

View file

@ -1,5 +1,14 @@
<?php <?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\Filters\Audio; namespace FFMpeg\Filters\Audio;
use FFMpeg\Media\Audio; use FFMpeg\Media\Audio;

View file

@ -0,0 +1,20 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Strime <contact@strime.io>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Filters\Concat;
use FFMpeg\Filters\FilterInterface;
use FFMpeg\Media\Concat;
interface ConcatFilterInterface extends FilterInterface
{
public function apply(Concat $concat);
}

View file

@ -0,0 +1,24 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Strime <contact@strime.io>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Filters\Concat;
use FFMpeg\Media\Concat;
class ConcatFilters
{
private $concat;
public function __construct(Concat $concat)
{
$this->concat = $concat;
}
}

View file

@ -0,0 +1,20 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Strime <contact@strime.io>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Filters\Gif;
use FFMpeg\Filters\FilterInterface;
use FFMpeg\Media\Gif;
interface GifFilterInterface extends FilterInterface
{
public function apply(Gif $gif);
}

View file

@ -0,0 +1,24 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Strime <contact@strime.io>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Filters\Gif;
use FFMpeg\Media\Gif;
class GifFilters
{
private $gif;
public function __construct(Gif $gif)
{
$this->gif = $gif;
}
}

View file

@ -0,0 +1,128 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Strime <romain@strime.io>
*
* 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\Exception\InvalidArgumentException;
use FFMpeg\Exception\RuntimeException;
use FFMpeg\Media\Video;
use FFMpeg\Format\VideoInterface;
class ExtractMultipleFramesFilter implements VideoFilterInterface
{
/** will extract a frame every second */
const FRAMERATE_EVERY_SEC = '1/1';
/** will extract a frame every 2 seconds */
const FRAMERATE_EVERY_2SEC = '1/2';
/** will extract a frame every 5 seconds */
const FRAMERATE_EVERY_5SEC = '1/5';
/** will extract a frame every 10 seconds */
const FRAMERATE_EVERY_10SEC = '1/10';
/** will extract a frame every 30 seconds */
const FRAMERATE_EVERY_30SEC = '1/30';
/** will extract a frame every minute */
const FRAMERATE_EVERY_60SEC = '1/60';
/** @var integer */
private $priority;
private $frameRate;
private $destinationFolder;
public function __construct($frameRate = self::FRAMERATE_EVERY_SEC, $destinationFolder = __DIR__, $priority = 0)
{
$this->priority = $priority;
$this->frameRate = $frameRate;
// Make sure that the destination folder has a trailing slash
if(strcmp( substr($destinationFolder, -1), "/") != 0)
$destinationFolder .= "/";
// Set the destination folder
$this->destinationFolder = $destinationFolder;
}
/**
* {@inheritdoc}
*/
public function getPriority()
{
return $this->priority;
}
/**
* {@inheritdoc}
*/
public function getFrameRate()
{
return $this->frameRate;
}
/**
* {@inheritdoc}
*/
public function getDestinationFolder()
{
return $this->destinationFolder;
}
/**
* {@inheritdoc}
*/
public function apply(Video $video, VideoInterface $format)
{
$commands = array();
$duration = 0;
try {
// Get the duration of the video
foreach ($video->getStreams()->videos() as $stream) {
if ($stream->has('duration')) {
$duration = $stream->get('duration');
}
}
// Get the number of frames per second we have to extract.
if(preg_match('/(\d+)(?:\s*)([\+\-\*\/])(?:\s*)(\d+)/', $this->frameRate, $matches) !== FALSE){
$operator = $matches[2];
switch($operator){
case '/':
$nbFramesPerSecond = $matches[1] / $matches[3];
break;
default:
throw new InvalidArgumentException('The frame rate is not a proper division: ' . $this->frameRate);
break;
}
}
// Set the number of digits to use in the exported filenames
$nbImages = ceil( $duration * $nbFramesPerSecond );
if($nbImages < 100)
$nbDigitsInFileNames = "02";
elseif($nbImages < 1000)
$nbDigitsInFileNames = "03";
else
$nbDigitsInFileNames = "06";
// Set the parameters
$commands[] = '-vf';
$commands[] = 'fps=' . $this->frameRate;
$commands[] = $this->destinationFolder . 'frame-%'.$nbDigitsInFileNames.'d.jpg';
}
catch (RuntimeException $e) {
throw new RuntimeException('An error occured while extracting the frames: ' . $e->getMessage() . '. The code: ' . $e->getCode());
}
return $commands;
}
}

View file

@ -0,0 +1,59 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Strime <contact@strime.io>
*
* 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\Media\Video;
use FFMpeg\Format\VideoInterface;
class PadFilter implements VideoFilterInterface
{
/** @var Dimension */
private $dimension;
/** @var integer */
private $priority;
public function __construct(Dimension $dimension, $priority = 0)
{
$this->dimension = $dimension;
$this->priority = $priority;
}
/**
* {@inheritdoc}
*/
public function getPriority()
{
return $this->priority;
}
/**
* @return Dimension
*/
public function getDimension()
{
return $this->dimension;
}
/**
* {@inheritdoc}
*/
public function apply(Video $video, VideoInterface $format)
{
$commands = array();
$commands[] = '-vf';
$commands[] = 'scale=iw*min(' . $this->dimension->getWidth() . '/iw\,' . $this->dimension->getHeight() .'/ih):ih*min(' . $this->dimension->getWidth() . '/iw\,' . $this->dimension->getHeight() .'/ih),pad=' . $this->dimension->getWidth() . ':' . $this->dimension->getHeight() . ':(' . $this->dimension->getWidth() . '-iw)/2:(' . $this->dimension->getHeight() .'-ih)/2';
return $commands;
}
}

View file

@ -98,8 +98,10 @@ class ResizeFilter implements VideoFilterInterface
if (null !== $dimensions) { if (null !== $dimensions) {
$dimensions = $this->getComputedDimensions($dimensions, $format->getModulus()); $dimensions = $this->getComputedDimensions($dimensions, $format->getModulus());
$commands[] = '-s'; // Using Filter to have ordering
$commands[] = $dimensions->getWidth() . 'x' . $dimensions->getHeight(); $commands[] = '-vf';
$commands[] = '[in]scale=' . $dimensions->getWidth() . ':' . $dimensions->getHeight() . ' [out]';
} }
return $commands; return $commands;

View file

@ -46,7 +46,7 @@ class VideoFilters extends AudioFilters
* Changes the video framerate. * Changes the video framerate.
* *
* @param FrameRate $framerate * @param FrameRate $framerate
* @param type $gop * @param Integer $gop
* *
* @return VideoFilters * @return VideoFilters
*/ */
@ -57,6 +57,21 @@ class VideoFilters extends AudioFilters
return $this; return $this;
} }
/**
* Extract multiple frames from the video
*
* @param string $frameRate
* @param string $destinationFolder
*
* @return $this
*/
public function extractMultipleFrames($frameRate = ExtractMultipleFramesFilter::FRAMERATE_EVERY_2SEC, $destinationFolder = __DIR__)
{
$this->media->addFilter(new ExtractMultipleFramesFilter($frameRate, $destinationFolder));
return $this;
}
/** /**
* Synchronizes audio and video. * Synchronizes audio and video.
* *
@ -98,6 +113,20 @@ class VideoFilters extends AudioFilters
return $this; return $this;
} }
/**
* Adds padding (black bars) to a video.
*
* @param Dimension $dimension
*
* @return VideoFilters
*/
public function pad(Dimension $dimension)
{
$this->media->addFilter(new PadFilter($dimension));
return $this;
}
public function rotate($angle) public function rotate($angle)
{ {
$this->media->addFilter(new RotateFilter($angle, 30)); $this->media->addFilter(new RotateFilter($angle, 30));
@ -132,4 +161,18 @@ class VideoFilters extends AudioFilters
return $this; return $this;
} }
/**
* Applies a custom filter: -vf foo bar
*
* @param string $parameters
*
* @return VideoFilters
*/
public function custom($parameters)
{
$this->media->addFilter(new CustomFilter($parameters));
return $this;
}
} }

View file

@ -0,0 +1,74 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Strime <contact@strime.io>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Filters\Waveform;
use FFMpeg\Exception\RuntimeException;
use FFMpeg\Media\Waveform;
class WaveformDownmixFilter implements WaveformFilterInterface
{
/** @var boolean */
private $downmix;
/** @var integer */
private $priority;
// By default, the downmix value is set to FALSE.
public function __construct($downmix = FALSE, $priority = 0)
{
$this->downmix = $downmix;
$this->priority = $priority;
}
/**
* {@inheritdoc}
*/
public function getDownmix()
{
return $this->downmix;
}
/**
* {@inheritdoc}
*/
public function getPriority()
{
return $this->priority;
}
/**
* {@inheritdoc}
*/
public function apply(Waveform $waveform)
{
$commands = array();
foreach ($waveform->getAudio()->getStreams() as $stream) {
if ($stream->isAudio()) {
try {
// If the downmix parameter is set to TRUE, we add an option to the FFMPEG command
if($this->downmix == TRUE) {
$commands[] = '"aformat=channel_layouts=mono"';
}
break;
} catch (RuntimeException $e) {
}
}
}
return $commands;
}
}

View file

@ -0,0 +1,20 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Strime <contact@strime.io>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Filters\Waveform;
use FFMpeg\Filters\FilterInterface;
use FFMpeg\Media\Waveform;
interface WaveformFilterInterface extends FilterInterface
{
public function apply(Waveform $waveform);
}

View file

@ -0,0 +1,38 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Strime <contact@strime.io>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Filters\Waveform;
use FFMpeg\Media\Waveform;
class WaveformFilters
{
private $waveform;
public function __construct(Waveform $waveform)
{
$this->waveform = $waveform;
}
/**
* Sets the downmix of the output waveform.
*
* If you want a simpler waveform, sets the downmix to TRUE.
*
* @return WaveformFilters
*/
public function setDownmix()
{
$this->waveform->addFilter(new WaveformDownmixFilter());
return $this;
}
}

View file

@ -121,10 +121,10 @@ abstract class DefaultAudio extends EventEmitter implements AudioInterface, Prog
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function createProgressListener(MediaTypeInterface $media, FFProbe $ffprobe, $pass, $total) public function createProgressListener(MediaTypeInterface $media, FFProbe $ffprobe, $pass, $total, $duration = 0)
{ {
$format = $this; $format = $this;
$listener = new AudioProgressListener($ffprobe, $media->getPathfile(), $pass, $total); $listener = new AudioProgressListener($ffprobe, $media->getPathfile(), $pass, $total, $duration);
$listener->on('progress', function () use ($media, $format) { $listener->on('progress', function () use ($media, $format) {
$format->emit('progress', array_merge(array($media, $format), func_get_args())); $format->emit('progress', array_merge(array($media, $format), func_get_args()));
}); });

View file

@ -80,12 +80,13 @@ abstract class AbstractProgressListener extends EventEmitter implements Listener
* *
* @throws RuntimeException * @throws RuntimeException
*/ */
public function __construct(FFProbe $ffprobe, $pathfile, $currentPass, $totalPass) public function __construct(FFProbe $ffprobe, $pathfile, $currentPass, $totalPass, $duration = 0)
{ {
$this->ffprobe = $ffprobe; $this->ffprobe = $ffprobe;
$this->pathfile = $pathfile; $this->pathfile = $pathfile;
$this->currentPass = $currentPass; $this->currentPass = $currentPass;
$this->totalPass = $totalPass; $this->totalPass = $totalPass;
$this->duration = $duration;
} }
/** /**
@ -120,6 +121,14 @@ abstract class AbstractProgressListener extends EventEmitter implements Listener
return $this->totalPass; return $this->totalPass;
} }
/**
* @return int
*/
public function getCurrentTime()
{
return $this->currentTime;
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
@ -171,6 +180,12 @@ abstract class AbstractProgressListener extends EventEmitter implements Listener
if ($this->lastOutput !== null) { if ($this->lastOutput !== null) {
$delta = $currentTime - $this->lastOutput; $delta = $currentTime - $this->lastOutput;
// Check the type of the currentSize variable and convert it to an integer if needed.
if(!is_numeric($currentSize)) {
$currentSize = (int)$currentSize;
}
$deltaSize = $currentSize - $this->currentSize; $deltaSize = $currentSize - $this->currentSize;
$rate = $deltaSize * $delta; $rate = $deltaSize * $delta;
if ($rate > 0) { if ($rate > 0) {
@ -240,9 +255,8 @@ abstract class AbstractProgressListener extends EventEmitter implements Listener
return; return;
} }
$this->totalSize = $format->get('size') / 1024; $this->duration = (int) $this->duration > 0 ? $this->duration : $format->get('duration');
$this->duration = $format->get('duration'); $this->totalSize = $format->get('size') / 1024 * ($this->duration / $format->get('duration'));
$this->initialized = true; $this->initialized = true;
} }
} }

View file

@ -24,8 +24,9 @@ interface ProgressableInterface extends EventEmitterInterface
* @param FFProbe $ffprobe * @param FFProbe $ffprobe
* @param Integer $pass The current pas snumber * @param Integer $pass The current pas snumber
* @param Integer $total The total pass number * @param Integer $total The total pass number
* @param Integer $duration The new video duration
* *
* @return array An array of listeners * @return array An array of listeners
*/ */
public function createProgressListener(MediaTypeInterface $media, FFProbe $ffprobe, $pass, $total); public function createProgressListener(MediaTypeInterface $media, FFProbe $ffprobe, $pass, $total, $duration = 0);
} }

View file

@ -32,6 +32,9 @@ abstract class DefaultVideo extends DefaultAudio implements VideoInterface
/** @var Integer */ /** @var Integer */
protected $modulus = 16; protected $modulus = 16;
/** @var Array */
protected $additionalParamaters;
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
@ -97,10 +100,35 @@ abstract class DefaultVideo extends DefaultAudio implements VideoInterface
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function createProgressListener(MediaTypeInterface $media, FFProbe $ffprobe, $pass, $total) public function getAdditionalParameters()
{
return $this->additionalParamaters;
}
/**
* Sets additional parameters.
*
* @param array $additionalParamaters
* @throws InvalidArgumentException
*/
public function setAdditionalParameters($additionalParamaters)
{
if (!is_array($additionalParamaters)) {
throw new InvalidArgumentException('Wrong additionalParamaters value');
}
$this->additionalParamaters = $additionalParamaters;
return $this;
}
/**
* {@inheritdoc}
*/
public function createProgressListener(MediaTypeInterface $media, FFProbe $ffprobe, $pass, $total, $duration = 0)
{ {
$format = $this; $format = $this;
$listeners = array(new VideoProgressListener($ffprobe, $media->getPathfile(), $pass, $total)); $listeners = array(new VideoProgressListener($ffprobe, $media->getPathfile(), $pass, $total, $duration));
foreach ($listeners as $listener) { foreach ($listeners as $listener) {
$listener->on('progress', function () use ($format, $media) { $listener->on('progress', function () use ($format, $media) {

View file

@ -52,6 +52,6 @@ class WebM extends DefaultVideo
*/ */
public function getAvailableVideoCodecs() public function getAvailableVideoCodecs()
{ {
return array('libvpx'); return array('libvpx', 'libvpx-vp9');
} }
} }

View file

@ -19,6 +19,9 @@ class X264 extends DefaultVideo
/** @var boolean */ /** @var boolean */
private $bframesSupport = true; private $bframesSupport = true;
/** @var integer */
private $passes = 2;
public function __construct($audioCodec = 'libfaac', $videoCodec = 'libx264') public function __construct($audioCodec = 'libfaac', $videoCodec = 'libx264')
{ {
$this $this
@ -51,7 +54,7 @@ class X264 extends DefaultVideo
*/ */
public function getAvailableAudioCodecs() public function getAvailableAudioCodecs()
{ {
return array('libvo_aacenc', 'libfaac', 'libmp3lame', 'libfdk_aac'); return array('aac', 'libvo_aacenc', 'libfaac', 'libmp3lame', 'libfdk_aac');
} }
/** /**
@ -62,12 +65,23 @@ class X264 extends DefaultVideo
return array('libx264'); return array('libx264');
} }
/**
* @param $passes
*
* @return X264
*/
public function setPasses($passes)
{
$this->passes = $passes;
return $this;
}
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function getPasses() public function getPasses()
{ {
return 2; return $this->passes;
} }
/** /**

View file

@ -54,4 +54,11 @@ interface VideoInterface extends AudioInterface
* @return array * @return array
*/ */
public function getAvailableVideoCodecs(); public function getAvailableVideoCodecs();
/**
* Returns the list of available video codecs for this format.
*
* @return array
*/
public function getAdditionalParameters();
} }

View file

@ -54,9 +54,7 @@ class Audio extends AbstractStreamableMedia
* *
* @param FormatInterface $format * @param FormatInterface $format
* @param string $outputPathfile * @param string $outputPathfile
*
* @return Audio * @return Audio
*
* @throws RuntimeException * @throws RuntimeException
*/ */
public function save(FormatInterface $format, $outputPathfile) public function save(FormatInterface $format, $outputPathfile)
@ -64,9 +62,42 @@ class Audio extends AbstractStreamableMedia
$listeners = null; $listeners = null;
if ($format instanceof ProgressableInterface) { if ($format instanceof ProgressableInterface) {
$listeners = $format->createProgressListener($this, $this->ffprobe, 1, 1); $listeners = $format->createProgressListener($this, $this->ffprobe, 1, 1, 0);
} }
$commands = $this->buildCommand($format, $outputPathfile);
try {
$this->driver->command($commands, false, $listeners);
} catch (ExecutionFailureException $e) {
$this->cleanupTemporaryFile($outputPathfile);
throw new RuntimeException('Encoding failed', $e->getCode(), $e);
}
return $this;
}
/**
* Returns the final command as a string, useful for debugging purposes.
*
* @param FormatInterface $format
* @param string $outputPathfile
* @return string
* @since 0.11.0
*/
public function getFinalCommand(FormatInterface $format, $outputPathfile) {
return implode(' ', $this->buildCommand($format, $outputPathfile));
}
/**
* Builds the command which will be executed with the provided format
*
* @param FormatInterface $format
* @param string $outputPathfile
* @return string[] An array which are the components of the command
* @since 0.11.0
*/
protected function buildCommand(FormatInterface $format, $outputPathfile) {
$commands = array('-y', '-i', $this->pathfile); $commands = array('-y', '-i', $this->pathfile);
$filters = clone $this->filters; $filters = clone $this->filters;
@ -93,13 +124,19 @@ class Audio extends AbstractStreamableMedia
} }
$commands[] = $outputPathfile; $commands[] = $outputPathfile;
try { return $commands;
$this->driver->command($commands, false, $listeners);
} catch (ExecutionFailureException $e) {
$this->cleanupTemporaryFile($outputPathfile);
throw new RuntimeException('Encoding failed', $e->getCode(), $e);
} }
return $this; /**
* Gets the waveform of the video.
*
* @param integer $width
* @param integer $height
* @param array $colors Array of colors for ffmpeg to use. Color format is #000000 (RGB hex string with #)
* @return Waveform
*/
public function waveform($width = 640, $height = 120, $colors = array(Waveform::DEFAULT_COLOR))
{
return new Waveform($this, $this->driver, $this->ffprobe, $width, $height, $colors);
} }
} }

264
src/FFMpeg/Media/Concat.php Normal file
View file

@ -0,0 +1,264 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Strime <contact@strime.io>
*
* 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 Alchemy\BinaryDriver\Exception\InvalidArgumentException;
use FFMpeg\Filters\Concat\ConcatFilterInterface;
use FFMpeg\Filters\Concat\ConcatFilters;
use FFMpeg\Driver\FFMpegDriver;
use FFMpeg\FFProbe;
use FFMpeg\Filters\Audio\SimpleFilter;
use FFMpeg\Exception\RuntimeException;
use FFMpeg\Format\FormatInterface;
use FFMpeg\Filters\FilterInterface;
use FFMpeg\Format\ProgressableInterface;
use FFMpeg\Format\AudioInterface;
use FFMpeg\Format\VideoInterface;
use Neutron\TemporaryFilesystem\Manager as FsManager;
class Concat extends AbstractMediaType
{
/** @var array */
private $sources;
public function __construct($sources, FFMpegDriver $driver, FFProbe $ffprobe)
{
parent::__construct($sources, $driver, $ffprobe);
$this->sources = $sources;
}
/**
* Returns the path to the sources.
*
* @return string
*/
public function getSources()
{
return $this->sources;
}
/**
* {@inheritdoc}
*
* @return ConcatFilters
*/
public function filters()
{
return new ConcatFilters($this);
}
/**
* {@inheritdoc}
*
* @return Concat
*/
public function addFilter(ConcatFilterInterface $filter)
{
$this->filters->add($filter);
return $this;
}
/**
* Saves the concatenated video in the given array, considering that the sources videos are all encoded with the same codec.
*
* @param array $outputPathfile
* @param string $streamCopy
*
* @return Concat
*
* @throws RuntimeException
*/
public function saveFromSameCodecs($outputPathfile, $streamCopy = TRUE)
{
/**
* @see https://ffmpeg.org/ffmpeg-formats.html#concat
* @see https://trac.ffmpeg.org/wiki/Concatenate
*/
// Create the file which will contain the list of videos
$fs = FsManager::create();
$sourcesFile = $fs->createTemporaryFile('ffmpeg-concat');
// Set the content of this file
$fileStream = @fopen($sourcesFile, 'w');
if($fileStream === false) {
throw new ExecutionFailureException('Cannot open the temporary file.');
}
$count_videos = 0;
if(is_array($this->sources) && (count($this->sources) > 0)) {
foreach ($this->sources as $videoPath) {
$line = "";
if($count_videos != 0)
$line .= "\n";
$line .= "file ".$videoPath;
fwrite($fileStream, $line);
$count_videos++;
}
}
else {
throw new InvalidArgumentException('The list of videos is not a valid array.');
}
fclose($fileStream);
$commands = array(
'-f', 'concat', '-safe', '0',
'-i', $sourcesFile
);
// Check if stream copy is activated
if($streamCopy === TRUE) {
$commands[] = '-c';
$commands[] = 'copy';
}
// If not, we can apply filters
else {
foreach ($this->filters as $filter) {
$commands = array_merge($commands, $filter->apply($this));
}
}
// Set the output file in the command
$commands = array_merge($commands, array($outputPathfile));
// Execute the command
try {
$this->driver->command($commands);
} catch (ExecutionFailureException $e) {
$this->cleanupTemporaryFile($outputPathfile);
$this->cleanupTemporaryFile($sourcesFile);
throw new RuntimeException('Unable to save concatenated video', $e->getCode(), $e);
}
$this->cleanupTemporaryFile($sourcesFile);
return $this;
}
/**
* Saves the concatenated video in the given filename, considering that the sources videos are all encoded with the same codec.
*
* @param string $outputPathfile
*
* @return Concat
*
* @throws RuntimeException
*/
public function saveFromDifferentCodecs(FormatInterface $format, $outputPathfile)
{
/**
* @see https://ffmpeg.org/ffmpeg-formats.html#concat
* @see https://trac.ffmpeg.org/wiki/Concatenate
*/
// Check the validity of the parameter
if(!is_array($this->sources) || (count($this->sources) == 0)) {
throw new InvalidArgumentException('The list of videos is not a valid array.');
}
// Create the commands variable
$commands = array();
// Prepare the parameters
$nbSources = 0;
$files = array();
// For each source, check if this is a legit file
// and prepare the parameters
foreach ($this->sources as $videoPath) {
$files[] = '-i';
$files[] = $videoPath;
$nbSources++;
}
$commands = array_merge($commands, $files);
// Set the parameters of the request
$commands[] = '-filter_complex';
$complex_filter = '';
for($i=0; $i<$nbSources; $i++) {
$complex_filter .= '['.$i.':v:0] ['.$i.':a:0] ';
}
$complex_filter .= 'concat=n='.$nbSources.':v=1:a=1 [v] [a]';
$commands[] = $complex_filter;
$commands[] = '-map';
$commands[] = '[v]';
$commands[] = '-map';
$commands[] = '[a]';
// Prepare the filters
$filters = clone $this->filters;
$filters->add(new SimpleFilter($format->getExtraParams(), 10));
if ($this->driver->getConfiguration()->has('ffmpeg.threads')) {
$filters->add(new SimpleFilter(array('-threads', $this->driver->getConfiguration()->get('ffmpeg.threads'))));
}
if ($format instanceof VideoInterface) {
if (null !== $format->getVideoCodec()) {
$filters->add(new SimpleFilter(array('-vcodec', $format->getVideoCodec())));
}
}
if ($format instanceof AudioInterface) {
if (null !== $format->getAudioCodec()) {
$filters->add(new SimpleFilter(array('-acodec', $format->getAudioCodec())));
}
}
// Add the filters
foreach ($this->filters as $filter) {
$commands = array_merge($commands, $filter->apply($this));
}
if ($format instanceof AudioInterface) {
if (null !== $format->getAudioKiloBitrate()) {
$commands[] = '-b:a';
$commands[] = $format->getAudioKiloBitrate() . 'k';
}
if (null !== $format->getAudioChannels()) {
$commands[] = '-ac';
$commands[] = $format->getAudioChannels();
}
}
// If the user passed some additional parameters
if ($format instanceof VideoInterface) {
if (null !== $format->getAdditionalParameters()) {
foreach ($format->getAdditionalParameters() as $additionalParameter) {
$commands[] = $additionalParameter;
}
}
}
// Set the output file in the command
$commands[] = $outputPathfile;
$failure = null;
try {
$this->driver->command($commands);
} catch (ExecutionFailureException $e) {
throw new RuntimeException('Encoding failed', $e->getCode(), $e);
}
return $this;
}
}

View file

@ -85,27 +85,32 @@ class Frame extends AbstractMediaType
* *
* @throws RuntimeException * @throws RuntimeException
*/ */
public function save($pathfile, $accurate = false) public function save($pathfile, $accurate = false, $returnBase64 = false)
{ {
/** /**
* might be optimized with http://ffmpeg.org/trac/ffmpeg/wiki/Seeking%20with%20FFmpeg * might be optimized with http://ffmpeg.org/trac/ffmpeg/wiki/Seeking%20with%20FFmpeg
* @see http://ffmpeg.org/ffmpeg.html#Main-options * @see http://ffmpeg.org/ffmpeg.html#Main-options
*/ */
$outputFormat = $returnBase64 ? "image2pipe" : "image2";
if (!$accurate) { if (!$accurate) {
$commands = array( $commands = array(
'-y', '-ss', (string) $this->timecode, '-y', '-ss', (string) $this->timecode,
'-i', $this->pathfile, '-i', $this->pathfile,
'-vframes', '1', '-vframes', '1',
'-f', 'image2' '-f', $outputFormat
); );
} else { } else {
$commands = array( $commands = array(
'-y', '-i', $this->pathfile, '-y', '-i', $this->pathfile,
'-vframes', '1', '-ss', (string) $this->timecode, '-vframes', '1', '-ss', (string) $this->timecode,
'-f', 'image2' '-f', $outputFormat
); );
} }
if($returnBase64) {
array_push($commands, "-");
}
foreach ($this->filters as $filter) { foreach ($this->filters as $filter) {
$commands = array_merge($commands, $filter->apply($this)); $commands = array_merge($commands, $filter->apply($this));
} }
@ -113,12 +118,16 @@ class Frame extends AbstractMediaType
$commands = array_merge($commands, array($pathfile)); $commands = array_merge($commands, array($pathfile));
try { try {
if(!$returnBase64) {
$this->driver->command($commands); $this->driver->command($commands);
return $this;
}
else {
return $this->driver->command($commands);
}
} catch (ExecutionFailureException $e) { } catch (ExecutionFailureException $e) {
$this->cleanupTemporaryFile($pathfile); $this->cleanupTemporaryFile($pathfile);
throw new RuntimeException('Unable to save frame', $e->getCode(), $e); throw new RuntimeException('Unable to save frame', $e->getCode(), $e);
} }
return $this;
} }
} }

137
src/FFMpeg/Media/Gif.php Normal file
View file

@ -0,0 +1,137 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Strime <contact@strime.io>
*
* 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\Gif\GifFilterInterface;
use FFMpeg\Filters\Gif\GifFilters;
use FFMpeg\Driver\FFMpegDriver;
use FFMpeg\FFProbe;
use FFMpeg\Exception\RuntimeException;
use FFMpeg\Coordinate\TimeCode;
use FFMpeg\Coordinate\Dimension;
class Gif extends AbstractMediaType
{
/** @var TimeCode */
private $timecode;
/** @var Dimension */
private $dimension;
/** @var integer */
private $duration;
/** @var Video */
private $video;
public function __construct(Video $video, FFMpegDriver $driver, FFProbe $ffprobe, TimeCode $timecode, Dimension $dimension, $duration = null)
{
parent::__construct($video->getPathfile(), $driver, $ffprobe);
$this->timecode = $timecode;
$this->dimension = $dimension;
$this->duration = $duration;
$this->video = $video;
}
/**
* Returns the video related to the gif.
*
* @return Video
*/
public function getVideo()
{
return $this->video;
}
/**
* {@inheritdoc}
*
* @return GifFilters
*/
public function filters()
{
return new GifFilters($this);
}
/**
* {@inheritdoc}
*
* @return Gif
*/
public function addFilter(GifFilterInterface $filter)
{
$this->filters->add($filter);
return $this;
}
/**
* @return TimeCode
*/
public function getTimeCode()
{
return $this->timecode;
}
/**
* @return Dimension
*/
public function getDimension()
{
return $this->dimension;
}
/**
* Saves the gif in the given filename.
*
* @param string $pathfile
*
* @return Gif
*
* @throws RuntimeException
*/
public function save($pathfile)
{
/**
* @see http://ffmpeg.org/ffmpeg.html#Main-options
*/
$commands = array(
'-ss', (string)$this->timecode
);
if(null !== $this->duration) {
$commands[] = '-t';
$commands[] = (string)$this->duration;
}
$commands[] = '-i';
$commands[] = $this->pathfile;
$commands[] = '-vf';
$commands[] = 'scale=' . $this->dimension->getWidth() . ':-1';
$commands[] = '-gifflags';
$commands[] = '+transdiff';
$commands[] = '-y';
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 gif', $e->getCode(), $e);
}
return $this;
}
}

View file

@ -13,6 +13,7 @@ namespace FFMpeg\Media;
use Alchemy\BinaryDriver\Exception\ExecutionFailureException; use Alchemy\BinaryDriver\Exception\ExecutionFailureException;
use FFMpeg\Coordinate\TimeCode; use FFMpeg\Coordinate\TimeCode;
use FFMpeg\Coordinate\Dimension;
use FFMpeg\Filters\Audio\SimpleFilter; use FFMpeg\Filters\Audio\SimpleFilter;
use FFMpeg\Exception\InvalidArgumentException; use FFMpeg\Exception\InvalidArgumentException;
use FFMpeg\Exception\RuntimeException; use FFMpeg\Exception\RuntimeException;
@ -23,12 +24,24 @@ use FFMpeg\Format\ProgressableInterface;
use FFMpeg\Format\AudioInterface; use FFMpeg\Format\AudioInterface;
use FFMpeg\Format\VideoInterface; use FFMpeg\Format\VideoInterface;
use Neutron\TemporaryFilesystem\Manager as FsManager; use Neutron\TemporaryFilesystem\Manager as FsManager;
use FFMpeg\Filters\Video\ClipFilter;
class Video extends Audio class Video extends Audio
{ {
/** /**
* {@inheritdoc} * FileSystem Manager instance
* * @var Manager
*/
protected $fs;
/**
* FileSystem Manager ID
* @var int
*/
protected $fsId;
/**
* @inheritDoc
* @return VideoFilters * @return VideoFilters
*/ */
public function filters() public function filters()
@ -37,8 +50,7 @@ class Video extends Audio
} }
/** /**
* {@inheritdoc} * @inheritDoc
*
* @return Video * @return Video
*/ */
public function addFilter(FilterInterface $filter) public function addFilter(FilterInterface $filter)
@ -53,13 +65,76 @@ class Video extends Audio
* *
* @param FormatInterface $format * @param FormatInterface $format
* @param string $outputPathfile * @param string $outputPathfile
*
* @return Video * @return Video
*
* @throws RuntimeException * @throws RuntimeException
*/ */
public function save(FormatInterface $format, $outputPathfile) public function save(FormatInterface $format, $outputPathfile)
{ {
$passes = $this->buildCommand($format, $outputPathfile);
$failure = null;
$totalPasses = $format->getPasses();
foreach ($passes as $pass => $passCommands) {
try {
/** add listeners here */
$listeners = null;
if ($format instanceof ProgressableInterface) {
$filters = clone $this->filters;
$duration = 0;
// check the filters of the video, and if the video has the ClipFilter then
// take the new video duration and send to the
// FFMpeg\Format\ProgressListener\AbstractProgressListener class
foreach ($filters as $filter) {
if($filter instanceof ClipFilter){
$duration = $filter->getDuration()->toSeconds();
break;
}
}
$listeners = $format->createProgressListener($this, $this->ffprobe, $pass + 1, $totalPasses, $duration);
}
$this->driver->command($passCommands, false, $listeners);
} catch (ExecutionFailureException $e) {
$failure = $e;
break;
}
}
$this->fs->clean($this->fsId);
if (null !== $failure) {
throw new RuntimeException('Encoding failed', $failure->getCode(), $failure);
}
return $this;
}
/**
* NOTE: This method is different to the Audio's one, because Video is using passes.
* @inheritDoc
*/
public function getFinalCommand(FormatInterface $format, $outputPathfile) {
$finalCommands = array();
foreach($this->buildCommand($format, $outputPathfile) as $pass => $passCommands) {
$finalCommands[] = implode(' ', $passCommands);
}
$this->fs->clean($this->fsId);
return $finalCommands;
}
/**
* **NOTE:** This creates passes instead of a single command!
*
* @inheritDoc
* @return string[][]
*/
protected function buildCommand(FormatInterface $format, $outputPathfile) {
$commands = array('-y', '-i', $this->pathfile); $commands = array('-y', '-i', $this->pathfile);
$filters = clone $this->filters; $filters = clone $this->filters;
@ -119,13 +194,72 @@ class Video extends Audio
} }
} }
$fs = FsManager::create(); // If the user passed some additional parameters
$fsId = uniqid('ffmpeg-passes'); if ($format instanceof VideoInterface) {
$passPrefix = $fs->createTemporaryDirectory(0777, 50, $fsId) . '/' . uniqid('pass-'); if (null !== $format->getAdditionalParameters()) {
foreach ($format->getAdditionalParameters() as $additionalParameter) {
$commands[] = $additionalParameter;
}
}
}
// Merge Filters into one command
$videoFilterVars = $videoFilterProcesses = array();
for($i=0;$i<count($commands);$i++) {
$command = $commands[$i];
if ( $command == '-vf' ) {
$commandSplits = explode(";", $commands[$i + 1]);
if ( count($commandSplits) == 1 ) {
$commandSplit = $commandSplits[0];
$command = trim($commandSplit);
if ( preg_match("/^\[in\](.*?)\[out\]$/is", $command, $match) ) {
$videoFilterProcesses[] = $match[1];
} else {
$videoFilterProcesses[] = $command;
}
} else {
foreach($commandSplits as $commandSplit) {
$command = trim($commandSplit);
if ( preg_match("/^\[[^\]]+\](.*?)\[[^\]]+\]$/is", $command, $match) ) {
$videoFilterProcesses[] = $match[1];
} else {
$videoFilterVars[] = $command;
}
}
}
unset($commands[$i]);
unset($commands[$i + 1]);
$i++;
}
}
$videoFilterCommands = $videoFilterVars;
$lastInput = 'in';
foreach($videoFilterProcesses as $i => $process) {
$command = '[' . $lastInput .']';
$command .= $process;
$lastInput = 'p' . $i;
if($i === (count($videoFilterProcesses) - 1)) {
$command .= '[out]';
} else {
$command .= '[' . $lastInput . ']';
}
$videoFilterCommands[] = $command;
}
$videoFilterCommand = implode(';', $videoFilterCommands);
if($videoFilterCommand) {
$commands[] = '-vf';
$commands[] = $videoFilterCommand;
}
$this->fs = FsManager::create();
$this->fsId = uniqid('ffmpeg-passes');
$passPrefix = $this->fs->createTemporaryDirectory(0777, 50, $this->fsId) . '/' . uniqid('pass-');
$passes = array(); $passes = array();
$totalPasses = $format->getPasses(); $totalPasses = $format->getPasses();
if (1 > $totalPasses) { if(!$totalPasses) {
throw new InvalidArgumentException('Pass number should be a positive value.'); throw new InvalidArgumentException('Pass number should be a positive value.');
} }
@ -144,31 +278,7 @@ class Video extends Audio
$passes[] = $pass; $passes[] = $pass;
} }
$failure = null; return $passes;
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;
} }
/** /**
@ -181,4 +291,28 @@ class Video extends Audio
{ {
return new Frame($this, $this->driver, $this->ffprobe, $at); return new Frame($this, $this->driver, $this->ffprobe, $at);
} }
/**
* Extracts a gif from a sequence of the video.
*
* @param TimeCode $at
* @param Dimension $dimension
* @param integer $duration
* @return Gif
*/
public function gif(TimeCode $at, Dimension $dimension, $duration = null)
{
return new Gif($this, $this->driver, $this->ffprobe, $at, $dimension, $duration);
}
/**
* Concatenates a list of videos into one unique video.
*
* @param array $sources
* @return Concat
*/
public function concat($sources)
{
return new Concat($sources, $this->driver, $this->ffprobe);
}
} }

View file

@ -0,0 +1,163 @@
<?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\Exception\InvalidArgumentException;
use FFMpeg\Filters\Waveform\WaveformFilterInterface;
use FFMpeg\Filters\Waveform\WaveformFilters;
use FFMpeg\Driver\FFMpegDriver;
use FFMpeg\FFProbe;
use FFMpeg\Exception\RuntimeException;
class Waveform extends AbstractMediaType
{
const DEFAULT_COLOR = '#000000';
/** @var Video */
protected $audio;
protected $width;
protected $height;
/**
* @var array
*/
protected $colors;
public function __construct(Audio $audio, FFMpegDriver $driver, FFProbe $ffprobe, $width, $height, $colors = array(self::DEFAULT_COLOR))
{
parent::__construct($audio->getPathfile(), $driver, $ffprobe);
$this->audio = $audio;
$this->width = $width;
$this->height = $height;
$this->setColors($colors);
}
/**
* Returns the audio related to the waveform.
*
* @return Audio
*/
public function getAudio()
{
return $this->audio;
}
/**
* {@inheritdoc}
*
* @return WaveformFilters
*/
public function filters()
{
return new WaveformFilters($this);
}
/**
* {@inheritdoc}
*
* @return Waveform
*/
public function addFilter(WaveformFilterInterface $filter)
{
$this->filters->add($filter);
return $this;
}
/**
* Parameter should be an array containing at least one valid color represented as a HTML color string. For
* example #FFFFFF or #000000. By default the color is set to black. Keep in mind that if you save the waveform
* as jpg file, it will appear completely black and to avoid this you can set the waveform color to white (#FFFFFF).
* Saving waveforms to png is strongly suggested.
*
* @param array $colors
*/
public function setColors(array $colors)
{
foreach ($colors as $row => $value)
{
if (!preg_match('/^#(?:[0-9a-fA-F]{6})$/', $value))
{
//invalid color
//unset($colors[$row]);
throw new InvalidArgumentException("The provided color '$value' is invalid");
}
}
if (count($colors))
{
$this->colors = $colors;
}
}
/**
* Returns an array of colors that will be passed to ffmpeg to use for waveform generation. Colors are applied ONLY
* to the waveform. Background cannot be controlled that easily and it is probably easier to save the waveform
* as a transparent png file and then add background of choice.
*
* @return array
*/
public function getColors()
{
return $this->colors;
}
/**
* Compiles the selected colors into a string, using a pipe separator.
*
* @return string
*/
protected function compileColors()
{
return implode('|', $this->colors);
}
/**
* Saves the waveform in the given filename.
*
* @param string $pathfile
*
* @return Waveform
*
* @throws RuntimeException
*/
public function save($pathfile)
{
/**
* might be optimized with http://ffmpeg.org/trac/ffmpeg/wiki/Seeking%20with%20FFmpeg
* @see http://ffmpeg.org/ffmpeg.html#Main-options
*/
$commands = array(
'-y', '-i', $this->pathfile, '-filter_complex',
'showwavespic=colors='.$this->compileColors().':s='.$this->width.'x'.$this->height,
'-frames:v', '1'
);
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 waveform', $e->getCode(), $e);
}
return $this;
}
}

View file

@ -1,16 +0,0 @@
<?php
namespace FFMpeg\Tests\Coordinate;
use FFMpeg\Tests\TestCase;
use FFMpeg\Coordinate\Point;
class PointTest extends TestCase
{
public function testGetters()
{
$point = new Point(4, 25);
$this->assertEquals(4, $point->getX());
$this->assertEquals(25, $point->getY());
}
}

View file

@ -1,6 +1,6 @@
<?php <?php
namespace FFMpeg\Functional; namespace Tests\FFMpeg\Functional;
use FFMpeg\FFProbe; use FFMpeg\FFProbe;
@ -9,13 +9,26 @@ class FFProbeTest extends FunctionalTestCase
public function testProbeOnFile() public function testProbeOnFile()
{ {
$ffprobe = FFProbe::create(); $ffprobe = FFProbe::create();
$this->assertGreaterThan(0, count($ffprobe->streams(__DIR__ . '/../../files/Audio.mp3'))); $this->assertGreaterThan(0, count($ffprobe->streams(__DIR__ . '/../files/Audio.mp3')));
}
public function testValidateExistingFile()
{
$ffprobe = FFProbe::create();
$this->assertTrue($ffprobe->isValid(__DIR__ . '/../files/sample.3gp'));
}
public function testValidateNonExistingFile()
{
$ffprobe = FFProbe::create();
$this->assertFalse($ffprobe->isValid(__DIR__ . '/../files/WrongFile.mp4'));
} }
/** /**
* @expectedException FFMpeg\Exception\RuntimeException * @expectedException FFMpeg\Exception\RuntimeException
*/ */
public function testProbeOnUnexistantFile() public function testProbeOnNonExistantFile()
{ {
$ffprobe = FFProbe::create(); $ffprobe = FFProbe::create();
$ffprobe->streams('/path/to/no/file'); $ffprobe->streams('/path/to/no/file');
@ -24,6 +37,6 @@ class FFProbeTest extends FunctionalTestCase
public function testProbeOnRemoteFile() public function testProbeOnRemoteFile()
{ {
$ffprobe = FFProbe::create(); $ffprobe = FFProbe::create();
$this->assertGreaterThan(0, count($ffprobe->streams('http://video-js.zencoder.com/oceans-clip.mp4'))); $this->assertGreaterThan(0, count($ffprobe->streams('http://vjs.zencdn.net/v/oceans.mp4')));
} }
} }

View file

@ -1,10 +1,11 @@
<?php <?php
namespace FFMpeg\Functional; namespace Tests\FFMpeg\Functional;
use FFMpeg\FFMpeg; use FFMpeg\FFMpeg;
use PHPUnit\Framework\TestCase;
abstract class FunctionalTestCase extends \PHPUnit_Framework_TestCase abstract class FunctionalTestCase extends TestCase
{ {
/** /**
* @return FFMpeg * @return FFMpeg

View file

@ -1,6 +1,6 @@
<?php <?php
namespace FFMpeg\Functional; namespace Tests\FFMpeg\Functional;
use FFMpeg\Coordinate\Dimension; use FFMpeg\Coordinate\Dimension;
use FFMpeg\Filters\Video\ResizeFilter; use FFMpeg\Filters\Video\ResizeFilter;
@ -18,14 +18,44 @@ class VideoTranscodeTest extends FunctionalTestCase
} }
$ffmpeg = $this->getFFMpeg(); $ffmpeg = $this->getFFMpeg();
$video = $ffmpeg->open(__DIR__ . '/../../files/Test.ogv'); $video = $ffmpeg->open(__DIR__ . '/../files/Test.ogv');
$this->assertInstanceOf('FFMpeg\Media\Video', $video); $this->assertInstanceOf('FFMpeg\Media\Video', $video);
$lastPercentage = null; $lastPercentage = null;
$phpunit = $this; $phpunit = $this;
$codec = new X264('libvo_aacenc'); $codec = new X264('aac');
$codec->on('progress', function ($video, $codec, $percentage) use ($phpunit, &$lastPercentage) {
if (null !== $lastPercentage) {
$phpunit->assertGreaterThanOrEqual($lastPercentage, $percentage);
}
$lastPercentage = $percentage;
$phpunit->assertGreaterThanOrEqual(0, $percentage);
$phpunit->assertLessThanOrEqual(100, $percentage);
});
$video->save($codec, $filename);
$this->assertFileExists($filename);
unlink($filename);
}
public function testAacTranscodeX264()
{
$filename = __DIR__ . '/output/output-x264_2.mp4';
if (is_file($filename)) {
unlink(__DIR__ . '/output/output-x264_2.mp4');
}
$ffmpeg = $this->getFFMpeg();
$video = $ffmpeg->open(__DIR__ . '/../files/sample.3gp');
$this->assertInstanceOf('FFMpeg\Media\Video', $video);
$lastPercentage = null;
$phpunit = $this;
$codec = new X264('aac');
$codec->on('progress', function ($video, $codec, $percentage) use ($phpunit, &$lastPercentage) { $codec->on('progress', function ($video, $codec, $percentage) use ($phpunit, &$lastPercentage) {
if (null !== $lastPercentage) { if (null !== $lastPercentage) {
$phpunit->assertGreaterThanOrEqual($lastPercentage, $percentage); $phpunit->assertGreaterThanOrEqual($lastPercentage, $percentage);
@ -46,16 +76,16 @@ class VideoTranscodeTest extends FunctionalTestCase
public function testTranscodeInvalidFile() public function testTranscodeInvalidFile()
{ {
$ffmpeg = $this->getFFMpeg(); $ffmpeg = $this->getFFMpeg();
$ffmpeg->open(__DIR__ . '/../../files/UnknownFileTest.ogv'); $ffmpeg->open(__DIR__ . '/../files/UnknownFileTest.ogv');
} }
public function testSaveInvalidForgedVideo() public function testSaveInvalidForgedVideo()
{ {
$ffmpeg = $this->getFFMpeg(); $ffmpeg = $this->getFFMpeg();
$video = new Video(__DIR__ . '/../../files/UnknownFileTest.ogv', $ffmpeg->getFFMpegDriver(), $ffmpeg->getFFProbe()); $video = new Video(__DIR__ . '/../files/UnknownFileTest.ogv', $ffmpeg->getFFMpegDriver(), $ffmpeg->getFFProbe());
$this->setExpectedException('FFMpeg\Exception\RuntimeException'); $this->setExpectedException('FFMpeg\Exception\RuntimeException');
$video->save(new X264('libvo_aacenc'), __DIR__ . '/output/output-x264.mp4'); $video->save(new X264('aac'), __DIR__ . '/output/output-x264.mp4');
} }
public function testTranscodePortraitVideo() public function testTranscodePortraitVideo()
@ -72,12 +102,12 @@ class VideoTranscodeTest extends FunctionalTestCase
} }
$ffmpeg = $this->getFFMpeg(); $ffmpeg = $this->getFFMpeg();
$video = $ffmpeg->open(__DIR__ . '/../../files/portrait.MOV'); $video = $ffmpeg->open(__DIR__ . '/../files/portrait.MOV');
$video->filters() $video->filters()
->resize(new Dimension(320, 240), ResizeFilter::RESIZEMODE_INSET) ->resize(new Dimension(320, 240), ResizeFilter::RESIZEMODE_INSET)
->rotate(RotateFilter::ROTATE_90); ->rotate(RotateFilter::ROTATE_90);
$video->save(new X264('libvo_aacenc'), $filename); $video->save(new X264('aac'), $filename);
$dimension = $ffmpeg->getFFProbe() $dimension = $ffmpeg->getFFProbe()
->streams($filename) ->streams($filename)

View file

@ -1,9 +1,9 @@
<?php <?php
namespace FFMpeg\Tests\Coordinate; namespace Tests\FFMpeg\Unit\Coordinate;
use FFMpeg\Coordinate\Dimension; use FFMpeg\Coordinate\Dimension;
use FFMpeg\Tests\TestCase; use Tests\FFMpeg\Unit\TestCase;
use FFMpeg\Coordinate\AspectRatio; use FFMpeg\Coordinate\AspectRatio;
class AspectRatioTest extends TestCase class AspectRatioTest extends TestCase

View file

@ -1,8 +1,8 @@
<?php <?php
namespace FFMpeg\Tests\Coordinate; namespace Tests\FFMpeg\Unit\Coordinate;
use FFMpeg\Tests\TestCase; use Tests\FFMpeg\Unit\TestCase;
use FFMpeg\Coordinate\Dimension; use FFMpeg\Coordinate\Dimension;
class DimensionTest extends TestCase class DimensionTest extends TestCase

View file

@ -1,8 +1,8 @@
<?php <?php
namespace FFMpeg\Tests\Coordinate; namespace Tests\FFMpeg\Unit\Coordinate;
use FFMpeg\Tests\TestCase; use Tests\FFMpeg\Unit\TestCase;
use FFMpeg\Coordinate\FrameRate; use FFMpeg\Coordinate\FrameRate;
class FrameRateTest extends TestCase class FrameRateTest extends TestCase

View file

@ -0,0 +1,23 @@
<?php
namespace Tests\FFMpeg\Unit\Coordinate;
use Tests\FFMpeg\Unit\TestCase;
use FFMpeg\Coordinate\Point;
class PointTest extends TestCase
{
public function testGetters()
{
$point = new Point(4, 25);
$this->assertEquals(4, $point->getX());
$this->assertEquals(25, $point->getY());
}
public function testDynamicPointGetters()
{
$point = new Point("t*100", "t", true);
$this->assertEquals("t*100", $point->getX());
$this->assertEquals("t", $point->getY());
}
}

View file

@ -1,8 +1,8 @@
<?php <?php
namespace FFMpeg\Tests\Coordinate; namespace Tests\FFMpeg\Unit\Coordinate;
use FFMpeg\Tests\TestCase; use Tests\FFMpeg\Unit\TestCase;
use FFMpeg\Coordinate\TimeCode; use FFMpeg\Coordinate\TimeCode;
class TimeCodeTest extends TestCase class TimeCodeTest extends TestCase

View file

@ -1,10 +1,10 @@
<?php <?php
namespace FFMpeg\Tests\Driver; namespace Tests\FFMpeg\Unit\Driver;
use Alchemy\BinaryDriver\Configuration; use Alchemy\BinaryDriver\Configuration;
use FFMpeg\Driver\FFMpegDriver; use FFMpeg\Driver\FFMpegDriver;
use FFMpeg\Tests\TestCase; use Tests\FFMpeg\Unit\TestCase;
use Symfony\Component\Process\ExecutableFinder; use Symfony\Component\Process\ExecutableFinder;
class FFMpegDriverTest extends TestCase class FFMpegDriverTest extends TestCase

View file

@ -1,10 +1,10 @@
<?php <?php
namespace FFMpeg\Tests\Driver; namespace Tests\FFMpeg\Unit\Driver;
use Alchemy\BinaryDriver\Configuration; use Alchemy\BinaryDriver\Configuration;
use FFMpeg\Driver\FFProbeDriver; use FFMpeg\Driver\FFProbeDriver;
use FFMpeg\Tests\TestCase; use Tests\FFMpeg\Unit\TestCase;
use Symfony\Component\Process\ExecutableFinder; use Symfony\Component\Process\ExecutableFinder;
class FFProbeDriverTest extends TestCase class FFProbeDriverTest extends TestCase

View file

@ -1,11 +1,12 @@
<?php <?php
namespace FFMpeg\Tests; namespace Tests\FFMpeg\Unit;
use FFMpeg\FFMpegServiceProvider; use FFMpeg\FFMpegServiceProvider;
use Silex\Application; use Silex\Application;
use PHPUnit\Framework\TestCase as BaseTestCase;
class FFMpegServiceProviderTest extends \PHPUnit_Framework_TestCase class FFMpegServiceProviderTest extends BaseTestCase
{ {
public function testWithConfig() public function testWithConfig()
{ {

View file

@ -1,6 +1,6 @@
<?php <?php
namespace FFMpeg\Tests; namespace Tests\FFMpeg\Unit;
use FFMpeg\FFMpeg; use FFMpeg\FFMpeg;
use FFMpeg\FFProbe\DataMapping\StreamCollection; use FFMpeg\FFProbe\DataMapping\StreamCollection;

View file

@ -1,8 +1,8 @@
<?php <?php
namespace FFMpeg\Tests\FFProbe\DataMapping; namespace Tests\FFMpeg\Unit\FFProbe\DataMapping;
use FFMpeg\Tests\TestCase; use Tests\FFMpeg\Unit\TestCase;
use FFMpeg\FFProbe\DataMapping\AbstractData; use FFMpeg\FFProbe\DataMapping\AbstractData;
class AbstractDataTest extends TestCase class AbstractDataTest extends TestCase

View file

@ -1,8 +1,8 @@
<?php <?php
namespace FFMpeg\Tests\FFProbe\DataMapping; namespace Tests\FFMpeg\Unit\FFProbe\DataMapping;
use FFMpeg\Tests\TestCase; use Tests\FFMpeg\Unit\TestCase;
use FFMpeg\FFProbe\DataMapping\StreamCollection; use FFMpeg\FFProbe\DataMapping\StreamCollection;
class StreamCollectionTest extends TestCase class StreamCollectionTest extends TestCase

View file

@ -1,9 +1,9 @@
<?php <?php
namespace FFMpeg\Tests\FFProbe\DataMapping; namespace Tests\FFMpeg\Unit\FFProbe\DataMapping;
use FFMpeg\Coordinate\Dimension; use FFMpeg\Coordinate\Dimension;
use FFMpeg\Tests\TestCase; use Tests\FFMpeg\Unit\TestCase;
use FFMpeg\FFProbe\DataMapping\Stream; use FFMpeg\FFProbe\DataMapping\Stream;
class StreamTest extends TestCase class StreamTest extends TestCase

View file

@ -1,8 +1,8 @@
<?php <?php
namespace FFMpeg\Tests\FFProbe; namespace Tests\FFMpeg\Unit\FFProbe;
use FFMpeg\Tests\TestCase; use Tests\FFMpeg\Unit\TestCase;
use FFMpeg\FFProbe\Mapper; use FFMpeg\FFProbe\Mapper;
use FFMpeg\FFProbe; use FFMpeg\FFProbe;
use FFMpeg\FFProbe\DataMapping\Format; use FFMpeg\FFProbe\DataMapping\Format;
@ -31,8 +31,8 @@ class MapperTest extends TestCase
public function provideMappings() public function provideMappings()
{ {
$format = json_decode(file_get_contents(__DIR__ . '/../../../fixtures/ffprobe/show_format.json'), true); $format = json_decode(file_get_contents(__DIR__ . '/../../fixtures/ffprobe/show_format.json'), true);
$streams = json_decode(file_get_contents(__DIR__ . '/../../../fixtures/ffprobe/show_streams.json'), true); $streams = json_decode(file_get_contents(__DIR__ . '/../../fixtures/ffprobe/show_streams.json'), true);
return array( return array(
array(FFProbe::TYPE_FORMAT, $format, new Format($format['format'])), array(FFProbe::TYPE_FORMAT, $format, new Format($format['format'])),

View file

@ -1,9 +1,9 @@
<?php <?php
namespace FFMpeg\Tests\FFProbe; namespace Tests\FFMpeg\Unit\FFProbe;
use Alchemy\BinaryDriver\Exception\ExecutionFailureException; use Alchemy\BinaryDriver\Exception\ExecutionFailureException;
use FFMpeg\Tests\TestCase; use Tests\FFMpeg\Unit\TestCase;
use FFMpeg\FFProbe\OptionsTester; use FFMpeg\FFProbe\OptionsTester;
class OptionsTesterTest extends TestCase class OptionsTesterTest extends TestCase
@ -55,7 +55,7 @@ class OptionsTesterTest extends TestCase
public function provideOptions() public function provideOptions()
{ {
$data = file_get_contents(__DIR__ . '/../../../fixtures/ffprobe/help.raw'); $data = file_get_contents(__DIR__ . '/../../fixtures/ffprobe/help.raw');
return array( return array(
array(true, $data, '-print_format'), array(true, $data, '-print_format'),

View file

@ -1,8 +1,8 @@
<?php <?php
namespace FFMpeg\Tests\FFProbe; namespace Tests\FFMpeg\Unit\FFProbe;
use FFMpeg\Tests\TestCase; use Tests\FFMpeg\Unit\TestCase;
use FFMpeg\FFProbe\OutputParser; use FFMpeg\FFProbe\OutputParser;
use FFMpeg\FFProbe; use FFMpeg\FFProbe;
@ -28,11 +28,11 @@ class OutputParserTest extends TestCase
public function provideTypeDataAndOutput() public function provideTypeDataAndOutput()
{ {
$expectedFormat = json_decode(file_get_contents(__DIR__ . '/../../../fixtures/ffprobe/show_format.json'), true); $expectedFormat = json_decode(file_get_contents(__DIR__ . '/../../fixtures/ffprobe/show_format.json'), true);
$expectedStreams = json_decode(file_get_contents(__DIR__ . '/../../../fixtures/ffprobe/show_streams.json'), true); $expectedStreams = json_decode(file_get_contents(__DIR__ . '/../../fixtures/ffprobe/show_streams.json'), true);
$rawFormat = file_get_contents(__DIR__ . '/../../../fixtures/ffprobe/show_format.raw'); $rawFormat = file_get_contents(__DIR__ . '/../../fixtures/ffprobe/show_format.raw');
$rawStreams = file_get_contents(__DIR__ . '/../../../fixtures/ffprobe/show_streams.raw'); $rawStreams = file_get_contents(__DIR__ . '/../../fixtures/ffprobe/show_streams.raw');
return array( return array(
array(FFProbe::TYPE_FORMAT, $rawFormat, $expectedFormat), array(FFProbe::TYPE_FORMAT, $rawFormat, $expectedFormat),

View file

@ -1,6 +1,6 @@
<?php <?php
namespace FFMpeg\Tests; namespace Tests\FFMpeg\Unit;
use FFMpeg\FFProbe; use FFMpeg\FFProbe;
use Symfony\Component\Process\ExecutableFinder; use Symfony\Component\Process\ExecutableFinder;

View file

@ -0,0 +1,47 @@
<?php
namespace Tests\FFMpeg\Unit\Filters\Audio;
use FFMpeg\Filters\Audio\AudioFilters;
use FFMpeg\Coordinate\TimeCode;
use Tests\FFMpeg\Unit\TestCase;
class AudioClipTest extends TestCase {
public function testClipping() {
$capturedFilter = null;
$audio = $this->getAudioMock();
$audio->expects($this->once())
->method('addFilter')
->with($this->isInstanceOf('FFMpeg\Filters\Audio\AudioClipFilter'))
->will($this->returnCallback(function ($filter) use (&$capturedFilter) {
$capturedFilter = $filter;
}));
$format = $this->getMock('FFMpeg\Format\AudioInterface');
$filters = new AudioFilters($audio);
$filters->clip(TimeCode::fromSeconds(5));
$this->assertEquals(array(0 => '-ss', 1 => '00:00:05.00', 2 => '-acodec', 3 => 'copy'), $capturedFilter->apply($audio, $format));
}
public function testClippingWithDuration() {
$capturedFilter = null;
$audio = $this->getAudioMock();
$audio->expects($this->once())
->method('addFilter')
->with($this->isInstanceOf('FFMpeg\Filters\Audio\AudioClipFilter'))
->will($this->returnCallback(function ($filter) use (&$capturedFilter) {
$capturedFilter = $filter;
}));
$format = $this->getMock('FFMpeg\Format\AudioInterface');
$filters = new AudioFilters($audio);
$filters->clip(TimeCode::fromSeconds(5), TimeCode::fromSeconds(5));
$this->assertEquals(array(0 => '-ss', 1 => '00:00:05.00', 2 => '-t', 3 => '00:00:05.00', 4 => '-acodec', 5 => 'copy'), $capturedFilter->apply($audio, $format));
}
}

View file

@ -1,9 +1,9 @@
<?php <?php
namespace FFMpeg\Tests\Filters\Audio; namespace Tests\FFMpeg\Unit\Filters\Audio;
use FFMpeg\Filters\Audio\AudioFilters; use FFMpeg\Filters\Audio\AudioFilters;
use FFMpeg\Tests\TestCase; use Tests\FFMpeg\Unit\TestCase;
class AudioFiltersTest extends TestCase class AudioFiltersTest extends TestCase
{ {

View file

@ -0,0 +1,64 @@
<?php
namespace Tests\FFMpeg\Unit\Filters\Audio;
use FFMpeg\Filters\Audio\AudioFilters;
use Tests\FFMpeg\Unit\TestCase;
class AudioMetadataTest extends TestCase
{
public function testAddMetadata()
{
$capturedFilter = null;
$audio = $this->getAudioMock();
$audio->expects($this->once())
->method('addFilter')
->with($this->isInstanceOf('FFMpeg\Filters\Audio\AddMetadataFilter'))
->will($this->returnCallback(function ($filter) use (&$capturedFilter) {
$capturedFilter = $filter;
}));
$format = $this->getMock('FFMpeg\Format\AudioInterface');
$filters = new AudioFilters($audio);
$filters->addMetadata(array('title' => "Hello World"));
$this->assertEquals(array(0 => "-metadata", 1 => "title=Hello World"), $capturedFilter->apply($audio, $format));
}
public function testAddArtwork()
{
$capturedFilter = null;
$audio = $this->getAudioMock();
$audio->expects($this->once())
->method('addFilter')
->with($this->isInstanceOf('FFMpeg\Filters\Audio\AddMetadataFilter'))
->will($this->returnCallback(function ($filter) use (&$capturedFilter) {
$capturedFilter = $filter;
}));
$format = $this->getMock('FFMpeg\Format\AudioInterface');
$filters = new AudioFilters($audio);
$filters->addMetadata(array('genre' => 'Some Genre', 'artwork' => "/path/to/file.jpg"));
$this->assertEquals(array(0 => "-i", 1 => "/path/to/file.jpg", 2 => "-map", 3 => "0", 4 => "-map", 5 => "1", 6 => "-metadata", 7 => "genre=Some Genre"), $capturedFilter->apply($audio, $format));
$this->assertEquals(array(0 => "-i", 1 => "/path/to/file.jpg", 2 => "-map", 3 => "0", 4 => "-map", 5 => "1", 6 => "-metadata", 7 => "genre=Some Genre"), $capturedFilter->apply($audio, $format));
}
public function testRemoveMetadata()
{
$capturedFilter = null;
$audio = $this->getAudioMock();
$audio->expects($this->once())
->method('addFilter')
->with($this->isInstanceOf('FFMpeg\Filters\Audio\AddMetadataFilter'))
->will($this->returnCallback(function ($filter) use (&$capturedFilter) {
$capturedFilter = $filter;
}));
$format = $this->getMock('FFMpeg\Format\AudioInterface');
$filters = new AudioFilters($audio);
$filters->addMetadata();
$this->assertEquals(array(0 => "-map_metadata", 1 => "-1", 2 => "-vn"), $capturedFilter->apply($audio, $format));
}
}

View file

@ -1,9 +1,9 @@
<?php <?php
namespace FFMpeg\Tests\Filters\Audio; namespace Tests\FFMpeg\Unit\Filters\Audio;
use FFMpeg\Filters\Audio\AudioResamplableFilter; use FFMpeg\Filters\Audio\AudioResamplableFilter;
use FFMpeg\Tests\TestCase; use Tests\FFMpeg\Unit\TestCase;
class AudioResamplableFilterTest extends TestCase class AudioResamplableFilterTest extends TestCase
{ {

View file

@ -1,10 +1,10 @@
<?php <?php
namespace FFMpeg\Tests\Filters; namespace Tests\FFMpeg\Unit\Filters;
use FFMpeg\Filters\FiltersCollection; use FFMpeg\Filters\FiltersCollection;
use FFMpeg\Filters\Audio\SimpleFilter; use FFMpeg\Filters\Audio\SimpleFilter;
use FFMpeg\Tests\TestCase; use Tests\FFMpeg\Unit\TestCase;
class FiltersCollectionTest extends TestCase class FiltersCollectionTest extends TestCase
{ {

View file

@ -1,8 +1,8 @@
<?php <?php
namespace FFMpeg\Tests\Filters\Frame; namespace Tests\FFMpeg\Unit\Filters\Frame;
use FFMpeg\Tests\TestCase; use Tests\FFMpeg\Unit\TestCase;
use FFMpeg\Filters\Frame\DisplayRatioFixerFilter; use FFMpeg\Filters\Frame\DisplayRatioFixerFilter;
use FFMpeg\Media\Frame; use FFMpeg\Media\Frame;
use FFMpeg\Coordinate\TimeCode; use FFMpeg\Coordinate\TimeCode;

View file

@ -1,8 +1,8 @@
<?php <?php
namespace FFMpeg\Tests\Filters\Frame; namespace Tests\FFMpeg\Unit\Filters\Frame;
use FFMpeg\Tests\TestCase; use Tests\FFMpeg\Unit\TestCase;
use FFMpeg\Filters\Frame\FrameFilters; use FFMpeg\Filters\Frame\FrameFilters;
class FrameFiltersTest extends TestCase class FrameFiltersTest extends TestCase

View file

@ -1,13 +1,13 @@
<?php <?php
namespace FFMpeg\Tests\Filters\Video; namespace Tests\FFMpeg\Unit\Filters\Video;
use FFMpeg\Coordinate\Dimension; use FFMpeg\Coordinate\Dimension;
use FFMpeg\Coordinate\Point; use FFMpeg\Coordinate\Point;
use FFMpeg\FFProbe\DataMapping\Stream; use FFMpeg\FFProbe\DataMapping\Stream;
use FFMpeg\FFProbe\DataMapping\StreamCollection; use FFMpeg\FFProbe\DataMapping\StreamCollection;
use FFMpeg\Filters\Video\CropFilter; use FFMpeg\Filters\Video\CropFilter;
use FFMpeg\Tests\TestCase; use Tests\FFMpeg\Unit\TestCase;
class CropFilterTest extends TestCase class CropFilterTest extends TestCase
{ {

View file

@ -1,10 +1,10 @@
<?php <?php
namespace FFMpeg\Tests\Filters\Video; namespace Tests\FFMpeg\Unit\Filters\Video;
use FFMpeg\Filters\Video\CustomFilter; use FFMpeg\Filters\Video\CustomFilter;
use FFMpeg\Filters\Video\FrameRateFilter; use FFMpeg\Filters\Video\FrameRateFilter;
use FFMpeg\Tests\TestCase; use Tests\FFMpeg\Unit\TestCase;
use FFMpeg\Coordinate\FrameRate; use FFMpeg\Coordinate\FrameRate;
class CustomFilterTest extends TestCase class CustomFilterTest extends TestCase

View file

@ -0,0 +1,51 @@
<?php
namespace Tests\FFMpeg\Unit\Filters\Video;
use FFMpeg\Filters\Video\ExtractMultipleFramesFilter;
use Tests\FFMpeg\Unit\TestCase;
use FFMpeg\FFProbe\DataMapping\Stream;
use FFMpeg\FFProbe\DataMapping\StreamCollection;
class ExtractMultipleFramesFilterTest extends TestCase
{
/**
* @dataProvider provideFrameRates
*/
public function testApply($frameRate, $destinationFolder, $duration, $modulus, $expected)
{
$video = $this->getVideoMock();
$pathfile = '/path/to/file'.mt_rand();
$format = $this->getMock('FFMpeg\Format\VideoInterface');
$format->expects($this->any())
->method('getModulus')
->will($this->returnValue($modulus));
$streams = new StreamCollection(array(
new Stream(array(
'codec_type' => 'video',
'duration' => $duration,
))
));
$video->expects($this->once())
->method('getStreams')
->will($this->returnValue($streams));
$filter = new ExtractMultipleFramesFilter($frameRate, $destinationFolder);
$this->assertEquals($expected, $filter->apply($video, $format));
}
public function provideFrameRates()
{
return array(
array(ExtractMultipleFramesFilter::FRAMERATE_EVERY_SEC, '/', 100, 2, array('-vf', 'fps=1/1', '/frame-%03d.jpg')),
array(ExtractMultipleFramesFilter::FRAMERATE_EVERY_2SEC, '/', 100, 2, array('-vf', 'fps=1/2', '/frame-%02d.jpg')),
array(ExtractMultipleFramesFilter::FRAMERATE_EVERY_5SEC, '/', 100, 2, array('-vf', 'fps=1/5', '/frame-%02d.jpg')),
array(ExtractMultipleFramesFilter::FRAMERATE_EVERY_10SEC, '/', 100, 2, array('-vf', 'fps=1/10', '/frame-%02d.jpg')),
array(ExtractMultipleFramesFilter::FRAMERATE_EVERY_30SEC, '/', 100, 2, array('-vf', 'fps=1/30', '/frame-%02d.jpg')),
array(ExtractMultipleFramesFilter::FRAMERATE_EVERY_60SEC, '/', 100, 2, array('-vf', 'fps=1/60', '/frame-%02d.jpg')),
);
}
}

View file

@ -1,9 +1,9 @@
<?php <?php
namespace FFMpeg\Tests\Filters\Video; namespace Tests\FFMpeg\Unit\Filters\Video;
use FFMpeg\Filters\Video\FrameRateFilter; use FFMpeg\Filters\Video\FrameRateFilter;
use FFMpeg\Tests\TestCase; use Tests\FFMpeg\Unit\TestCase;
use FFMpeg\Coordinate\FrameRate; use FFMpeg\Coordinate\FrameRate;
class FrameRateFilterTest extends TestCase class FrameRateFilterTest extends TestCase

View file

@ -0,0 +1,44 @@
<?php
namespace Tests\FFMpeg\Unit\Filters\Video;
use FFMpeg\Filters\Video\PadFilter;
use Tests\FFMpeg\Unit\TestCase;
use FFMpeg\FFProbe\DataMapping\Stream;
use FFMpeg\FFProbe\DataMapping\StreamCollection;
use FFMpeg\Coordinate\Dimension;
class PadFilterTest extends TestCase
{
/**
* @dataProvider provideDimensions
*/
public function testApply(Dimension $dimension, $width, $height, $expected)
{
$video = $this->getVideoMock();
$pathfile = '/path/to/file'.mt_rand();
$format = $this->getMock('FFMpeg\Format\VideoInterface');
$streams = new StreamCollection(array(
new Stream(array(
'codec_type' => 'video',
'width' => $width,
'height' => $height,
))
));
$filter = new PadFilter($dimension);
$this->assertEquals($expected, $filter->apply($video, $format));
}
public function provideDimensions()
{
return array(
array(new Dimension(1000, 800), 640, 480, array('-vf', 'scale=iw*min(1000/iw\,800/ih):ih*min(1000/iw\,800/ih),pad=1000:800:(1000-iw)/2:(800-ih)/2')),
array(new Dimension(300, 600), 640, 480, array('-vf', 'scale=iw*min(300/iw\,600/ih):ih*min(300/iw\,600/ih),pad=300:600:(300-iw)/2:(600-ih)/2')),
array(new Dimension(100, 900), 640, 480, array('-vf', 'scale=iw*min(100/iw\,900/ih):ih*min(100/iw\,900/ih),pad=100:900:(100-iw)/2:(900-ih)/2')),
array(new Dimension(1200, 200), 640, 480, array('-vf', 'scale=iw*min(1200/iw\,200/ih):ih*min(1200/iw\,200/ih),pad=1200:200:(1200-iw)/2:(200-ih)/2')),
);
}
}

View file

@ -1,9 +1,9 @@
<?php <?php
namespace FFMpeg\Tests\Filters\Video; namespace Tests\FFMpeg\Unit\Filters\Video;
use FFMpeg\Filters\Video\ResizeFilter; use FFMpeg\Filters\Video\ResizeFilter;
use FFMpeg\Tests\TestCase; use Tests\FFMpeg\Unit\TestCase;
use FFMpeg\FFProbe\DataMapping\Stream; use FFMpeg\FFProbe\DataMapping\Stream;
use FFMpeg\FFProbe\DataMapping\StreamCollection; use FFMpeg\FFProbe\DataMapping\StreamCollection;
use FFMpeg\Coordinate\Dimension; use FFMpeg\Coordinate\Dimension;
@ -42,34 +42,34 @@ class ResizeFilterTest extends TestCase
public function provideDimensions() public function provideDimensions()
{ {
return array( return array(
array(new Dimension(320, 240), ResizeFilter::RESIZEMODE_FIT, 640, 480, 2, array('-s', '320x240')), array(new Dimension(320, 240), ResizeFilter::RESIZEMODE_FIT, 640, 480, 2, array('-vf', '[in]scale=320:240 [out]')),
array(new Dimension(320, 240), ResizeFilter::RESIZEMODE_INSET, 640, 480, 2, array('-s', '320x240')), array(new Dimension(320, 240), ResizeFilter::RESIZEMODE_INSET, 640, 480, 2, array('-vf', '[in]scale=320:240 [out]')),
array(new Dimension(320, 240), ResizeFilter::RESIZEMODE_SCALE_HEIGHT, 640, 480, 2, array('-s', '320x240')), array(new Dimension(320, 240), ResizeFilter::RESIZEMODE_SCALE_HEIGHT, 640, 480, 2, array('-vf', '[in]scale=320:240 [out]')),
array(new Dimension(320, 240), ResizeFilter::RESIZEMODE_SCALE_WIDTH, 640, 480, 2, array('-s', '320x240')), array(new Dimension(320, 240), ResizeFilter::RESIZEMODE_SCALE_WIDTH, 640, 480, 2, array('-vf', '[in]scale=320:240 [out]')),
array(new Dimension(640, 480), ResizeFilter::RESIZEMODE_FIT, 320, 240, 2, array('-s', '640x480')), array(new Dimension(640, 480), ResizeFilter::RESIZEMODE_FIT, 320, 240, 2, array('-vf', '[in]scale=640:480 [out]')),
array(new Dimension(640, 480), ResizeFilter::RESIZEMODE_INSET, 320, 240, 2, array('-s', '640x480')), array(new Dimension(640, 480), ResizeFilter::RESIZEMODE_INSET, 320, 240, 2, array('-vf', '[in]scale=640:480 [out]')),
array(new Dimension(640, 480), ResizeFilter::RESIZEMODE_SCALE_HEIGHT, 320, 240, 2, array('-s', '640x480')), array(new Dimension(640, 480), ResizeFilter::RESIZEMODE_SCALE_HEIGHT, 320, 240, 2, array('-vf', '[in]scale=640:480 [out]')),
array(new Dimension(640, 480), ResizeFilter::RESIZEMODE_SCALE_WIDTH, 320, 240, 2, array('-s', '640x480')), array(new Dimension(640, 480), ResizeFilter::RESIZEMODE_SCALE_WIDTH, 320, 240, 2, array('-vf', '[in]scale=640:480 [out]')),
array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_FIT, 1280, 720, 2, array('-s', '640x360')), array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_FIT, 1280, 720, 2, array('-vf', '[in]scale=640:360 [out]')),
array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_INSET, 1280, 720, 2, array('-s', '640x360')), array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_INSET, 1280, 720, 2, array('-vf', '[in]scale=640:360 [out]')),
array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_SCALE_HEIGHT, 1280, 720, 2, array('-s', '640x360')), array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_SCALE_HEIGHT, 1280, 720, 2, array('-vf', '[in]scale=640:360 [out]')),
array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_SCALE_WIDTH, 1280, 720, 2, array('-s', '640x360')), array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_SCALE_WIDTH, 1280, 720, 2, array('-vf', '[in]scale=640:360 [out]')),
array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_FIT, 1280, 720, 2, array('-s', '640x360')), array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_FIT, 1280, 720, 2, array('-vf', '[in]scale=640:360 [out]')),
array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_INSET, 1280, 720, 2, array('-s', '640x360')), array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_INSET, 1280, 720, 2, array('-vf', '[in]scale=640:360 [out]')),
array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_SCALE_HEIGHT, 1280, 720, 2, array('-s', '640x360')), array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_SCALE_HEIGHT, 1280, 720, 2, array('-vf', '[in]scale=640:360 [out]')),
array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_SCALE_WIDTH, 1280, 720, 2, array('-s', '640x360')), array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_SCALE_WIDTH, 1280, 720, 2, array('-vf', '[in]scale=640:360 [out]')),
// test non standard dimension // test non standard dimension
array(new Dimension(700, 150), ResizeFilter::RESIZEMODE_INSET, 123, 456, 2, array('-s', '62x150'), true), array(new Dimension(700, 150), ResizeFilter::RESIZEMODE_INSET, 123, 456, 2, array('-vf', '[in]scale=62:150 [out]'), true),
array(new Dimension(700, 150), ResizeFilter::RESIZEMODE_INSET, 123, 456, 2, array('-s', '40x150'), false), array(new Dimension(700, 150), ResizeFilter::RESIZEMODE_INSET, 123, 456, 2, array('-vf', '[in]scale=40:150 [out]'), false),
array(new Dimension(320, 320), ResizeFilter::RESIZEMODE_FIT, 640, 480, 2, array('-s', '320x320')), array(new Dimension(320, 320), ResizeFilter::RESIZEMODE_FIT, 640, 480, 2, array('-vf', '[in]scale=320:320 [out]')),
array(new Dimension(320, 320), ResizeFilter::RESIZEMODE_INSET, 640, 480, 2, array('-s', '320x240')), array(new Dimension(320, 320), ResizeFilter::RESIZEMODE_INSET, 640, 480, 2, array('-vf', '[in]scale=320:240 [out]')),
array(new Dimension(320, 320), ResizeFilter::RESIZEMODE_SCALE_HEIGHT, 640, 480, 2, array('-s', '320x240')), array(new Dimension(320, 320), ResizeFilter::RESIZEMODE_SCALE_HEIGHT, 640, 480, 2, array('-vf', '[in]scale=320:240 [out]')),
array(new Dimension(320, 320), ResizeFilter::RESIZEMODE_SCALE_WIDTH, 640, 480, 2, array('-s', '426x320')), array(new Dimension(320, 320), ResizeFilter::RESIZEMODE_SCALE_WIDTH, 640, 480, 2, array('-vf', '[in]scale=426:320 [out]')),
); );
} }
} }

View file

@ -1,11 +1,11 @@
<?php <?php
namespace FFMpeg\Tests\Filters\Video; namespace Tests\FFMpeg\Unit\Filters\Video;
use FFMpeg\FFProbe\DataMapping\Stream; use FFMpeg\FFProbe\DataMapping\Stream;
use FFMpeg\FFProbe\DataMapping\StreamCollection; use FFMpeg\FFProbe\DataMapping\StreamCollection;
use FFMpeg\Filters\Video\RotateFilter; use FFMpeg\Filters\Video\RotateFilter;
use FFMpeg\Tests\TestCase; use Tests\FFMpeg\Unit\TestCase;
class RotateFilterTest extends TestCase class RotateFilterTest extends TestCase
{ {

View file

@ -1,8 +1,8 @@
<?php <?php
namespace FFMpeg\Tests\Filters\Video; namespace Tests\FFMpeg\Unit\Filters\Video;
use FFMpeg\Tests\TestCase; use Tests\FFMpeg\Unit\TestCase;
use FFMpeg\Filters\Video\SynchronizeFilter; use FFMpeg\Filters\Video\SynchronizeFilter;
class SynchronizeFilterTest extends TestCase class SynchronizeFilterTest extends TestCase

View file

@ -1,8 +1,8 @@
<?php <?php
namespace FFMpeg\Tests\Filters\Video; namespace Tests\FFMpeg\Unit\Filters\Video;
use FFMpeg\Tests\TestCase; use Tests\FFMpeg\Unit\TestCase;
use FFMpeg\Filters\Video\VideoFilters; use FFMpeg\Filters\Video\VideoFilters;
use FFMpeg\Filters\Video\ResizeFilter; use FFMpeg\Filters\Video\ResizeFilter;

View file

@ -1,12 +1,12 @@
<?php <?php
namespace FFMpeg\Tests\Filters\Video; namespace Tests\FFMpeg\Unit\Filters\Video;
use FFMpeg\FFProbe\DataMapping\Stream; use FFMpeg\FFProbe\DataMapping\Stream;
use FFMpeg\FFProbe\DataMapping\StreamCollection; use FFMpeg\FFProbe\DataMapping\StreamCollection;
use FFMpeg\Filters\Video\RotateFilter; use FFMpeg\Filters\Video\RotateFilter;
use FFMpeg\Filters\Video\WatermarkFilter; use FFMpeg\Filters\Video\WatermarkFilter;
use FFMpeg\Tests\TestCase; use Tests\FFMpeg\Unit\TestCase;
class WatermarkFilterTest extends TestCase class WatermarkFilterTest extends TestCase
{ {
@ -19,8 +19,8 @@ class WatermarkFilterTest extends TestCase
$format = $this->getMock('FFMpeg\Format\VideoInterface'); $format = $this->getMock('FFMpeg\Format\VideoInterface');
$filter = new WatermarkFilter(__DIR__ . '/../../../../files/watermark.png'); $filter = new WatermarkFilter(__DIR__ . '/../../../files/watermark.png');
$this->assertEquals(array('-vf', 'movie='.__DIR__ .'/../../../../files/watermark.png [watermark]; [in][watermark] overlay=0:0 [out]'), $filter->apply($video, $format)); $this->assertEquals(array('-vf', 'movie='.__DIR__ .'/../../../files/watermark.png [watermark]; [in][watermark] overlay=0:0 [out]'), $filter->apply($video, $format));
// check size of video is unchanged // check size of video is unchanged
$this->assertEquals(320, $stream->get('width')); $this->assertEquals(320, $stream->get('width'));
@ -33,31 +33,31 @@ class WatermarkFilterTest extends TestCase
$format = $this->getMock('FFMpeg\Format\VideoInterface'); $format = $this->getMock('FFMpeg\Format\VideoInterface');
// test position absolute // test position absolute
$filter = new WatermarkFilter(__DIR__ . '/../../../../files/watermark.png', array( $filter = new WatermarkFilter(__DIR__ . '/../../../files/watermark.png', array(
'position' => 'absolute', 'position' => 'absolute',
'x' => 10, 'y' => 5 'x' => 10, 'y' => 5
)); ));
$this->assertEquals(array('-vf', 'movie='.__DIR__ .'/../../../../files/watermark.png [watermark]; [in][watermark] overlay=10:5 [out]'), $filter->apply($video, $format)); $this->assertEquals(array('-vf', 'movie='.__DIR__ .'/../../../files/watermark.png [watermark]; [in][watermark] overlay=10:5 [out]'), $filter->apply($video, $format));
// test position relative // test position relative
$filter = new WatermarkFilter(__DIR__ . '/../../../../files/watermark.png', array( $filter = new WatermarkFilter(__DIR__ . '/../../../files/watermark.png', array(
'position' => 'relative', 'position' => 'relative',
'bottom' => 10, 'left' => 5 'bottom' => 10, 'left' => 5
)); ));
$this->assertEquals(array('-vf', 'movie='.__DIR__ .'/../../../../files/watermark.png [watermark]; [in][watermark] overlay=5:main_h - 10 - overlay_h [out]'), $filter->apply($video, $format)); $this->assertEquals(array('-vf', 'movie='.__DIR__ .'/../../../files/watermark.png [watermark]; [in][watermark] overlay=5:main_h - 10 - overlay_h [out]'), $filter->apply($video, $format));
// test position relative // test position relative
$filter = new WatermarkFilter(__DIR__ . '/../../../../files/watermark.png', array( $filter = new WatermarkFilter(__DIR__ . '/../../../files/watermark.png', array(
'position' => 'relative', 'position' => 'relative',
'bottom' => 5, 'right' => 4 'bottom' => 5, 'right' => 4
)); ));
$this->assertEquals(array('-vf', 'movie='.__DIR__ .'/../../../../files/watermark.png [watermark]; [in][watermark] overlay=main_w - 4 - overlay_w:main_h - 5 - overlay_h [out]'), $filter->apply($video, $format)); $this->assertEquals(array('-vf', 'movie='.__DIR__ .'/../../../files/watermark.png [watermark]; [in][watermark] overlay=main_w - 4 - overlay_w:main_h - 5 - overlay_h [out]'), $filter->apply($video, $format));
// test position relative // test position relative
$filter = new WatermarkFilter(__DIR__ . '/../../../../files/watermark.png', array( $filter = new WatermarkFilter(__DIR__ . '/../../../files/watermark.png', array(
'position' => 'relative', 'position' => 'relative',
'left' => 5, 'top' => 11 'left' => 5, 'top' => 11
)); ));
$this->assertEquals(array('-vf', 'movie='.__DIR__ .'/../../../../files/watermark.png [watermark]; [in][watermark] overlay=5:11 [out]'), $filter->apply($video, $format)); $this->assertEquals(array('-vf', 'movie='.__DIR__ .'/../../../files/watermark.png [watermark]; [in][watermark] overlay=5:11 [out]'), $filter->apply($video, $format));
} }
} }

View file

@ -0,0 +1,28 @@
<?php
namespace Tests\FFMpeg\Unit\Filters\Waveform;
use Tests\FFMpeg\Unit\TestCase;
use FFMpeg\Filters\Waveform\WaveformDownmixFilter;
use FFMpeg\Media\Waveform;
use FFMpeg\Coordinate\TimeCode;
use FFMpeg\FFProbe\DataMapping\StreamCollection;
use FFMpeg\FFProbe\DataMapping\Stream;
class WaveformDownmixFilterTest extends TestCase
{
public function testApply()
{
$stream = new Stream(array('codec_type' => 'audio', 'width' => 960, 'height' => 720));
$streams = new StreamCollection(array($stream));
$audio = $this->getAudioMock(__FILE__);
$audio->expects($this->once())
->method('getStreams')
->will($this->returnValue($streams));
$waveform = new Waveform($audio, $this->getFFMpegDriverMock(), $this->getFFProbeMock(), 640, 120);
$filter = new WaveformDownmixFilter(TRUE);
$this->assertEquals(array('"aformat=channel_layouts=mono"'), $filter->apply($waveform));
}
}

View file

@ -0,0 +1,21 @@
<?php
namespace Tests\FFMpeg\Unit\Filters\Waveform;
use Tests\FFMpeg\Unit\TestCase;
use FFMpeg\Filters\Waveform\WaveformFilters;
class WaveformFiltersTest extends TestCase
{
public function testResize()
{
$Waveform = $this->getWaveformMock();
$filters = new WaveformFilters($Waveform);
$Waveform->expects($this->once())
->method('addFilter')
->with($this->isInstanceOf('FFMpeg\Filters\Waveform\WaveformDownmixFilter'));
$filters->setDownmix();
}
}

View file

@ -1,6 +1,6 @@
<?php <?php
namespace FFMpeg\Tests\Format\Audio; namespace Tests\FFMpeg\Unit\Format\Audio;
use FFMpeg\Format\Audio\Aac; use FFMpeg\Format\Audio\Aac;

View file

@ -1,8 +1,8 @@
<?php <?php
namespace FFMpeg\Tests\Format\Audio; namespace Tests\FFMpeg\Unit\Format\Audio;
use FFMpeg\Tests\TestCase; use Tests\FFMpeg\Unit\TestCase;
use FFMpeg\Format\Audio\DefaultAudio; use FFMpeg\Format\Audio\DefaultAudio;
abstract class AudioTestCase extends TestCase abstract class AudioTestCase extends TestCase

View file

@ -1,6 +1,6 @@
<?php <?php
namespace FFMpeg\Tests\Format\Audio; namespace Tests\FFMpeg\Unit\Format\Audio;
use FFMpeg\Format\Audio\Flac; use FFMpeg\Format\Audio\Flac;

View file

@ -1,6 +1,6 @@
<?php <?php
namespace FFMpeg\Tests\Format\Audio; namespace Tests\FFMpeg\Unit\Format\Audio;
use FFMpeg\Format\Audio\Mp3; use FFMpeg\Format\Audio\Mp3;

View file

@ -1,6 +1,6 @@
<?php <?php
namespace FFMpeg\Tests\Format\Audio; namespace Tests\FFMpeg\Unit\Format\Audio;
use FFMpeg\Format\Audio\Vorbis; use FFMpeg\Format\Audio\Vorbis;

View file

@ -1,6 +1,6 @@
<?php <?php
namespace FFMpeg\Tests\Format\Audio; namespace Tests\FFMpeg\Unit\Format\Audio;
use FFMpeg\Format\Audio\Wav; use FFMpeg\Format\Audio\Wav;

View file

@ -1,8 +1,8 @@
<?php <?php
namespace FFMpeg\Tests\Format\ProgressListener; namespace Tests\FFMpeg\Unit\Format\ProgressListener;
use FFMpeg\Tests\TestCase; use Tests\FFMpeg\Unit\TestCase;
use FFMpeg\Format\ProgressListener\AudioProgressListener; use FFMpeg\Format\ProgressListener\AudioProgressListener;
use FFMpeg\FFProbe\DataMapping\Format; use FFMpeg\FFProbe\DataMapping\Format;

View file

@ -1,8 +1,8 @@
<?php <?php
namespace FFMpeg\Tests\Format\ProgressListener; namespace Tests\FFMpeg\Unit\Format\ProgressListener;
use FFMpeg\Tests\TestCase; use Tests\FFMpeg\Unit\TestCase;
use FFMpeg\Format\ProgressListener\VideoProgressListener; use FFMpeg\Format\ProgressListener\VideoProgressListener;
use FFMpeg\FFProbe\DataMapping\Format; use FFMpeg\FFProbe\DataMapping\Format;
@ -11,7 +11,7 @@ class VideoProgressListenerTest extends TestCase
/** /**
* @dataProvider provideData * @dataProvider provideData
*/ */
public function testHandle($size, $duration, public function testHandle($size, $duration, $newVideoDuration,
$data, $expectedPercent, $expectedRemaining, $expectedRate, $data, $expectedPercent, $expectedRemaining, $expectedRate,
$data2, $expectedPercent2, $expectedRemaining2, $expectedRate2, $data2, $expectedPercent2, $expectedRemaining2, $expectedRate2,
$currentPass, $totalPass $currentPass, $totalPass
@ -26,7 +26,7 @@ class VideoProgressListenerTest extends TestCase
'duration' => $duration, 'duration' => $duration,
)))); ))));
$listener = new VideoProgressListener($ffprobe, __FILE__, $currentPass, $totalPass); $listener = new VideoProgressListener($ffprobe, __FILE__, $currentPass, $totalPass, $newVideoDuration);
$phpunit = $this; $phpunit = $this;
$n = 0; $n = 0;
$listener->on('progress', function ($percent, $remaining, $rate) use (&$n, $phpunit, $expectedPercent, $expectedRemaining, $expectedRate, $expectedPercent2, $expectedRemaining2, $expectedRate2) { $listener->on('progress', function ($percent, $remaining, $rate) use (&$n, $phpunit, $expectedPercent, $expectedRemaining, $expectedRate, $expectedPercent2, $expectedRemaining2, $expectedRate2) {
@ -57,6 +57,7 @@ class VideoProgressListenerTest extends TestCase
array( array(
147073958, 147073958,
281.147533, 281.147533,
281.147533,
'frame= 206 fps=202 q=10.0 size= 571kB time=00:00:07.12 bitrate= 656.8kbits/s dup=9 drop=0', 'frame= 206 fps=202 q=10.0 size= 571kB time=00:00:07.12 bitrate= 656.8kbits/s dup=9 drop=0',
2, 2,
0, 0,
@ -71,6 +72,7 @@ class VideoProgressListenerTest extends TestCase
array( array(
147073958, 147073958,
281.147533, 281.147533,
281.147533,
'frame= 206 fps=202 q=10.0 size= 571kB time=00:00:07.12 bitrate= 656.8kbits/s dup=9 drop=0', 'frame= 206 fps=202 q=10.0 size= 571kB time=00:00:07.12 bitrate= 656.8kbits/s dup=9 drop=0',
1, 1,
0, 0,
@ -81,6 +83,21 @@ class VideoProgressListenerTest extends TestCase
3868, 3868,
1, 1,
2 2
),
array(
147073958,
281.147533,
35,
'frame= 206 fps=202 q=10.0 size= 571kB time=00:00:07.12 bitrate= 656.8kbits/s dup=9 drop=0',
60,
0,
0,
'frame= 854 fps=113 q=20.0 size= 4430kB time=00:00:33.04 bitrate=1098.5kbits/s dup=36 drop=0',
97,
0,
3868,
2,
2
) )
); );
} }

View file

@ -1,6 +1,6 @@
<?php <?php
namespace FFMpeg\Tests\Format\Video; namespace Tests\FFMpeg\Unit\Format\Video;
use FFMpeg\Format\Video\Ogg; use FFMpeg\Format\Video\Ogg;

View file

@ -1,8 +1,8 @@
<?php <?php
namespace FFMpeg\Tests\Format\Video; namespace Tests\FFMpeg\Unit\Format\Video;
use FFMpeg\Tests\Format\Audio\AudioTestCase; use Tests\FFMpeg\Unit\Format\Audio\AudioTestCase;
abstract class VideoTestCase extends AudioTestCase abstract class VideoTestCase extends AudioTestCase
{ {

View file

@ -1,6 +1,6 @@
<?php <?php
namespace FFMpeg\Tests\Format\Video; namespace Tests\FFMpeg\Unit\Format\Video;
use FFMpeg\Format\Video\WMV3; use FFMpeg\Format\Video\WMV3;

View file

@ -1,6 +1,6 @@
<?php <?php
namespace FFMpeg\Tests\Format\Video; namespace Tests\FFMpeg\Unit\Format\Video;
use FFMpeg\Format\Video\WMV; use FFMpeg\Format\Video\WMV;

View file

@ -1,6 +1,6 @@
<?php <?php
namespace FFMpeg\Tests\Format\Video; namespace Tests\FFMpeg\Unit\Format\Video;
use FFMpeg\Format\Video\WebM; use FFMpeg\Format\Video\WebM;

View file

@ -1,6 +1,6 @@
<?php <?php
namespace FFMpeg\Tests\Format\Video; namespace Tests\FFMpeg\Unit\Format\Video;
use FFMpeg\Format\Video\X264; use FFMpeg\Format\Video\X264;

View file

@ -1,8 +1,8 @@
<?php <?php
namespace FFMpeg\Tests\Media; namespace Tests\FFMpeg\Unit\Media;
use FFMpeg\Tests\TestCase; use Tests\FFMpeg\Unit\TestCase;
abstract class AbstractMediaTestCase extends TestCase abstract class AbstractMediaTestCase extends TestCase
{ {

View file

@ -1,6 +1,6 @@
<?php <?php
namespace FFMpeg\Tests\Media; namespace Tests\FFMpeg\Unit\Media;
abstract class AbstractStreamableTestCase extends AbstractMediaTestCase abstract class AbstractStreamableTestCase extends AbstractMediaTestCase
{ {

Some files were not shown because too many files have changed in this diff Show more