🔥 Replace all other files with 💀

This commit is contained in:
Dan Jones 2022-08-21 15:37:37 -05:00
commit d187e02d24
124 changed files with 345 additions and 8227 deletions

13
.ac-php-conf.json Normal file
View file

@ -0,0 +1,13 @@
{
"use-cscope": null,
"tag-dir": null,
"filter": {
"php-file-ext-list": [
"php"
],
"php-path-list": [
"."
],
"php-path-list-without-subdir": []
}
}

15
.editorconfig Normal file
View file

@ -0,0 +1,15 @@
root = true
[*]
charset = utf-8
indent_size = 4
indent_style = space
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.{yml,yaml}]
indent_size = 2

22
.gitattributes vendored
View file

@ -1,7 +1,15 @@
.gitattributes export-ignore
.gitignore export-ignore
.github export-ignore
docs export-ignore
tests export-ignore
phpunit.xml.dist export-ignore
.travis.yml export-ignore
# Path-based git attributes
# https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html
# Ignore all test and documentation with "export-ignore".
/.github export-ignore
/.gitattributes export-ignore
/.gitignore export-ignore
/phpunit.xml.dist export-ignore
/psalm.xml.dist export-ignore
/tests export-ignore
/.editorconfig export-ignore
/.php-cs-fixer.dist.php export-ignore
/art export-ignore
/docs export-ignore
/UPGRADING.md export-ignore

1
.github/FUNDING.yml vendored Normal file
View file

@ -0,0 +1 @@
github: goodevilgenius

View file

@ -1,25 +0,0 @@
| 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.

14
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View file

@ -0,0 +1,14 @@
blank_issues_enabled: false
contact_links:
- name: Ask a question
url: https://github.com/goodevilgenius/ffmpeg-mappable-media/discussions/new?category=q-a
about: Ask the community for help
- name: Request a feature
url: https://github.com/goodevilgenius/ffmpeg-mappable-media/discussions/new?category=ideas
about: Share ideas for new features
- name: Report a security issue
url: https://github.com/goodevilgenius/ffmpeg-mappable-media/security/policy
about: Learn how to notify us for sensitive bugs
- name: Report a bug
url: https://github.com/goodevilgenius/ffmpeg-mappable-media/issues/new
about: Report a reproducable bug

View file

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

12
.github/dependabot.yml vendored Normal file
View file

@ -0,0 +1,12 @@
# Please see the documentation for all configuration options:
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
labels:
- "dependencies"

View file

@ -0,0 +1,32 @@
name: dependabot-auto-merge
on: pull_request_target
permissions:
pull-requests: write
contents: write
jobs:
dependabot:
runs-on: ubuntu-latest
if: ${{ github.actor == 'dependabot[bot]' }}
steps:
- name: Dependabot metadata
id: metadata
uses: dependabot/fetch-metadata@v1.3.3
with:
github-token: "${{ secrets.GITHUB_TOKEN }}"
- name: Auto-merge Dependabot PRs for semver-minor updates
if: ${{steps.metadata.outputs.update-type == 'version-update:semver-minor'}}
run: gh pr merge --auto --merge "$PR_URL"
env:
PR_URL: ${{github.event.pull_request.html_url}}
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
- name: Auto-merge Dependabot PRs for semver-patch updates
if: ${{steps.metadata.outputs.update-type == 'version-update:semver-patch'}}
run: gh pr merge --auto --merge "$PR_URL"
env:
PR_URL: ${{github.event.pull_request.html_url}}
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}

23
.github/workflows/php-cs-fixer.yml vendored Normal file
View file

@ -0,0 +1,23 @@
name: Check & fix styling
on: [push]
jobs:
php-cs-fixer:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
with:
ref: ${{ github.head_ref }}
- name: Run PHP CS Fixer
uses: docker://oskarstark/php-cs-fixer-ga
with:
args: --config=.php-cs-fixer.dist.php --allow-risky=yes
- name: Commit changes
uses: stefanzweifel/git-auto-commit-action@v4
with:
commit_message: Fix styling

37
.github/workflows/run-tests.yml vendored Normal file
View file

@ -0,0 +1,37 @@
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: true
matrix:
os: [ubuntu-latest, windows-latest]
php: [8.0]
stability: [prefer-lowest, prefer-stable]
name: P${{ matrix.php }} - ${{ matrix.stability }} - ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo
coverage: none
- name: Setup problem matchers
run: |
echo "::add-matcher::${{ runner.tool_cache }}/php.json"
echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
- name: Install dependencies
run: composer update --${{ matrix.stability }} --prefer-dist --no-interaction
- name: Execute tests
run: vendor/bin/pest

View file

@ -1,48 +0,0 @@
name: run-tests
on: [push, pull_request]
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: true
matrix:
os: [ubuntu-20.04]
php: [8.1, 8.0]
ffmpeg: [5.0, 4.4]
dependency-version: [prefer-lowest, prefer-stable]
name: ${{ matrix.os }} - P${{ matrix.php }} - FF${{ matrix.ffmpeg }} - ${{ matrix.dependency-version }}
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, mysql, mysqli, pdo_mysql, fileinfo
coverage: none
- name: Install FFmpeg
uses: Iamshankhadeep/setup-ffmpeg@ffmpeg-5.0-20220119
with:
version: ${{ matrix.ffmpeg }}
id: setup-ffmpeg
- name: Install dependencies
run: |
composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction --no-suggest
- name: Cache dependencies
uses: actions/cache@v2
with:
path: ~/.composer/cache/files
key: dependencies-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }}-dep-${{ matrix.dependency-version }}
- name: Execute tests
run: vendor/bin/phpunit
env:
FFMPEG_TEMPORARY_FILES_ROOT: ${{ github.workspace }}

28
.github/workflows/update-changelog.yml vendored Normal file
View file

@ -0,0 +1,28 @@
name: "Update Changelog"
on:
release:
types: [released]
jobs:
update:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
with:
ref: main
- name: Update Changelog
uses: stefanzweifel/changelog-updater-action@v1
with:
latest-version: ${{ github.event.release.name }}
release-notes: ${{ github.event.release.body }}
- name: Commit updated CHANGELOG
uses: stefanzweifel/git-auto-commit-action@v4
with:
branch: main
commit_message: Update CHANGELOG
file_pattern: CHANGELOG.md

22
.gitignore vendored
View file

