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/
/docs/build
composer.phar
composer.lock
phpunit.xml
sami.phar
.idea/

View file

@ -1,18 +1,39 @@
language: php
before_script:
- sudo apt-get update
- sudo apt-get install -y ffmpeg libavcodec-extra-53
- composer self-update
- composer install --no-interaction --prefer-source --dev
dist: trusty
branches:
only:
- master
- v1.x
cache:
directories:
- $HOME/.composer/cache
- $HOME/.cache
php:
- 5.3.3
- 5.3
- 5.4
- 5.5
- 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:
- vendor/bin/phpunit --verbose
- vendor/bin/phpunit --verbose -c phpunit-functional.xml.dist

View file

@ -1,101 +1,173 @@
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).
* Add crop filter (@cangelis).
* Fix watermark (@sujayjaju).
[Unreleased]
------------
* 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).
* Add watermark filter (@sylvainv).
* Add configuration for audio channels (@SimonSimCity).
- Updated changelog to follow [keepachangelog.com](http://keepachangelog.com/)
style you see now here.
* 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.
* Remove time_start metadata when using synchronize filter
* Remove restriction on filesystem resources.
- AbstractData::get no longer throws exceptions (@sujayjaju).
- Add crop filter (@cangelis).
- 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
* BC Break : Add FormatInterface::getPasses and FormatInterface::getExtraParams
- Add support for Wav and AAC audio formats (@MrHash).
- 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 #66 : Allow single pass encodings.
- Fix width / height dimensions extraction.
* 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 DisplayRatioFixer Frame filter.
- Add Rotate 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.
* Fix #47 : MediaTypeInterface::save adds filters depending on the codec.
* Save frame to target file without prompt.
- Add support for video to audio transcoding
- BC Break : Add FormatInterface::getPasses and FormatInterface::getExtraParams
* 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 timeout setting on audio/video encoding (@xammep-ua)
- Fix Invalid ratio computing.
* 0.2.3 (04-21-2013)
[0.3.3] - 2013-09-05
--------------------
* Add timeout getter and setter on FFMpeg and FFProbe
* Add timeout setting via second argument on FFMpeg::load and FFProbe::load
- Add convenient Stream::getDimensions method to extract video dimension.
- Add DisplayRatioFixer Frame filter.
* 0.2.2 (02-11-2013)
[0.3.2] - 2013-08-08
--------------------
* 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
- Fix A/V synchronization over flash and HTML5 players.
* 0.2.1 (02-04-2013)
[0.3.1] - 2013-08-06
--------------------
* Parse the output of FFProbe using correct EOL sequences (@ak76)
* Add process timeout customization (@pulse00)
* Fix `accurate` option (`FFMpeg::extractImage`)
- Allow use of FFProbe on remote URIs.
- Fix #47 : MediaTypeInterface::save adds filters depending on the codec.
- Save frame to target file without prompt.
* 0.2.0 (12-13-2012)
[0.3.0] - 2013-07-04
--------------------
* Add HelperInterface and support for realtime progress ( @pulse00 ).
* Add `accurate` option to `FFMpeg::extractImage` method.
- Complete rewrite of the library, lots of BC breaks, check the doc.
* 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

303
README.md
View file

@ -1,4 +1,4 @@
#PHP FFmpeg
# PHP FFmpeg
[![Build Status](https://secure.travis-ci.org/PHP-FFMpeg/PHP-FFMpeg.png?branch=master)](http://travis-ci.org/PHP-FFMpeg/PHP-FFMpeg)
@ -6,39 +6,38 @@
An Object Oriented library to convert video/audio files with FFmpeg / AVConv.
Check another amazing repo : [PHP FFMpeg extras](https://github.com/alchemy-fr/PHP-FFMpeg-Extras), you will find lots of Audio/Video formats there.
Check another amazing repo: [PHP FFMpeg extras](https://github.com/alchemy-fr/PHP-FFMpeg-Extras), you will find lots of Audio/Video formats there.
## Your attention please
### How this library works :
### How this library works:
This library requires a working FFMpeg install. You will need both FFMpeg and FFProbe binaries to use it.
Be sure that these binaries can be located with system PATH to get the benefit of the binary detection,
otherwise you should have to explicitely give the binaries path on load.
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/.
### Known issues :
### Known issues:
- Using rotate and resize will produce a corrupted output when using
[libav](http://libav.org/) 0.8. The bug is fixed in version 9. This bug does not
- Using rotate and resize will produce a corrupted output when using
[libav](http://libav.org/) 0.8. The bug is fixed in version 9. This bug does not
appear in latest ffmpeg version.
## Installation
The recommended way to install PHP-FFMpeg is through [Composer](https://getcomposer.org).
```json
{
"require": {
"php-ffmpeg/php-ffmpeg": "~0.5"
}
}
```bash
$ composer require php-ffmpeg/php-ffmpeg
```
## Basic Usage
```php
require 'vendor/autoload.php';
$ffmpeg = FFMpeg\FFMpeg::create();
$video = $ffmpeg->open('video.mpg');
$video
@ -62,14 +61,14 @@ to browse the source code as it is self-documented.
### FFMpeg
`FFMpeg\FFMpeg` is the main object to use to manipulate medias. To build it,
use the static `FFMpeg\FFMpeg::create` :
use the static `FFMpeg\FFMpeg::create`:
```php
$ffmpeg = FFMpeg\FFMpeg::create();
```
FFMpeg will autodetect ffmpeg and ffprobe binaries. If you want to give binary
paths explicitely, you can pass an array as configuration. A `Psr\Logger\LoggerInterface`
paths explicitly, you can pass an array as configuration. A `Psr\Logger\LoggerInterface`
can also be passed to log binary executions.
```php
@ -86,8 +85,8 @@ $ffmpeg = FFMpeg\FFMpeg::create(array(
`FFMpeg\FFMpeg` creates media based on URIs. URIs could be either a pointer to a
local filesystem resource, an HTTP resource or any resource supported by FFmpeg.
**Note** : To list all supported resource type of your FFmpeg build, use the
`-protocols` command :
**Note**: To list all supported resource type of your FFmpeg build, use the
`-protocols` command:
```
ffmpeg -protocols
@ -99,12 +98,12 @@ To open a resource, use the `FFMpeg\FFMpeg::open` method.
$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.
#### 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.
##### Transcoding
@ -115,15 +114,15 @@ pass a `FFMpeg\Format\FormatInterface` for that.
Please note that audio and video bitrate are set on the format.
```php
$format = new Format\Video\X264();
$format = new FFMpeg\Format\Video\X264();
$format->on('progress', function ($video, $format, $percentage) {
echo "$percentage % transcoded";
});
$format
-> setKiloBitrate(1000)
-> setAudioChannels(2)
-> setAudioKiloBitrate(256);
->setKiloBitrate(1000)
->setAudioChannels(2)
->setAudioKiloBitrate(256);
$video->save($format, 'video.avi');
```
@ -136,7 +135,7 @@ below for more informations.
You can extract a frame at any timecode using the `FFMpeg\Media\Video::frame`
method.
This code return a `FFMpeg\Media\Frame` instance corresponding to the second 42.
This code returns a `FFMpeg\Media\Frame` instance corresponding to the second 42.
You can pass any `FFMpeg\Coordinate\TimeCode` as argument, see dedicated
documentation below for more information.
@ -145,6 +144,54 @@ $frame = $video->frame(FFMpeg\Coordinate\TimeCode::fromSeconds(42));
$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
You can apply filters on `FFMpeg\Media\Video` with the `FFMpeg\Media\Video::addFilter`
@ -173,9 +220,9 @@ $video->filters()->rotate($angle);
The `$angle` parameter must be one of the following constants :
- `FFMpeg\Filters\Video\RotateFilter::ROTATE_90` : 90° clockwise
- `FFMpeg\Filters\Video\RotateFilter::ROTATE_180` : 180°
- `FFMpeg\Filters\Video\RotateFilter::ROTATE_270` : 90° counterclockwise
- `FFMpeg\Filters\Video\RotateFilter::ROTATE_90`: 90° clockwise
- `FFMpeg\Filters\Video\RotateFilter::ROTATE_180`: 180°
- `FFMpeg\Filters\Video\RotateFilter::ROTATE_270`: 90° counterclockwise
###### Resize
@ -185,12 +232,57 @@ Resizes a video to a given size.
$video->filters()->resize($dimension, $mode, $useStandards);
```
The resize filter takes three parameters :
The resize filter takes three parameters:
- `$dimension`, an instance of `FFMpeg\Coordinate\Dimension`
- `$mode`, one of the constants `FFMpeg\Filters\Video\ResizeFilter::RESIZEMODE_*` constants
- `$useStandards`, a boolean to force the use of the nearest aspect ratio standard.
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
Changes the frame rate of the video.
@ -199,7 +291,7 @@ Changes the frame rate of the video.
$video->filters()->framerate($framerate, $gop);
```
The framerate filter takes two parameters :
The framerate filter takes two parameters:
- `$framerate`, an instance of `FFMpeg\Coordinate\Framerate`
- `$gop`, a [GOP](https://wikipedia.org/wiki/Group_of_pictures) value (integer)
@ -228,9 +320,21 @@ The clip filter takes two parameters:
- `$start`, an instance of `FFMpeg\Coordinate\TimeCode`, specifies the start point of the clip
- `$duration`, optional, an instance of `FFMpeg\Coordinate\TimeCode`, specifies the duration of the clip
#### 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.
##### Transcoding
@ -250,8 +354,8 @@ $format->on('progress', function ($audio, $format, $percentage) {
});
$format
-> setAudioChannels(2)
-> setAudioKiloBitrate(256);
->setAudioChannels(2)
->setAudioKiloBitrate(256);
$audio->save($format, 'track.flac');
```
@ -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
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
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
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
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.
```php
$format = new Format\Video\X264();
$format = new FFMpeg\Format\Video\X264();
$format->on('progress', function ($video, $format, $percentage) {
echo "$percentage % transcoded";
});
@ -315,6 +510,18 @@ $video->save($format, 'video.avi');
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
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\Dimension` represent a dimension.
- `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.
### FFProbe
@ -379,9 +586,19 @@ $ffprobe
->get('duration'); // returns the duration property
```
##Using with Silex Microframework
### Validating media files
Service provider is easy to set up :
(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
Service provider is easy to set up:
```php
$app = new Silex\Application();
@ -390,7 +607,7 @@ $app->register(new FFMpeg\FFMpegServiceProvider());
$video = $app['ffmpeg']->open('video.mpeg');
```
Available options are as follow :
Available options are as follow:
```php
$app->register(new FFMpeg\FFMpegServiceProvider(), array(
@ -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
This project is licensed under the [MIT license](http://opensource.org/licenses/MIT).

View file

@ -5,40 +5,60 @@
"keywords": ["video processing", "video", "audio processing", "audio", "avconv", "ffmpeg", "avprobe", "ffprobe"],
"license": "MIT",
"authors": [
{
"name": "Romain Neutron",
"email": "imprec@gmail.com",
"homepage": "http://www.lickmychip.com/"
},
{
"name": "Phraseanet Team",
"email": "info@alchemy.fr",
"homepage": "http://www.phraseanet.com/"
}
{
"name": "Romain Neutron",
"email": "imprec@gmail.com",
"homepage": "http://www.lickmychip.com/"
},
{
"name": "Phraseanet Team",
"email": "info@alchemy.fr",
"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": {
"php" : ">=5.3.3",
"alchemy/binary-driver" : "~1.5",
"doctrine/cache" : "~1.0",
"evenement/evenement" : "~1.0",
"neutron/temporary-filesystem" : "~2.1, >=2.1.1"
"php": "^5.3.9 || ^7.0",
"alchemy/binary-driver": "^1.5",
"doctrine/cache": "^1.0",
"evenement/evenement": "^2.0 || ^1.0",
"neutron/temporary-filesystem": "^2.1.1"
},
"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"
},
"require-dev": {
"sami/sami" : "~1.0",
"silex/silex" : "~1.0",
"phpunit/phpunit" : "~3.7"
"sami/sami": "~1.0",
"silex/silex": "~1.0",
"phpunit/phpunit": "^4.8.36"
},
"autoload": {
"psr-0": {
"FFMpeg": "src"
}
},
"autoload-dev": {
"psr-4": {
"Tests\\FFMpeg\\": "tests"
}
},
"extra": {
"branch-alias": {
"dev-master": "0.5-dev"
"dev-master": "0.7-dev"
}
}
}

View file

@ -119,7 +119,17 @@
</tr>
<tr>
<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 class="last">
<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>
<h3 id="method_getPasses">
<div class="location">at line 68</div>
<code> public string
<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 integer
<strong>getPasses</strong>()</code>
</h3>
<div class="details">
@ -517,7 +552,7 @@
<table>
<tr>
<td>string</td>
<td>integer</td>
<td>
</td>
</tr>

View file

@ -171,6 +171,16 @@
<p>Exports the audio in the desired format, applies registered filters.</p>
</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>
</table>
@ -609,6 +619,59 @@
</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 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"
>
<testsuites>
<testsuite name="FFMpeg Tests Suite">
<directory>tests/FFMpeg/Tests</directory>
<testsuite name="unit">
<directory>tests/Unit</directory>
</testsuite>
<testsuite name="functional">
<directory>tests/Functional</directory>
</testsuite>
</testsuites>
<filter>
<blacklist>
<directory>vendor</directory>
<directory>tests</directory>
</blacklist>
</filter>
</phpunit>

View file

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

View file

@ -89,4 +89,32 @@ class TimeCode
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);
}
/**
* @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
*

View file

@ -11,8 +11,6 @@
namespace FFMpeg\FFProbe\DataMapping;
use FFMpeg\Exception\InvalidArgumentException;
abstract class AbstractData implements \Countable
{
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
/*
* 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\AddMetadataFilter;
use FFMpeg\Media\Audio;
use FFMpeg\Coordinate\TimeCode;
class AudioFilters
{
@ -26,4 +37,38 @@ class AudioFilters
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
/*
* 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\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) {
$dimensions = $this->getComputedDimensions($dimensions, $format->getModulus());
$commands[] = '-s';
$commands[] = $dimensions->getWidth() . 'x' . $dimensions->getHeight();
// Using Filter to have ordering
$commands[] = '-vf';
$commands[] = '[in]scale=' . $dimensions->getWidth() . ':' . $dimensions->getHeight() . ' [out]';
}
return $commands;

View file

@ -46,7 +46,7 @@ class VideoFilters extends AudioFilters
* Changes the video framerate.
*
* @param FrameRate $framerate
* @param type $gop
* @param Integer $gop
*
* @return VideoFilters
*/
@ -57,6 +57,21 @@ class VideoFilters extends AudioFilters
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.
*
@ -98,6 +113,20 @@ class VideoFilters extends AudioFilters
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)
{
$this->media->addFilter(new RotateFilter($angle, 30));
@ -132,4 +161,18 @@ class VideoFilters extends AudioFilters
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}
*/
public function createProgressListener(MediaTypeInterface $media, FFProbe $ffprobe, $pass, $total)
public function createProgressListener(MediaTypeInterface $media, FFProbe $ffprobe, $pass, $total, $duration = 0)
{
$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) {
$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
*/
public function __construct(FFProbe $ffprobe, $pathfile, $currentPass, $totalPass)
public function __construct(FFProbe $ffprobe, $pathfile, $currentPass, $totalPass, $duration = 0)
{
$this->ffprobe = $ffprobe;
$this->pathfile = $pathfile;
$this->currentPass = $currentPass;
$this->totalPass = $totalPass;
$this->duration = $duration;
}
/**
@ -120,6 +121,14 @@ abstract class AbstractProgressListener extends EventEmitter implements Listener
return $this->totalPass;
}
/**
* @return int
*/
public function getCurrentTime()
{
return $this->currentTime;
}
/**
* {@inheritdoc}
*/
@ -171,6 +180,12 @@ abstract class AbstractProgressListener extends EventEmitter implements Listener
if ($this->lastOutput !== null) {
$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;
$rate = $deltaSize * $delta;
if ($rate > 0) {
@ -240,9 +255,8 @@ abstract class AbstractProgressListener extends EventEmitter implements Listener
return;
}
$this->totalSize = $format->get('size') / 1024;
$this->duration = $format->get('duration');
$this->duration = (int) $this->duration > 0 ? $this->duration : $format->get('duration');
$this->totalSize = $format->get('size') / 1024 * ($this->duration / $format->get('duration'));
$this->initialized = true;
}
}

View file

@ -24,8 +24,9 @@ interface ProgressableInterface extends EventEmitterInterface
* @param FFProbe $ffprobe
* @param Integer $pass The current pas snumber
* @param Integer $total The total pass number
* @param Integer $duration The new video duration
*
* @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 */
protected $modulus = 16;
/** @var Array */
protected $additionalParamaters;
/**
* {@inheritdoc}
*/
@ -97,10 +100,35 @@ abstract class DefaultVideo extends DefaultAudio implements VideoInterface
/**
* {@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;
$listeners = array(new VideoProgressListener($ffprobe, $media->getPathfile(), $pass, $total));
$listeners = array(new VideoProgressListener($ffprobe, $media->getPathfile(), $pass, $total, $duration));
foreach ($listeners as $listener) {
$listener->on('progress', function () use ($format, $media) {

View file

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

View file

@ -19,6 +19,9 @@ class X264 extends DefaultVideo
/** @var boolean */
private $bframesSupport = true;
/** @var integer */
private $passes = 2;
public function __construct($audioCodec = 'libfaac', $videoCodec = 'libx264')
{
$this
@ -51,7 +54,7 @@ class X264 extends DefaultVideo
*/
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');
}
/**
* @param $passes
*
* @return X264
*/
public function setPasses($passes)
{
$this->passes = $passes;
return $this;
}
/**
* {@inheritDoc}
*/
public function getPasses()
{
return 2;
return $this->passes;
}
/**

View file

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

View file

@ -52,11 +52,9 @@ class Audio extends AbstractStreamableMedia
/**
* Exports the audio in the desired format, applies registered filters.
*
* @param FormatInterface $format
* @param string $outputPathfile
*
* @param FormatInterface $format
* @param string $outputPathfile
* @return Audio
*
* @throws RuntimeException
*/
public function save(FormatInterface $format, $outputPathfile)
@ -64,9 +62,42 @@ class Audio extends AbstractStreamableMedia
$listeners = null;
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);
$filters = clone $this->filters;
@ -93,13 +124,19 @@ class Audio extends AbstractStreamableMedia
}
$commands[] = $outputPathfile;
try {
$this->driver->command($commands, false, $listeners);
} catch (ExecutionFailureException $e) {
$this->cleanupTemporaryFile($outputPathfile);
throw new RuntimeException('Encoding failed', $e->getCode(), $e);
}
return $commands;
}
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,26 +85,31 @@ class Frame extends AbstractMediaType
*
* @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
* @see http://ffmpeg.org/ffmpeg.html#Main-options
*/
$outputFormat = $returnBase64 ? "image2pipe" : "image2";
if (!$accurate) {
$commands = array(
'-y', '-ss', (string) $this->timecode,
'-i', $this->pathfile,
'-vframes', '1',
'-f', 'image2'
'-f', $outputFormat
);
} else {
$commands = array(
'-y', '-i', $this->pathfile,
'-vframes', '1', '-ss', (string) $this->timecode,
'-f', 'image2'
'-f', $outputFormat
);
}
if($returnBase64) {
array_push($commands, "-");
}
foreach ($this->filters as $filter) {
$commands = array_merge($commands, $filter->apply($this));
@ -113,12 +118,16 @@ class Frame extends AbstractMediaType
$commands = array_merge($commands, array($pathfile));
try {
$this->driver->command($commands);
if(!$returnBase64) {
$this->driver->command($commands);
return $this;
}
else {
return $this->driver->command($commands);
}
} catch (ExecutionFailureException $e) {
$this->cleanupTemporaryFile($pathfile);
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 FFMpeg\Coordinate\TimeCode;
use FFMpeg\Coordinate\Dimension;
use FFMpeg\Filters\Audio\SimpleFilter;
use FFMpeg\Exception\InvalidArgumentException;
use FFMpeg\Exception\RuntimeException;
@ -23,12 +24,24 @@ use FFMpeg\Format\ProgressableInterface;
use FFMpeg\Format\AudioInterface;
use FFMpeg\Format\VideoInterface;
use Neutron\TemporaryFilesystem\Manager as FsManager;
use FFMpeg\Filters\Video\ClipFilter;
class Video extends Audio
{
/**
* {@inheritdoc}
*
* FileSystem Manager instance
* @var Manager
*/
protected $fs;
/**
* FileSystem Manager ID
* @var int
*/
protected $fsId;
/**
* @inheritDoc
* @return VideoFilters
*/
public function filters()
@ -37,8 +50,7 @@ class Video extends Audio
}
/**
* {@inheritdoc}
*
* @inheritDoc
* @return Video
*/
public function addFilter(FilterInterface $filter)
@ -51,15 +63,78 @@ class Video extends Audio
/**
* Exports the video in the desired format, applies registered filters.
*
* @param FormatInterface $format
* @param string $outputPathfile
*
* @param FormatInterface $format
* @param string $outputPathfile
* @return Video
*
* @throws RuntimeException
*/
public function save(FormatInterface $format, $outputPathfile)
{
$passes = $this->buildCommand($format, $outputPathfile);
$failure = null;
$totalPasses = $format->getPasses();
foreach ($passes as $pass => $passCommands) {
try {
/** add listeners here */
$listeners = null;
if ($format instanceof ProgressableInterface) {
$filters = clone $this->filters;
$duration = 0;
// check the filters of the video, and if the video has the ClipFilter then
// take the new video duration and send to the
// FFMpeg\Format\ProgressListener\AbstractProgressListener class
foreach ($filters as $filter) {
if($filter instanceof ClipFilter){
$duration = $filter->getDuration()->toSeconds();
break;
}
}
$listeners = $format->createProgressListener($this, $this->ffprobe, $pass + 1, $totalPasses, $duration);
}
$this->driver->command($passCommands, false, $listeners);
} catch (ExecutionFailureException $e) {
$failure = $e;
break;
}
}
$this->fs->clean($this->fsId);
if (null !== $failure) {
throw new RuntimeException('Encoding failed', $failure->getCode(), $failure);
}
return $this;
}
/**
* NOTE: This method is different to the Audio's one, because Video is using passes.
* @inheritDoc
*/
public function getFinalCommand(FormatInterface $format, $outputPathfile) {
$finalCommands = array();
foreach($this->buildCommand($format, $outputPathfile) as $pass => $passCommands) {
$finalCommands[] = implode(' ', $passCommands);
}
$this->fs->clean($this->fsId);
return $finalCommands;
}
/**
* **NOTE:** This creates passes instead of a single command!
*
* @inheritDoc
* @return string[][]
*/
protected function buildCommand(FormatInterface $format, $outputPathfile) {
$commands = array('-y', '-i', $this->pathfile);
$filters = clone $this->filters;
@ -119,17 +194,76 @@ class Video extends Audio
}
}
$fs = FsManager::create();
$fsId = uniqid('ffmpeg-passes');
$passPrefix = $fs->createTemporaryDirectory(0777, 50, $fsId) . '/' . uniqid('pass-');
// If the user passed some additional parameters
if ($format instanceof VideoInterface) {
if (null !== $format->getAdditionalParameters()) {
foreach ($format->getAdditionalParameters() as $additionalParameter) {
$commands[] = $additionalParameter;
}
}
}
// Merge Filters into one command
$videoFilterVars = $videoFilterProcesses = array();
for($i=0;$i<count($commands);$i++) {
$command = $commands[$i];
if ( $command == '-vf' ) {
$commandSplits = explode(";", $commands[$i + 1]);
if ( count($commandSplits) == 1 ) {
$commandSplit = $commandSplits[0];
$command = trim($commandSplit);
if ( preg_match("/^\[in\](.*?)\[out\]$/is", $command, $match) ) {
$videoFilterProcesses[] = $match[1];
} else {
$videoFilterProcesses[] = $command;
}
} else {
foreach($commandSplits as $commandSplit) {
$command = trim($commandSplit);
if ( preg_match("/^\[[^\]]+\](.*?)\[[^\]]+\]$/is", $command, $match) ) {
$videoFilterProcesses[] = $match[1];
} else {
$videoFilterVars[] = $command;
}
}
}
unset($commands[$i]);
unset($commands[$i + 1]);
$i++;
}
}
$videoFilterCommands = $videoFilterVars;
$lastInput = 'in';
foreach($videoFilterProcesses as $i => $process) {
$command = '[' . $lastInput .']';
$command .= $process;
$lastInput = 'p' . $i;
if($i === (count($videoFilterProcesses) - 1)) {
$command .= '[out]';
} else {
$command .= '[' . $lastInput . ']';
}
$videoFilterCommands[] = $command;
}
$videoFilterCommand = implode(';', $videoFilterCommands);
if($videoFilterCommand) {
$commands[] = '-vf';
$commands[] = $videoFilterCommand;
}
$this->fs = FsManager::create();
$this->fsId = uniqid('ffmpeg-passes');
$passPrefix = $this->fs->createTemporaryDirectory(0777, 50, $this->fsId) . '/' . uniqid('pass-');
$passes = array();
$totalPasses = $format->getPasses();
if (1 > $totalPasses) {
if(!$totalPasses) {
throw new InvalidArgumentException('Pass number should be a positive value.');
}
for ($i = 1; $i <= $totalPasses; $i++) {
for($i = 1; $i <= $totalPasses; $i++) {
$pass = $commands;
if ($totalPasses > 1) {
@ -144,31 +278,7 @@ class Video extends Audio
$passes[] = $pass;
}
$failure = null;
foreach ($passes as $pass => $passCommands) {
try {
/** add listeners here */
$listeners = null;
if ($format instanceof ProgressableInterface) {
$listeners = $format->createProgressListener($this, $this->ffprobe, $pass + 1, $totalPasses);
}
$this->driver->command($passCommands, false, $listeners);
} catch (ExecutionFailureException $e) {
$failure = $e;
break;
}
}
$fs->clean($fsId);
if (null !== $failure) {
throw new RuntimeException('Encoding failed', $failure->getCode(), $failure);
}
return $this;
return $passes;
}
/**
@ -181,4 +291,28 @@ class Video extends Audio
{
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
namespace FFMpeg\Functional;
namespace Tests\FFMpeg\Functional;
use FFMpeg\FFProbe;
@ -9,13 +9,26 @@ class FFProbeTest extends FunctionalTestCase
public function testProbeOnFile()
{
$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
*/
public function testProbeOnUnexistantFile()
public function testProbeOnNonExistantFile()
{
$ffprobe = FFProbe::create();
$ffprobe->streams('/path/to/no/file');
@ -24,6 +37,6 @@ class FFProbeTest extends FunctionalTestCase
public function testProbeOnRemoteFile()
{
$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
namespace FFMpeg\Functional;
namespace Tests\FFMpeg\Functional;
use FFMpeg\FFMpeg;
use PHPUnit\Framework\TestCase;
abstract class FunctionalTestCase extends \PHPUnit_Framework_TestCase
abstract class FunctionalTestCase extends TestCase
{
/**
* @return FFMpeg

View file

@ -1,6 +1,6 @@
<?php
namespace FFMpeg\Functional;
namespace Tests\FFMpeg\Functional;
use FFMpeg\Coordinate\Dimension;
use FFMpeg\Filters\Video\ResizeFilter;
@ -18,14 +18,44 @@ class VideoTranscodeTest extends FunctionalTestCase
}
$ffmpeg = $this->getFFMpeg();
$video = $ffmpeg->open(__DIR__ . '/../../files/Test.ogv');
$video = $ffmpeg->open(__DIR__ . '/../files/Test.ogv');
$this->assertInstanceOf('FFMpeg\Media\Video', $video);
$lastPercentage = null;
$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) {
if (null !== $lastPercentage) {
$phpunit->assertGreaterThanOrEqual($lastPercentage, $percentage);
@ -46,16 +76,16 @@ class VideoTranscodeTest extends FunctionalTestCase
public function testTranscodeInvalidFile()
{
$ffmpeg = $this->getFFMpeg();
$ffmpeg->open(__DIR__ . '/../../files/UnknownFileTest.ogv');
$ffmpeg->open(__DIR__ . '/../files/UnknownFileTest.ogv');
}
public function testSaveInvalidForgedVideo()
{
$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');
$video->save(new X264('libvo_aacenc'), __DIR__ . '/output/output-x264.mp4');
$video->save(new X264('aac'), __DIR__ . '/output/output-x264.mp4');
}
public function testTranscodePortraitVideo()
@ -72,12 +102,12 @@ class VideoTranscodeTest extends FunctionalTestCase
}
$ffmpeg = $this->getFFMpeg();
$video = $ffmpeg->open(__DIR__ . '/../../files/portrait.MOV');
$video = $ffmpeg->open(__DIR__ . '/../files/portrait.MOV');
$video->filters()
->resize(new Dimension(320, 240), ResizeFilter::RESIZEMODE_INSET)
->rotate(RotateFilter::ROTATE_90);
$video->save(new X264('libvo_aacenc'), $filename);
$video->save(new X264('aac'), $filename);
$dimension = $ffmpeg->getFFProbe()
->streams($filename)

View file

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

View file

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

View file

@ -1,8 +1,8 @@
<?php
namespace FFMpeg\Tests\Coordinate;
namespace Tests\FFMpeg\Unit\Coordinate;
use FFMpeg\Tests\TestCase;
use Tests\FFMpeg\Unit\TestCase;
use FFMpeg\Coordinate\FrameRate;
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
namespace FFMpeg\Tests\Coordinate;
namespace Tests\FFMpeg\Unit\Coordinate;
use FFMpeg\Tests\TestCase;
use Tests\FFMpeg\Unit\TestCase;
use FFMpeg\Coordinate\TimeCode;
class TimeCodeTest extends TestCase

View file

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

View file

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

View file

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

View file

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

View file

@ -1,8 +1,8 @@
<?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;
class AbstractDataTest extends TestCase

View file

@ -1,8 +1,8 @@
<?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;
class StreamCollectionTest extends TestCase

View file

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

View file

@ -1,8 +1,8 @@
<?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;
use FFMpeg\FFProbe\DataMapping\Format;
@ -31,8 +31,8 @@ class MapperTest extends TestCase
public function provideMappings()
{
$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);
$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);
return array(
array(FFProbe::TYPE_FORMAT, $format, new Format($format['format'])),

View file

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

View file

@ -1,8 +1,8 @@
<?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;
@ -28,11 +28,11 @@ class OutputParserTest extends TestCase
public function provideTypeDataAndOutput()
{
$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);
$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);
$rawFormat = file_get_contents(__DIR__ . '/../../../fixtures/ffprobe/show_format.raw');
$rawStreams = file_get_contents(__DIR__ . '/../../../fixtures/ffprobe/show_streams.raw');
$rawFormat = file_get_contents(__DIR__ . '/../../fixtures/ffprobe/show_format.raw');
$rawStreams = file_get_contents(__DIR__ . '/../../fixtures/ffprobe/show_streams.raw');
return array(
array(FFProbe::TYPE_FORMAT, $rawFormat, $expectedFormat),

View file

@ -1,6 +1,6 @@
<?php
namespace FFMpeg\Tests;
namespace Tests\FFMpeg\Unit;
use FFMpeg\FFProbe;
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
namespace FFMpeg\Tests\Filters\Audio;
namespace Tests\FFMpeg\Unit\Filters\Audio;
use FFMpeg\Filters\Audio\AudioFilters;
use FFMpeg\Tests\TestCase;
use Tests\FFMpeg\Unit\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
namespace FFMpeg\Tests\Filters\Audio;
namespace Tests\FFMpeg\Unit\Filters\Audio;
use FFMpeg\Filters\Audio\AudioResamplableFilter;
use FFMpeg\Tests\TestCase;
use Tests\FFMpeg\Unit\TestCase;
class AudioResamplableFilterTest extends TestCase
{

View file

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

View file

@ -1,8 +1,8 @@
<?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\Media\Frame;
use FFMpeg\Coordinate\TimeCode;

View file

@ -1,8 +1,8 @@
<?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;
class FrameFiltersTest extends TestCase

View file

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

View file

@ -1,10 +1,10 @@
<?php
namespace FFMpeg\Tests\Filters\Video;
namespace Tests\FFMpeg\Unit\Filters\Video;
use FFMpeg\Filters\Video\CustomFilter;
use FFMpeg\Filters\Video\FrameRateFilter;
use FFMpeg\Tests\TestCase;
use Tests\FFMpeg\Unit\TestCase;
use FFMpeg\Coordinate\FrameRate;
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
namespace FFMpeg\Tests\Filters\Video;
namespace Tests\FFMpeg\Unit\Filters\Video;
use FFMpeg\Filters\Video\FrameRateFilter;
use FFMpeg\Tests\TestCase;
use Tests\FFMpeg\Unit\TestCase;
use FFMpeg\Coordinate\FrameRate;
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
namespace FFMpeg\Tests\Filters\Video;
namespace Tests\FFMpeg\Unit\Filters\Video;
use FFMpeg\Filters\Video\ResizeFilter;
use FFMpeg\Tests\TestCase;
use Tests\FFMpeg\Unit\TestCase;
use FFMpeg\FFProbe\DataMapping\Stream;
use FFMpeg\FFProbe\DataMapping\StreamCollection;
use FFMpeg\Coordinate\Dimension;
@ -42,34 +42,34 @@ class ResizeFilterTest extends TestCase
public function provideDimensions()
{
return array(
array(new Dimension(320, 240), ResizeFilter::RESIZEMODE_FIT, 640, 480, 2, array('-s', '320x240')),
array(new Dimension(320, 240), ResizeFilter::RESIZEMODE_INSET, 640, 480, 2, array('-s', '320x240')),
array(new Dimension(320, 240), ResizeFilter::RESIZEMODE_SCALE_HEIGHT, 640, 480, 2, array('-s', '320x240')),
array(new Dimension(320, 240), ResizeFilter::RESIZEMODE_SCALE_WIDTH, 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('-vf', '[in]scale=320:240 [out]')),
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('-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_INSET, 320, 240, 2, array('-s', '640x480')),
array(new Dimension(640, 480), ResizeFilter::RESIZEMODE_SCALE_HEIGHT, 320, 240, 2, array('-s', '640x480')),
array(new Dimension(640, 480), ResizeFilter::RESIZEMODE_SCALE_WIDTH, 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('-vf', '[in]scale=640:480 [out]')),
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('-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_INSET, 1280, 720, 2, array('-s', '640x360')),
array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_SCALE_HEIGHT, 1280, 720, 2, array('-s', '640x360')),
array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_SCALE_WIDTH, 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('-vf', '[in]scale=640:360 [out]')),
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('-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_INSET, 1280, 720, 2, array('-s', '640x360')),
array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_SCALE_HEIGHT, 1280, 720, 2, array('-s', '640x360')),
array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_SCALE_WIDTH, 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('-vf', '[in]scale=640:360 [out]')),
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('-vf', '[in]scale=640:360 [out]')),
// 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('-s', '40x150'), false),
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('-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_INSET, 640, 480, 2, array('-s', '320x240')),
array(new Dimension(320, 320), ResizeFilter::RESIZEMODE_SCALE_HEIGHT, 640, 480, 2, array('-s', '320x240')),
array(new Dimension(320, 320), ResizeFilter::RESIZEMODE_SCALE_WIDTH, 640, 480, 2, array('-s', '426x320')),
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('-vf', '[in]scale=320:240 [out]')),
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('-vf', '[in]scale=426:320 [out]')),
);
}
}

View file

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

View file

@ -1,8 +1,8 @@
<?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;
class SynchronizeFilterTest extends TestCase

View file

@ -1,8 +1,8 @@
<?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\ResizeFilter;

View file

@ -1,12 +1,12 @@
<?php
namespace FFMpeg\Tests\Filters\Video;
namespace Tests\FFMpeg\Unit\Filters\Video;
use FFMpeg\FFProbe\DataMapping\Stream;
use FFMpeg\FFProbe\DataMapping\StreamCollection;
use FFMpeg\Filters\Video\RotateFilter;
use FFMpeg\Filters\Video\WatermarkFilter;
use FFMpeg\Tests\TestCase;
use Tests\FFMpeg\Unit\TestCase;
class WatermarkFilterTest extends TestCase
{
@ -19,8 +19,8 @@ class WatermarkFilterTest extends TestCase
$format = $this->getMock('FFMpeg\Format\VideoInterface');
$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));
$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));
// check size of video is unchanged
$this->assertEquals(320, $stream->get('width'));
@ -33,31 +33,31 @@ class WatermarkFilterTest extends TestCase
$format = $this->getMock('FFMpeg\Format\VideoInterface');
// test position absolute
$filter = new WatermarkFilter(__DIR__ . '/../../../../files/watermark.png', array(
$filter = new WatermarkFilter(__DIR__ . '/../../../files/watermark.png', array(
'position' => 'absolute',
'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
$filter = new WatermarkFilter(__DIR__ . '/../../../../files/watermark.png', array(
$filter = new WatermarkFilter(__DIR__ . '/../../../files/watermark.png', array(
'position' => 'relative',
'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
$filter = new WatermarkFilter(__DIR__ . '/../../../../files/watermark.png', array(
$filter = new WatermarkFilter(__DIR__ . '/../../../files/watermark.png', array(
'position' => 'relative',
'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
$filter = new WatermarkFilter(__DIR__ . '/../../../../files/watermark.png', array(
$filter = new WatermarkFilter(__DIR__ . '/../../../files/watermark.png', array(
'position' => 'relative',
'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
namespace FFMpeg\Tests\Format\Audio;
namespace Tests\FFMpeg\Unit\Format\Audio;
use FFMpeg\Format\Audio\Aac;

View file

@ -1,8 +1,8 @@
<?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;
abstract class AudioTestCase extends TestCase

View file

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

View file

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

View file

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

View file

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

View file

@ -1,8 +1,8 @@
<?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\FFProbe\DataMapping\Format;

View file

@ -1,8 +1,8 @@
<?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\FFProbe\DataMapping\Format;
@ -11,7 +11,7 @@ class VideoProgressListenerTest extends TestCase
/**
* @dataProvider provideData
*/
public function testHandle($size, $duration,
public function testHandle($size, $duration, $newVideoDuration,
$data, $expectedPercent, $expectedRemaining, $expectedRate,
$data2, $expectedPercent2, $expectedRemaining2, $expectedRate2,
$currentPass, $totalPass
@ -26,7 +26,7 @@ class VideoProgressListenerTest extends TestCase
'duration' => $duration,
))));
$listener = new VideoProgressListener($ffprobe, __FILE__, $currentPass, $totalPass);
$listener = new VideoProgressListener($ffprobe, __FILE__, $currentPass, $totalPass, $newVideoDuration);
$phpunit = $this;
$n = 0;
$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(
147073958,
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',
2,
0,
@ -71,6 +72,7 @@ class VideoProgressListenerTest extends TestCase
array(
147073958,
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',
1,
0,
@ -81,6 +83,21 @@ class VideoProgressListenerTest extends TestCase
3868,
1,
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
namespace FFMpeg\Tests\Format\Video;
namespace Tests\FFMpeg\Unit\Format\Video;
use FFMpeg\Format\Video\Ogg;

View file

@ -1,8 +1,8 @@
<?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
{

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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