@ -1,13 +1,13 @@
#/nbproject/
/vendor/
/docs/build
composer.phar
composer.lock
phpunit.xml
.idea
.php_cs
.php_cs.cache
.phpunit.result.cache
build
composer.lock
coverage
docs
phpunit.xml
psalm.xml
vendor
.php-cs-fixer.cache
/tests/Functional/output/output-*
/docs/doctum.phar
/docs/source/API/API/cache/twig/*
.idea/
/phpunit

39
.php-cs-fixer.dist.php Normal file
View file

@ -0,0 +1,39 @@
<?php
$finder = Symfony\Component\Finder\Finder::create()
->in([
__DIR__ . '/src',
__DIR__ . '/tests',
])
->name('*.php')
->ignoreDotFiles(true)
->ignoreVCS(true);
return (new PhpCsFixer\Config())
->setRules([
'@PSR12' => true,
'array_syntax' => ['syntax' => 'short'],
'ordered_imports' => ['sort_algorithm' => 'alpha'],
'no_unused_imports' => true,
'not_operator_with_successor_space' => true,
'trailing_comma_in_multiline' => true,
'phpdoc_scalar' => true,
'unary_operator_spaces' => true,
'binary_operator_spaces' => true,
'blank_line_before_statement' => [
'statements' => ['break', 'continue', 'declare', 'return', 'throw', 'try'],
],
'phpdoc_single_line_var_spacing' => true,
'phpdoc_var_without_name' => true,
'class_attributes_separation' => [
'elements' => [
'method' => 'one',
],
],
'method_argument_space' => [
'on_multiline' => 'ensure_fully_multiline',
'keep_multiple_spaces_after_comma' => true,
],
'single_trait_insert_per_statement' => true,
])
->setFinder($finder);

View file

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

View file

@ -1,22 +1,4 @@
# Changelog
All Notable changes to the library will be documented in this file
All notable changes to `ffmpeg-mappable-media` will be documented in this file.
## 1.0.1 - 2022-02-22
- Added configuration key to customize the temporary directory used for passes.
- Fix for the path of the default `ffmpeg-passes*` temporary directory.
## 1.0.0 - 2022-02-09
Upgraded dependencies, integrated the Alchemy Binary library, and dropped support for anything below PHP 8.0
- Support for Symfony 5.4 and 6.0
- Support for `psr/log` v3
- GitHub actions against FFmpeg 4.4 and 5.0
- Integrated the Alchemy Binary library
- Replaced `neutron/temporary-filesystem` with `spatie/temporary-directory`
- PHPUnit 9.5
- Removed Silex Service Provider
- Removed the auto-generated docs
- Removed support for anything below PHP 8.0

View file

@ -1,6 +1,6 @@
MIT License
The MIT License (MIT)
Copyright (c) 2012-2021 Alchemy
Copyright (c) danjones000 <danjones@goodevilgenius.org>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -9,13 +9,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View file

@ -1,64 +0,0 @@
CHANGELOG
---------
* 1.6.0 (2015-03-02)
* BC Break: bump minimum PHP versions
* Allow use of evenement v2.0 (thanks @patkar for the P/R)
* 1.5.0 (2013-06-21)
* BC Break : ConfigurationInterface::get does not throw exceptions anymore
in case the key does not exist. Second argument is a default value to return
in case the key does not exist.
* 1.4.1 (2013-05-23)
* Add third parameter to BinaryInterface::command method to pass a listener or
an array of listener that will be registered just the time of the command.
* 1.4.0 (2013-05-11)
* Extract process run management to ProcessRunner.
* Add support for process listeners.
* Provides bundled DebugListener.
* Add BinaryInterface::command method.
* BC break : ProcessRunnerInterface::run now takes an SplObjectStorage containing
listeners as second argument.
* BC break : BinaryInterface no longer implements LoggerAwareInterface
as it is now supported by ProcessRunner.
* 1.3.4 (2013-04-26)
* Add BinaryDriver::run method.
* 1.3.3 (2013-04-26)
* Add BinaryDriver::createProcessMock method.
* 1.3.2 (2013-04-26)
* Add BinaryDriverTestCase for testing BinaryDriver implementations.
* 1.3.1 (2013-04-24)
* Add timeouts handling
* 1.3.0 (2013-04-24)
* Add BinaryInterface and AbstractBinary
* 1.2.1 (2013-04-24)
* Add ConfigurationAwareInterface
* Add ProcessBuilderAwareInterface
* 1.2.0 (2013-04-24)
* Add BinaryDriver\Configuration
* 1.1.0 (2013-04-24)
* Add support for timeouts via `setTimeout` method
* 1.0.0 (2013-04-23)
* First stable version.

View file

@ -1,21 +0,0 @@
BinaryDriver is released with MIT License :
Copyright (c) 2013 Alchemy
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.

View file

@ -1,190 +0,0 @@
# Binary Driver
Binary-Driver is a set of PHP tools to build binary drivers.
[![Build Status](https://travis-ci.org/alchemy-fr/BinaryDriver.png?branch=master)](https://travis-ci.org/alchemy-fr/BinaryDriver)
## Why ?
You may wonder *Why building a library while I can use `exec` or
[symfony/process](https://github.com/symfony/Process) ?*.
Here is a simple answer :
- If you use `exec`, `passthru`, `system`, `proc_open` or any low level process
handling in PHP, you should have a look to [symfony/process](https://github.com/symfony/Process)
component that will provide an OO portable, testable and secure interface to
deal with this. It seems easy at first approach, but if you look at this
component [unit tests](https://github.com/symfony/Process/tree/master/Tests),
you will see that handling process in a simple interface can easily become a
nightmare.
- If you already use symfony/process, and want to build binary drivers, you
will always have the same common set of methods and objects to configure, log,
debug, and generate processes.
This library is a base to implement any binary driver with this common set of
needs.
## AbstractBinary
`AbstractBinary` provides an abstract class to build a binary driver. It implements
`BinaryInterface`.
Implementation example :
```php
use Alchemy\BinaryDriver\AbstractBinary;
class LsDriver extends AbstractBinary
{
public function getName()
{
return 'ls driver';
}
}
$parser = new LsParser();
$driver = Driver::load('ls');
// will return the output of `ls -a -l`
$parser->parse($driver->command(array('-a', '-l')));
```
### Binary detection troubleshooting
If you are using Nginx with PHP-fpm, executable detection may not work because of an empty `$_ENV['path']`.
To avoid having an empty `PATH` environment variable, add the following line to your `fastcgi_params`
config file (replace `/your/current/path/` with the output of `printenv PATH`) :
```
fastcgi_param PATH /your/current/path
```
## Logging
You can log events with a `Psr\Log\LoggerInterface` by passing it in the load
method as second argument :
```php
$logger = new Monolog\Logger('driver');
$driver = Driver::load('ls', $logger);
```
## Listeners
You can add custom listeners on processes.
Listeners are built on top of [Evenement](https://github.com/igorw/evenement)
and must implement `Alchemy\BinaryDriver\ListenerInterface`.
```php
use Symfony\Component\Process\Process;
class DebugListener extends EventEmitter implements ListenerInterface
{
public function handle($type, $data)
{
foreach (explode(PHP_EOL, $data) as $line) {
$this->emit($type === Process::ERR ? 'error' : 'out', array($line));
}
}
public function forwardedEvents()
{
// forward 'error' events to the BinaryInterface
return array('error');
}
}
$listener = new DebugListener();
$driver = CustomImplementation::load('php');
// adds listener
$driver->listen($listener);
$driver->on('error', function ($line) {
echo '[ERROR] ' . $line . PHP_EOL;
});
// removes listener
$driver->unlisten($listener);
```
### Bundled listeners
The debug listener is a simple listener to catch `stderr` and `stdout` outputs ;
read the implementation for customization.
```php
use Alchemy\BinaryDriver\Listeners\DebugListener;
$driver = CustomImplementation::load('php');
$driver->listen(new DebugListener());
$driver->on('debug', function ($line) {
echo $line;
});
```
## ProcessBuilderFactory
ProcessBuilderFactory ease spawning processes by generating Symfony [Process]
(http://symfony.com/doc/master/components/process.html) objects.
```php
use Alchemy\BinaryDriver\ProcessBuilderFactory;
$factory = new ProcessBuilderFactory('/usr/bin/php');
// return a Symfony\Component\Process\Process
$process = $factory->create('-v');
// echoes '/usr/bin/php' '-v'
echo $process->getCommandLine();
$process = $factory->create(array('-r', 'echo "Hello !";'));
// echoes '/usr/bin/php' '-r' 'echo "Hello !";'
echo $process->getCommandLine();
```
## Configuration
A simple configuration object, providing an `ArrayAccess` and `IteratorAggregate`
interface.
```php
use Alchemy\BinaryDriver\Configuration;
$conf = new Configuration(array('timeout' => 0));
echo $conf->get('timeout');
if ($conf->has('param')) {
$conf->remove('param');
}
$conf->set('timeout', 20);
$conf->all();
```
Same example using the `ArrayAccess` interface :
```php
use Alchemy\BinaryDriver\Configuration;
$conf = new Configuration(array('timeout' => 0));
echo $conf['timeout'];
if (isset($conf['param'])) {
unset($conf['param']);
}
$conf['timeout'] = 20;
```
## License
This project is released under the MIT license.

734
README.md
View file

@ -1,736 +1,60 @@
# PHP-FFMPEG
[![Latest Version on Packagist](https://img.shields.io/packagist/v/PHP-FFMpeg/PHP-FFMpeg.svg?style=flat-square)](https://packagist.org/packages/PHP-FFMpeg/PHP-FFMpeg)
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md)
![run-tests](https://github.com/PHP-FFMpeg/PHP-FFMpeg/workflows/run-tests/badge.svg)
[![Total Downloads](https://img.shields.io/packagist/dt/PHP-FFMpeg/PHP-FFMpeg.svg?style=flat-square)](https://packagist.org/packages/PHP-FFMpeg/PHP-FFMpeg)
[<img src="https://github-ads.s3.eu-central-1.amazonaws.com/support-ukraine.svg?t=1" />](https://supportukrainenow.org)
An Object-Oriented library to convert video/audio files with FFmpeg / AVConv.
# This is my package ffmpeg-mappable-media
## Your attention please
[![Latest Version on Packagist](https://img.shields.io/packagist/v/danjones000/ffmpeg-mappable-media.svg?style=flat-square)](https://packagist.org/packages/danjones000/ffmpeg-mappable-media)
[![Tests](https://codeberg.org/danjones000/ffmpeg-mappable-media/actions/workflows/run-tests.yml/badge.svg?branch=main)](https://codeberg.org/danjones000/ffmpeg-mappable-media/actions/workflows/run-tests.yml)
[![Total Downloads](https://img.shields.io/packagist/dt/danjones000/ffmpeg-mappable-media.svg?style=flat-square)](https://packagist.org/packages/danjones000/ffmpeg-mappable-media)
### How this library works:
This is where your description should go. Try and limit it to a paragraph or two. Consider adding a small example.
This library requires a working [FFMpeg install](https://ffmpeg.org/download.html). 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 explicitly give the binaries path on load.
## Support us
### Known issues:
[<img src="https://github-ads.s3.eu-central-1.amazonaws.com/ffmpeg-mappable-media.jpg?t=1" width="419px" />](https://spatie.be/github-ad-click/ffmpeg-mappable-media)
- 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.
We invest a lot of resources into creating [best in class open source packages](https://spatie.be/open-source). You can support us by [buying one of our paid products](https://spatie.be/open-source/support-us).
We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on [our contact page](https://spatie.be/about-us). We publish all received postcards on [our virtual postcard wall](https://spatie.be/open-source/postcards).
## Installation
This library requires PHP 8.0 or higher. For older versions of PHP, check out the [0.x-branch](https://github.com/PHP-FFMpeg/PHP-FFMpeg/tree/0.x).
The recommended way to install PHP-FFMpeg is through [Composer](https://getcomposer.org).
You can install the package via composer:
```bash
$ composer require php-ffmpeg/php-ffmpeg
composer require danjones000/ffmpeg-mappable-media
```
## Basic Usage
## Usage
```php
require 'vendor/autoload.php';
$ffmpeg = FFMpeg\FFMpeg::create();
$video = $ffmpeg->open('video.mpg');
$video
->filters()
->resize(new FFMpeg\Coordinate\Dimension(320, 240))
->synchronize();
$video
->frame(FFMpeg\Coordinate\TimeCode::fromSeconds(10))
->save('frame.jpg');
$video
->save(new FFMpeg\Format\Video\X264(), 'export-x264.mp4')
->save(new FFMpeg\Format\Video\WMV(), 'export-wmv.wmv')
->save(new FFMpeg\Format\Video\WebM(), 'export-webm.webm');
$skeleton = new Danjones\FFMpeg();
echo $skeleton->echoPhrase('Hello, Danjones!');
```
## Documentation
## Testing
This documentation is an introduction to discover the API. It's recommended
to browse the source code as it is self-documented.
### FFMpeg
`FFMpeg\FFMpeg` is the main object to use to manipulate medias. To build it,
use the static `FFMpeg\FFMpeg::create`:
```php
$ffmpeg = FFMpeg\FFMpeg::create();
```bash
composer test
```
FFMpeg will autodetect ffmpeg and ffprobe binaries. If you want to give binary
paths explicitly, you can pass an array as configuration. A `Psr\Logger\LoggerInterface`
can also be passed to log binary executions.
## Changelog
```php
$ffmpeg = FFMpeg\FFMpeg::create(array(
'ffmpeg.binaries' => '/opt/local/ffmpeg/bin/ffmpeg',
'ffprobe.binaries' => '/opt/local/ffmpeg/bin/ffprobe',
'timeout' => 3600, // The timeout for the underlying process
'ffmpeg.threads' => 12, // The number of threads that FFMpeg should use
), $logger);
```
Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.
You may pass a `temporary_directory` key to specify a path for temporary files.
## Contributing
```php
$ffmpeg = FFMpeg\FFMpeg::create(array(
'temporary_directory' => '/var/ffmpeg-tmp'
), $logger);
```
Please see [CONTRIBUTING](https://codeberg.org/spatie/.github/blob/main/CONTRIBUTING.md) for details.
### Manipulate media
## Security Vulnerabilities
`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.
Please review [our security policy](../../security/policy) on how to report security vulnerabilities.
**Note**: To list all supported resource type of your FFmpeg build, use the
`-protocols` command:
## Credits
```
ffmpeg -protocols
```
To open a resource, use the `FFMpeg\FFMpeg::open` method.
```php
$ffmpeg->open('video.mpeg');
```
Two types of media can be resolved: `FFMpeg\Media\Audio` and `FFMpeg\Media\Video`.
A third type, `FFMpeg\Media\Frame`, is available through videos.
### Video
`FFMpeg\Media\Video` can be transcoded, ie: change codec, isolate audio or
video. Frames can be extracted.
##### Transcoding
You can transcode videos using the `FFMpeg\Media\Video:save` method. You will
pass a `FFMpeg\Format\FormatInterface` for that.
Please note that audio and video bitrate are set on the format. You can disable the `-b:v` option by setting the kilo bitrate to 0.
```php
$format = new FFMpeg\Format\Video\X264();
$format->on('progress', function ($video, $format, $percentage) {
echo "$percentage % transcoded";
});
$format
->setKiloBitrate(1000)
->setAudioChannels(2)
->setAudioKiloBitrate(256);
$video->save($format, 'video.avi');
```
Transcoding progress can be monitored in realtime, see Format documentation
below for more information.
##### Extracting image
You can extract a frame at any timecode using the `FFMpeg\Media\Video::frame`
method.
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.
```php
$frame = $video->frame(FFMpeg\Coordinate\TimeCode::fromSeconds(42));
$frame->save('image.jpg');
```
If you want to extract multiple images from the 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');
```
By default, this will save the frames as `jpg` images.
You are able to override this using `setFrameFileType` to save the frames in another format:
```php
$frameFileType = 'jpg'; // either 'jpg', 'jpeg' or 'png'
$filter = new ExtractMultipleFramesFilter($frameRate, $destinationFolder);
$filter->setFrameFileType($frameFileType);
$video->addFilter($filter);
```
##### Clip
Cuts the video at a desired point. Use input seeking method. It is faster option than use filter clip.
```php
$clip = $video->clip(FFMpeg\Coordinate\TimeCode::fromSeconds(30), FFMpeg\Coordinate\TimeCode::fromSeconds(15));
$clip->save(new FFMpeg\Format\Video\X264(), 'video.avi');
```
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
On clip you can apply same filters as on video. For example resizing filter.
```php
$clip = $video->clip(FFMpeg\Coordinate\TimeCode::fromSeconds(30), FFMpeg\Coordinate\TimeCode::fromSeconds(15));
$clip->filters()->resize(new FFMpeg\Coordinate\Dimension(320, 240), FFMpeg\Filters\Video\ResizeFilter::RESIZEMODE_INSET, true);
$clip->save(new FFMpeg\Format\Video\X264(), 'video.avi');
```
##### 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 output 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`
method. Video accepts Audio and Video filters.
You can build your own filters and some are bundled in PHP-FFMpeg - they are
accessible through the `FFMpeg\Media\Video::filters` method.
Filters are chainable
```php
$video
->filters()
->resize($dimension, $mode, $useStandards)
->framerate($framerate, $gop)
->synchronize();
```
###### Rotate
Rotates a video to a given angle.
```php
$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
###### Resize
Resizes a video to a given size.
```php
$video->filters()->resize($dimension, $mode, $useStandards);
```
The resize filter takes three parameters:
- `$dimension`, an instance of `FFMpeg\Coordinate\Dimension`
- `$mode`, one of the constants `FFMpeg\Filters\Video\ResizeFilter::RESIZEMODE_*` constants
- `$useStandards`, a boolean to force the use of the nearest aspect ratio standard.
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.
```php
$video->filters()->framerate($framerate, $gop);
```
The framerate filter takes two parameters:
- `$framerate`, an instance of `FFMpeg\Coordinate\FrameRate`
- `$gop`, a [GOP](https://wikipedia.org/wiki/Group_of_pictures) value (integer)
###### Synchronize
Synchronizes audio and video.
Some containers may use a delay that results in desynchronized outputs. This
filter solves this issue.
```php
$video->filters()->synchronize();
```
###### Clip
Cuts the video at a desired point.
```php
$video->filters()->clip(FFMpeg\Coordinate\TimeCode::fromSeconds(30), FFMpeg\Coordinate\TimeCode::fromSeconds(15));
```
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
###### Crop
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
You can transcode audios using the `FFMpeg\Media\Audio:save` method. You will
pass a `FFMpeg\Format\FormatInterface` for that.
Please note that audio kilobitrate is set on the audio format.
```php
$ffmpeg = FFMpeg\FFMpeg::create();
$audio = $ffmpeg->open('track.mp3');
$format = new FFMpeg\Format\Audio\Flac();
$format->on('progress', function ($audio, $format, $percentage) {
echo "$percentage % transcoded";
});
$format
->setAudioChannels(2)
->setAudioKiloBitrate(256);
$audio->save($format, 'track.flac');
```
Transcoding progress can be monitored in realtime, see Format documentation
below for more information.
##### Filters
You can apply filters on `FFMpeg\Media\Audio` with the `FFMpeg\Media\Audio::addFilter`
method. It only accepts audio filters.
You can build your own filters and some are bundled in PHP-FFMpeg - they are
accessible through the `FFMpeg\Media\Audio::filters` method.
##### 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.
```php
$audio->filters()->resample($rate);
```
The resample filter takes two parameters :
- `$rate`, a valid audio sample rate value (integer)
#### Frame
A frame is an image at a timecode of a video; see documentation above about
frame extraction.
You can save frames using the `FFMpeg\Media\Frame::save` method.
```php
$frame->save('target.jpg');
```
This method has a second optional boolean parameter. Set it to true to get
accurate images; it takes more time to execute.
#### 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 recommend 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 different codec, do as follow:
```php
// In order to instantiate the video object, you HAVE TO pass a path to a valid video file.
// We recommend 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).
### AdvancedMedia
AdvancedMedia may have multiple inputs and multiple outputs.
This class has been developed primarily to use with `-filter_complex`.
So, its `filters()` method accepts only filters that can be used inside `-filter_complex` command.
AdvancedMedia already contains some built-in filters.
#### Base usage
For example:
```php
$advancedMedia = $ffmpeg->openAdvanced(array('video_1.mp4', 'video_2.mp4'));
$advancedMedia->filters()
->custom('[0:v][1:v]', 'hstack', '[v]');
$advancedMedia
->map(array('0:a', '[v]'), new X264('aac', 'libx264'), 'output.mp4')
->save();
```
This code takes 2 input videos, stacks they horizontally in 1 output video and adds to this new video the audio from the first video.
(It is impossible with simple filtergraph that has only 1 input and only 1 output).
#### Complicated example
A more difficult example of possibilities of the AdvancedMedia. Consider all input videos already have the same resolution and duration. ("xstack" filter has been added in the 4.1 version of the ffmpeg).
```php
$inputs = array(
'video_1.mp4',
'video_2.mp4',
'video_3.mp4',
'video_4.mp4',
);
$advancedMedia = $ffmpeg->openAdvanced($inputs);
$advancedMedia->filters()
->custom('[0:v]', 'negate', '[v0negate]')
->custom('[1:v]', 'edgedetect', '[v1edgedetect]')
->custom('[2:v]', 'hflip', '[v2hflip]')
->custom('[3:v]', 'vflip', '[v3vflip]')
->xStack('[v0negate][v1edgedetect][v2hflip][v3vflip]', XStackFilter::LAYOUT_2X2, 4, '[resultv]');
$advancedMedia
->map(array('0:a'), new Mp3(), 'video_1.mp3')
->map(array('1:a'), new Flac(), 'video_2.flac')
->map(array('2:a'), new Wav(), 'video_3.wav')
->map(array('3:a'), new Aac(), 'video_4.aac')
->map(array('[resultv]'), new X264('aac', 'libx264'), 'output.mp4')
->save();
```
This code takes 4 input videos, then the negates the first video, stores result in `[v0negate]` stream, detects edges in the second video, stores result in `[v1edgedetect]` stream, horizontally flips the third video, stores result in `[v2hflip]` stream, vertically flips the fourth video, stores result in `[v3vflip]` stream, then takes this 4 generated streams ans combine them in one 2x2 collage video.
Then saves audios from the original videos into the 4 different formats and saves the generated collage video into the separate file.
As you can see, you can take multiple input sources, perform the complicated processing for them and produce multiple output files in the same time, in the one ffmpeg command.
#### Just give me a map!
You do not have to use `-filter_complex`. You can use only `-map` options. For example, just extract the audio from the video:
```php
$advancedMedia = $ffmpeg->openAdvanced(array('video.mp4'));
$advancedMedia
->map(array('0:a'), new Mp3(), 'output.mp3')
->save();
```
#### Customisation
If you need you can extra customize the result ffmpeg command of the AdvancedMedia:
```php
$advancedMedia = $ffmpeg->openAdvanced($inputs);
$advancedMedia
->setInitialParameters(array('the', 'params', 'that', 'will', 'be', 'added', 'before', '-i', 'part', 'of', 'the', 'command'))
->setAdditionalParameters(array('the', 'params', 'that', 'will', 'be', 'added', 'at', 'the', 'end', 'of', 'the', 'command'));
```
#### Formats
A format implements `FFMpeg\Format\FormatInterface`. To save to a video file,
use `FFMpeg\Format\VideoInterface`, and `FFMpeg\Format\AudioInterface` for
audio files.
A format can also extend `FFMpeg\Format\ProgressableInterface` to get realtime
information about the transcoding.
Predefined formats already provide progress information as events.
```php
$format = new FFMpeg\Format\Video\X264();
$format->on('progress', function ($video, $format, $percentage) {
echo "$percentage % transcoded";
});
$video->save($format, 'video.avi');
```
The callback provided for the event can be any callable.
##### 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');
```
##### Add initial parameters
You can also add initial parameters to your encoding requests based on your video format. This can be expecially handy in overriding a default input codec in FFMpeg.
The argument of the setInitialParameters method is an array.
```php
$format = new FFMpeg\Format\Video\X264();
$format->setInitialParameters(array('-acodec', 'libopus'));
$video->save($format, 'video.avi');
```
##### Create your own format
The easiest way to create a format is to extend the abstract
`FFMpeg\Format\Video\DefaultVideo` and `FFMpeg\Format\Audio\DefaultAudio`.
and implement the following methods.
```php
class CustomWMVFormat extends FFMpeg\Format\Video\DefaultVideo
{
public function __construct($audioCodec = 'wmav2', $videoCodec = 'wmv2')
{
$this
->setAudioCodec($audioCodec)
->setVideoCodec($videoCodec);
}
public function supportBFrames()
{
return false;
}
public function getAvailableAudioCodecs()
{
return array('wmav2');
}
public function getAvailableVideoCodecs()
{
return array('wmv2');
}
}
```
#### Coordinates
FFMpeg uses 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. (Supports dynamic points since v0.10.0)
- `FFMpeg\Coordinate\TimeCode` represent a timecode.
### FFProbe
`FFMpeg\FFProbe` is used internally by `FFMpeg\FFMpeg` to probe medias. You can
also use it to extract media metadata.
```php
$ffprobe = FFMpeg\FFProbe::create();
$ffprobe
->streams('/path/to/video/mp4') // extracts streams informations
->videos() // filters video streams
->first() // returns the first video stream
->get('codec_name'); // returns the codec_name property
```
```php
$ffprobe = FFMpeg\FFProbe::create();
$ffprobe
->format('/path/to/video/mp4') // extracts file informations
->get('duration'); // returns the duration property
```
### Validating media files
(since 0.10.0)
You can validate media files using PHP-FFMpeg's FFProbe wrapper.
```php
$ffprobe = FFMpeg\FFProbe::create();
$ffprobe->isValid('/path/to/file/to/check'); // returns bool
```
- [Dan Jones](https://codeberg.org/danjones000)
- [All Contributors](../../contributors)
## License
This project is licensed under the [MIT license](http://opensource.org/licenses/MIT).
Music: "Favorite Secrets" by Waylon Thornton
From the Free Music Archive
[CC BY NC SA](http://creativecommons.org/licenses/by-nc-sa/3.0/us/)
Music: "Siesta" by Jahzzar
From the Free Music Archive
[CC BY SA](https://creativecommons.org/licenses/by-sa/3.0/)
The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

View file

@ -1,8 +1,8 @@
{
"name": "php-ffmpeg/php-ffmpeg",
"type": "library",
"description": "FFMpeg PHP, an Object Oriented library to communicate with AVconv / ffmpeg",
"name": "danjones000/ffmpeg-mappable-media",
"description": "This is my package ffmpeg-mappable-media",
"keywords": [
"danjones000",
"video processing",
"video",
"audio processing",
@ -12,64 +12,46 @@
"avprobe",
"ffprobe"
],
"homepage": "https://codeberg.org/danjones000/ffmpeg-mappable-media",
"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": "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"
},
{
"name": "Pascal Baljet",
"email": "pascal@protone.media",
"homepage": "https://protone.media"
"name": "Dan Jones",
"email": "danjones@goodevilgenius.org",
"role": "Developer",
"homepage": "https://goodevilgenius.org"
}
],
"require": {
"php": "^8.0 || ^8.1",
"evenement/evenement": "^3.0",
"psr/log": "^1.0 || ^2.0 || ^3.0",
"spatie/temporary-directory": "^2.0",
"symfony/process": "^5.4 || ^6.0",
"symfony/cache": "^5.4 || ^6.0"
},
"suggest": {
"php-ffmpeg/extras": "A compilation of common audio & video drivers for PHP-FFMpeg"
"php": "^8.0",
"php-ffmpeg/php-ffmpeg": "^1.0"
},
"require-dev": {
"phpunit/phpunit": "^9.5.10",
"mockery/mockery": "^1.5"
"friendsofphp/php-cs-fixer": "^3.0",
"pestphp/pest": "^1.20",
"spatie/ray": "^1.28"
},
"autoload": {
"psr-4": {
"FFMpeg\\": "src/FFMpeg",
"Alchemy\\BinaryDriver\\": "src/Alchemy/BinaryDriver"
"Danjones\\FFMpeg\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"Alchemy\\Tests\\BinaryDriver\\": "tests/Alchemy/BinaryDriver",
"Tests\\FFMpeg\\": "tests/FFMpeg"
"Danjones\\FFMpeg\\Tests\\": "tests"
}
}
},
"scripts": {
"test": "vendor/bin/pest",
"test-coverage": "vendor/bin/pest --coverage",
"format": "vendor/bin/php-cs-fixer fix --allow-risky=yes"
},
"config": {
"sort-packages": true,
"allow-plugins": {
"pestphp/pest-plugin": true
}
},
"minimum-stability": "dev",
"prefer-stable": true
}

View file

@ -1,24 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false" backupStaticAttributes="false" colors="true" convertErrorsToExceptions="true" convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" stopOnFailure="false" verbose="false" bootstrap="tests/bootstrap.php">
<php>
<env name="SYMFONY_PHPUNIT_REMOVE_RETURN_TYPEHINT" value="true"/>
<env name="SYMFONY_DEPRECATIONS_HELPER" value="disabled" />
</php>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
backupGlobals="false"
backupStaticAttributes="false"
bootstrap="vendor/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
executionOrder="random"
failOnWarning="true"
failOnRisky="true"
failOnEmptyTestSuite="true"
beStrictAboutOutputDuringTests="true"
verbose="true"
>
<testsuites>
<testsuite name="Alchemy">
<directory>tests/Alchemy/BinaryDriver</directory>
</testsuite>
<testsuite name="FFMpeg Unit">
<directory>tests/FFMpeg/Unit</directory>
</testsuite>
<testsuite name="FFMpeg Functional">
<directory>tests/FFMpeg/Functional</directory>
<testsuite name="Danjones Test Suite">
<directory>tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">src/</directory>
</whitelist>
</filter>
<coverage>
<include>
<directory suffix=".php">./src</directory>
</include>
<report>
<html outputDirectory="build/coverage"/>
<text outputFile="build/coverage.txt"/>
<clover outputFile="build/logs/clover.xml"/>
</report>
</coverage>
<logging>
<junit outputFile="build/report.junit.xml"/>
</logging>
</phpunit>

View file

@ -1,300 +0,0 @@
<?php
namespace Alchemy\Tests\BinaryDriver;
use Alchemy\BinaryDriver\AbstractBinary;
use Alchemy\BinaryDriver\BinaryDriverTestCase;
use Alchemy\BinaryDriver\Configuration;
use Alchemy\BinaryDriver\Exception\ExecutableNotFoundException;
use Alchemy\BinaryDriver\Listeners\ListenerInterface;
use Symfony\Component\Process\ExecutableFinder;
class AbstractBinaryTest extends BinaryDriverTestCase
{
protected function getPhpBinary()
{
$finder = new ExecutableFinder();
$php = $finder->find('php');
if (null === $php) {
$this->markTestSkipped('Unable to find a php binary');
}
return $php;
}
public function testSimpleLoadWithBinaryPath()
{
$php = $this->getPhpBinary();
$imp = Implementation::load($php);
$this->assertInstanceOf('Alchemy\Tests\BinaryDriver\Implementation', $imp);
$this->assertEquals($php, $imp->getProcessBuilderFactory()->getBinary());
}
public function testMultipleLoadWithBinaryPath()
{
$php = $this->getPhpBinary();
$imp = Implementation::load(['/zz/path/to/unexisting/command', $php]);
$this->assertInstanceOf('Alchemy\Tests\BinaryDriver\Implementation', $imp);
$this->assertEquals($php, $imp->getProcessBuilderFactory()->getBinary());
}
public function testSimpleLoadWithBinaryName()
{
$php = $this->getPhpBinary();
$imp = Implementation::load('php');
$this->assertInstanceOf('Alchemy\Tests\BinaryDriver\Implementation', $imp);
$this->assertEquals($php, $imp->getProcessBuilderFactory()->getBinary());
}
public function testMultipleLoadWithBinaryName()
{
$php = $this->getPhpBinary();
$imp = Implementation::load(['bachibouzouk', 'php']);
$this->assertInstanceOf('Alchemy\Tests\BinaryDriver\Implementation', $imp);
$this->assertEquals($php, $imp->getProcessBuilderFactory()->getBinary());
}
public function testLoadWithMultiplePathExpectingAFailure()
{
$this->expectException(ExecutableNotFoundException::class);
Implementation::load(['bachibouzouk', 'moribon']);
}
public function testLoadWithUniquePathExpectingAFailure()
{
$this->expectException(ExecutableNotFoundException::class);
Implementation::load('bachibouzouk');
}
public function testLoadWithCustomLogger()
{
$logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
$imp = Implementation::load('php', $logger);
$this->assertEquals($logger, $imp->getProcessRunner()->getLogger());
}
public function testLoadWithCustomConfigurationAsArray()
{
$conf = ['timeout' => 200];
$imp = Implementation::load('php', null, $conf);
$this->assertEquals($conf, $imp->getConfiguration()->all());
}
public function testLoadWithCustomConfigurationAsObject()
{
$conf = $this->getMockBuilder('Alchemy\BinaryDriver\ConfigurationInterface')->getMock();
$imp = Implementation::load('php', null, $conf);
$this->assertEquals($conf, $imp->getConfiguration());
}
public function testProcessBuilderFactoryGetterAndSetters()
{
$imp = Implementation::load('php');
$factory = $this->getMockBuilder('Alchemy\BinaryDriver\ProcessBuilderFactoryInterface')->getMock();
$imp->setProcessBuilderFactory($factory);
$this->assertEquals($factory, $imp->getProcessBuilderFactory());
}
public function testConfigurationGetterAndSetters()
{
$imp = Implementation::load('php');
$conf = $this->getMockBuilder('Alchemy\BinaryDriver\ConfigurationInterface')->getMock();
$imp->setConfiguration($conf);
$this->assertEquals($conf, $imp->getConfiguration());
}
public function testTimeoutIsSetOnConstruction()
{
$imp = Implementation::load('php', null, ['timeout' => 42]);
$this->assertEquals(42, $imp->getProcessBuilderFactory()->getTimeout());
}
public function testTimeoutIsSetOnConfigurationSetting()
{
$imp = Implementation::load('php', null);
$imp->setConfiguration(new Configuration(['timeout' => 42]));
$this->assertEquals(42, $imp->getProcessBuilderFactory()->getTimeout());
}
public function testTimeoutIsSetOnProcessBuilderSetting()
{
$imp = Implementation::load('php', null, ['timeout' => 42]);
$factory = $this->getMockBuilder('Alchemy\BinaryDriver\ProcessBuilderFactoryInterface')->getMock();
$factory->expects($this->once())
->method('setTimeout')
->with(42);
$imp->setProcessBuilderFactory($factory);
}
public function testListenRegistersAListener()
{
$imp = Implementation::load('php');
$listeners = $this->getMockBuilder('Alchemy\BinaryDriver\Listeners\Listeners')
->disableOriginalConstructor()
->getMock();
$listener = $this->getMockBuilder('Alchemy\BinaryDriver\Listeners\ListenerInterface')->getMock();
$listeners->expects($this->once())
->method('register')
->with($this->equalTo($listener), $this->equalTo($imp));
$reflexion = new \ReflectionClass('Alchemy\BinaryDriver\AbstractBinary');
$prop = $reflexion->getProperty('listenersManager');
$prop->setAccessible(true);
$prop->setValue($imp, $listeners);
$imp->listen($listener);
}
/**
* @dataProvider provideCommandParameters
*/
public function testCommandRunsAProcess($parameters, $bypassErrors, $expectedParameters, $output)
{
$imp = Implementation::load('php');
$factory = $this->getMockBuilder('Alchemy\BinaryDriver\ProcessBuilderFactoryInterface')->getMock();
$processRunner = $this->getMockBuilder('Alchemy\BinaryDriver\ProcessRunnerInterface')->getMock();
$process = $this->getMockBuilder('Symfony\Component\Process\Process')
->disableOriginalConstructor()
->getMock();
$processRunner->expects($this->once())
->method('run')
->with($this->equalTo($process), $this->isInstanceOf('SplObjectStorage'), $this->equalTo($bypassErrors))
->will($this->returnValue($output));
$factory->expects($this->once())
->method('create')
->with($expectedParameters)
->will($this->returnValue($process));
$imp->setProcessBuilderFactory($factory);
$imp->setProcessRunner($processRunner);
$this->assertEquals($output, $imp->command($parameters, $bypassErrors));
}
/**
* @dataProvider provideCommandWithListenersParameters
*/
public function testCommandWithTemporaryListeners($parameters, $bypassErrors, $expectedParameters, $output, $count, $listeners)
{
$imp = Implementation::load('php');
$factory = $this->getMockBuilder('Alchemy\BinaryDriver\ProcessBuilderFactoryInterface')->getMock();
$processRunner = $this->getMockBuilder('Alchemy\BinaryDriver\ProcessRunnerInterface')->getMock();
$process = $this->getMockBuilder('Symfony\Component\Process\Process')
->disableOriginalConstructor()
->getMock();
$firstStorage = $secondStorage = null;
$processRunner->expects($this->exactly(2))
->method('run')
->with($this->equalTo($process), $this->isInstanceOf('SplObjectStorage'), $this->equalTo($bypassErrors))
->will($this->returnCallback(function ($process, $storage, $errors) use ($output, &$firstStorage, &$secondStorage) {
if (null === $firstStorage) {
$firstStorage = $storage;
} else {
$secondStorage = $storage;
}
return $output;
}));
$factory->expects($this->exactly(2))
->method('create')
->with($expectedParameters)
->will($this->returnValue($process));
$imp->setProcessBuilderFactory($factory);
$imp->setProcessRunner($processRunner);
$this->assertEquals($output, $imp->command($parameters, $bypassErrors, $listeners));
$this->assertCount($count, $firstStorage);
$this->assertEquals($output, $imp->command($parameters, $bypassErrors));
$this->assertCount(0, $secondStorage);
}
public function provideCommandWithListenersParameters()
{
return [
['-a', false, ['-a'], 'loubda', 2, [$this->getMockListener(), $this->getMockListener()]],
['-a', false, ['-a'], 'loubda', 1, [$this->getMockListener()]],
['-a', false, ['-a'], 'loubda', 1, $this->getMockListener()],
['-a', false, ['-a'], 'loubda', 0, []],
];
}
public function provideCommandParameters()
{
return [
['-a', false, ['-a'], 'loubda'],
['-a', true, ['-a'], 'loubda'],
['-a -b', false, ['-a -b'], 'loubda'],
[['-a'], false, ['-a'], 'loubda'],
[['-a'], true, ['-a'], 'loubda'],
[['-a', '-b'], false, ['-a', '-b'], 'loubda'],
];
}
public function testUnlistenUnregistersAListener()
{
$imp = Implementation::load('php');
$listeners = $this->getMockBuilder('Alchemy\BinaryDriver\Listeners\Listeners')
->disableOriginalConstructor()
->getMock();
$listener = $this->getMockBuilder('Alchemy\BinaryDriver\Listeners\ListenerInterface')->getMock();
$listeners->expects($this->once())
->method('unregister')
->with($this->equalTo($listener), $this->equalTo($imp));
$reflexion = new \ReflectionClass('Alchemy\BinaryDriver\AbstractBinary');
$prop = $reflexion->getProperty('listenersManager');
$prop->setAccessible(true);
$prop->setValue($imp, $listeners);
$imp->unlisten($listener);
}
/**
* @return \PHPUnit_Framework_MockObject_MockObject
*/
private function getMockListener()
{
$listener = $this->getMockBuilder(ListenerInterface::class)->getMock();
$listener->expects($this->any())
->method('forwardedEvents')
->willReturn([]);
return $listener;
}
}
class Implementation extends AbstractBinary
{
public function getName()
{
return 'Implementation';
}
}

View file

@ -1,98 +0,0 @@
<?php
namespace Alchemy\Tests\BinaryDriver;
use Alchemy\BinaryDriver\Exception\InvalidArgumentException;
use Alchemy\BinaryDriver\ProcessBuilderFactory;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Process\ExecutableFinder;
abstract class AbstractProcessBuilderFactoryTest extends TestCase
{
public static $phpBinary;
private $original;
/**
* @return ProcessBuilderFactory
*/
abstract protected function getProcessBuilderFactory($binary);
public function setUp(): void
{
ProcessBuilderFactory::$emulateSfLTS = null;
if (null === static::$phpBinary) {
$this->markTestSkipped('Unable to detect php binary, skipping');
}
}
public static function setUpBeforeClass(): void
{
$finder = new ExecutableFinder();
static::$phpBinary = $finder->find('php');
}
public function testThatBinaryIsSetOnConstruction()
{
$factory = $this->getProcessBuilderFactory(static::$phpBinary);
$this->assertEquals(static::$phpBinary, $factory->getBinary());
}
public function testGetSetBinary()
{
$finder = new ExecutableFinder();
$phpUnit = $finder->find('phpunit');
if (null === $phpUnit) {
$this->markTestSkipped('Unable to detect phpunit binary, skipping');
}
$factory = $this->getProcessBuilderFactory(static::$phpBinary);
$factory->useBinary($phpUnit);
$this->assertEquals($phpUnit, $factory->getBinary());
}
public function testUseNonExistantBinary()
{
$this->expectException(InvalidArgumentException::class);
$factory = $this->getProcessBuilderFactory(static::$phpBinary);
$factory->useBinary('itissureitdoesnotexist');
}
public function testCreateShouldReturnAProcess()
{
$factory = $this->getProcessBuilderFactory(static::$phpBinary);
$process = $factory->create();
$this->assertInstanceOf('Symfony\Component\Process\Process', $process);
$this->assertEquals("'" . static::$phpBinary . "'", $process->getCommandLine());
}
public function testCreateWithStringArgument()
{
$factory = $this->getProcessBuilderFactory(static::$phpBinary);
$process = $factory->create('-v');
$this->assertInstanceOf('Symfony\Component\Process\Process', $process);
$this->assertEquals("'" . static::$phpBinary . "' '-v'", $process->getCommandLine());
}
public function testCreateWithArrayArgument()
{
$factory = $this->getProcessBuilderFactory(static::$phpBinary);
$process = $factory->create(['-r', 'echo "Hello !";']);
$this->assertInstanceOf('Symfony\Component\Process\Process', $process);
$this->assertEquals("'" . static::$phpBinary . "' '-r' 'echo \"Hello !\";'", $process->getCommandLine());
}
public function testCreateWithTimeout()
{
$factory = $this->getProcessBuilderFactory(static::$phpBinary);
$factory->setTimeout(200);
$process = $factory->create(['-i']);
$this->assertInstanceOf('Symfony\Component\Process\Process', $process);
$this->assertEquals(200, $process->getTimeout());
}
}

View file

@ -1,78 +0,0 @@
<?php
namespace Alchemy\Tests\BinaryDriver;
use Alchemy\BinaryDriver\Configuration;
use PHPUnit\Framework\TestCase;
class ConfigurationTest extends TestCase
{
public function testArrayAccessImplementation()
{
$configuration = new Configuration(['key' => 'value']);
$this->assertTrue(isset($configuration['key']));
$this->assertEquals('value', $configuration['key']);
$this->assertFalse(isset($configuration['key2']));
unset($configuration['key']);
$this->assertFalse(isset($configuration['key']));
$configuration['key2'] = 'value2';
$this->assertTrue(isset($configuration['key2']));
$this->assertEquals('value2', $configuration['key2']);
}
public function testGetOnNonExistentKeyShouldReturnDefaultValue()
{
$conf = new Configuration();
$this->assertEquals('booba', $conf->get('hooba', 'booba'));
$this->assertEquals(null, $conf->get('hooba'));
}
public function testSetHasGetRemove()
{
$configuration = new Configuration(['key' => 'value']);
$this->assertTrue($configuration->has('key'));
$this->assertEquals('value', $configuration->get('key'));
$this->assertFalse($configuration->has('key2'));
$configuration->remove('key');
$this->assertFalse($configuration->has('key'));
$configuration->set('key2', 'value2');
$this->assertTrue($configuration->has('key2'));
$this->assertEquals('value2', $configuration->get('key2'));
}
public function testIterator()
{
$data = [
'key1' => 'value1',
'key2' => 'value2',
'key3' => 'value3',
];
$captured = [];
$conf = new Configuration($data);
foreach ($conf as $key => $value) {
$captured[$key] = $value;
}
$this->assertEquals($data, $captured);
}
public function testAll()
{
$data = [
'key1' => 'value1',
'key2' => 'value2',
'key3' => 'value3',
];
$conf = new Configuration($data);
$this->assertEquals($data, $conf->all());
}
}

View file

@ -1,33 +0,0 @@
<?php
namespace Alchemy\Tests\BinaryDriver\Exceptions;
use Alchemy\BinaryDriver\BinaryDriverTestCase;
use Alchemy\BinaryDriver\Exception\ExecutionFailureException;
use Alchemy\BinaryDriver\ProcessRunner;
class ExecutionFailureExceptionTest extends BinaryDriverTestCase
{
public function getProcessRunner($logger)
{
return new ProcessRunner($logger, 'test-runner');
}
public function testGetExceptionInfo(){
$logger = $this->createLoggerMock();
$runner = $this->getProcessRunner($logger);
$process = $this->createProcessMock(1, false, '--helloworld--', null, "Error Output", true);
try{
$runner->run($process, new \SplObjectStorage(), false);
$this->fail('An exception should have been raised');
}
catch (ExecutionFailureException $e){
$this->assertEquals("--helloworld--", $e->getCommand());
$this->assertEquals("Error Output", $e->getErrorOutput());
}
}
}

View file

@ -1,54 +0,0 @@
<?php
namespace Alchemy\Tests\BinaryDriver;
use Alchemy\BinaryDriver\ProcessBuilderFactory;
use LogicException;
use Symfony\Component\Process\Process;
use Symfony\Component\Process\ProcessBuilder;
class LTSProcessBuilder extends ProcessBuilder
{
private $arguments;
private $prefix;
private $timeout;
public function __construct(array $arguments = array())
{
$this->arguments = $arguments;
parent::__construct($arguments);
}
public function setArguments(array $arguments)
{
$this->arguments = $arguments;
return $this;
}
public function setPrefix($prefix)
{
$this->prefix = $prefix;
return $this;
}
public function setTimeout($timeout)
{
$this->timeout = $timeout;
return $this;
}
public function getProcess()
{
if (!$this->prefix && !count($this->arguments)) {
throw new LogicException('You must add() command arguments before calling getProcess().');
}
$args = $this->prefix ? array_merge(array($this->prefix), $this->arguments) : $this->arguments;
$script = implode(' ', array_map('escapeshellarg', $args));
return new Process($script, null, null, null, $this->timeout);
}
}

View file

@ -1,28 +0,0 @@
<?php
namespace Alchemy\Tests\BinaryDriver;
use Alchemy\BinaryDriver\ProcessBuilderFactory;
class LTSProcessBuilderFactoryTest extends AbstractProcessBuilderFactoryTest
{
public function setUp(): void
{
if (!class_exists('Symfony\Component\Process\ProcessBuilder')) {
$this->markTestSkipped('ProcessBuilder is not available.');
return;
}
parent::setUp();
}
protected function getProcessBuilderFactory($binary)
{
$factory = new ProcessBuilderFactory($binary);
$factory->setBuilder(new LTSProcessBuilder());
ProcessBuilderFactory::$emulateSfLTS = false;
$factory->useBinary($binary);
return $factory;
}
}

View file

@ -1,34 +0,0 @@
<?php
namespace Alchemy\Tests\BinaryDriver\Listeners;
use Alchemy\BinaryDriver\Listeners\DebugListener;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Process\Process;
class DebugListenerTest extends TestCase
{
public function testHandle()
{
$listener = new DebugListener();
$lines = [];
$listener->on('debug', function ($line) use (&$lines) {
$lines[] = $line;
});
$listener->handle(Process::ERR, "first line\nsecond line");
$listener->handle(Process::OUT, "cool output");
$listener->handle('unknown', "lalala");
$listener->handle(Process::OUT, "another output\n");
$expected = [
'[ERROR] first line',
'[ERROR] second line',
'[OUT] cool output',
'[OUT] another output',
'[OUT] ',
];
$this->assertEquals($expected, $lines);
}
}

View file

@ -1,93 +0,0 @@
<?php
namespace Alchemy\Tests\BinaryDriver\Listeners;
use Alchemy\BinaryDriver\Listeners\ListenerInterface;
use Alchemy\BinaryDriver\Listeners\Listeners;
use Evenement\EventEmitter;
use PHPUnit\Framework\TestCase;
class ListenersTest extends TestCase
{
public function testRegister()
{
$listener = new MockListener();
$listeners = new Listeners();
$listeners->register($listener);
$n = 0;
$listener->on('received', function ($type, $data) use (&$n, &$capturedType, &$capturedData) {
$n++;
$capturedData = $data;
$capturedType = $type;
});
$type = 'type';
$data = 'data';
$listener->handle($type, $data);
$listener->handle($type, $data);
$listeners->unregister($listener);
$listener->handle($type, $data);
$this->assertEquals(3, $n);
$this->assertEquals($type, $capturedType);
$this->assertEquals($data, $capturedData);
}
public function testRegisterAndForwardThenUnregister()
{
$listener = new MockListener();
$target = new EventEmitter();
$n = 0;
$target->on('received', function ($type, $data) use (&$n, &$capturedType, &$capturedData) {
$n++;
$capturedData = $data;
$capturedType = $type;
});
$m = 0;
$listener->on('received', function ($type, $data) use (&$m, &$capturedType2, &$capturedData2) {
$m++;
$capturedData2 = $data;
$capturedType2 = $type;
});
$listeners = new Listeners();
$listeners->register($listener, $target);
$type = 'type';
$data = 'data';
$listener->handle($type, $data);
$listener->handle($type, $data);
$listeners->unregister($listener, $target);
$listener->handle($type, $data);
$this->assertEquals(2, $n);
$this->assertEquals(3, $m);
$this->assertEquals($type, $capturedType);
$this->assertEquals($data, $capturedData);
$this->assertEquals($type, $capturedType2);
$this->assertEquals($data, $capturedData2);
}
}
class MockListener extends EventEmitter implements ListenerInterface
{
public function handle($type, $data)
{
$this->emit('received', [$type, $data]);
}
public function forwardedEvents()
{
return ['received'];
}
}

View file

@ -1,15 +0,0 @@
<?php
namespace Alchemy\Tests\BinaryDriver;
use Alchemy\BinaryDriver\ProcessBuilderFactory;
class NONLTSProcessBuilderFactoryTest extends AbstractProcessBuilderFactoryTest
{
protected function getProcessBuilderFactory($binary)
{
ProcessBuilderFactory::$emulateSfLTS = true;
return new ProcessBuilderFactory($binary);
}
}

View file

@ -1,208 +0,0 @@
<?php
/*
* This file is part of Alchemy\BinaryDriver.
*
* (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 Alchemy\Tests\BinaryDriver;
use Alchemy\BinaryDriver\ProcessRunner;
use Alchemy\BinaryDriver\BinaryDriverTestCase;
use Alchemy\BinaryDriver\Exception\ExecutionFailureException;
use Alchemy\BinaryDriver\Listeners\ListenerInterface;
use Evenement\EventEmitter;
use Symfony\Component\Process\Exception\RuntimeException as ProcessRuntimeException;
class ProcessRunnerTest extends BinaryDriverTestCase
{
public function getProcessRunner($logger)
{
return new ProcessRunner($logger, 'test-runner');
}
public function testRunSuccessFullProcess()
{
$logger = $this->createLoggerMock();
$runner = $this->getProcessRunner($logger);
$process = $this->createProcessMock(1, true, '--helloworld--', "Kikoo Romain", null, true);
$logger
->expects($this->never())
->method('error');
$logger
->expects($this->exactly(2))
->method('info');
$this->assertEquals('Kikoo Romain', $runner->run($process, new \SplObjectStorage(), false));
}
public function testRunSuccessFullProcessBypassingErrors()
{
$logger = $this->createLoggerMock();
$runner = $this->getProcessRunner($logger);
$process = $this->createProcessMock(1, true, '--helloworld--', "Kikoo Romain", null, true);
$logger
->expects($this->never())
->method('error');
$logger
->expects($this->exactly(2))
->method('info');
$this->assertEquals('Kikoo Romain', $runner->run($process, new \SplObjectStorage(), true));
}
public function testRunFailingProcess()
{
$logger = $this->createLoggerMock();
$runner = $this->getProcessRunner($logger);
$process = $this->createProcessMock(1, false, '--helloworld--', null, null, true);
$logger
->expects($this->once())
->method('error');
$logger
->expects($this->once())
->method('info');
try {
$runner->run($process, new \SplObjectStorage(), false);
$this->fail('An exception should have been raised');
} catch (ExecutionFailureException $e) {
}
}
public function testRunFailingProcessWithException()
{
$logger = $this->createLoggerMock();
$runner = $this->getProcessRunner($logger);
$exception = new ProcessRuntimeException('Process Failed');
$process = $this->getMockBuilder('Symfony\Component\Process\Process')
->disableOriginalConstructor()
->getMock();
$process->expects($this->once())
->method('run')
->will($this->throwException($exception));
$logger
->expects($this->once())
->method('error');
$logger
->expects($this->once())
->method('info');
try {
$runner->run($process, new \SplObjectStorage(), false);
$this->fail('An exception should have been raised');
} catch (ExecutionFailureException $e) {
$this->assertEquals($exception, $e->getPrevious());
}
}
public function testRunfailingProcessBypassingErrors()
{
$logger = $this->createLoggerMock();
$runner = $this->getProcessRunner($logger);
$process = $this->createProcessMock(1, false, '--helloworld--', 'Hello output', null, true);
$logger
->expects($this->once())
->method('error');
$logger
->expects($this->once())
->method('info');
$this->assertNull($runner->run($process, new \SplObjectStorage(), true));
}
public function testRunFailingProcessWithExceptionBypassingErrors()
{
$logger = $this->createLoggerMock();
$runner = $this->getProcessRunner($logger);
$exception = new ProcessRuntimeException('Process Failed');
$process = $this->getMockBuilder('Symfony\Component\Process\Process')
->disableOriginalConstructor()
->getMock();
$process->expects($this->once())
->method('run')
->will($this->throwException($exception));
$logger
->expects($this->once())
->method('error');
$logger
->expects($this->once())
->method('info');
$this->assertNull($runner->run($process, new \SplObjectStorage(), true));
}
public function testRunSuccessFullProcessWithHandlers()
{
$logger = $this->createLoggerMock();
$runner = $this->getProcessRunner($logger);
$capturedCallback = null;
$process = $this->createProcessMock(1, true, '--helloworld--', "Kikoo Romain", null, true);
$process->expects($this->once())
->method('run')
->with($this->isInstanceOf('Closure'))
->will($this->returnCallback(function ($callback) use (&$capturedCallback) {
$capturedCallback = $callback;
}));
$logger
->expects($this->never())
->method('error');
$logger
->expects($this->exactly(2))
->method('info');
$listener = new TestListener();
$storage = new \SplObjectStorage();
$storage->attach($listener);
$capturedType = $capturedData = null;
$listener->on('received', function ($type, $data) use (&$capturedType, &$capturedData) {
$capturedData = $data;
$capturedType = $type;
});
$this->assertEquals('Kikoo Romain', $runner->run($process, $storage, false));
$type = 'err';
$data = 'data';
$capturedCallback($type, $data);
$this->assertEquals($data, $capturedData);
$this->assertEquals($type, $capturedType);
}
}
class TestListener extends EventEmitter implements ListenerInterface
{
public function handle($type, $data)
{
return $this->emit('received', array($type, $data));
}
public function forwardedEvents()
{
return array();
}
}

5
tests/ExampleTest.php Normal file
View file

@ -0,0 +1,5 @@
<?php
it('can test', function () {
expect(true)->toBeTrue();
});

View file

@ -1,13 +0,0 @@
<?php
namespace Tests\FFMpeg;
use PHPUnit\Framework\TestCase;
class BaseTestCase extends TestCase
{
public function assertScalar($value, $message = '')
{
$this->assertTrue(is_scalar($value), $message);
}
}

View file

@ -1,300 +0,0 @@
<?php
namespace Tests\FFMpeg\Functional;
use FFMpeg\Coordinate\Dimension;
use FFMpeg\Filters\AdvancedMedia\TestSrcFilter;
use FFMpeg\Filters\AdvancedMedia\XStackFilter;
use FFMpeg\Format\Audio\Mp3;
use FFMpeg\Format\Video\X264;
class AdvancedMediaTest extends FunctionalTestCase
{
/**
* Path prefix to avoid conflicts with another tests.
*/
public const OUTPUT_PATH_PREFIX = 'output/advanced_media_';
public function testRunWithoutComplexFilterTestExtractAudio()
{
$ffmpeg = $this->getFFMpeg();
$inputs = [realpath(__DIR__.'/../files/Test.ogv')];
$format = new Mp3();
$output = __DIR__.'/'.self::OUTPUT_PATH_PREFIX.'extracted_with_map.mp3';
// You can run it without -filter_complex, just using -map.
$advancedMedia = $ffmpeg->openAdvanced($inputs);
$advancedMedia
->map(['0:a'], $format, $output)
->save();
$this->assertFileExists($output);
$this->assertEquals(
'MP2/3 (MPEG audio layer 2/3)',
$ffmpeg->open($output)->getFormat()->get('format_long_name')
);
unlink($output);
}
public function testAudio()
{
$ffmpeg = $this->getFFMpeg();
$inputs = [realpath(__DIR__.'/../files/Audio.mp3')];
$format = new Mp3();
$format->setAudioKiloBitrate(30);
$output = __DIR__.'/'.self::OUTPUT_PATH_PREFIX.'audio_test.mp3';
$advancedMedia = $ffmpeg->openAdvanced($inputs);
$advancedMedia
->map(['0:a'], $format, $output)
->save();
$this->assertFileExists($output);
$this->assertEquals(
'MP2/3 (MPEG audio layer 2/3)',
$ffmpeg->open($output)->getFormat()->get('format_long_name')
);
unlink($output);
}
public function testMultipleInputs()
{
$ffmpeg = $this->getFFMpeg();
$inputs = [
realpath(__DIR__.'/../files/portrait.MOV'),
realpath(__DIR__.'/../files/portrait.MOV'),
];
$format = new X264('aac', 'libx264');
$output = __DIR__.'/'.self::OUTPUT_PATH_PREFIX.'multiple_inputs_test.mp4';
$advancedMedia = $ffmpeg->openAdvanced($inputs);
$advancedMedia->filters()
->custom('[0:v][1:v]', 'hstack', '[v]');
$advancedMedia
->map(['0:a', '[v]'], $format, $output)
->save();
$this->assertFileExists($output);
$this->assertEquals(
'QuickTime / MOV',
$ffmpeg->open($output)->getFormat()->get('format_long_name')
);
unlink($output);
}
/**
* @covers \FFMpeg\Media\AdvancedMedia::map
*/
public function testMultipleOutputsTestAbsenceOfInputs()
{
$ffmpeg = $this->getFFMpeg();
// in this test we use only computed inputs
// and can ignore -i part of the command, pass empty inputs array.
$inputs = [];
$formatX264 = new X264('aac', 'libx264');
$formatMp3 = new Mp3();
$outputMp3 = __DIR__.'/'.self::OUTPUT_PATH_PREFIX.'test_multiple_outputs.mp3';
$outputVideo1 = __DIR__.'/'.self::OUTPUT_PATH_PREFIX.'test_multiple_outputs_v1.mp4';
$outputVideo2 = __DIR__.'/'.self::OUTPUT_PATH_PREFIX.'test_multiple_outputs_v2.mp4';
$advancedMedia = $ffmpeg->openAdvanced($inputs);
$advancedMedia->filters()
->sine('[a]', 5)
->testSrc('[v1]', TestSrcFilter::TESTSRC, '160x120', 5)
->testSrc('[v2]', TestSrcFilter::TESTSRC, '160x120', 5)
->custom('[v1]', 'negate', '[v1negate]')
->custom('[v2]', 'edgedetect', '[v2edgedetect]');
$advancedMedia
->map(['[a]'], $formatMp3, $outputMp3)
->map(['[v1negate]'], $formatX264, $outputVideo1)
->map(['[v2edgedetect]'], $formatX264, $outputVideo2)
->save();
$this->assertFileExists($outputMp3);
$this->assertEquals(
'MP2/3 (MPEG audio layer 2/3)',
$ffmpeg->open($outputMp3)->getFormat()->get('format_long_name')
);
unlink($outputMp3);
$this->assertFileExists($outputVideo1);
$this->assertEquals(
'QuickTime / MOV',
$ffmpeg->open($outputVideo1)->getFormat()->get('format_long_name')
);
unlink($outputVideo1);
$this->assertFileExists($outputVideo2);
$this->assertEquals(
'QuickTime / MOV',
$ffmpeg->open($outputVideo2)->getFormat()->get('format_long_name')
);
unlink($outputVideo2);
}
/**
* @covers \FFMpeg\Filters\AdvancedMedia\TestSrcFilter
* @covers \FFMpeg\Filters\AdvancedMedia\SineFilter
*/
public function testTestSrcFilterTestSineFilter()
{
$ffmpeg = $this->getFFMpeg();
$inputs = [realpath(__DIR__.'/../files/Test.ogv')];
$format = new X264('aac', 'libx264');
$output = __DIR__.'/'.self::OUTPUT_PATH_PREFIX.'testsrc.mp4';
$advancedMedia = $ffmpeg->openAdvanced($inputs);
$advancedMedia->filters()
->sine('[a]', 10)
->testSrc('[v]', TestSrcFilter::TESTSRC, '160x120', 10);
$advancedMedia
->map(['[a]', '[v]'], $format, $output)
->save();
$this->assertFileExists($output);
$this->assertEquals(
'QuickTime / MOV',
$ffmpeg->open($output)->getFormat()->get('format_long_name')
);
unlink($output);
}
/**
* XStack filter is supported starting from 4.1 ffmpeg version.
*
* @covers \FFMpeg\Filters\AdvancedMedia\XStackFilter
* @covers \FFMpeg\Filters\AdvancedMedia\SineFilter
*/
public function testXStackFilter()
{
$xStack = new XStackFilter('', 0);
$ffmpeg = $this->getFFMpeg();
$ffmpegVersion = $ffmpeg->getFFMpegDriver()->getVersion();
if (version_compare($ffmpegVersion, $xStack->getMinimalFFMpegVersion(), '<')) {
$this->markTestSkipped('XStack filter is supported starting from ffmpeg version '
.$xStack->getMinimalFFMpegVersion().', your version is '
.$ffmpegVersion);
return;
}
$inputs = [realpath(__DIR__.'/../files/Test.ogv')];
$format = new X264('aac', 'libx264');
$output = __DIR__.'/'.self::OUTPUT_PATH_PREFIX.'xstack_test.mp4';
$advancedMedia = $ffmpeg->openAdvanced($inputs);
$advancedMedia->filters()
->sine('[a]', 5)
->testSrc('[v1]', TestSrcFilter::TESTSRC, '160x120', 5)
->testSrc('[v2]', TestSrcFilter::TESTSRC, '160x120', 5)
->testSrc('[v3]', TestSrcFilter::TESTSRC, '160x120', 5)
->testSrc('[v4]', TestSrcFilter::TESTSRC, '160x120', 5)
->xStack(
'[v1][v2][v3][v4]',
XStackFilter::LAYOUT_2X2,
4,
'[v]'
);
$advancedMedia
->map(['[a]', '[v]'], $format, $output)
->save();
$this->assertFileExists($output);
$this->assertEquals(
'QuickTime / MOV',
$ffmpeg->open($output)->getFormat()->get('format_long_name')
);
unlink($output);
}
public function testOfCompatibilityWithExistedFilters()
{
$ffmpeg = $this->getFFMpeg();
$inputs = [realpath(__DIR__.'/../files/Test.ogv')];
$watermark = realpath(__DIR__.'/../files/watermark.png');
$format = new X264('aac', 'libx264');
$output = __DIR__.'/'.self::OUTPUT_PATH_PREFIX.'test_of_compatibility_with_existed_filters.mp4';
$advancedMedia = $ffmpeg->openAdvanced($inputs);
$advancedMedia->filters()
// For unknown reasons WatermarkFilter produce an error on Windows,
// because the path to the watermark becomes corrupted.
// This behaviour related with Alchemy\BinaryDriver\AbstractBinary::command().
// The path inside filter becomes like
// "D:ServerswwwPHP-FFMpegtestsfileswatermark.png" (without slashes).
// But on Linux systems filter works as expected.
//->watermark('[0:v]', $watermark, '[v]')
->pad('[0:v]', new Dimension(300, 100), '[v]');
$advancedMedia
->map(['0:a', '[v]'], $format, $output)
->save();
$this->assertFileExists($output);
$this->assertEquals(
'QuickTime / MOV',
$ffmpeg->open($output)->getFormat()->get('format_long_name')
);
unlink($output);
}
public function testForceDisableAudio()
{
$ffmpeg = $this->getFFMpeg();
$format = new X264();
$advancedMedia1 = $ffmpeg->openAdvanced([__FILE__]);
$advancedMedia1
->map(['test'], $format, 'outputFile.mp4', false);
$this->assertStringContainsString('acodec', $advancedMedia1->getFinalCommand());
$advancedMedia2 = $ffmpeg->openAdvanced([__FILE__]);
$advancedMedia2
->map(['test'], $format, 'outputFile.mp4', true);
$this->assertStringNotContainsString('acodec', $advancedMedia2->getFinalCommand());
}
public function testForceDisableVideo()
{
$ffmpeg = $this->getFFMpeg();
$format = new X264();
$advancedMedia1 = $ffmpeg->openAdvanced([__FILE__]);
$advancedMedia1->map(
['test'],
$format,
'outputFile.mp4',
false,
false
);
$this->assertStringContainsString('vcodec', $advancedMedia1->getFinalCommand());
$advancedMedia2 = $ffmpeg->openAdvanced([__FILE__]);
$advancedMedia2->map(
['test'],
$format,
'outputFile.mp4',
false,
true
);
$this->assertStringNotContainsString('vcodec', $advancedMedia2->getFinalCommand());
}
public function testGlobalOptions()
{
$configuration = [
'ffmpeg.threads' => 3,
'ffmpeg.filter_threads' => 13,
'ffmpeg.filter_complex_threads' => 24,
];
$ffmpeg = $this->getFFMpeg($configuration);
$advancedMedia = $ffmpeg->openAdvanced([__FILE__]);
$command = $advancedMedia->getFinalCommand();
foreach ($configuration as $optionName => $optionValue) {
$optionName = str_replace('ffmpeg.', '', $optionName);
$this->assertStringContainsString('-'.$optionName.' '.$optionValue, $command);
}
}
}

View file

@ -1,28 +0,0 @@
<?php
namespace Tests\FFMpeg\Functional;
class AudioConcatenationTest extends FunctionalTestCase
{
public function testSimpleAudioFileConcatTest()
{
$ffmpeg = $this->getFFMpeg();
$files = [
realpath(__DIR__ . '/../files/Jahzzar_-_05_-_Siesta.mp3'),
realpath(__DIR__ . '/../files/02_-_Favorite_Secrets.mp3'),
];
$audio = $ffmpeg->open(reset($files));
$this->assertInstanceOf('FFMpeg\Media\Audio', $audio);
clearstatcache();
$filename = __DIR__ . '/output/concat-output.mp3';
$audio->concat($files)->saveFromSameCodecs($filename, true);
$this->assertFileExists($filename);
unlink($filename);
}
}

View file

@ -1,40 +0,0 @@
<?php
namespace Tests\FFMpeg\Functional;
use FFMpeg\FFProbe;
class FFProbeTest extends FunctionalTestCase
{
public function testProbeOnFile()
{
$ffprobe = FFProbe::create();
$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'));
}
public function testProbeOnNonExistantFile()
{
$this->expectException('\FFMpeg\Exception\RuntimeException');
$ffprobe = FFProbe::create();
$ffprobe->streams('/path/to/no/file');
}
public function testProbeOnRemoteFile()
{
$ffprobe = FFProbe::create();
$this->assertGreaterThan(0, count($ffprobe->streams('http://vjs.zencdn.net/v/oceans.mp4')));
}
}

View file

@ -1,19 +0,0 @@
<?php
namespace Tests\FFMpeg\Functional;
use FFMpeg\FFMpeg;
use Tests\FFMpeg\BaseTestCase;
abstract class FunctionalTestCase extends BaseTestCase
{
/**
* @param array $configuration
*
* @return FFMpeg
*/
public function getFFMpeg($configuration = [])
{
return FFMpeg::create(array_merge(['timeout' => 300], $configuration));
}
}

View file

@ -1,146 +0,0 @@
<?php
namespace Tests\FFMpeg\Functional;
use FFMpeg\Coordinate\Dimension;
use FFMpeg\Filters\Video\ResizeFilter;
use FFMpeg\Filters\Video\RotateFilter;
use FFMpeg\Format\Video\X264;
use FFMpeg\Media\Video;
class VideoTranscodeTest extends FunctionalTestCase
{
public function testSimpleTranscodeX264()
{
$filename = __DIR__.'/output/output-x264.mp4';
if (is_file($filename)) {
unlink(__DIR__.'/output/output-x264.mp4');
}
$ffmpeg = $this->getFFMpeg();
$video = $ffmpeg->open(__DIR__.'/../files/Test.ogv');
$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);
}
$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);
}
$lastPercentage = $percentage;
$phpunit->assertGreaterThanOrEqual(0, $percentage);
$phpunit->assertLessThanOrEqual(100, $percentage);
});
$video->save($codec, $filename);
$this->assertFileExists($filename);
unlink($filename);
}
public function testTranscodeInvalidFile()
{
$this->expectException('\FFMpeg\Exception\RuntimeException');
$ffmpeg = $this->getFFMpeg();
$ffmpeg->open(__DIR__.'/../files/UnknownFileTest.ogv');
}
public function testSaveInvalidForgedVideo()
{
$ffmpeg = $this->getFFMpeg();
$video = new Video(__DIR__.'/../files/UnknownFileTest.ogv', $ffmpeg->getFFMpegDriver(), $ffmpeg->getFFProbe());
$this->expectException('\FFMpeg\Exception\RuntimeException');
$video->save(new X264('aac'), __DIR__.'/output/output-x264.mp4');
}
public function testTranscodePortraitVideo()
{
$info = $this->getNameAndVersion();
if ('avconv' === $info['name'] && version_compare($info['version'], '0.9', '<')) {
$this->markTestSkipped('This version of avconv is buggy and does not support this test.');
}
$filename = __DIR__.'/output/output-x264.mp4';
if (is_file($filename)) {
unlink(__DIR__.'/output/output-x264.mp4');
}
$ffmpeg = $this->getFFMpeg();
$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('aac'), $filename);
$dimension = $ffmpeg->getFFProbe()
->streams($filename)
->videos()
->first()
->getDimensions();
$this->assertLessThan(1, $dimension->getRatio(false)->getValue());
$this->assertEquals(240, $dimension->getHeight());
$this->assertFileExists($filename);
unlink($filename);
}
private function getNameAndVersion()
{
$binary = $this
->getFFMpeg()
->getFFMpegDriver()
->getProcessBuilderFactory()
->getBinary();
$output = $matches = null;
exec($binary.' -version 2>&1', $output);
if (!isset($output[0])) {
return ['name' => null, 'version' => null];
}
preg_match('/^([a-z]+)\s+version\s+([0-9\.]+)/i', $output[0], $matches);
if (count($matches) > 0) {
return ['name' => $matches[1], 'version' => $matches[2]];
}
return ['name' => null, 'version' => null];
}
}

View file

@ -1,83 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\Coordinate;
use FFMpeg\Coordinate\AspectRatio;
use FFMpeg\Coordinate\Dimension;
use Tests\FFMpeg\Unit\TestCase;
class AspectRatioTest extends TestCase
{
/**
* @dataProvider provideDimensionsAndExpectedratio
*/
public function testFromDimensions($width, $height, $strategy, $expected, $calculatedWidth, $calculatedHeight, $modulus = 2)
{
$ratio = AspectRatio::create(new Dimension($width, $height), $strategy);
$this->assertEquals($expected, $ratio->getValue());
$this->assertEquals($calculatedHeight, $ratio->calculateHeight(240, $modulus));
$this->assertEquals($calculatedWidth, $ratio->calculateWidth(320, $modulus));
}
public function provideDimensionsAndExpectedratio()
{
return [
//AR_5_4
[720, 576, false, 5 / 4, 400, 192],
[720, 577, false, 5 / 4, 400, 192],
[720, 620, false, 720 / 620, 372, 206],
[720, 576, true, 5 / 4, 400, 192],
//AR_ROTATED_4_5
[576, 720, false, 4 / 5, 256, 300],
[576, 720, true, 4 / 5, 256, 300],
//AR_4_3
[320, 240, false, 4 / 3, 426, 180],
[320, 240, true, 4 / 3, 426, 180],
//AR_ROTATED_3_4
[240, 320, false, 3 / 4, 240, 320],
[240, 320, true, 3 / 4, 240, 320],
//AR_16_9
[1920, 1080, false, 16 / 9, 568, 136],
[1920, 1080, true, 16 / 9, 568, 136],
[1280, 720, false, 16 / 9, 568, 136],
[1280, 720, true, 16 / 9, 568, 136],
[3840, 2160, false, 16 / 9, 568, 136],
[3840, 2160, true, 16 / 9, 568, 136],
// modulus 4
[1920, 1080, false, 16 / 9, 568, 136, 4],
[1920, 1080, true, 16 / 9, 568, 136, 4],
[1280, 720, false, 16 / 9, 568, 136, 4],
[1280, 720, true, 16 / 9, 568, 136, 4],
[3840, 2160, false, 16 / 9, 568, 136, 4],
[3840, 2160, true, 16 / 9, 568, 136, 4],
// modulus 16
[1920, 1080, false, 16 / 9, 576, 128, 16],
[1920, 1080, true, 16 / 9, 576, 128, 16],
[1280, 720, false, 16 / 9, 576, 128, 16],
[1280, 720, true, 16 / 9, 576, 128, 16],
[3840, 2160, false, 16 / 9, 576, 128, 16],
[3840, 2160, true, 16 / 9, 576, 128, 16],
//AR_ROTATED_9_16
[1080, 1920, false, 9 / 16, 180, 426],
[1080, 1920, true, 9 / 16, 180, 426],
[720, 1280, false, 9 / 16, 180, 426],
[720, 1280, true, 9 / 16, 180, 426],
[2160, 3840, false, 9 / 16, 180, 426],
[2160, 3840, true, 9 / 16, 180, 426],
//AR_3_2
[360, 240, false, 3 / 2, 480, 160],
[360, 240, true, 3 / 2, 480, 160],
//AR_ROTATED_2_3
[240, 360, false, 2 / 3, 214, 360],
[240, 360, true, 2 / 3, 214, 360],
//AR_5_3
//AR_ROTATED_3_5
//AR_1_1
//AR_1_DOT_85_1
//AR_ROTATED_1_DOT_85
//AR_2_DOT_39_1
//AR_ROTATED_2_DOT_39
];
}
}

View file

@ -1,38 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\Coordinate;
use FFMpeg\Coordinate\Dimension;
use Tests\FFMpeg\Unit\TestCase;
class DimensionTest extends TestCase
{
/**
* @dataProvider provideInvalidDimensions
*/
public function testInvalidDimensions($width, $height)
{
$this->expectException('\FFMpeg\Exception\InvalidArgumentException');
new Dimension($width, $height);
}
public function provideInvalidDimensions()
{
return [
[320, 0],
[320, -10],
[0, 240],
[-10, 240],
[0, 0],
[0, -10],
[-10, 0],
];
}
public function testGetters()
{
$dimension = new Dimension(320, 240);
$this->assertEquals(320, $dimension->getWidth());
$this->assertEquals(240, $dimension->getHeight());
}
}

View file

@ -1,31 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\Coordinate;
use FFMpeg\Coordinate\FrameRate;
use Tests\FFMpeg\Unit\TestCase;
class FrameRateTest extends TestCase
{
public function testGetter()
{
$fr = new FrameRate(23.997);
$this->assertEquals(23.997, $fr->getValue());
}
/**
* @dataProvider provideInvalidFrameRates
*/
public function testInvalidFrameRate($value)
{
$this->expectException('\FFMpeg\Exception\InvalidArgumentException');
new FrameRate($value);
}
public function provideInvalidFrameRates()
{
return [
[0], [-1.5], [-2],
];
}
}

View file

@ -1,23 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\Coordinate;
use FFMpeg\Coordinate\Point;
use Tests\FFMpeg\Unit\TestCase;
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,58 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\Coordinate;
use FFMpeg\Coordinate\TimeCode;
use Tests\FFMpeg\Unit\TestCase;
class TimeCodeTest extends TestCase
{
/**
* @dataProvider provideTimecodes
*/
public function testFromString($timecode, $expected)
{
$tc = TimeCode::fromString($timecode);
$this->assertEquals((string) $tc, $expected);
}
public function provideTimeCodes()
{
return [
['1:02:04:05:20', '26:04:05.20'],
['1:02:04:05.20', '26:04:05.20'],
['02:04:05:20', '02:04:05.20'],
['02:04:05.20', '02:04:05.20'],
['00:00:05.20', '00:00:05.20'],
['00:00:00.00', '00:00:00.00'],
];
}
public function testFromInvalidString()
{
$this->expectException('\FFMpeg\Exception\InvalidArgumentException');
TimeCode::fromString('lalali lala');
}
/**
* @dataProvider provideSeconds
*/
public function testFromSeconds($seconds, $expected)
{
$tc = TimeCode::fromSeconds($seconds);
$this->assertEquals($expected, (string) $tc);
}
public function provideSeconds()
{
return [
[0.467, '00:00:00.47'],
[12.467, '00:00:12.47'],
[59.867, '00:00:59.87'],
[72.467, '00:01:12.47'],
[3599.467, '00:59:59.47'],
[3600.467, '01:00:00.47'],
[86422.467, '24:00:22.47'],
];
}
}

View file

@ -1,49 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\Driver;
use Alchemy\BinaryDriver\Configuration;
use FFMpeg\Driver\FFMpegDriver;
use Symfony\Component\Process\ExecutableFinder;
use Tests\FFMpeg\Unit\TestCase;
class FFMpegDriverTest extends TestCase
{
public function setUp(): void
{
$executableFinder = new ExecutableFinder();
$found = false;
foreach (['avconv', 'ffmpeg'] as $name) {
if (null !== $executableFinder->find($name)) {
$found = true;
break;
}
}
if (!$found) {
$this->markTestSkipped('Neither ffmpeg or avconv found');
}
}
public function testCreate()
{
$logger = $this->getLoggerMock();
$ffmpeg = FFMpegDriver::create($logger, []);
$this->assertInstanceOf('FFMpeg\Driver\FFMpegDriver', $ffmpeg);
$this->assertEquals($logger, $ffmpeg->getProcessRunner()->getLogger());
}
public function testCreateWithConfig()
{
$conf = new Configuration();
$ffmpeg = FFMpegDriver::create($this->getLoggerMock(), $conf);
$this->assertEquals($conf, $ffmpeg->getConfiguration());
}
public function testCreateFailureThrowsAnException()
{
$this->expectException('\FFMpeg\Exception\ExecutableNotFoundException');
FFMpegDriver::create($this->getLoggerMock(), ['ffmpeg.binaries' => '/path/to/nowhere']);
}
}

View file

@ -1,49 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\Driver;
use Alchemy\BinaryDriver\Configuration;
use FFMpeg\Driver\FFProbeDriver;
use Symfony\Component\Process\ExecutableFinder;
use Tests\FFMpeg\Unit\TestCase;
class FFProbeDriverTest extends TestCase
{
public function setUp(): void
{
$executableFinder = new ExecutableFinder();
$found = false;
foreach (['avprobe', 'ffprobe'] as $name) {
if (null !== $executableFinder->find($name)) {
$found = true;
break;
}
}
if (!$found) {
$this->markTestSkipped('Neither ffprobe or avprobe found');
}
}
public function testCreate()
{
$logger = $this->getLoggerMock();
$ffprobe = FFProbeDriver::create([], $logger);
$this->assertInstanceOf('FFMpeg\Driver\FFProbeDriver', $ffprobe);
$this->assertEquals($logger, $ffprobe->getProcessRunner()->getLogger());
}
public function testCreateWithConfig()
{
$conf = new Configuration();
$ffprobe = FFProbeDriver::create($conf, $this->getLoggerMock());
$this->assertEquals($conf, $ffprobe->getConfiguration());
}
public function testCreateFailureThrowsAnException()
{
$this->expectException('\FFMpeg\Exception\ExecutableNotFoundException');
FFProbeDriver::create(['ffprobe.binaries' => '/path/to/nowhere']);
}
}

View file

@ -1,110 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit;
use FFMpeg\FFMpeg;
use FFMpeg\FFProbe\DataMapping\Stream;
use FFMpeg\FFProbe\DataMapping\StreamCollection;
class FFMpegTest extends TestCase
{
public function testOpenInvalid()
{
$this->expectException(
'\FFMpeg\Exception\RuntimeException',
'Unable to probe "/path/to/unknown/file"'
);
$ffmpeg = new FFMpeg($this->getFFMpegDriverMock(), $this->getFFProbeMock());
$ffmpeg->open('/path/to/unknown/file');
}
public function testOpenAudio()
{
$streams = $this->getStreamCollectionMock();
$streams->expects($this->once())
->method('audios')
->will($this->returnValue(new StreamCollection([new Stream([])])));
$streams->expects($this->once())
->method('videos')
->will($this->returnValue([]));
$ffprobe = $this->getFFProbeMock();
$ffprobe->expects($this->once())
->method('streams')
->with(__FILE__)
->will($this->returnValue($streams));
$ffmpeg = new FFMpeg($this->getFFMpegDriverMock(), $ffprobe);
$this->assertInstanceOf('FFMpeg\Media\Audio', $ffmpeg->open(__FILE__));
}
public function testOpenVideo()
{
$streams = $this->getStreamCollectionMock();
$streams->expects($this->once())
->method('videos')
->will($this->returnValue(new StreamCollection([new Stream([])])));
$streams->expects($this->never())
->method('audios');
$ffprobe = $this->getFFProbeMock();
$ffprobe->expects($this->once())
->method('streams')
->with(__FILE__)
->will($this->returnValue($streams));
$ffmpeg = new FFMpeg($this->getFFMpegDriverMock(), $ffprobe);
$this->assertInstanceOf('FFMpeg\Media\Video', $ffmpeg->open(__FILE__));
}
public function testOpenUnknown()
{
$this->expectException('\FFMpeg\Exception\InvalidArgumentException');
$ffprobe = $this->getFFProbeMock();
$ffprobe->expects($this->once())
->method('streams')
->with(__FILE__)
->will($this->returnValue(new StreamCollection()));
$ffmpeg = new FFMpeg($this->getFFMpegDriverMock(), $ffprobe);
$ffmpeg->open(__FILE__);
}
public function testCreateWithoutLoggerOrProbe()
{
$this->assertInstanceOf('FFMpeg\FFMpeg', FFMpeg::create());
}
public function testCreateWithLoggerAndProbe()
{
$logger = $this->getLoggerMock();
$ffprobe = $this->getFFProbeMock();
$ffmpeg = FFMpeg::create(['timeout' => 42], $logger, $ffprobe);
$this->assertInstanceOf('FFMpeg\FFMpeg', $ffmpeg);
$this->assertSame($logger, $ffmpeg->getFFMpegDriver()->getProcessRunner()->getLogger());
$this->assertSame($ffprobe, $ffmpeg->getFFProbe());
$this->assertSame(42, $ffmpeg->getFFMpegDriver()->getProcessBuilderFactory()->getTimeout());
}
public function testGetSetFFProbe()
{
$ffprobe = $this->getFFProbeMock();
$ffmpeg = new FFMpeg($this->getFFMpegDriverMock(), $ffprobe);
$this->assertSame($ffprobe, $ffmpeg->getFFProbe());
$anotherFFProbe = $this->getFFProbeMock();
$ffmpeg->setFFProbe($anotherFFProbe);
$this->assertSame($anotherFFProbe, $ffmpeg->getFFProbe());
}
public function testGetSetDriver()
{
$driver = $this->getFFMpegDriverMock();
$ffmpeg = new FFMpeg($driver, $this->getFFProbeMock());
$this->assertSame($driver, $ffmpeg->getFFMpegDriver());
$anotherDriver = $this->getFFMpegDriverMock();
$ffmpeg->setFFMpegDriver($anotherDriver);
$this->assertSame($anotherDriver, $ffmpeg->getFFMpegDriver());
}
}

View file

@ -1,52 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\FFProbe\DataMapping;
use FFMpeg\FFProbe\DataMapping\AbstractData;
use Tests\FFMpeg\Unit\TestCase;
class AbstractDataTest extends TestCase
{
public function testHas()
{
$imp = new Implementation(['key1' => 'value1', 'key2' => 'value2']);
$this->assertTrue($imp->has('key1'));
$this->assertTrue($imp->has('key2'));
$this->assertFalse($imp->has('value1'));
$this->assertFalse($imp->has('key3'));
}
public function testGet()
{
$imp = new Implementation(['key1' => 'value1', 'key2' => 'value2']);
$this->assertEquals('value1', $imp->get('key1'));
$this->assertEquals('value2', $imp->get('key2'));
}
public function testGetDefault()
{
$imp = new Implementation(['key1' => 'value1', 'key2' => 'value2']);
$this->assertSame('yololo', $imp->get('key3', 'yololo'));
}
public function testKeys()
{
$imp = new Implementation(['key1' => 'value1', 'key2' => 'value2']);
$this->assertEquals(['key1', 'key2'], $imp->keys());
}
public function testAll()
{
$values = ['key1' => 'value1', 'key2' => 'value2'];
$imp = new Implementation($values);
$this->assertEquals($values, $imp->all());
}
}
class Implementation extends AbstractData
{
}

View file

@ -1,89 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\FFProbe\DataMapping;
use FFMpeg\FFProbe\DataMapping\StreamCollection;
use Tests\FFMpeg\Unit\TestCase;
class StreamCollectionTest extends TestCase
{
public function testAdd()
{
$stream = $this->getStreamMock();
$collection = new StreamCollection();
$this->assertEquals([], $collection->all());
$collection->add($stream);
$this->assertEquals([$stream], $collection->all());
$collection->add($stream);
$this->assertEquals([$stream, $stream], $collection->all());
}
public function testVideos()
{
$audio = $this->getStreamMock();
$audio->expects($this->once())
->method('isVideo')
->will($this->returnValue(false));
$video = $this->getStreamMock();
$video->expects($this->once())
->method('isVideo')
->will($this->returnValue(true));
$collection = new StreamCollection([$audio, $video]);
$videos = $collection->videos();
$this->assertInstanceOf('FFMpeg\FFProbe\DataMapping\StreamCollection', $videos);
$this->assertCount(1, $videos);
$this->assertEquals([$video], $videos->all());
}
public function testAudios()
{
$audio = $this->getStreamMock();
$audio->expects($this->once())
->method('isAudio')
->will($this->returnValue(true));
$video = $this->getStreamMock();
$video->expects($this->once())
->method('isAudio')
->will($this->returnValue(false));
$collection = new StreamCollection([$audio, $video]);
$audios = $collection->audios();
$this->assertInstanceOf('FFMpeg\FFProbe\DataMapping\StreamCollection', $audios);
$this->assertCount(1, $audios);
$this->assertEquals([$audio], $audios->all());
}
public function testCount()
{
$stream = $this->getStreamMock();
$collection = new StreamCollection([$stream]);
$this->assertCount(1, $collection);
}
public function testGetIterator()
{
$audio = $this->getStreamMock();
$video = $this->getStreamMock();
$collection = new StreamCollection([$audio, $video]);
$this->assertInstanceOf('\Iterator', $collection->getIterator());
$this->assertCount(2, $collection->getIterator());
}
public function testFirst()
{
$stream1 = $this->getStreamMock();
$stream2 = $this->getStreamMock();
$coll = new StreamCollection([$stream1, $stream2]);
$this->assertSame($stream1, $coll->first());
}
}

View file

@ -1,136 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\FFProbe\DataMapping;
use FFMpeg\Coordinate\Dimension;
use FFMpeg\FFProbe\DataMapping\Stream;
use Tests\FFMpeg\Unit\TestCase;
class StreamTest extends TestCase
{
/**
* @dataProvider provideAudioCases
*/
public function testIsAudio($isAudio, $properties)
{
$stream = new Stream($properties);
$this->assertTrue($isAudio === $stream->isAudio());
}
public function provideAudioCases()
{
return [
[true, ['codec_type' => 'audio']],
[false, ['codec_type' => 'video']],
];
}
/**
* @dataProvider provideVideoCases
*/
public function testIsVideo($isVideo, $properties)
{
$stream = new Stream($properties);
$this->assertTrue($isVideo === $stream->isVideo());
}
public function provideVideoCases()
{
return [
[true, ['codec_type' => 'video']],
[false, ['codec_type' => 'audio']],
];
}
public function testGetDimensionsFromAudio()
{
$this->expectException(
'\FFMpeg\Exception\LogicException',
'Dimensions can only be retrieved from video streams.'
);
$stream = new Stream(['codec_type' => 'audio']);
$stream->getDimensions();
}
public function testGetDimensionsFromVideo()
{
$stream = new Stream(['codec_type' => 'video', 'width' => 960, 'height' => 720]);
$this->assertEquals(new Dimension(960, 720), $stream->getDimensions());
}
/**
* @dataProvider provideInvalidPropertiesForDimensionsExtraction
*/
public function testUnableToGetDimensionsFromVideo($properties)
{
$this->expectException(
'\FFMpeg\Exception\RuntimeException',
'Unable to extract dimensions.'
);
$stream = new Stream(['codec_type' => 'video', 'width' => 960]);
$stream->getDimensions();
}
public function provideInvalidPropertiesForDimensionsExtraction()
{
return [
['codec_type' => 'video', 'width' => 960],
['codec_type' => 'video', 'height' => 960],
];
}
/**
* @dataProvider providePropertiesForDimensionsExtraction
*/
public function testGetDimensionsFromVideoWithDisplayRatio($data)
{
$stream = new Stream([
'codec_type' => 'video',
'width' => $data['width'],
'height' => $data['height'],
'sample_aspect_ratio' => $data['sar'],
'display_aspect_ratio' => $data['dar'],
]);
$this->assertEquals(new Dimension($data['result_width'], $data['result_height']), $stream->getDimensions());
}
/**
* @dataProvider provideInvalidRatios
*/
public function testGetDimensionsFromVideoWithInvalidDisplayRatio($invalidRatio)
{
$stream = new Stream(['codec_type' => 'video', 'width' => 960, 'height' => 720, 'sample_aspect_ratio' => $invalidRatio, 'display_aspect_ratio' => '16:9']);
$this->assertEquals(new Dimension(960, 720), $stream->getDimensions());
}
public function provideInvalidRatios()
{
return [['0:1'], ['2:1:3']];
}
public function providePropertiesForDimensionsExtraction()
{
return [
[
['width' => '960', 'height' => '720',
'sar' => '4:3', 'dar' => '16:9',
'result_width' => '1280', 'result_height' => '720', ],
],
[
['width' => '1920', 'height' => '1080',
'sar' => '1:1', 'dar' => '16:9',
'result_width' => '1920', 'result_height' => '1080', ],
],
[
['width' => '640', 'height' => '480',
'sar' => '75:74', 'dar' => '50:37',
'result_width' => '649', 'result_height' => '480', ],
],
[
['width' => '720', 'height' => '576',
'sar' => '52:28', 'dar' => '16:9',
'result_width' => '1337', 'result_height' => '752', ],
],
];
}
}

View file

@ -1,42 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\FFProbe;
use FFMpeg\FFProbe;
use FFMpeg\FFProbe\DataMapping\Format;
use FFMpeg\FFProbe\DataMapping\Stream;
use FFMpeg\FFProbe\DataMapping\StreamCollection;
use FFMpeg\FFProbe\Mapper;
use Tests\FFMpeg\Unit\TestCase;
class MapperTest extends TestCase
{
/**
* @dataProvider provideMappings
*/
public function testMap($type, $data, $expected)
{
$mapper = new Mapper();
$this->assertEquals($expected, $mapper->map($type, $data));
}
public function testMapInvalidArgument()
{
$this->expectException('\FFMpeg\Exception\InvalidArgumentException');
$mapper = new Mapper();
$mapper->map('cool type', 'data');
}
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);
return [
[FFProbe::TYPE_FORMAT, $format, new Format($format['format'])],
[FFProbe::TYPE_STREAMS, $streams, new StreamCollection(array_map(function ($streamData) {
return new Stream($streamData);
}, $streams['streams']))],
];
}
}

View file

@ -1,132 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\FFProbe;
use FFMpeg\FFProbe\OptionsTester;
use Symfony\Component\Cache\CacheItem;
use Tests\FFMpeg\Unit\TestCase;
class OptionsTesterTest extends TestCase
{
public function testHasOptionWithOldFFProbe()
{
$this->expectException(
'\FFMpeg\Exception\RuntimeException',
'Your FFProbe version is too old and does not support `-help` option, please upgrade.'
);
$cache = $this->getCacheMock();
$executionFailerExceptionMock = $this->getMockBuilder('Alchemy\BinaryDriver\Exception\ExecutionFailureException')
->disableOriginalConstructor()
->getMock();
$ffprobe = $this->getFFProbeDriverMock();
$ffprobe->expects($this->once())
->method('command')
->with(['-help', '-loglevel', 'quiet'])
->will($this->throwException($executionFailerExceptionMock));
$tester = new OptionsTester($ffprobe, $cache);
$tester->has('-print_format');
}
/**
* @dataProvider provideOptions
*/
public function testHasOptionWithCacheEmpty($isPresent, $data, $optionName)
{
$cache = $this->getCacheMock();
$cache->expects($this->exactly(2))
->method('getItem')
->will($this->returnValue(new CacheItem));
$cache->expects($this->exactly(2))
->method('hasItem')
->will($this->returnValue(false));
$cache->expects($this->exactly(2))
->method('save');
$ffprobe = $this->getFFProbeDriverMock();
$ffprobe->expects($this->once())
->method('command')
->with(['-help', '-loglevel', 'quiet'])
->will($this->returnValue($data));
$tester = new OptionsTester($ffprobe, $cache);
$this->assertTrue($isPresent === $tester->has($optionName));
}
public function provideOptions()
{
$data = file_get_contents(__DIR__ . '/../../fixtures/ffprobe/help.raw');
return [
[true, $data, '-print_format'],
[false, $data, '-another_print_format'],
];
}
/**
* @dataProvider provideOptions
*/
public function testHasOptionWithHelpCacheLoaded($isPresent, $data, $optionName)
{
$cache = $this->getCacheMock();
$cacheItem = new CacheItem;
$cacheItem->set($data);
$cache->expects($this->exactly(2))
->method('getItem')
->willReturnOnConsecutiveCalls(
$this->returnValue($cacheItem),
$this->returnValue(new CacheItem)
);
$cache->expects($this->exactly(2))
->method('hasItem')
->willReturnOnConsecutiveCalls(
$this->returnValue(false),
$this->returnValue(true)
);
$cache->expects($this->once())
->method('save');
$ffprobe = $this->getFFProbeDriverMock();
$ffprobe->expects($this->never())
->method('command');
$tester = new OptionsTester($ffprobe, $cache);
$this->assertTrue($isPresent === $tester->has($optionName));
}
/**
* @dataProvider provideOptions
*/
public function testHasOptionWithCacheFullyLoaded($isPresent, $data, $optionName)
{
$cache = $this->getCacheMock();
$cacheItem = new CacheItem();
$cacheItem->set($isPresent);
$cache->expects($this->once())
->method('getItem')
->with(md5('option-' . $optionName))
->will($this->returnValue($cacheItem));
$cache->expects($this->once())
->method('hasItem')
->with(md5('option-' . $optionName))
->will($this->returnValue(true));
$ffprobe = $this->getFFProbeDriverMock();
$ffprobe->expects($this->never())
->method('command');
$tester = new OptionsTester($ffprobe, $cache);
$this->assertTrue($isPresent === $tester->has($optionName));
}
}

View file

@ -1,40 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\FFProbe;
use FFMpeg\FFProbe;
use FFMpeg\FFProbe\OutputParser;
use Tests\FFMpeg\Unit\TestCase;
class OutputParserTest extends TestCase
{
/**
* @dataProvider provideTypeDataAndOutput
*/
public function testParse($type, $data, $expectedOutput)
{
$parser = new OutputParser();
$this->assertEquals($expectedOutput, $parser->parse($type, $data));
}
public function testParseWithInvalidArgument()
{
$this->expectException('\FFMpeg\Exception\InvalidArgumentException');
$parser = new OutputParser();
$parser->parse('comme ca', 'data');
}
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);
$rawFormat = file_get_contents(__DIR__.'/../../fixtures/ffprobe/show_format.raw');
$rawStreams = file_get_contents(__DIR__.'/../../fixtures/ffprobe/show_streams.raw');
return [
[FFProbe::TYPE_FORMAT, $rawFormat, $expectedFormat],
[FFProbe::TYPE_STREAMS, $rawStreams, $expectedStreams],
];
}
}

View file

@ -1,301 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit;
use Alchemy\BinaryDriver\Configuration;
use Alchemy\BinaryDriver\ConfigurationInterface;
use FFMpeg\FFProbe;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Process\ExecutableFinder;
class FFProbeTest extends TestCase
{
public function testGetSetParser()
{
$ffprobe = new FFProbe($this->getFFProbeDriverMock(), $this->getCacheMock());
$parser = $this->getFFProbeParserMock();
$ffprobe->setParser($parser);
$this->assertSame($parser, $ffprobe->getParser());
}
public function testGetSetFFProbeDriver()
{
$ffprobe = new FFProbe($this->getFFProbeDriverMock(), $this->getCacheMock());
$driver = $this->getFFProbeDriverMock();
$ffprobe->setFFProbeDriver($driver);
$this->assertSame($driver, $ffprobe->getFFProbeDriver());
}
public function testGetSetFFProbeMapper()
{
$ffprobe = new FFProbe($this->getFFProbeDriverMock(), $this->getCacheMock());
$mapper = $this->getFFProbeMapperMock();
$ffprobe->setMapper($mapper);
$this->assertSame($mapper, $ffprobe->getMapper());
}
public function testGetSetOptionsTester()
{
$ffprobe = new FFProbe($this->getFFProbeDriverMock(), $this->getCacheMock());
$tester = $this->getFFProbeOptionsTesterMock();
$ffprobe->setOptionsTester($tester);
$this->assertSame($tester, $ffprobe->getOptionsTester());
}
public function testGetSetCache()
{
$ffprobe = new FFProbe($this->getFFProbeDriverMock(), $this->getCacheMock());
$cache = $this->getCacheMock();
$ffprobe->setCache($cache);
$this->assertSame($cache, $ffprobe->getCache());
}
public function provideDataWhitoutCache()
{
$stream = $this->getStreamMock();
$format = $this->getFormatMock();
return [
[$stream, 'streams', ['-show_streams', '-print_format'], FFProbe::TYPE_STREAMS, [__FILE__, '-show_streams', '-print_format', 'json'], false],
[$format, 'format', ['-show_format', '-print_format'], FFProbe::TYPE_FORMAT, [__FILE__, '-show_format', '-print_format', 'json'], false],
[$stream, 'streams', ['-show_streams'], FFProbe::TYPE_STREAMS, [__FILE__, '-show_streams'], true],
[$format, 'format', ['-show_format'], FFProbe::TYPE_FORMAT, [__FILE__, '-show_format'], true],
];
}
/**
* @dataProvider provideDataWhitoutCache
*/
public function testProbeWithoutCache($output, $method, $commands, $type, $caughtCommands, $isRaw)
{
$pathfile = __FILE__;
$data = ['key' => 'value'];
$rawData = 'raw data';
$ffprobe = new FFProbe($this->getFFProbeDriverMock(), $this->getCacheMock());
$mapper = $this->getFFProbeMapperMock();
$mapper->expects($this->once())
->method('map')
->with($type, $data)
->will($this->returnValue($output));
$parser = $this->getFFProbeParserMock();
if ($isRaw) {
$parser->expects($this->once())
->method('parse')
->with($type, $rawData)
->will($this->returnValue($data));
} else {
$parser->expects($this->never())
->method('parse');
}
$tester = $this->getFFProbeOptionsTesterMockWithOptions($commands);
$cache = $this->getCacheMock();
$cache->expects($this->once())
->method('hasItem')
->will($this->returnValue(false));
$cache->expects($this->once())
->method('getItem')
->will($this->returnValue(new CacheItem));
$cache->expects($this->once())
->method('save')
->with($this->anything());
$driver = $this->getFFProbeDriverMock();
$driver->expects($this->once())
->method('command')
->with($caughtCommands)
->will($this->returnValue($isRaw ? $rawData : json_encode($data)));
$ffprobe->setOptionsTester($tester)
->setCache($cache)
->setMapper($mapper)
->setFFProbeDriver($driver)
->setParser($parser);
$this->assertEquals($output, call_user_func([$ffprobe, $method], $pathfile));
}
public function provideDataForInvalidJson()
{
$stream = $this->getStreamMock();
$format = $this->getFormatMock();
return [
[$stream, 'streams', ['-show_streams', '-print_format'], FFProbe::TYPE_STREAMS, [__FILE__, '-show_streams', '-print_format', 'json']],
[$format, 'format', ['-show_format', '-print_format'], FFProbe::TYPE_FORMAT, [__FILE__, '-show_format', '-print_format', 'json']],
];
}
/**
* @dataProvider provideDataForInvalidJson
*/
public function testProbeWithWrongJson($output, $method, $commands, $type, $caughtCommands)
{
$pathfile = __FILE__;
$data = ['key' => 'value'];
$ffprobe = new FFProbe($this->getFFProbeDriverMock(), $this->getCacheMock());
$mapper = $this->getFFProbeMapperMock();
$mapper->expects($this->once())
->method('map')
->with($this->isType('string'), 'good data parsed')
->will($this->returnValue($output));
$parser = $this->getFFProbeParserMock();
$parser->expects($this->once())
->method('parse')
->with($this->isType('string', json_encode($data).'lala'))
->will($this->returnValue('good data parsed'));
$tester = $this->getFFProbeOptionsTesterMockWithOptions($commands);
$cache = $this->getCacheMock();
$cache->expects($this->exactly(2))
->method('hasItem')
->will($this->returnValue(false));
$cache->expects($this->once())
->method('getItem')
->will($this->returnValue(new CacheItem));
$driver = $this->getFFProbeDriverMock();
$driver->expects($this->exactly(2))
->method('command')
->will($this->returnValue(json_encode($data).'lala'));
$ffprobe->setOptionsTester($tester)
->setCache($cache)
->setMapper($mapper)
->setFFProbeDriver($driver)
->setParser($parser);
$this->assertEquals($output, call_user_func([$ffprobe, $method], $pathfile));
}
public function provideProbingDataWithCache()
{
$stream = $this->getStreamMock();
$format = $this->getFormatMock();
return [
[$stream, 'streams'],
[$format, 'format'],
];
}
/**
* @dataProvider provideProbingDataWithCache
*/
public function testProbeWithCache($output, $method)
{
$pathfile = __FILE__;
$ffprobe = new FFProbe($this->getFFProbeDriverMock(), $this->getCacheMock());
$mapper = $this->getFFProbeMapperMock();
$mapper->expects($this->never())
->method('map');
$tester = $this->getFFProbeOptionsTesterMock();
$cacheItem = new CacheItem;
$cacheItem->set($output);
$cache = $this->getCacheMock();
$cache->expects($this->once())
->method('hasItem')
->will($this->returnValue(true));
$cache->expects($this->once())
->method('getItem')
->will($this->returnValue($cacheItem));
$cache->expects($this->never())
->method('save');
$driver = $this->getFFProbeDriverMock();
$driver->expects($this->never())
->method('command');
$ffprobe->setOptionsTester($tester)
->setCache($cache)
->setMapper($mapper)
->setFFProbeDriver($driver);
$this->assertEquals($output, call_user_func([$ffprobe, $method], $pathfile));
}
public function provideProbeMethod()
{
return [
['streams'],
['format'],
];
}
/**
* @dataProvider provideProbeMethod
*/
public function testProbeWithoutShowStreamsAvailable($method)
{
$this->expectException('\FFMpeg\Exception\RuntimeException');
$pathfile = __FILE__;
$ffprobe = new FFProbe($this->getFFProbeDriverMock(), $this->getCacheMock());
$ffprobe->setOptionsTester($this->getFFProbeOptionsTesterMock());
call_user_func([$ffprobe, $method], $pathfile);
}
/**
* @dataProvider provideCreateOptions
*/
public function testCreate($logger, $conf, $cache)
{
$finder = new ExecutableFinder();
$found = false;
foreach (['avprobe', 'ffprobe'] as $name) {
if (null !== $finder->find($name)) {
$found = true;
}
}
if (!$found) {
$this->markTestSkipped('Unable to find avprobe or ffprobe on system');
}
$ffprobe = FFProbe::create();
$this->assertInstanceOf('FFMpeg\FFprobe', $ffprobe);
$ffprobe = FFProbe::create($conf, $logger, $cache);
$this->assertInstanceOf('FFMpeg\FFprobe', $ffprobe);
if (null !== $cache) {
$this->assertSame($cache, $ffprobe->getCache());
}
if (null !== $logger) {
$this->assertSame($logger, $ffprobe->getFFProbeDriver()->getProcessRunner()->getLogger());
}
if ($conf instanceof ConfigurationInterface) {
$this->assertSame($conf, $ffprobe->getFFProbeDriver()->getConfiguration());
}
}
public function provideCreateOptions()
{
return [
[null, ['key' => 'value'], null],
[$this->getLoggerMock(), ['key' => 'value'], null],
[null, new Configuration(), null],
[null, ['key' => 'value'], $this->getCacheMock()],
];
}
}

View file

@ -1,48 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\Filters\Audio;
use FFMpeg\Coordinate\TimeCode;
use FFMpeg\Filters\Audio\AudioFilters;
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->getMockBuilder('FFMpeg\Format\AudioInterface')->getMock();
$filters = new AudioFilters($audio);
$filters->clip(TimeCode::fromSeconds(5));
$this->assertEquals([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->getMockBuilder('FFMpeg\Format\AudioInterface')->getMock();
$filters = new AudioFilters($audio);
$filters->clip(TimeCode::fromSeconds(5), TimeCode::fromSeconds(5));
$this->assertEquals([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,26 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\Filters\Audio;
use FFMpeg\Filters\Audio\AudioFilters;
use Tests\FFMpeg\Unit\TestCase;
class AudioFiltersTest extends TestCase
{
public function testResample()
{
$capturedFilter = null;
$audio = $this->getAudioMock();
$audio->expects($this->once())
->method('addFilter')
->with($this->isInstanceOf('FFMpeg\Filters\Audio\AudioResamplableFilter'))
->will($this->returnCallback(function ($filter) use (&$capturedFilter) {
$capturedFilter = $filter;
}));
$filters = new AudioFilters($audio);
$filters->resample(8000);
$this->assertEquals(8000, $capturedFilter->getRate());
}
}

View file

@ -1,64 +0,0 @@
<?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->getMockBuilder('FFMpeg\Format\AudioInterface')->getMock();
$filters = new AudioFilters($audio);
$filters->addMetadata(['title' => 'Hello World']);
$this->assertEquals([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->getMockBuilder('FFMpeg\Format\AudioInterface')->getMock();
$filters = new AudioFilters($audio);
$filters->addMetadata(['genre' => 'Some Genre', 'artwork' => '/path/to/file.jpg']);
$this->assertEquals([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([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->getMockBuilder('FFMpeg\Format\AudioInterface')->getMock();
$filters = new AudioFilters($audio);
$filters->addMetadata();
$this->assertEquals([0 => '-map_metadata', 1 => '-1', 2 => '-vn'], $capturedFilter->apply($audio, $format));
}
}

View file

@ -1,24 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\Filters\Audio;
use FFMpeg\Filters\Audio\AudioResamplableFilter;
use Tests\FFMpeg\Unit\TestCase;
class AudioResamplableFilterTest extends TestCase
{
public function testGetRate()
{
$filter = new AudioResamplableFilter(500);
$this->assertEquals(500, $filter->getRate());
}
public function testApply()
{
$audio = $this->getAudioMock();
$format = $this->getMockBuilder('FFMpeg\Format\AudioInterface')->getMock();
$filter = new AudioResamplableFilter(500);
$this->assertEquals(['-ac', 2, '-ar', 500], $filter->apply($audio, $format));
}
}

View file

@ -1,18 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\Filters\Audio;
use FFMpeg\Filters\Audio\CustomFilter;
use Tests\FFMpeg\Unit\TestCase;
class CustomFilterTest extends TestCase
{
public function testApplyCustomFilter()
{
$audio = $this->getAudioMock();
$format = $this->getMockBuilder('FFMpeg\Format\AudioInterface')->getMock();
$filter = new CustomFilter('whatever i put would end up as a filter');
$this->assertEquals(['-af', 'whatever i put would end up as a filter'], $filter->apply($audio, $format));
}
}

View file

@ -1,62 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\Filters;
use FFMpeg\Filters\Audio\SimpleFilter;
use FFMpeg\Filters\FiltersCollection;
use Tests\FFMpeg\Unit\TestCase;
class FiltersCollectionTest extends TestCase
{
public function testCount()
{
$coll = new FiltersCollection();
$this->assertCount(0, $coll);
$coll->add($this->getMockBuilder('FFMpeg\Filters\FilterInterface')->getMock());
$this->assertCount(1, $coll);
$coll->add($this->getMockBuilder('FFMpeg\Filters\FilterInterface')->getMock());
$this->assertCount(2, $coll);
}
public function testIterator()
{
$coll = new FiltersCollection();
$coll->add($this->getMockBuilder('FFMpeg\Filters\FilterInterface')->getMock());
$coll->add($this->getMockBuilder('FFMpeg\Filters\FilterInterface')->getMock());
$this->assertInstanceOf('\ArrayIterator', $coll->getIterator());
$this->assertCount(2, $coll->getIterator());
}
public function testEmptyIterator()
{
$coll = new FiltersCollection();
$this->assertInstanceOf('\ArrayIterator', $coll->getIterator());
}
public function testIteratorSort()
{
$coll = new FiltersCollection();
$coll->add(new SimpleFilter(['a']));
$coll->add(new SimpleFilter(['1'], 12));
$coll->add(new SimpleFilter(['b']));
$coll->add(new SimpleFilter(['2'], 12));
$coll->add(new SimpleFilter(['c']));
$coll->add(new SimpleFilter(['3'], 10));
$coll->add(new SimpleFilter(['d']));
$coll->add(new SimpleFilter(['4'], -2));
$coll->add(new SimpleFilter(['e']));
$data = [];
$video = $this->getVideoMock();
$format = $this->getMockBuilder('FFMpeg\Format\AudioInterface')->getMock();
foreach ($coll as $filter) {
$data = array_merge($data, $filter->apply($video, $format));
}
$this->assertEquals(['1', '2', '3', 'a', 'b', 'c', 'd', 'e', '4'], $data);
}
}

View file

@ -1,17 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\Filters\Frame;
use FFMpeg\Filters\Frame\CustomFrameFilter;
use Tests\FFMpeg\Unit\TestCase;
class CustomFrameFilterTest extends TestCase
{
public function testApplyCustomFrameFilter()
{
$frame = $this->getFrameMock();
$filter = new CustomFrameFilter('whatever i put would end up as a filter');
$this->assertEquals(['-vf', 'whatever i put would end up as a filter'], $filter->apply($frame));
}
}

View file

@ -1,28 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\Filters\Frame;
use FFMpeg\Coordinate\TimeCode;
use FFMpeg\FFProbe\DataMapping\Stream;
use FFMpeg\FFProbe\DataMapping\StreamCollection;
use FFMpeg\Filters\Frame\DisplayRatioFixerFilter;
use FFMpeg\Media\Frame;
use Tests\FFMpeg\Unit\TestCase;
class DisplayRatioFixerFilterTest extends TestCase
{
public function testApply()
{
$stream = new Stream(['codec_type' => 'video', 'width' => 960, 'height' => 720]);
$streams = new StreamCollection([$stream]);
$video = $this->getVideoMock(__FILE__);
$video->expects($this->once())
->method('getStreams')
->will($this->returnValue($streams));
$frame = new Frame($video, $this->getFFMpegDriverMock(), $this->getFFProbeMock(), new TimeCode(0, 0, 0, 0));
$filter = new DisplayRatioFixerFilter();
$this->assertEquals(['-s', '960x720'], $filter->apply($frame));
}
}

View file

@ -1,21 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\Filters\Frame;
use FFMpeg\Filters\Frame\FrameFilters;
use Tests\FFMpeg\Unit\TestCase;
class FrameFiltersTest extends TestCase
{
public function testResize()
{
$frame = $this->getFrameMock();
$filters = new FrameFilters($frame);
$frame->expects($this->once())
->method('addFilter')
->with($this->isInstanceOf('FFMpeg\Filters\Frame\DisplayRatioFixerFilter'));
$filters->fixDisplayRatio();
}
}

View file

@ -1,38 +0,0 @@
<?php
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 Tests\FFMpeg\Unit\TestCase;
class CropFilterTest extends TestCase
{
public function testCommandParamsAreCorrectAndStreamIsUpdated()
{
$stream = new Stream(['width' => 320, 'height' => 240, 'codec_type' => 'video']);
$streams = new StreamCollection([$stream]);
$video = $this->getVideoMock();
$video->expects($this->once())
->method('getStreams')
->will($this->returnValue($streams));
$format = $this->getMockBuilder('FFMpeg\Format\VideoInterface')->getMock();
$dimension = new Dimension(200, 150);
$point = new Point(25, 35);
$filter = new CropFilter($point, $dimension);
$expected = [
'-filter:v',
'crop='.$dimension->getWidth().':'.$dimension->getHeight().':'.$point->getX().':'.$point->getY(),
];
$this->assertEquals($expected, $filter->apply($video, $format));
$this->assertEquals(200, $stream->get('width'));
$this->assertEquals(150, $stream->get('height'));
}
}

View file

@ -1,18 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\Filters\Video;
use FFMpeg\Filters\Video\CustomFilter;
use Tests\FFMpeg\Unit\TestCase;
class CustomFilterTest extends TestCase
{
public function testApplyCustomFilter()
{
$video = $this->getVideoMock();
$format = $this->getMockBuilder('FFMpeg\Format\VideoInterface')->getMock();
$filter = new CustomFilter('whatever i put would end up as a filter');
$this->assertEquals(['-vf', 'whatever i put would end up as a filter'], $filter->apply($video, $format));
}
}

View file

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

View file

@ -1,44 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\Filters\Video;
use FFMpeg\Coordinate\FrameRate;
use FFMpeg\Filters\Video\FrameRateFilter;
use Tests\FFMpeg\Unit\TestCase;
class FrameRateFilterTest extends TestCase
{
public function testApplyWithAFormatThatSupportsBFrames()
{
$framerate = new FrameRate(54);
$gop = 42;
$video = $this->getVideoMock();
$format = $this->getMockBuilder('FFMpeg\Format\VideoInterface')->getMock();
$format->expects($this->any())
->method('supportBFrames')
->will($this->returnValue(true));
$expected = ['-r', 54, '-b_strategy', '1', '-bf', '3', '-g', 42];
$filter = new FrameRateFilter($framerate, $gop);
$this->assertEquals($expected, $filter->apply($video, $format));
}
public function testApplyWithAFormatThatDoesNotSupportsBFrames()
{
$framerate = new FrameRate(54);
$gop = 42;
$video = $this->getVideoMock();
$format = $this->getMockBuilder('FFMpeg\Format\VideoInterface')->getMock();
$format->expects($this->any())
->method('supportBFrames')
->will($this->returnValue(false));
$expected = ['-r', 54];
$filter = new FrameRateFilter($framerate, $gop);
$this->assertEquals($expected, $filter->apply($video, $format));
}
}

View file

@ -1,44 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\Filters\Video;
use FFMpeg\Coordinate\Dimension;
use FFMpeg\FFProbe\DataMapping\Stream;
use FFMpeg\FFProbe\DataMapping\StreamCollection;
use FFMpeg\Filters\Video\PadFilter;
use Tests\FFMpeg\Unit\TestCase;
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->getMockBuilder('FFMpeg\Format\VideoInterface')->getMock();
$streams = new StreamCollection([
new Stream([
'codec_type' => 'video',
'width' => $width,
'height' => $height,
]),
]);
$filter = new PadFilter($dimension);
$this->assertEquals($expected, $filter->apply($video, $format));
}
public function provideDimensions()
{
return [
[new Dimension(1000, 800), 640, 480, ['-vf', 'scale=iw*min(1000/iw\,800/ih):ih*min(1000/iw\,800/ih),pad=1000:800:(1000-iw)/2:(800-ih)/2']],
[new Dimension(300, 600), 640, 480, ['-vf', 'scale=iw*min(300/iw\,600/ih):ih*min(300/iw\,600/ih),pad=300:600:(300-iw)/2:(600-ih)/2']],
[new Dimension(100, 900), 640, 480, ['-vf', 'scale=iw*min(100/iw\,900/ih):ih*min(100/iw\,900/ih),pad=100:900:(100-iw)/2:(900-ih)/2']],
[new Dimension(1200, 200), 640, 480, ['-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,75 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\Filters\Video;
use FFMpeg\Coordinate\Dimension;
use FFMpeg\FFProbe\DataMapping\Stream;
use FFMpeg\FFProbe\DataMapping\StreamCollection;
use FFMpeg\Filters\Video\ResizeFilter;
use Tests\FFMpeg\Unit\TestCase;
class ResizeFilterTest extends TestCase
{
/**
* @dataProvider provideDimensions
*/
public function testApply(Dimension $dimension, $mode, $width, $height, $modulus, $expected, $forceStandards = true)
{
$video = $this->getVideoMock();
$pathfile = '/path/to/file'.mt_rand();
$format = $this->getMockBuilder('FFMpeg\Format\VideoInterface')->getMock();
$format->expects($this->any())
->method('getModulus')
->will($this->returnValue($modulus));
$streams = new StreamCollection([
new Stream([
'codec_type' => 'video',
'width' => $width,
'height' => $height,
]),
]);
$video->expects($this->once())
->method('getStreams')
->will($this->returnValue($streams));
$filter = new ResizeFilter($dimension, $mode, $forceStandards);
$this->assertEquals($expected, $filter->apply($video, $format));
}
public function provideDimensions()
{
return [
[new Dimension(320, 240), ResizeFilter::RESIZEMODE_FIT, 640, 480, 2, ['-vf', '[in]scale=320:240 [out]']],
[new Dimension(320, 240), ResizeFilter::RESIZEMODE_INSET, 640, 480, 2, ['-vf', '[in]scale=320:240 [out]']],
[new Dimension(320, 240), ResizeFilter::RESIZEMODE_SCALE_HEIGHT, 640, 480, 2, ['-vf', '[in]scale=320:240 [out]']],
[new Dimension(320, 240), ResizeFilter::RESIZEMODE_SCALE_WIDTH, 640, 480, 2, ['-vf', '[in]scale=320:240 [out]']],
[new Dimension(640, 480), ResizeFilter::RESIZEMODE_FIT, 320, 240, 2, ['-vf', '[in]scale=640:480 [out]']],
[new Dimension(640, 480), ResizeFilter::RESIZEMODE_INSET, 320, 240, 2, ['-vf', '[in]scale=640:480 [out]']],
[new Dimension(640, 480), ResizeFilter::RESIZEMODE_SCALE_HEIGHT, 320, 240, 2, ['-vf', '[in]scale=640:480 [out]']],
[new Dimension(640, 480), ResizeFilter::RESIZEMODE_SCALE_WIDTH, 320, 240, 2, ['-vf', '[in]scale=640:480 [out]']],
[new Dimension(640, 360), ResizeFilter::RESIZEMODE_FIT, 1280, 720, 2, ['-vf', '[in]scale=640:360 [out]']],
[new Dimension(640, 360), ResizeFilter::RESIZEMODE_INSET, 1280, 720, 2, ['-vf', '[in]scale=640:360 [out]']],
[new Dimension(640, 360), ResizeFilter::RESIZEMODE_SCALE_HEIGHT, 1280, 720, 2, ['-vf', '[in]scale=640:360 [out]']],
[new Dimension(640, 360), ResizeFilter::RESIZEMODE_SCALE_WIDTH, 1280, 720, 2, ['-vf', '[in]scale=640:360 [out]']],
[new Dimension(640, 360), ResizeFilter::RESIZEMODE_FIT, 1280, 720, 2, ['-vf', '[in]scale=640:360 [out]']],
[new Dimension(640, 360), ResizeFilter::RESIZEMODE_INSET, 1280, 720, 2, ['-vf', '[in]scale=640:360 [out]']],
[new Dimension(640, 360), ResizeFilter::RESIZEMODE_SCALE_HEIGHT, 1280, 720, 2, ['-vf', '[in]scale=640:360 [out]']],
[new Dimension(640, 360), ResizeFilter::RESIZEMODE_SCALE_WIDTH, 1280, 720, 2, ['-vf', '[in]scale=640:360 [out]']],
// test non standard dimension
[new Dimension(700, 150), ResizeFilter::RESIZEMODE_INSET, 123, 456, 2, ['-vf', '[in]scale=62:150 [out]'], true],
[new Dimension(700, 150), ResizeFilter::RESIZEMODE_INSET, 123, 456, 2, ['-vf', '[in]scale=40:150 [out]'], false],
[new Dimension(320, 320), ResizeFilter::RESIZEMODE_FIT, 640, 480, 2, ['-vf', '[in]scale=320:320 [out]']],
[new Dimension(320, 320), ResizeFilter::RESIZEMODE_INSET, 640, 480, 2, ['-vf', '[in]scale=320:240 [out]']],
[new Dimension(320, 320), ResizeFilter::RESIZEMODE_SCALE_HEIGHT, 640, 480, 2, ['-vf', '[in]scale=320:240 [out]']],
[new Dimension(320, 320), ResizeFilter::RESIZEMODE_SCALE_WIDTH, 640, 480, 2, ['-vf', '[in]scale=426:320 [out]']],
];
}
}

View file

@ -1,72 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\Filters\Video;
use FFMpeg\FFProbe\DataMapping\Stream;
use FFMpeg\FFProbe\DataMapping\StreamCollection;
use FFMpeg\Filters\Video\RotateFilter;
use Tests\FFMpeg\Unit\TestCase;
class RotateFilterTest extends TestCase
{
/**
* @dataProvider provide90degresTranspositions
*/
public function testApplyWithSizeTransformation($value)
{
$stream = new Stream(['width' => 320, 'height' => 240, 'codec_type' => 'video']);
$streams = new StreamCollection([$stream]);
$video = $this->getVideoMock();
$video->expects($this->once())
->method('getStreams')
->will($this->returnValue($streams));
$format = $this->getMockBuilder('FFMpeg\Format\VideoInterface')->getMock();
$filter = new RotateFilter($value);
$this->assertEquals(['-vf', $value, '-metadata:s:v:0', 'rotate=0'], $filter->apply($video, $format));
$this->assertEquals(240, $stream->get('width'));
$this->assertEquals(320, $stream->get('height'));
}
public function provide90degresTranspositions()
{
return [
[RotateFilter::ROTATE_90],
[RotateFilter::ROTATE_270],
];
}
/**
* @dataProvider provideDegresWithoutTranspositions
*/
public function testApplyWithoutSizeTransformation($value)
{
$video = $this->getVideoMock();
$video->expects($this->never())
->method('getStreams');
$format = $this->getMockBuilder('FFMpeg\Format\VideoInterface')->getMock();
$filter = new RotateFilter($value);
$this->assertEquals(['-vf', $value, '-metadata:s:v:0', 'rotate=0'], $filter->apply($video, $format));
}
public function provideDegresWithoutTranspositions()
{
return [
[RotateFilter::ROTATE_180],
];
}
public function testApplyInvalidAngle()
{
$this->expectException(
'\FFMpeg\Exception\InvalidArgumentException',
'Invalid angle value.'
);
new RotateFilter('90');
}
}

View file

@ -1,18 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\Filters\Video;
use FFMpeg\Filters\Video\SynchronizeFilter;
use Tests\FFMpeg\Unit\TestCase;
class SynchronizeFilterTest extends TestCase
{
public function testApply()
{
$video = $this->getVideoMock();
$format = $this->getMockBuilder('FFMpeg\Format\VideoInterface')->getMock();
$filter = new SynchronizeFilter();
$this->assertEquals(['-async', '1', '-metadata:s:v:0', 'start_time=0'], $filter->apply($video, $format));
}
}

View file

@ -1,79 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\Filters\Video;
use FFMpeg\Filters\Video\ResizeFilter;
use FFMpeg\Filters\Video\VideoFilters;
use Tests\FFMpeg\Unit\TestCase;
class VideoFiltersTest extends TestCase
{
/**
* @dataProvider provideResizeOptions
*/
public function testResize($mode, $forceStandards)
{
$capturedFilter = null;
$video = $this->getVideoMock();
$filters = new VideoFilters($video);
$dimension = $this->getDimensionMock();
$video->expects($this->once())
->method('addFilter')
->with($this->isInstanceOf('FFMpeg\Filters\Video\ResizeFilter'))
->will($this->returnCallback(function ($filter) use (&$capturedFilter) {
$capturedFilter = $filter;
}));
$filters->resize($dimension, $mode, $forceStandards);
$this->assertSame($mode, $capturedFilter->getMode());
$this->assertSame($forceStandards, $capturedFilter->areStandardsForced());
$this->assertSame($dimension, $capturedFilter->getDimension());
}
public function provideResizeOptions()
{
return [
[ResizeFilter::RESIZEMODE_FIT, true],
[ResizeFilter::RESIZEMODE_SCALE_WIDTH, true],
[ResizeFilter::RESIZEMODE_SCALE_HEIGHT, false],
[ResizeFilter::RESIZEMODE_INSET, false],
];
}
public function testResample()
{
$capturedFilter = null;
$video = $this->getVideoMock();
$filters = new VideoFilters($video);
$framerate = $this->getFramerateMock();
$gop = 42;
$video->expects($this->once())
->method('addFilter')
->with($this->isInstanceOf('FFMpeg\Filters\Video\FrameRateFilter'))
->will($this->returnCallback(function ($filter) use (&$capturedFilter) {
$capturedFilter = $filter;
}));
$filters->framerate($framerate, $gop);
$this->assertSame($framerate, $capturedFilter->getFramerate());
$this->assertSame($gop, $capturedFilter->getGOP());
}
public function testSynchronize()
{
$video = $this->getVideoMock();
$filters = new VideoFilters($video);
$video->expects($this->once())
->method('addFilter')
->with($this->isInstanceOf('FFMpeg\Filters\Video\SynchronizeFilter'));
$filters->synchronize();
}
}

View file

@ -1,62 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\Filters\Video;
use FFMpeg\FFProbe\DataMapping\Stream;
use FFMpeg\FFProbe\DataMapping\StreamCollection;
use FFMpeg\Filters\Video\WatermarkFilter;
use Tests\FFMpeg\Unit\TestCase;
class WatermarkFilterTest extends TestCase
{
public function testApplyWatermark()
{
$stream = new Stream(['width' => 320, 'height' => 240, 'codec_type' => 'video']);
$streams = new StreamCollection([$stream]);
$video = $this->getVideoMock();
$format = $this->getMockBuilder('FFMpeg\Format\VideoInterface')->getMock();
$filter = new WatermarkFilter(__DIR__.'/../../../files/watermark.png');
$this->assertEquals(['-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'));
$this->assertEquals(240, $stream->get('height'));
}
public function testDifferentCoordinaates()
{
$video = $this->getVideoMock();
$format = $this->getMockBuilder('FFMpeg\Format\VideoInterface')->getMock();
// test position absolute
$filter = new WatermarkFilter(__DIR__.'/../../../files/watermark.png', [
'position' => 'absolute',
'x' => 10, 'y' => 5,
]);
$this->assertEquals(['-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', [
'position' => 'relative',
'bottom' => 10, 'left' => 5,
]);
$this->assertEquals(['-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', [
'position' => 'relative',
'bottom' => 5, 'right' => 4,
]);
$this->assertEquals(['-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', [
'position' => 'relative',
'left' => 5, 'top' => 11,
]);
$this->assertEquals(['-vf', 'movie='.__DIR__.'/../../../files/watermark.png [watermark]; [in][watermark] overlay=5:11 [out]'], $filter->apply($video, $format));
}
}

View file

@ -1,27 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\Filters\Waveform;
use FFMpeg\FFProbe\DataMapping\Stream;
use FFMpeg\FFProbe\DataMapping\StreamCollection;
use FFMpeg\Filters\Waveform\WaveformDownmixFilter;
use FFMpeg\Media\Waveform;
use Tests\FFMpeg\Unit\TestCase;
class WaveformDownmixFilterTest extends TestCase
{
public function testApply()
{
$stream = new Stream(['codec_type' => 'audio', 'width' => 960, 'height' => 720]);
$streams = new StreamCollection([$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(['"aformat=channel_layouts=mono"'], $filter->apply($waveform));
}
}

View file

@ -1,21 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\Filters\Waveform;
use FFMpeg\Filters\Waveform\WaveformFilters;
use Tests\FFMpeg\Unit\TestCase;
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,13 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\Format\Audio;
use FFMpeg\Format\Audio\Aac;
class AacTest extends AudioTestCase
{
public function getFormat()
{
return new Aac();
}
}

View file

@ -1,118 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\Format\Audio;
use FFMpeg\Format\Audio\DefaultAudio;
use Tests\FFMpeg\Unit\TestCase;
abstract class AudioTestCase extends TestCase
{
public function testExtraParams()
{
$extraParams = $this->getFormat()->getExtraParams();
$this->assertIsArray($extraParams);
foreach ($extraParams as $param) {
$this->assertScalar($param);
}
}
public function testGetAudioCodec()
{
$this->assertScalar($this->getFormat()->getAudioCodec());
$this->assertContains($this->getFormat()->getAudioCodec(), $this->getFormat()->getAvailableAudioCodecs());
}
public function testSetAudioCodec()
{
$format = $this->getFormat();
foreach ($format->getAvailableAudioCodecs() as $codec) {
$format->setAudioCodec($codec);
$this->assertEquals($codec, $format->getAudioCodec());
}
}
public function testSetInvalidAudioCodec()
{
$this->expectException('\FFMpeg\Exception\InvalidArgumentException');
$this->getFormat()->setAudioCodec('invalid-random-audio-codec');
}
public function testGetAvailableAudioCodecs()
{
$this->assertGreaterThan(0, count($this->getFormat()->getAvailableAudioCodecs()));
}
public function testGetAudioKiloBitrate()
{
$this->assertIsInt($this->getFormat()->getAudioKiloBitrate());
}
public function testSetAudioKiloBitrate()
{
$format = $this->getFormat();
$format->setAudioKiloBitrate(256);
$this->assertEquals(256, $format->getAudioKiloBitrate());
}
public function testSetInvalidKiloBitrate()
{
$this->expectException('\FFMpeg\Exception\InvalidArgumentException');
$this->getFormat()->setAudioKiloBitrate(0);
}
public function testSetNegativeKiloBitrate()
{
$this->expectException('\FFMpeg\Exception\InvalidArgumentException');
$this->getFormat()->setAudioKiloBitrate(-10);
}
public function testGetAudioChannels()
{
$this->assertNull($this->getFormat()->getAudioChannels());
}
public function testSetAudioChannels()
{
$format = $this->getFormat();
$format->setAudioChannels(2);
$this->assertEquals(2, $format->getAudioChannels());
}
public function testSetInvalidChannels()
{
$this->expectException('\FFMpeg\Exception\InvalidArgumentException');
$this->getFormat()->setAudioChannels(0);
}
public function testSetNegativeChannels()
{
$this->expectException('\FFMpeg\Exception\InvalidArgumentException');
$this->getFormat()->setAudioChannels(-10);
}
public function testCreateProgressListener()
{
$media = $this->getMockBuilder('FFMpeg\Media\MediaTypeInterface')->getMock();
$media->expects($this->any())
->method('getPathfile')
->will($this->returnValue(__FILE__));
$format = $this->getFormat();
$ffprobe = $this->getFFProbeMock();
foreach ($format->createProgressListener($media, $ffprobe, 1, 3) as $listener) {
$this->assertInstanceOf('FFMpeg\Format\ProgressListener\AudioProgressListener', $listener);
$this->assertSame($ffprobe, $listener->getFFProbe());
$this->assertSame(__FILE__, $listener->getPathfile());
$this->assertSame(1, $listener->getCurrentPass());
$this->assertSame(3, $listener->getTotalPass());
}
}
/**
* @return DefaultAudio
*/
abstract public function getFormat();
}

View file

@ -1,13 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\Format\Audio;
use FFMpeg\Format\Audio\Flac;
class FlacTest extends AudioTestCase
{
public function getFormat()
{
return new Flac();
}
}

View file

@ -1,13 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\Format\Audio;
use FFMpeg\Format\Audio\Mp3;
class Mp3Test extends AudioTestCase
{
public function getFormat()
{
return new Mp3();
}
}

View file

@ -1,13 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\Format\Audio;
use FFMpeg\Format\Audio\Vorbis;
class VorbisTest extends AudioTestCase
{
public function getFormat()
{
return new Vorbis();
}
}

View file

@ -1,13 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\Format\Audio;
use FFMpeg\Format\Audio\Wav;
class WavTest extends AudioTestCase
{
public function getFormat()
{
return new Wav();
}
}

View file

@ -1,95 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\Format\ProgressListener;
use FFMpeg\FFProbe\DataMapping\Format;
use FFMpeg\Format\ProgressListener\AudioProgressListener;
use Tests\FFMpeg\Unit\TestCase;
class AudioProgressListenerTest extends TestCase
{
/**
* @dataProvider provideData
*/
public function testHandle(
$size,
$duration,
$data,
$expectedPercent,
$expectedRemaining,
$expectedRate,
$data2,
$expectedPercent2,
$expectedRemaining2,
$expectedRate2,
$currentPass,
$totalPass
) {
$ffprobe = $this->getFFProbeMock();
$ffprobe->expects($this->once())
->method('format')
->with(__FILE__)
->will($this->returnValue(new Format([
'size' => $size,
'duration' => $duration,
])));
$listener = new AudioProgressListener($ffprobe, __FILE__, $currentPass, $totalPass);
$phpunit = $this;
$n = 0;
$listener->on('progress', function ($percent, $remaining, $rate) use (&$n, $phpunit, $expectedPercent, $expectedRemaining, $expectedRate, $expectedPercent2, $expectedRemaining2, $expectedRate2) {
if (0 === $n) {
$phpunit->assertEquals($expectedPercent, $percent);
$phpunit->assertEquals($expectedRemaining, $remaining);
$phpunit->assertEquals($expectedRate, $rate);
} elseif (1 === $n) {
$phpunit->assertEquals($expectedPercent2, $percent);
$phpunit->assertEquals($expectedRemaining2, $remaining);
$phpunit->assertLessThan($expectedRate2 + 3, $rate);
$phpunit->assertGreaterThan($expectedRate2 - 3, $rate);
}
++$n;
});
// first one does not trigger progress event
$listener->handle('any-type'.mt_rand(), $data);
sleep(1);
$listener->handle('any-type'.mt_rand(), $data);
sleep(1);
$listener->handle('any-type'.mt_rand(), $data2);
$this->assertEquals(2, $n);
}
public function provideData()
{
return [
[
2894412,
180.900750,
'size= 712kB time=00:00:45.50 bitrate= 128.1kbits/s',
25,
0,
0,
'size= 1274kB time=00:01:29.32 bitrate= 142.8kbits/s',
49,
2,
563,
1,
1,
],
[
2894412,
180.900750,
'size= 712kB time=00:00:45.50 bitrate= 128.1kbits/s',
12,
0,
0,
'size= 1274kB time=00:01:29.32 bitrate= 142.8kbits/s',
24,
2,
563,
1,
2,
],
];
}
}

View file

@ -1,113 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\Format\ProgressListener;
use FFMpeg\FFProbe\DataMapping\Format;
use FFMpeg\Format\ProgressListener\VideoProgressListener;
use Tests\FFMpeg\Unit\TestCase;
class VideoProgressListenerTest extends TestCase
{
/**
* @dataProvider provideData
*/
public function testHandle(
$size,
$duration,
$newVideoDuration,
$data,
$expectedPercent,
$expectedRemaining,
$expectedRate,
$data2,
$expectedPercent2,
$expectedRemaining2,
$expectedRate2,
$currentPass,
$totalPass
) {
$ffprobe = $this->getFFProbeMock();
$ffprobe->expects($this->once())
->method('format')
->with(__FILE__)
->will($this->returnValue(new Format([
'size' => $size,
'duration' => $duration,
])));
$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) {
if (0 === $n) {
$phpunit->assertEquals($expectedPercent, $percent);
$phpunit->assertEquals($expectedRemaining, $remaining);
$phpunit->assertEquals($expectedRate, $rate);
} elseif (1 === $n) {
$phpunit->assertEquals($expectedPercent2, $percent);
$phpunit->assertEquals($expectedRemaining2, $remaining);
$phpunit->assertLessThan($expectedRate2 + 10, $rate);
$phpunit->assertGreaterThan($expectedRate2 - 10, $rate);
}
++$n;
});
// first one does not trigger progress event
$listener->handle('any-type'.mt_rand(), $data);
sleep(1);
$listener->handle('any-type'.mt_rand(), $data);
sleep(1);
$listener->handle('any-type'.mt_rand(), $data2);
$this->assertEquals(2, $n);
}
public function provideData()
{
return [
[
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,
0,
'frame= 854 fps=113 q=20.0 size= 4430kB time=00:00:33.04 bitrate=1098.5kbits/s dup=36 drop=0',
11,
32,
3868,
1,
1,
],
[
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,
0,
'frame= 854 fps=113 q=20.0 size= 4430kB time=00:00:33.04 bitrate=1098.5kbits/s dup=36 drop=0',
5,
32,
3868,
1,
2,
],
[
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,16 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\Format\Video;
use FFMpeg\Format\Video\X264;
use Tests\FFMpeg\Unit\TestCase;
class InitialParametersTest extends TestCase
{
public function testApplyInitialParameters()
{
$format = new X264();
$format->setInitialParameters(['-acodec', 'libopus']);
$this->assertEquals(['-acodec', 'libopus'], $format->getInitialParameters());
}
}

View file

@ -1,13 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\Format\Video;
use FFMpeg\Format\Video\Ogg;
class OggTest extends VideoTestCase
{
public function getFormat()
{
return new Ogg();
}
}

View file

@ -1,90 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\Format\Video;
use Tests\FFMpeg\Unit\Format\Audio\AudioTestCase;
abstract class VideoTestCase extends AudioTestCase
{
public function testGetVideoCodec()
{
$this->assertScalar($this->getFormat()->getVideoCodec());
$this->assertContains($this->getFormat()->getVideoCodec(), $this->getFormat()->getAvailableVideoCodecs());
}
public function testSupportBFrames()
{
$this->assertIsBool($this->getFormat()->supportBFrames());
}
public function testSetVideoCodec()
{
$format = $this->getFormat();
foreach ($format->getAvailableVideoCodecs() as $codec) {
$format->setVideoCodec($codec);
$this->assertEquals($codec, $format->getVideoCodec());
}
}
public function testGetKiloBitrate()
{
$this->assertIsInt($this->getFormat()->getKiloBitrate());
}
public function testSetKiloBitrate()
{
$format = $this->getFormat();
$format->setKiloBitrate(2560);
$this->assertEquals(2560, $format->getKiloBitrate());
}
public function testSetKiloBitrateBelowZero()
{
$this->expectException('FFMpeg\Exception\InvalidArgumentException');
$format = $this->getFormat();
$format->setKiloBitrate(-1);
}
public function testSetInvalidVideoCodec()
{
$this->expectException('FFMpeg\Exception\InvalidArgumentException');
$this->getFormat()->setVideoCodec('invalid-random-video-codec');
}
public function testGetAvailableVideoCodecs()
{
$this->assertGreaterThan(0, count($this->getFormat()->getAvailableVideoCodecs()));
}
public function testCreateProgressListener()
{
$media = $this->getMockBuilder('FFMpeg\Media\MediaTypeInterface')->getMock();
$media->expects($this->any())
->method('getPathfile')
->will($this->returnValue(__FILE__));
$format = $this->getFormat();
$ffprobe = $this->getFFProbeMock();
foreach ($format->createProgressListener($media, $ffprobe, 1, 3) as $listener) {
$this->assertInstanceOf('FFMpeg\Format\ProgressListener\VideoProgressListener', $listener);
$this->assertSame($ffprobe, $listener->getFFProbe());
$this->assertSame(__FILE__, $listener->getPathfile());
$this->assertSame(1, $listener->getCurrentPass());
$this->assertSame(3, $listener->getTotalPass());
}
}
public function testGetPasses()
{
$this->assertIsInt($this->getFormat()->getPasses());
$this->assertGreaterThan(0, $this->getFormat()->getPasses());
}
public function testGetModulus()
{
$this->assertIsInt($this->getFormat()->getModulus());
$this->assertGreaterThan(0, $this->getFormat()->getModulus());
$this->assertEquals(0, $this->getFormat()->getModulus() % 2);
}
}

View file

@ -1,13 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\Format\Video;
use FFMpeg\Format\Video\WMV3;
class WMV3Test extends VideoTestCase
{
public function getFormat()
{
return new WMV3();
}
}

View file

@ -1,13 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\Format\Video;
use FFMpeg\Format\Video\WMV;
class WMVTest extends VideoTestCase
{
public function getFormat()
{
return new WMV();
}
}

View file

@ -1,13 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\Format\Video;
use FFMpeg\Format\Video\WebM;
class WebMTest extends VideoTestCase
{
public function getFormat()
{
return new WebM();
}
}

View file

@ -1,13 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\Format\Video;
use FFMpeg\Format\Video\X264;
class X264Test extends VideoTestCase
{
public function getFormat()
{
return new X264();
}
}

View file

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

View file

@ -1,38 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\Media;
abstract class AbstractStreamableTestCase extends AbstractMediaTestCase
{
public function testGetStreams()
{
$classname = $this->getClassName();
$ffprobe = $this->getFFProbeMock();
$format = $this->getFormatMock();
$ffprobe->expects($this->once())
->method('format')
->with(__FILE__)
->will($this->returnValue($format));
$media = new $classname(__FILE__, $this->getFFMpegDriverMock(), $ffprobe);
$this->assertSame($format, $media->getFormat());
}
public function testGetFormat()
{
$classname = $this->getClassName();
$ffprobe = $this->getFFProbeMock();
$streams = $this->getStreamCollectionMock();
$ffprobe->expects($this->once())
->method('streams')
->with(__FILE__)
->will($this->returnValue($streams));
$media = new $classname(__FILE__, $this->getFFMpegDriverMock(), $ffprobe);
$this->assertSame($streams, $media->getStreams());
}
abstract protected function getClassName();
}

View file

@ -1,73 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\Media;
use FFMpeg\Media\AdvancedMedia;
class AdvancedMediaTest extends AbstractMediaTestCase
{
public function testGetInputs()
{
$driver = $this->getFFMpegDriverMock();
$ffprobe = $this->getFFProbeMock();
$advancedMedia = new AdvancedMedia([__FILE__, __FILE__], $driver, $ffprobe);
$this->assertSame([__FILE__, __FILE__], $advancedMedia->getInputs());
}
public function testGetInputsCount()
{
$driver = $this->getFFMpegDriverMock();
$ffprobe = $this->getFFProbeMock();
$advancedMedia = new AdvancedMedia([__FILE__, __FILE__], $driver, $ffprobe);
$this->assertEquals(2, $advancedMedia->getInputsCount());
}
public function testFiltersReturnFilters()
{
$driver = $this->getFFMpegDriverMock();
$ffprobe = $this->getFFProbeMock();
$advancedMedia = new AdvancedMedia([__FILE__, __FILE__], $driver, $ffprobe);
$this->assertInstanceOf('FFMpeg\Filters\AdvancedMedia\ComplexFilters', $advancedMedia->filters());
}
public function testGetTemporaryDirectoryWithoutCustomConfiguration()
{
$driver = $this->getFFMpegDriverMock();
$ffprobe = $this->getFFProbeMock();
$configuration = $this->getConfigurationMock();
$driver->expects($this->any())
->method('getConfiguration')
->will($this->returnValue($configuration));
$configuration->expects($this->once())
->method('get')
->with($this->equalTo('temporary_directory'))
->will($this->returnValue(null));
$advancedMedia = new AdvancedMedia([__FILE__, __FILE__], $driver, $ffprobe);
$this->assertEquals('', $advancedMedia->getTemporaryDirectory()->path());
}
public function testGetTemporaryDirectoryWithCustomConfiguration()
{
$driver = $this->getFFMpegDriverMock();
$ffprobe = $this->getFFProbeMock();
$configuration = $this->getConfigurationMock();
$driver->expects($this->any())
->method('getConfiguration')
->will($this->returnValue($configuration));
$configuration->expects($this->once())
->method('get')
->with($this->equalTo('temporary_directory'))
->will($this->returnValue('/var/ffmpeg'));
$advancedMedia = new AdvancedMedia([__FILE__, __FILE__], $driver, $ffprobe);
$this->assertEquals('/var/ffmpeg', $advancedMedia->getTemporaryDirectory()->path());
}
}

View file

@ -1,10 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\Media;
use FFMpeg\Format\AudioInterface;
use FFMpeg\Format\ProgressableInterface;
abstract class AudioProg implements ProgressableInterface, AudioInterface
{
}

View file

@ -1,341 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\Media;
use FFMpeg\Exception\RuntimeException;
use FFMpeg\Media\Audio;
class AudioTest extends AbstractStreamableTestCase
{
public function testFiltersReturnsAudioFilters()
{
$driver = $this->getFFMpegDriverMock();
$ffprobe = $this->getFFProbeMock();
$audio = new Audio(__FILE__, $driver, $ffprobe);
$this->assertInstanceOf('FFMpeg\Filters\Audio\AudioFilters', $audio->filters());
}
public function testAddFiltersAddsAFilter()
{
$driver = $this->getFFMpegDriverMock();
$ffprobe = $this->getFFProbeMock();
$filters = $this->getMockBuilder('FFMpeg\Filters\FiltersCollection')
->disableOriginalConstructor()
->getMock();
$audio = new Audio(__FILE__, $driver, $ffprobe);
$audio->setFiltersCollection($filters);
$filter = $this->getMockBuilder('FFMpeg\Filters\Audio\AudioFilterInterface')->getMock();
$filters->expects($this->once())
->method('add')
->with($filter);
$audio->addFilter($filter);
}
public function testAddAVideoFilterThrowsException()
{
$driver = $this->getFFMpegDriverMock();
$ffprobe = $this->getFFProbeMock();
$filters = $this->getMockBuilder('FFMpeg\Filters\FiltersCollection')
->disableOriginalConstructor()
->getMock();
$audio = new Audio(__FILE__, $driver, $ffprobe);
$audio->setFiltersCollection($filters);
$filter = $this->getMockBuilder('FFMpeg\Filters\Video\VideoFilterInterface')->getMock();
$filters->expects($this->never())
->method('add');
$this->expectException('\FFMpeg\Exception\InvalidArgumentException');
$audio->addFilter($filter);
}
public function testSaveWithFailure()
{
$driver = $this->getFFMpegDriverMock();
$ffprobe = $this->getFFProbeMock();
$outputPathfile = '/target/file';
$format = $this->getMockBuilder('FFMpeg\Format\AudioInterface')->getMock();
$format->expects($this->any())
->method('getExtraParams')
->will($this->returnValue([]));
$configuration = $this->getMockBuilder('Alchemy\BinaryDriver\ConfigurationInterface')->getMock();
$driver->expects($this->any())
->method('getConfiguration')
->will($this->returnValue($configuration));
$failure = new RuntimeException('failed to encode');
$driver->expects($this->once())
->method('command')
->will($this->throwException($failure));
$audio = new Audio(__FILE__, $driver, $ffprobe);
$this->expectException('\FFMpeg\Exception\RuntimeException');
$audio->save($format, $outputPathfile);
}
public function testSaveAppliesFilters()
{
$driver = $this->getFFMpegDriverMock();
$ffprobe = $this->getFFProbeMock();
$outputPathfile = '/target/file';
$format = $this->getMockBuilder('FFMpeg\Format\AudioInterface')->getMock();
$format->expects($this->any())
->method('getExtraParams')
->will($this->returnValue([]));
$configuration = $this->getMockBuilder('Alchemy\BinaryDriver\ConfigurationInterface')->getMock();
$driver->expects($this->any())
->method('getConfiguration')
->will($this->returnValue($configuration));
$audio = new Audio(__FILE__, $driver, $ffprobe);
$filter = $this->getMockBuilder('FFMpeg\Filters\Audio\AudioFilterInterface')->getMock();
$filter->expects($this->once())
->method('apply')
->with($audio, $format)
->will($this->returnValue(['extra-filter-command']));
$capturedCommands = [];
$driver->expects($this->once())
->method('command')
->with($this->isType('array'), false, $this->anything())
->will($this->returnCallback(function ($commands, $errors, $listeners) use (&$capturedCommands) {
$capturedCommands[] = $commands;
}));
$audio->addFilter($filter);
$audio->save($format, $outputPathfile);
foreach ($capturedCommands as $commands) {
$this->assertEquals('-y', $commands[0]);
$this->assertEquals('-i', $commands[1]);
$this->assertEquals(__FILE__, $commands[2]);
$this->assertEquals('extra-filter-command', $commands[3]);
}
}
/**
* @dataProvider provideSaveData
*/
public function testSaveShouldSave($threads, $expectedCommands, $expectedListeners, $format)
{
$driver = $this->getFFMpegDriverMock();
$ffprobe = $this->getFFProbeMock();
$configuration = $this->getMockBuilder('Alchemy\BinaryDriver\ConfigurationInterface')->getMock();
$driver->expects($this->any())
->method('getConfiguration')
->will($this->returnValue($configuration));
$configuration->expects($this->once())
->method('has')
->with($this->equalTo('ffmpeg.threads'))
->will($this->returnValue($threads));
if ($threads) {
$configuration->expects($this->once())
->method('get')
->with($this->equalTo('ffmpeg.threads'))
->will($this->returnValue(24));
} else {
$configuration->expects($this->never())
->method('get');
}
$capturedCommand = $capturedListeners = null;
$driver->expects($this->once())
->method('command')
->with($this->isType('array'), false, $this->anything())
->will($this->returnCallback(function ($commands, $errors, $listeners) use (&$capturedCommand, &$capturedListeners) {
$capturedCommand = $commands;
$capturedListeners = $listeners;
}));
$outputPathfile = '/target/file';
$audio = new Audio(__FILE__, $driver, $ffprobe);
$audio->save($format, $outputPathfile);
$this->assertEquals($expectedCommands, $capturedCommand);
$this->assertEquals($expectedListeners, $capturedListeners);
}
public function provideSaveData()
{
$format = $this->getMockBuilder('FFMpeg\Format\AudioInterface')->getMock();
$format->expects($this->any())
->method('getExtraParams')
->will($this->returnValue([]));
$format->expects($this->any())
->method('getAudioKiloBitrate')
->will($this->returnValue(663));
$format->expects($this->any())
->method('getAudioChannels')
->will($this->returnValue(5));
$audioFormat = $this->getMockBuilder('FFMpeg\Format\AudioInterface')->getMock();
$audioFormat->expects($this->any())
->method('getExtraParams')
->will($this->returnValue([]));
$audioFormat->expects($this->any())
->method('getAudioKiloBitrate')
->will($this->returnValue(664));
$audioFormat->expects($this->any())
->method('getAudioChannels')
->will($this->returnValue(5));
$audioFormat->expects($this->any())
->method('getAudioCodec')
->will($this->returnValue('patati-patata-audio'));
$formatExtra = $this->getMockBuilder('FFMpeg\Format\AudioInterface')->getMock();
$formatExtra->expects($this->any())
->method('getExtraParams')
->will($this->returnValue(['extra', 'param']));
$formatExtra->expects($this->any())
->method('getAudioKiloBitrate')
->will($this->returnValue(665));
$formatExtra->expects($this->any())
->method('getAudioChannels')
->will($this->returnValue(5));
$listeners = [$this->getMockBuilder('Alchemy\BinaryDriver\Listeners\ListenerInterface')->getMock()];
$progressableFormat = $this->getMockBuilder('Tests\FFMpeg\Unit\Media\AudioProg')
->disableOriginalConstructor()->getMock();
$progressableFormat->expects($this->any())
->method('getExtraParams')
->will($this->returnValue([]));
$progressableFormat->expects($this->any())
->method('createProgressListener')
->will($this->returnValue($listeners));
$progressableFormat->expects($this->any())
->method('getAudioKiloBitrate')
->will($this->returnValue(666));
$progressableFormat->expects($this->any())
->method('getAudioChannels')
->will($this->returnValue(5));
return [
[false, [
'-y', '-i', __FILE__,
'-b:a', '663k',
'-ac', '5',
'/target/file',
], null, $format],
[false, [
'-y', '-i', __FILE__,
'-acodec', 'patati-patata-audio',
'-b:a', '664k',
'-ac', '5',
'/target/file',
], null, $audioFormat],
[false, [
'-y', '-i', __FILE__,
'extra', 'param',
'-b:a', '665k',
'-ac', '5',
'/target/file',
], null, $formatExtra],
[true, [
'-y', '-i', __FILE__,
'-threads', 24,
'-b:a', '663k',
'-ac', '5',
'/target/file',
], null, $format],
[true, [
'-y', '-i', __FILE__,
'extra', 'param',
'-threads', 24,
'-b:a', '665k',
'-ac', '5',
'/target/file',
], null, $formatExtra],
[false, [
'-y', '-i', __FILE__,
'-b:a', '666k',
'-ac', '5',
'/target/file',
], $listeners, $progressableFormat],
[true, [
'-y', '-i', __FILE__,
'-threads', 24,
'-b:a', '666k',
'-ac', '5',
'/target/file',
], $listeners, $progressableFormat],
];
}
public function testSaveShouldNotStoreCodecFiltersInTheMedia()
{
$driver = $this->getFFMpegDriverMock();
$ffprobe = $this->getFFProbeMock();
$configuration = $this->getMockBuilder('Alchemy\BinaryDriver\ConfigurationInterface')->getMock();
$driver->expects($this->any())
->method('getConfiguration')
->will($this->returnValue($configuration));
$configuration->expects($this->any())
->method('has')
->with($this->equalTo('ffmpeg.threads'))
->will($this->returnValue(true));
$configuration->expects($this->any())
->method('get')
->with($this->equalTo('ffmpeg.threads'))
->will($this->returnValue(24));
$capturedCommands = [];
$driver->expects($this->exactly(2))
->method('command')
->with($this->isType('array'), false, $this->anything())
->will($this->returnCallback(function ($commands, $errors, $listeners) use (&$capturedCommands, &$capturedListeners) {
$capturedCommands[] = $commands;
}));
$outputPathfile = '/target/file';
$format = $this->getMockBuilder('FFMpeg\Format\AudioInterface')->getMock();
$format->expects($this->any())
->method('getExtraParams')
->will($this->returnValue(['param']));
$audio = new Audio(__FILE__, $driver, $ffprobe);
$audio->save($format, $outputPathfile);
$audio->save($format, $outputPathfile);
$expected = [
'-y', '-i', __FILE__, 'param', '-threads', 24, '/target/file',
];
foreach ($capturedCommands as $capturedCommand) {
$this->assertEquals($expected, $capturedCommand);
}
}
public function getClassName()
{
return 'FFMpeg\Media\Audio';
}
}

View file

@ -1,67 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\Media;
use FFMpeg\Media\Clip;
class ClipTest extends AbstractMediaTestCase
{
/**
* @dataProvider provideBuildOptions
*/
public function testBuildCommand($startValue, $durationValue, $commands)
{
$configuration = $this->getConfigurationMock();
$driver = $this->getFFMpegDriverMock();
$driver->expects($this->any())
->method('getConfiguration')
->will($this->returnValue($configuration));
$ffprobe = $this->getFFProbeMock();
$start = $this->getTimeCodeMock();
$start->expects($this->once())
->method('__toString')
->will($this->returnValue($startValue));
$duration = null;
if (null !== $durationValue) {
$duration = $this->getTimeCodeMock();
$duration->expects($this->once())
->method('__toString')
->will($this->returnValue($durationValue));
}
$outputPathfile = '/target/file';
$format = $this->getMockBuilder('FFMpeg\Format\VideoInterface')->getMock();
$format->expects($this->any())
->method('getPasses')
->will($this->returnValue(1));
$format->expects($this->any())
->method('getExtraParams')
->will($this->returnValue([]));
$clip = new Clip($this->getVideoMock(__FILE__), $driver, $ffprobe, $start, $duration);
$fc = $clip->getFinalCommand($format, $outputPathfile);
$this->assertCount(1, $fc);
$this->assertStringStartsWith(implode(' ', $commands), $fc[0]);
}
public function provideBuildOptions()
{
return [
['SS01', null, [
'-y', '-ss', 'SS01',
'-i', __FILE__, ],
],
['SS02', 'D02', [
'-y', '-ss', 'SS02',
'-i', __FILE__,
'-t', 'D02', ],
],
];
}
}

View file

@ -1,161 +0,0 @@
<?php
namespace Tests\FFMpeg\Unit\Media;
use FFMpeg\Media\Concat;
use Mockery;
use Spatie\TemporaryDirectory\TemporaryDirectory;
class ConcatTest extends AbstractMediaTestCase
{
public function testGetSources()
{
$driver = $this->getFFMpegDriverMock();
$ffprobe = $this->getFFProbeMock();
$concat = new Concat([__FILE__, __FILE__], $driver, $ffprobe);
$this->assertSame([__FILE__, __FILE__], $concat->getSources());
}
public function testFiltersReturnFilters()
{
$driver = $this->getFFMpegDriverMock();
$ffprobe = $this->getFFProbeMock();
$concat = new Concat([__FILE__, __FILE__], $driver, $ffprobe);
$this->assertInstanceOf('FFMpeg\Filters\Concat\ConcatFilters', $concat->filters());
}
public function testAddFiltersAddsAFilter()
{
$driver = $this->getFFMpegDriverMock();
$ffprobe = $this->getFFProbeMock();
$filters = $this->getMockBuilder('FFMpeg\Filters\FiltersCollection')
->disableOriginalConstructor()
->getMock();
$filter = $this->getMockBuilder('FFMpeg\Filters\Concat\ConcatFilterInterface')->getMock();
$filters->expects($this->once())
->method('add')
->with($filter);
$concat = new Concat([__FILE__, __FILE__], $driver, $ffprobe);
$concat->setFiltersCollection($filters);
$concat->addFilter($filter);
}
/**
* @dataProvider provideSaveFromSameCodecsOptions
*/
public function testSaveFromSameCodecs($streamCopy, $commands)
{
$driver = $this->getFFMpegDriverMock();
$ffprobe = $this->getFFProbeMock();
$pathfile = '/target/destination';
array_push($commands, $pathfile);
$configuration = Mockery::mock('Alchemy\BinaryDriver\ConfigurationInterface');
$driver->expects($this->any())
->method('getConfiguration')
->will($this->returnValue($configuration));
$configuration->shouldReceive('get')
->once()
->with('temporary_directory')
->andReturnNull();
$driver->expects($this->exactly(1))
->method('command')
->with($this->isType('array'), false, $this->anything())
->will($this->returnCallback(function ($commands, $errors, $listeners) {
}));
$concat = new Concat([__FILE__, 'concat-2.mp4'], $driver, $ffprobe);
$concat->saveFromSameCodecs($pathfile, $streamCopy);
$this->assertEquals('-f', $commands[0]);
$this->assertEquals('concat', $commands[1]);
$this->assertEquals('-safe', $commands[2]);
$this->assertEquals('0', $commands[3]);
$this->assertEquals('-i', $commands[4]);
if (isset($commands[6]) && (0 == strcmp($commands[6], '-c'))) {
$this->assertEquals('-c', $commands[6]);
$this->assertEquals('copy', $commands[7]);
}
}
public function provideSaveFromSameCodecsOptions()
{
$fs = (new TemporaryDirectory())->create();
$tmpFile = $fs->path('ffmpeg-concat');
touch($tmpFile);
return [
[
true,
[
'-f', 'concat',
'-safe', '0',
'-i', $tmpFile,
'-c', 'copy',
],
],
[
false,
[
'-f', 'concat',
'-safe', '0',
'-i', $tmpFile,
],
],
];
}
/**
* @dataProvider provideSaveFromDifferentCodecsOptions
*/
public function testSaveFromDifferentCodecs($commands)
{
$driver = $this->getFFMpegDriverMock();
$ffprobe = $this->getFFProbeMock();
$format = $this->getFormatInterfaceMock();
$pathfile = '/target/destination';
array_push($commands, $pathfile);
$configuration = $this->getMockBuilder('Alchemy\BinaryDriver\ConfigurationInterface')->getMock();
$driver->expects($this->any())
->method('getConfiguration')
->will($this->returnValue($configuration));
$driver->expects($this->once())
->method('command')
->with($commands);
$concat = new Concat([__FILE__, 'concat-2.mp4'], $driver, $ffprobe);
$this->assertSame($concat, $concat->saveFromDifferentCodecs($format, $pathfile));
}
public function provideSaveFromDifferentCodecsOptions()
{
return [
[
[
'-i', __FILE__,
'-i', 'concat-2.mp4',
'-filter_complex',
'[0:v:0] [0:a:0] [1:v:0] [1:a:0] concat=n=2:v=1:a=1 [v] [a]',
'-map', '[v]',
'-map', '[a]',
],
],
];
}
}

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