diff --git a/composer.json b/composer.json
index 3e1a5bb..906606a 100644
--- a/composer.json
+++ b/composer.json
@@ -17,25 +17,23 @@
}
],
"require": {
- "php" : ">=5.3.3",
- "symfony/process" : "~2.1",
- "monolog/monolog" : "~1.0"
+ "php" : ">=5.3.3",
+ "alchemy/binary-driver" : "~1.5",
+ "doctrine/cache" : "~1.0",
+ "evenement/evenement" : "~1.0",
+ "symfony/process" : "~2.0"
},
"suggest": {
- "php-ffmpeg/extras": "A compilation of common audio & video drivers for PHP-FFMpeg"
+ "php-ffmpeg/extras" : "A compilation of common audio & video drivers for PHP-FFMpeg"
},
"require-dev": {
- "sami/sami" : "~1.0",
- "silex/silex" : "~1.0"
+ "sami/sami" : "dev-master@dev",
+ "silex/silex" : "~1.0",
+ "phpunit/phpunit" : "~3.7"
},
"autoload": {
"psr-0": {
"FFMpeg": "src"
}
- },
- "extra": {
- "branch-alias": {
- "dev-master": "0.3-dev"
- }
}
}
diff --git a/composer.lock b/composer.lock
index 4429cb6..97993c9 100644
--- a/composer.lock
+++ b/composer.lock
@@ -3,8 +3,166 @@
"This file locks the dependencies of your project to a known state",
"Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file"
],
- "hash": "531fb2f99e17d12b1e8edc8957c170d7",
+ "hash": "336691561433798ee6f95d41970f1eae",
"packages": [
+ {
+ "name": "alchemy/binary-driver",
+ "version": "1.5.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/alchemy-fr/BinaryDriver.git",
+ "reference": "1.5.0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/alchemy-fr/BinaryDriver/zipball/1.5.0",
+ "reference": "1.5.0",
+ "shasum": ""
+ },
+ "require": {
+ "evenement/evenement": ">=1.0,<2.0",
+ "monolog/monolog": ">=1.3,<2.0",
+ "php": ">=5.3.3",
+ "psr/log": ">=1.0,<2.0",
+ "symfony/process": ">=2.0,<3.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": ">=3.7,<4.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-0": {
+ "Alchemy": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Romain Neutron",
+ "email": "imprec@gmail.com",
+ "homepage": "http://www.lickmychip.com/"
+ },
+ {
+ "name": "Nicolas Le Goff",
+ "email": "legoff.n@gmail.com"
+ },
+ {
+ "name": "Phraseanet Team",
+ "email": "info@alchemy.fr",
+ "homepage": "http://www.phraseanet.com/"
+ }
+ ],
+ "description": "A set of tools to build binary drivers",
+ "keywords": [
+ "binary",
+ "driver"
+ ],
+ "time": "2013-06-21 15:51:20"
+ },
+ {
+ "name": "doctrine/cache",
+ "version": "v1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/cache.git",
+ "reference": "v1.0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://github.com/doctrine/cache/archive/v1.0.zip",
+ "reference": "v1.0",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.2"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-0": {
+ "Doctrine\\Common\\Cache\\": "lib/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jonathan Wage",
+ "email": "jonwage@gmail.com",
+ "homepage": "http://www.jwage.com/"
+ },
+ {
+ "name": "Guilherme Blanco",
+ "email": "guilhermeblanco@gmail.com",
+ "homepage": "http://www.instaclick.com"
+ },
+ {
+ "name": "Roman Borschel",
+ "email": "roman@code-factory.org"
+ },
+ {
+ "name": "Benjamin Eberlei",
+ "email": "kontakt@beberlei.de"
+ },
+ {
+ "name": "Johannes Schmitt",
+ "email": "schmittjoh@gmail.com",
+ "homepage": "http://jmsyst.com",
+ "role": "Developer of wrapped JMSSerializerBundle"
+ }
+ ],
+ "description": "Caching library offering an object-oriented API for many cache backends",
+ "homepage": "http://www.doctrine-project.org",
+ "keywords": [
+ "cache",
+ "caching"
+ ],
+ "time": "2013-01-10 22:43:46"
+ },
+ {
+ "name": "evenement/evenement",
+ "version": "v1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/igorw/evenement",
+ "reference": "v1.0.0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://github.com/igorw/evenement/zipball/v1.0.0",
+ "reference": "v1.0.0",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-0": {
+ "Evenement": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Igor Wiedler",
+ "email": "igor@wiedler.ch",
+ "homepage": "http://wiedler.ch/igor/"
+ }
+ ],
+ "description": "Événement is a very simple event dispatching library for PHP 5.3",
+ "keywords": [
+ "event-dispatcher"
+ ],
+ "time": "2012-05-30 08:01:08"
+ },
{
"name": "monolog/monolog",
"version": "1.5.0",
@@ -107,17 +265,17 @@
},
{
"name": "symfony/process",
- "version": "v2.2.1",
+ "version": "v2.3.1",
"target-dir": "Symfony/Component/Process",
"source": {
"type": "git",
"url": "https://github.com/symfony/Process.git",
- "reference": "v2.2.1"
+ "reference": "v2.3.1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/Process/zipball/v2.2.1",
- "reference": "v2.2.1",
+ "url": "https://api.github.com/repos/symfony/Process/zipball/v2.3.1",
+ "reference": "v2.3.1",
"shasum": ""
},
"require": {
@@ -126,7 +284,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.2-dev"
+ "dev-master": "2.3-dev"
}
},
"autoload": {
@@ -150,10 +308,64 @@
],
"description": "Symfony Process Component",
"homepage": "http://symfony.com",
- "time": "2013-03-23 07:49:54"
+ "time": "2013-05-06 20:03:44"
}
],
"packages-dev": [
+ {
+ "name": "dflydev/markdown",
+ "version": "v1.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/dflydev/dflydev-markdown.git",
+ "reference": "v1.0.2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://github.com/dflydev/dflydev-markdown/zipball/v1.0.2",
+ "reference": "v1.0.2",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-0": {
+ "dflydev\\markdown": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "New BSD License"
+ ],
+ "authors": [
+ {
+ "name": "Dragonfly Development Inc.",
+ "email": "info@dflydev.com",
+ "homepage": "http://dflydev.com"
+ },
+ {
+ "name": "Beau Simensen",
+ "email": "beau@dflydev.com",
+ "homepage": "http://beausimensen.com"
+ },
+ {
+ "name": "Michel Fortin",
+ "homepage": "http://michelf.com"
+ },
+ {
+ "name": "John Gruber",
+ "homepage": "http://daringfireball.net"
+ }
+ ],
+ "description": "PHP Markdown & Extra",
+ "homepage": "http://github.com/dflydev/dflydev-markdown",
+ "keywords": [
+ "markdown"
+ ],
+ "time": "2012-01-15 19:36:37"
+ },
{
"name": "nikic/php-parser",
"version": "v0.9.3",
@@ -193,6 +405,363 @@
],
"time": "2012-11-22 18:54:05"
},
+ {
+ "name": "phpunit/php-code-coverage",
+ "version": "1.2.11",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
+ "reference": "1.2.11"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/1.2.11",
+ "reference": "1.2.11",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3",
+ "phpunit/php-file-iterator": ">=1.3.0@stable",
+ "phpunit/php-text-template": ">=1.1.1@stable",
+ "phpunit/php-token-stream": ">=1.1.3@stable"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "3.7.*"
+ },
+ "suggest": {
+ "ext-dom": "*",
+ "ext-xdebug": ">=2.0.5"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "PHP/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "include-path": [
+ ""
+ ],
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sb@sebastian-bergmann.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
+ "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
+ "keywords": [
+ "coverage",
+ "testing",
+ "xunit"
+ ],
+ "time": "2013-05-23 18:23:24"
+ },
+ {
+ "name": "phpunit/php-file-iterator",
+ "version": "1.3.3",
+ "source": {
+ "type": "git",
+ "url": "git://github.com/sebastianbergmann/php-file-iterator.git",
+ "reference": "1.3.3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://github.com/sebastianbergmann/php-file-iterator/zipball/1.3.3",
+ "reference": "1.3.3",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "File/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "include-path": [
+ ""
+ ],
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sb@sebastian-bergmann.de",
+ "role": "lead"
+ }
+ ],
+ "description": "FilterIterator implementation that filters files based on a list of suffixes.",
+ "homepage": "http://www.phpunit.de/",
+ "keywords": [
+ "filesystem",
+ "iterator"
+ ],
+ "time": "2012-10-11 04:44:38"
+ },
+ {
+ "name": "phpunit/php-text-template",
+ "version": "1.1.4",
+ "source": {
+ "type": "git",
+ "url": "git://github.com/sebastianbergmann/php-text-template.git",
+ "reference": "1.1.4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://github.com/sebastianbergmann/php-text-template/zipball/1.1.4",
+ "reference": "1.1.4",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "Text/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "include-path": [
+ ""
+ ],
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sb@sebastian-bergmann.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Simple template engine.",
+ "homepage": "https://github.com/sebastianbergmann/php-text-template/",
+ "keywords": [
+ "template"
+ ],
+ "time": "2012-10-31 11:15:28"
+ },
+ {
+ "name": "phpunit/php-timer",
+ "version": "1.0.4",
+ "source": {
+ "type": "git",
+ "url": "git://github.com/sebastianbergmann/php-timer.git",
+ "reference": "1.0.4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://github.com/sebastianbergmann/php-timer/zipball/1.0.4",
+ "reference": "1.0.4",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "PHP/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "include-path": [
+ ""
+ ],
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sb@sebastian-bergmann.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Utility class for timing",
+ "homepage": "http://www.phpunit.de/",
+ "keywords": [
+ "timer"
+ ],
+ "time": "2012-10-11 04:45:58"
+ },
+ {
+ "name": "phpunit/php-token-stream",
+ "version": "1.1.5",
+ "source": {
+ "type": "git",
+ "url": "git://github.com/sebastianbergmann/php-token-stream.git",
+ "reference": "1.1.5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://github.com/sebastianbergmann/php-token-stream/zipball/1.1.5",
+ "reference": "1.1.5",
+ "shasum": ""
+ },
+ "require": {
+ "ext-tokenizer": "*",
+ "php": ">=5.3.3"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "PHP/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "include-path": [
+ ""
+ ],
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sb@sebastian-bergmann.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Wrapper around PHP's tokenizer extension.",
+ "homepage": "http://www.phpunit.de/",
+ "keywords": [
+ "tokenizer"
+ ],
+ "time": "2012-10-11 04:47:14"
+ },
+ {
+ "name": "phpunit/phpunit",
+ "version": "3.7.21",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/phpunit.git",
+ "reference": "3.7.21"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3.7.21",
+ "reference": "3.7.21",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-pcre": "*",
+ "ext-reflection": "*",
+ "ext-spl": "*",
+ "php": ">=5.3.3",
+ "phpunit/php-code-coverage": ">=1.2.1,<1.3.0",
+ "phpunit/php-file-iterator": ">=1.3.1",
+ "phpunit/php-text-template": ">=1.1.1",
+ "phpunit/php-timer": ">=1.0.2,<1.1.0",
+ "phpunit/phpunit-mock-objects": ">=1.2.0,<1.3.0",
+ "symfony/yaml": ">=2.0,<3.0"
+ },
+ "require-dev": {
+ "pear-pear/pear": "1.9.4"
+ },
+ "suggest": {
+ "ext-json": "*",
+ "ext-simplexml": "*",
+ "ext-tokenizer": "*",
+ "phpunit/php-invoker": ">=1.1.0,<1.2.0"
+ },
+ "bin": [
+ "composer/bin/phpunit"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.7.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "PHPUnit/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "include-path": [
+ "",
+ "../../symfony/yaml/"
+ ],
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "The PHP Unit Testing framework.",
+ "homepage": "http://www.phpunit.de/",
+ "keywords": [
+ "phpunit",
+ "testing",
+ "xunit"
+ ],
+ "time": "2013-05-23 18:54:29"
+ },
+ {
+ "name": "phpunit/phpunit-mock-objects",
+ "version": "1.2.3",
+ "source": {
+ "type": "git",
+ "url": "git://github.com/sebastianbergmann/phpunit-mock-objects.git",
+ "reference": "1.2.3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://github.com/sebastianbergmann/phpunit-mock-objects/archive/1.2.3.zip",
+ "reference": "1.2.3",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3",
+ "phpunit/php-text-template": ">=1.1.1@stable"
+ },
+ "suggest": {
+ "ext-soap": "*"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "PHPUnit/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "include-path": [
+ ""
+ ],
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sb@sebastian-bergmann.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Mock Object library for PHPUnit",
+ "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/",
+ "keywords": [
+ "mock",
+ "xunit"
+ ],
+ "time": "2013-01-13 10:24:48"
+ },
{
"name": "pimple/pimple",
"version": "v1.0.2",
@@ -241,19 +810,20 @@
},
{
"name": "sami/sami",
- "version": "v1.0",
+ "version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/fabpot/Sami.git",
- "reference": "v1.0"
+ "reference": "9503a81e0b505be1cd9f7eec93e0da8b2615c211"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/fabpot/Sami/zipball/v1.0",
- "reference": "v1.0",
+ "url": "https://api.github.com/repos/fabpot/Sami/zipball/9503a81e0b505be1cd9f7eec93e0da8b2615c211",
+ "reference": "9503a81e0b505be1cd9f7eec93e0da8b2615c211",
"shasum": ""
},
"require": {
+ "dflydev/markdown": "1.0.*",
"nikic/php-parser": "0.9.*",
"php": ">=5.3.0",
"pimple/pimple": "1.0.*",
@@ -293,7 +863,7 @@
"keywords": [
"phpdoc"
],
- "time": "2013-04-05 13:01:32"
+ "time": "2013-06-02 12:00:43"
},
{
"name": "silex/silex",
@@ -379,26 +949,32 @@
},
{
"name": "symfony/console",
- "version": "v2.2.1",
+ "version": "v2.3.1",
"target-dir": "Symfony/Component/Console",
"source": {
"type": "git",
"url": "https://github.com/symfony/Console.git",
- "reference": "v2.2.1"
+ "reference": "v2.3.1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/Console/zipball/v2.2.1",
- "reference": "v2.2.1",
+ "url": "https://api.github.com/repos/symfony/Console/zipball/v2.3.1",
+ "reference": "v2.3.1",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
+ "require-dev": {
+ "symfony/event-dispatcher": ">=2.1,<3.0"
+ },
+ "suggest": {
+ "symfony/event-dispatcher": ""
+ },
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.2-dev"
+ "dev-master": "2.3-dev"
}
},
"autoload": {
@@ -422,21 +998,77 @@
],
"description": "Symfony Console Component",
"homepage": "http://symfony.com",
- "time": "2013-03-19 20:48:08"
+ "time": "2013-06-11 07:15:14"
+ },
+ {
+ "name": "symfony/debug",
+ "version": "v2.3.1",
+ "target-dir": "Symfony/Component/Debug",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/Debug.git",
+ "reference": "v2.3.1"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/Debug/zipball/v2.3.1",
+ "reference": "v2.3.1",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "symfony/http-foundation": ">=2.1,<3.0",
+ "symfony/http-kernel": ">=2.1,<3.0"
+ },
+ "suggest": {
+ "symfony/class-loader": "",
+ "symfony/http-foundation": "",
+ "symfony/http-kernel": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.3-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Symfony\\Component\\Debug\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "http://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Debug Component",
+ "homepage": "http://symfony.com",
+ "time": "2013-06-02 11:58:44"
},
{
"name": "symfony/event-dispatcher",
- "version": "v2.2.1",
+ "version": "v2.3.1",
"target-dir": "Symfony/Component/EventDispatcher",
"source": {
"type": "git",
"url": "https://github.com/symfony/EventDispatcher.git",
- "reference": "v2.2.1"
+ "reference": "v2.3.1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/EventDispatcher/zipball/v2.2.1",
- "reference": "v2.2.1",
+ "url": "https://api.github.com/repos/symfony/EventDispatcher/zipball/v2.3.1",
+ "reference": "v2.3.1",
"shasum": ""
},
"require": {
@@ -446,13 +1078,13 @@
"symfony/dependency-injection": ">=2.0,<3.0"
},
"suggest": {
- "symfony/dependency-injection": "2.2.*",
- "symfony/http-kernel": "2.2.*"
+ "symfony/dependency-injection": "",
+ "symfony/http-kernel": ""
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.2-dev"
+ "dev-master": "2.3-dev"
}
},
"autoload": {
@@ -476,21 +1108,21 @@
],
"description": "Symfony EventDispatcher Component",
"homepage": "http://symfony.com",
- "time": "2013-02-11 11:26:43"
+ "time": "2013-05-13 14:36:40"
},
{
"name": "symfony/filesystem",
- "version": "v2.2.1",
+ "version": "v2.3.1",
"target-dir": "Symfony/Component/Filesystem",
"source": {
"type": "git",
"url": "https://github.com/symfony/Filesystem.git",
- "reference": "v2.2.1"
+ "reference": "v2.3.1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/Filesystem/zipball/v2.2.1",
- "reference": "v2.2.1",
+ "url": "https://api.github.com/repos/symfony/Filesystem/zipball/v2.3.1",
+ "reference": "v2.3.1",
"shasum": ""
},
"require": {
@@ -499,7 +1131,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.2-dev"
+ "dev-master": "2.3-dev"
}
},
"autoload": {
@@ -523,21 +1155,21 @@
],
"description": "Symfony Filesystem Component",
"homepage": "http://symfony.com",
- "time": "2013-01-17 15:25:59"
+ "time": "2013-06-04 15:02:05"
},
{
"name": "symfony/finder",
- "version": "v2.2.1",
+ "version": "v2.3.1",
"target-dir": "Symfony/Component/Finder",
"source": {
"type": "git",
"url": "https://github.com/symfony/Finder.git",
- "reference": "v2.2.1"
+ "reference": "v2.3.1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/Finder/zipball/v2.2.1",
- "reference": "v2.2.1",
+ "url": "https://api.github.com/repos/symfony/Finder/zipball/v2.3.1",
+ "reference": "v2.3.1",
"shasum": ""
},
"require": {
@@ -546,7 +1178,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.2-dev"
+ "dev-master": "2.3-dev"
}
},
"autoload": {
@@ -570,21 +1202,21 @@
],
"description": "Symfony Finder Component",
"homepage": "http://symfony.com",
- "time": "2013-04-01 07:51:50"
+ "time": "2013-06-02 12:05:51"
},
{
"name": "symfony/http-foundation",
- "version": "v2.2.1",
+ "version": "v2.3.1",
"target-dir": "Symfony/Component/HttpFoundation",
"source": {
"type": "git",
"url": "https://github.com/symfony/HttpFoundation.git",
- "reference": "v2.2.1"
+ "reference": "v2.3.1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/HttpFoundation/zipball/v2.2.1",
- "reference": "v2.2.1",
+ "url": "https://api.github.com/repos/symfony/HttpFoundation/zipball/v2.3.1",
+ "reference": "v2.3.1",
"shasum": ""
},
"require": {
@@ -593,7 +1225,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.2-dev"
+ "dev-master": "2.3-dev"
}
},
"autoload": {
@@ -620,28 +1252,29 @@
],
"description": "Symfony HttpFoundation Component",
"homepage": "http://symfony.com",
- "time": "2013-04-06 10:15:43"
+ "time": "2013-05-10 06:00:03"
},
{
"name": "symfony/http-kernel",
- "version": "v2.2.1",
+ "version": "v2.3.1",
"target-dir": "Symfony/Component/HttpKernel",
"source": {
"type": "git",
"url": "https://github.com/symfony/HttpKernel.git",
- "reference": "v2.2.1"
+ "reference": "v2.3.1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/HttpKernel/zipball/v2.2.1",
- "reference": "v2.2.1",
+ "url": "https://api.github.com/repos/symfony/HttpKernel/zipball/v2.3.1",
+ "reference": "v2.3.1",
"shasum": ""
},
"require": {
"php": ">=5.3.3",
"psr/log": ">=1.0,<2.0",
+ "symfony/debug": ">=2.3,<3.0",
"symfony/event-dispatcher": ">=2.1,<3.0",
- "symfony/http-foundation": ">=2.2,<2.3-dev"
+ "symfony/http-foundation": ">=2.2,<3.0"
},
"require-dev": {
"symfony/browser-kit": "2.2.*",
@@ -651,21 +1284,21 @@
"symfony/dependency-injection": ">=2.0,<3.0",
"symfony/finder": ">=2.0,<3.0",
"symfony/process": ">=2.0,<3.0",
- "symfony/routing": ">=2.2,<2.3-dev",
- "symfony/stopwatch": ">=2.2,<2.3-dev"
+ "symfony/routing": ">=2.2,<3.0",
+ "symfony/stopwatch": ">=2.2,<3.0"
},
"suggest": {
- "symfony/browser-kit": "2.2.*",
- "symfony/class-loader": "2.2.*",
- "symfony/config": "2.2.*",
- "symfony/console": "2.2.*",
- "symfony/dependency-injection": "2.2.*",
- "symfony/finder": "2.2.*"
+ "symfony/browser-kit": "",
+ "symfony/class-loader": "",
+ "symfony/config": "",
+ "symfony/console": "",
+ "symfony/dependency-injection": "",
+ "symfony/finder": ""
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.2-dev"
+ "dev-master": "2.3-dev"
}
},
"autoload": {
@@ -689,21 +1322,21 @@
],
"description": "Symfony HttpKernel Component",
"homepage": "http://symfony.com",
- "time": "2013-04-06 10:16:33"
+ "time": "2013-06-11 11:46:38"
},
{
"name": "symfony/routing",
- "version": "v2.2.1",
+ "version": "v2.3.1",
"target-dir": "Symfony/Component/Routing",
"source": {
"type": "git",
"url": "https://github.com/symfony/Routing.git",
- "reference": "v2.2.1"
+ "reference": "v2.3.1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/Routing/zipball/v2.2.1",
- "reference": "v2.2.1",
+ "url": "https://api.github.com/repos/symfony/Routing/zipball/v2.3.1",
+ "reference": "v2.3.1",
"shasum": ""
},
"require": {
@@ -712,18 +1345,18 @@
"require-dev": {
"doctrine/common": ">=2.2,<3.0",
"psr/log": ">=1.0,<2.0",
- "symfony/config": ">=2.2,<2.3-dev",
+ "symfony/config": ">=2.2,<3.0",
"symfony/yaml": ">=2.0,<3.0"
},
"suggest": {
- "doctrine/common": "~2.2",
- "symfony/config": "2.2.*",
- "symfony/yaml": "2.2.*"
+ "doctrine/common": "",
+ "symfony/config": "",
+ "symfony/yaml": ""
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.2-dev"
+ "dev-master": "2.3-dev"
}
},
"autoload": {
@@ -747,21 +1380,21 @@
],
"description": "Symfony Routing Component",
"homepage": "http://symfony.com",
- "time": "2013-03-23 12:03:22"
+ "time": "2013-05-20 08:57:26"
},
{
"name": "symfony/yaml",
- "version": "v2.2.1",
+ "version": "v2.3.1",
"target-dir": "Symfony/Component/Yaml",
"source": {
"type": "git",
"url": "https://github.com/symfony/Yaml.git",
- "reference": "v2.2.1"
+ "reference": "v2.3.1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/Yaml/zipball/v2.2.1",
- "reference": "v2.2.1",
+ "url": "https://api.github.com/repos/symfony/Yaml/zipball/v2.3.1",
+ "reference": "v2.3.1",
"shasum": ""
},
"require": {
@@ -770,7 +1403,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.2-dev"
+ "dev-master": "2.3-dev"
}
},
"autoload": {
@@ -794,20 +1427,20 @@
],
"description": "Symfony Yaml Component",
"homepage": "http://symfony.com",
- "time": "2013-03-23 07:49:54"
+ "time": "2013-05-10 18:12:13"
},
{
"name": "twig/twig",
- "version": "v1.13.0",
+ "version": "v1.13.1",
"source": {
"type": "git",
"url": "https://github.com/fabpot/Twig.git",
- "reference": "v1.13.0"
+ "reference": "v1.13.1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/fabpot/Twig/zipball/v1.13.0",
- "reference": "v1.13.0",
+ "url": "https://api.github.com/repos/fabpot/Twig/zipball/v1.13.1",
+ "reference": "v1.13.1",
"shasum": ""
},
"require": {
@@ -843,16 +1476,16 @@
"keywords": [
"templating"
],
- "time": "2013-05-10 15:12:43"
+ "time": "2013-06-06 06:06:01"
}
],
"aliases": [
],
"minimum-stability": "stable",
- "stability-flags": [
-
- ],
+ "stability-flags": {
+ "sami/sami": 20
+ },
"platform": {
"php": ">=5.3.3"
},
diff --git a/phpunit-functional.xml.dist b/phpunit-functional.xml.dist
new file mode 100644
index 0000000..3f0f738
--- /dev/null
+++ b/phpunit-functional.xml.dist
@@ -0,0 +1,27 @@
+
+
+
+
+ tests/FFMpeg/Functional
+
+
+
+
+ vendor
+ tests
+
+
+
+
+
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 4e55c40..f36c568 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -9,20 +9,11 @@
stopOnFailure="false"
syntaxCheck="true"
verbose="false"
- bootstrap="vendor/autoload.php"
+ bootstrap="tests/bootstrap.php"
>
-
-
-
-
-
-
-
- tests
+ tests/FFMpeg/Tests
diff --git a/src/FFMpeg/Binary.php b/src/FFMpeg/Binary.php
deleted file mode 100644
index 0ede05f..0000000
--- a/src/FFMpeg/Binary.php
+++ /dev/null
@@ -1,128 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace FFMpeg;
-
-use FFMpeg\Exception\BinaryNotFoundException;
-use FFMpeg\Exception\InvalidArgumentException;
-use Monolog\Logger;
-use Symfony\Component\Process\ExecutableFinder;
-
-/**
- * Binary abstract class
- *
- * @author Romain Neutron imprec@gmail.com
- */
-abstract class Binary implements AdapterInterface
-{
- protected $binary;
-
- /**
- *
- * @var Logger
- */
- protected $logger;
-
- /**
- * @var Integer
- */
- protected $timeout;
-
- /**
- * Binary constructor
- *
- * @param type $binary The path file to the binary
- * @param Logger $logger A logger
- * @param Integer $timeout The timout for the underlying process, 0 means no timeout
- */
- public function __construct($binary, Logger $logger, $timeout = 60)
- {
- if (!is_executable($binary)) {
- throw new \FFMpeg\Exception\BinaryNotFoundException(sprintf('`%s` is not a valid binary', $binary));
- }
-
- $this->binary = $binary;
- $this->logger = $logger;
- $this->setTimeout($timeout);
- }
-
- /**
- * Returns the current timeout for underlying processes.
- *
- * @return integer|float
- */
- public function getTimeout()
- {
- return $this->timeout;
- }
-
- /**
- * Sets the timeout for the underlying processes, use 0 to disable timeout.
- *
- * @param integer|float $timeout
- *
- * @return Binary
- */
- public function setTimeout($timeout)
- {
- if (0 > $timeout) {
- throw new InvalidArgumentException('Timeout must be a non-negative value');
- }
-
- $this->timeout = $timeout;
-
- return $this;
- }
-
- /**
- * Destructor
- */
- public function __destruct()
- {
- $this->binary = $binary = $this->logger = null;
- }
-
- /**
- * {@inheritdoc}
- *
- * @param Logger $logger A logger
- * @param Integer $timeout The timout for the underlying process, 0 means no timeout
- *
- * @return Binary The binary
- *
- * @throws Exception\BinaryNotFoundException
- */
- public static function load(Logger $logger, $timeout = 60)
- {
- $finder = new ExecutableFinder();
- $binary = null;
-
- foreach (static::getBinaryName() as $candidate) {
- if (null !== $binary = $finder->find($candidate)) {
- break;
- }
- }
-
- if (null === $binary) {
- throw new BinaryNotFoundException('Binary not found');
- }
-
- return new static($binary, $logger, $timeout);
- }
-
- /**
- * Return the binary name
- */
- protected static function getBinaryName()
- {
- throw new \Exception('Should be implemented');
- }
-}
diff --git a/src/FFMpeg/Coordinate/AspectRatio.php b/src/FFMpeg/Coordinate/AspectRatio.php
new file mode 100644
index 0000000..a3c298a
--- /dev/null
+++ b/src/FFMpeg/Coordinate/AspectRatio.php
@@ -0,0 +1,248 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FFMpeg\Coordinate;
+
+use FFMpeg\Exception\InvalidArgumentException;
+
+// see http://en.wikipedia.org/wiki/List_of_common_resolutions
+class AspectRatio
+{
+ // named 4:3 or 1.33:1 Traditional TV
+ const AR_4_3 = '4/3';
+ // named 16:9 or 1.77:1 HD video standard
+ const AR_16_9 = '16/9';
+
+ // named 3:2 or 1.5:1 see http://en.wikipedia.org/wiki/135_film
+ const AR_3_2 = '3/2';
+ // named 5:3 or 1.66:1 see http://en.wikipedia.org/wiki/Super_16_mm
+ const AR_5_3 = '5/3';
+
+ // mostly used in Photography
+ const AR_5_4 = '5/4';
+ const AR_1_1 = '1/1';
+
+ // 1.85:1 US widescreen cinema standard see http://en.wikipedia.org/wiki/Widescreen#Film
+ const AR_1_DOT_85_1 = '1.85:1';
+ // 2.39:1 or 2.40:1 Current widescreen cinema standard see http://en.wikipedia.org/wiki/Anamorphic_format
+ const AR_2_DOT_39_1 = '2.39:1';
+
+ // Rotated constants
+
+ // Rotated 4:3
+ const AR_ROTATED_3_4 = '3/4';
+ // Rotated 16:9
+ const AR_ROTATED_9_16 = '9/16';
+
+ // Rotated 3:2
+ const AR_ROTATED_2_3 = '2/3';
+ // Rotated 5:3
+ const AR_ROTATED_3_5 = '3/5';
+
+ // Rotated 5:4
+ const AR_ROTATED_4_5 = '4/5';
+
+ // Rotated 1.85
+ const AR_ROTATED_1_DOT_85 = '1/1.85';
+ // Rotated 2.39
+ const AR_ROTATED_2_DOT_39 = '1/2.39';
+
+ /** @var float */
+ private $ratio;
+
+ public function __construct($ratio)
+ {
+ $this->ratio = $ratio;
+ }
+
+ /**
+ * Returns the value of the ratio.
+ *
+ * @return float
+ */
+ public function getValue()
+ {
+ return $this->ratio;
+ }
+
+ /**
+ * Compute the best width for given height and modulus.
+ *
+ * @param Integer $height
+ * @param Integer $modulus
+ *
+ * @return Integer
+ */
+ public function calculateWidth($height, $modulus = 1)
+ {
+ $maxPossibleWidth = $this->getMultipleUp(ceil($this->ratio * $height), $modulus);
+ $minPossibleWidth = $this->getMultipleDown(floor($this->ratio * $height), $modulus);
+
+ $maxRatioDiff = abs($this->ratio - ($maxPossibleWidth / $height));
+ $minRatioDiff = abs($this->ratio - ($minPossibleWidth / $height));
+
+ return $maxRatioDiff < $minRatioDiff ? $maxPossibleWidth : $minPossibleWidth;
+ }
+
+ /**
+ * Compute the best height for given width and modulus.
+ *
+ * @param Integer $width
+ * @param Integer $modulus
+ *
+ * @return Integer
+ */
+ public function calculateHeight($width, $modulus = 1)
+ {
+ $maxPossibleHeight = $this->getMultipleUp(ceil($width / $this->ratio), $modulus);
+ $minPossibleHeight = $this->getMultipleDown(floor($width / $this->ratio), $modulus);
+
+ $maxRatioDiff = abs($this->ratio - ($width / $maxPossibleHeight));
+ $minRatioDiff = abs($this->ratio - ($width / $minPossibleHeight));
+
+ return $maxRatioDiff < $minRatioDiff ? $maxPossibleHeight : $minPossibleHeight;
+ }
+
+ private function getMultipleUp($value, $multiple)
+ {
+ while (0 !== $value % $multiple) {
+ $value++;
+ }
+
+ return $value;
+ }
+
+ private function getMultipleDown($value, $multiple)
+ {
+ while (0 !== $value % $multiple) {
+ $value--;
+ }
+
+ return $value;
+ }
+
+ /**
+ * Creates a ratio based on Dimension.
+ *
+ * The strategy parameter forces by default to use standardized ratios. If
+ * custom ratio need to be used, disable it.
+ *
+ * @param Dimension $dimension
+ * @param Boolean $forceStandards Whether to force or not standard ratios
+ *
+ * @return AspectRatio
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function create(Dimension $dimension, $forceStandards = true)
+ {
+ $incoming = $dimension->getWidth() / $dimension->getHeight();
+
+ if ($forceStandards) {
+ return new static(static::nearestStrategy($incoming));
+ } else {
+ return new static(static::customStrategy($incoming));
+ }
+ }
+
+ private static function valueFromName($name)
+ {
+ switch ($name) {
+ case static::AR_4_3:
+ return 4 / 3;
+ case static::AR_16_9:
+ return 16 / 9;
+ case static::AR_1_1:
+ return 1 / 1;
+ case static::AR_1_DOT_85_1:
+ return 1.85;
+ case static::AR_2_DOT_39_1:
+ return 2.39;
+ case static::AR_3_2:
+ return 3 / 2;
+ case static::AR_5_3:
+ return 5 / 3;
+ case static::AR_5_4:
+ return 5 / 4;
+ case static::AR_ROTATED_3_4:
+ return 3 / 4;
+ case static::AR_ROTATED_9_16:
+ return 9 / 16;
+ case static::AR_ROTATED_2_3:
+ return 2 / 3;
+ case static::AR_ROTATED_3_5:
+ return 3 / 5;
+ case static::AR_ROTATED_4_5:
+ return 4 / 5;
+ case static::AR_ROTATED_1_DOT_85:
+ return 1 / 1.85;
+ case static::AR_ROTATED_2_DOT_39:
+ return 1 / 2.39;
+ default:
+ throw new InvalidArgumentException(sprintf('Unable to find value for %s', $name));
+ }
+ }
+
+ private static function customStrategy($incoming)
+ {
+ $try = static::nearestStrategy($incoming);
+
+ if (abs($try - $incoming) < $try * 0.05) {
+ return $try;
+ }
+
+ return $incoming;
+ }
+
+ private static function nearestStrategy($incoming)
+ {
+ $availables = array(
+ static::AR_4_3 => static::valueFromName(static::AR_4_3),
+ static::AR_16_9 => static::valueFromName(static::AR_16_9),
+ static::AR_1_1 => static::valueFromName(static::AR_1_1),
+ static::AR_1_DOT_85_1 => static::valueFromName(static::AR_1_DOT_85_1),
+ static::AR_2_DOT_39_1 => static::valueFromName(static::AR_2_DOT_39_1),
+ static::AR_3_2 => static::valueFromName(static::AR_3_2),
+ static::AR_5_3 => static::valueFromName(static::AR_5_3),
+ static::AR_5_4 => static::valueFromName(static::AR_5_4),
+
+ // Rotated
+ static::AR_ROTATED_4_5 => static::valueFromName(static::AR_ROTATED_4_5),
+ static::AR_ROTATED_9_16 => static::valueFromName(static::AR_ROTATED_9_16),
+ static::AR_ROTATED_2_3 => static::valueFromName(static::AR_ROTATED_2_3),
+ static::AR_ROTATED_3_5 => static::valueFromName(static::AR_ROTATED_3_5),
+ static::AR_ROTATED_3_4 => static::valueFromName(static::AR_ROTATED_3_4),
+ static::AR_ROTATED_1_DOT_85 => static::valueFromName(static::AR_ROTATED_1_DOT_85),
+ static::AR_ROTATED_2_DOT_39 => static::valueFromName(static::AR_ROTATED_2_DOT_39),
+ );
+ asort($availables);
+
+ $previous = $current = null;
+
+ foreach ($availables as $name => $value) {
+ $current = $value;
+ if ($incoming <= $value) {
+ break;
+ }
+ $previous = $value;
+ }
+
+ if (null === $previous) {
+ return $current;
+ }
+
+ if (($current - $incoming) < ($incoming - $previous)) {
+ return $current;
+ }
+
+ return $previous;
+ }
+}
diff --git a/src/FFMpeg/Format/Dimension.php b/src/FFMpeg/Coordinate/Dimension.php
similarity index 76%
rename from src/FFMpeg/Format/Dimension.php
rename to src/FFMpeg/Coordinate/Dimension.php
index 605d6ef..15e448d 100644
--- a/src/FFMpeg/Format/Dimension.php
+++ b/src/FFMpeg/Coordinate/Dimension.php
@@ -9,19 +9,17 @@
* file that was distributed with this source code.
*/
-namespace FFMpeg\Format;
+namespace FFMpeg\Coordinate;
use FFMpeg\Exception\InvalidArgumentException;
/**
* Dimension object, used for manipulating width and height couples
- *
- * @author Romain Neutron imprec@gmail.com
*/
class Dimension
{
- protected $width;
- protected $height;
+ private $width;
+ private $height;
/**
* Constructor
@@ -59,4 +57,16 @@ class Dimension
{
return $this->height;
}
+
+ /**
+ * Get the ratio
+ *
+ * @param type $forceStandards Whether or not force the use of standards ratios;
+ *
+ * @return AspectRatio
+ */
+ public function getRatio($forceStandards = true)
+ {
+ return AspectRatio::create($this, $forceStandards);
+ }
}
diff --git a/src/FFMpeg/Coordinate/FrameRate.php b/src/FFMpeg/Coordinate/FrameRate.php
new file mode 100644
index 0000000..ed0c7b2
--- /dev/null
+++ b/src/FFMpeg/Coordinate/FrameRate.php
@@ -0,0 +1,36 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FFMpeg\Coordinate;
+
+use FFMpeg\Exception\InvalidArgumentException;
+
+class FrameRate
+{
+ private $value;
+
+ public function __construct($value)
+ {
+ if ($value <= 0) {
+ throw new InvalidArgumentException('Invalid frame rate, must be positive value.');
+ }
+
+ $this->value = $value;
+ }
+
+ /**
+ * @return float
+ */
+ public function getValue()
+ {
+ return $this->value;
+ }
+}
diff --git a/src/FFMpeg/Coordinate/Point.php b/src/FFMpeg/Coordinate/Point.php
new file mode 100644
index 0000000..2ee5c62
--- /dev/null
+++ b/src/FFMpeg/Coordinate/Point.php
@@ -0,0 +1,40 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FFMpeg\Coordinate;
+
+class Point
+{
+ private $x;
+ private $Y;
+
+ public function __construct($x, $y)
+ {
+ $this->x = (int) $x;
+ $this->y = (int) $y;
+ }
+
+ /**
+ * @return integer
+ */
+ public function getX()
+ {
+ return $this->x;
+ }
+
+ /**
+ * @return integer
+ */
+ public function getY()
+ {
+ return $this->y;
+ }
+}
diff --git a/src/FFMpeg/Coordinate/TimeCode.php b/src/FFMpeg/Coordinate/TimeCode.php
new file mode 100644
index 0000000..14864cb
--- /dev/null
+++ b/src/FFMpeg/Coordinate/TimeCode.php
@@ -0,0 +1,66 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FFMpeg\Coordinate;
+
+use FFMpeg\Exception\InvalidArgumentException;
+
+class TimeCode
+{
+ //see http://www.dropframetimecode.org/
+ private $hours;
+ private $minutes;
+ private $seconds;
+ private $frames;
+
+ public function __construct($hours, $minutes, $seconds, $frames)
+ {
+ $this->hours = $hours;
+ $this->minutes = $minutes;
+ $this->seconds = $seconds;
+ $this->frames = $frames;
+ }
+
+ public function __toString()
+ {
+ return sprintf('%02d:%02d:%02d.%02d', $this->hours, $this->minutes, $this->seconds, $this->frames);
+ }
+
+ /**
+ * Creates timecode from string
+ *
+ * @param string $timecode
+ *
+ * @return TimeCode
+ *
+ * @throws InvalidArgumentException In case an invalid timecode is supplied
+ */
+ public static function fromString($timecode)
+ {
+ $days = 0;
+
+ if (preg_match('/^[0-9]+:[0-9]+:[0-9]+:[0-9]+\.[0-9]+$/', $timecode)) {
+ list($days, $hours, $minutes, $seconds, $frames) = sscanf($timecode, '%d:%d:%d:%d.%d');
+ } elseif (preg_match('/^[0-9]+:[0-9]+:[0-9]+:[0-9]+:[0-9]+$/', $timecode)) {
+ list($days, $hours, $minutes, $seconds, $frames) = sscanf($timecode, '%d:%d:%d:%d:%d');
+ } elseif (preg_match('/^[0-9]+:[0-9]+:[0-9]+\.[0-9]+$/', $timecode)) {
+ list($hours, $minutes, $seconds, $frames) = sscanf($timecode, '%d:%d:%d.%s');
+ } elseif (preg_match('/^[0-9]+:[0-9]+:[0-9]+:[0-9]+$/', $timecode)) {
+ list($hours, $minutes, $seconds, $frames) = sscanf($timecode, '%d:%d:%d:%s');
+ } else {
+ throw new InvalidArgumentException(sprintf('Unable to parse timecode %s', $timecode));
+ }
+
+ $hours += $days * 24;
+
+ return new static($hours, $minutes, $seconds, $frames);
+ }
+}
diff --git a/src/FFMpeg/Driver/FFMpegDriver.php b/src/FFMpeg/Driver/FFMpegDriver.php
new file mode 100644
index 0000000..12b64c8
--- /dev/null
+++ b/src/FFMpeg/Driver/FFMpegDriver.php
@@ -0,0 +1,40 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FFMpeg\Driver;
+
+use Alchemy\BinaryDriver\AbstractBinary;
+use Alchemy\BinaryDriver\Configuration;
+use Psr\Log\LoggerInterface;
+
+class FFMpegDriver extends AbstractBinary
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function getName()
+ {
+ return 'ffmpeg';
+ }
+
+ /**
+ * Creates an FFMpegDriver.
+ *
+ * @param LoggerInterface $logger
+ * @param array|Configuration $configuration
+ *
+ * @return FFMpegDriver
+ */
+ public static function create(LoggerInterface $logger, $configuration)
+ {
+ return static::load(array('avconv', 'ffmpeg'), $logger, $configuration);
+ }
+}
diff --git a/src/FFMpeg/Driver/FFProbeDriver.php b/src/FFMpeg/Driver/FFProbeDriver.php
new file mode 100644
index 0000000..2a8fb32
--- /dev/null
+++ b/src/FFMpeg/Driver/FFProbeDriver.php
@@ -0,0 +1,47 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FFMpeg\Driver;
+
+use Alchemy\BinaryDriver\AbstractBinary;
+use Alchemy\BinaryDriver\Configuration;
+use Alchemy\BinaryDriver\ConfigurationInterface;
+use Psr\Log\LoggerInterface;
+
+class FFProbeDriver extends AbstractBinary
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function getName()
+ {
+ return 'ffprobe';
+ }
+
+ /**
+ * Creates an FFProbeDriver
+ *
+ * @param array|ConfigurationInterface $configuration
+ * @param LoggerInterface $logger
+ *
+ * @return FFProbeDriver
+ */
+ public static function create($configuration, LoggerInterface $logger = null)
+ {
+ if (!$configuration instanceof ConfigurationInterface) {
+ $configuration = new Configuration($configuration);
+ }
+
+ $binaries = $configuration->get('ffprobe.binaries', array('avprobe', 'ffprobe'));
+
+ return static::load($binaries, $logger, $configuration);
+ }
+}
diff --git a/src/FFMpeg/Exception/ExceptionInterface.php b/src/FFMpeg/Exception/ExceptionInterface.php
index a98ed0e..df1bfb0 100644
--- a/src/FFMpeg/Exception/ExceptionInterface.php
+++ b/src/FFMpeg/Exception/ExceptionInterface.php
@@ -13,5 +13,4 @@ namespace FFMpeg\Exception;
interface ExceptionInterface
{
-
}
diff --git a/src/FFMpeg/Exception/InvalidArgumentException.php b/src/FFMpeg/Exception/InvalidArgumentException.php
index df17f0c..c2ef8bd 100644
--- a/src/FFMpeg/Exception/InvalidArgumentException.php
+++ b/src/FFMpeg/Exception/InvalidArgumentException.php
@@ -13,5 +13,4 @@ namespace FFMpeg\Exception;
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
{
-
}
diff --git a/src/FFMpeg/Exception/RuntimeException.php b/src/FFMpeg/Exception/RuntimeException.php
index 7a49b12..14261da 100644
--- a/src/FFMpeg/Exception/RuntimeException.php
+++ b/src/FFMpeg/Exception/RuntimeException.php
@@ -13,5 +13,4 @@ namespace FFMpeg\Exception;
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
-
}
diff --git a/src/FFMpeg/FFMpeg.php b/src/FFMpeg/FFMpeg.php
index cc50d7e..78acd4e 100644
--- a/src/FFMpeg/FFMpeg.php
+++ b/src/FFMpeg/FFMpeg.php
@@ -11,466 +11,126 @@
namespace FFMpeg;
+use Alchemy\BinaryDriver\ConfigurationInterface;
+use FFMpeg\Driver\FFMpegDriver;
use FFMpeg\Exception\InvalidArgumentException;
-use FFMpeg\Exception\LogicException;
-use FFMpeg\Exception\RuntimeException;
-use FFMpeg\Format\AudioInterface;
-use FFMpeg\Format\VideoInterface;
-use FFMpeg\Format\Video\Resamplable as VideoResamplable;
-use FFMpeg\Format\Video\Resizable as VideoResizable;
-use FFMpeg\Format\Video\Transcodable as VideoTranscodable;
-use FFMpeg\Format\Audio\Resamplable as AudioResamplable;
-use FFMpeg\Format\Audio\Transcodable as AudioTranscodable;
-use FFMpeg\Helper\HelperInterface;
-use Symfony\Component\Process\Process;
-use Symfony\Component\Process\ProcessBuilder;
+use FFMpeg\Media\Audio;
+use FFMpeg\Media\Video;
+use Alchemy\BinaryDriver\Configuration;
+use Psr\Log\LoggerInterface;
-/**
- * FFMpeg driver
- *
- * @author Romain Neutron imprec@gmail.com
- */
-class FFMpeg extends Binary
+class FFMpeg
{
- protected $pathfile;
+ /** @var FFMpegDriver */
+ private $driver;
+ /** @var FFProbe */
+ private $ffprobe;
+
+ public function __construct(FFMpegDriver $ffmpeg, FFProbe $ffprobe)
+ {
+ $this->driver = $ffmpeg;
+ $this->ffprobe = $ffprobe;
+ }
/**
+ * Sets ffprobe
*
- * @var FFProbe
+ * @param FFProbe
+ *
+ * @return FFMpeg
*/
- protected $prober;
- protected $threads = 1;
-
- /**
- * @var HelperInterface[]
- */
- protected $helpers = array();
-
- /**
- * Destructor
- */
- public function __destruct()
+ public function setFFProbe(FFProbe $ffprobe)
{
- $this->prober = null;
- parent::__destruct();
- }
-
- /**
- * @param HelperInterface $helper
- * @return \FFMpeg\FFMpeg
- */
- public function attachHelper(HelperInterface $helper)
- {
- $this->helpers[] = $helper;
- $helper->setProber($this->prober);
-
- // ensure the helpers have the path to the file in case
- // they need to probe for format information
- if ($this->pathfile !== null) {
- $helper->open($this->pathfile);
- }
+ $this->ffprobe = $ffprobe;
return $this;
}
- public function setThreads($threads)
+ /**
+ * Gets FFProbe
+ *
+ * @return FFProbe
+ */
+ public function getFFProbe()
{
- if ($threads > 64 || $threads < 1) {
- throw new InvalidArgumentException('Invalid `threads` value ; threads must fit in range 1 - 64');
- }
+ return $this->ffprobe;
+ }
- $this->threads = (int) $threads;
+ /**
+ * Sets ffmpeg driver
+ *
+ * @return FFMpeg
+ */
+ public function setFFMpegDriver(FFMpegDriver $ffmpeg)
+ {
+ $this->driver = $ffmpeg;
return $this;
}
- public function getThreads()
+ /**
+ * Gets the ffmpeg driver
+ *
+ * @return FFMpegDriver
+ */
+ public function getFFMpegDriver()
{
- return $this->threads;
+ return $this->driver;
}
/**
* Opens a file in order to be processed
*
- * @param string $pathfile A pathfile
- * @return \FFMpeg\FFMpeg
+ * @param string $pathfile A pathfile
+ *
+ * @return Audio|Video
+ *
* @throws InvalidArgumentException
*/
public function open($pathfile)
{
if (!file_exists($pathfile)) {
- $this->logger->addError(sprintf('FFmpeg failed to open %s', $pathfile));
-
- throw new InvalidArgumentException(sprintf('File %s does not exist', $pathfile));
+ throw new InvalidArgumentException(sprintf('File %s does not exists', $pathfile));
}
- $this->logger->addInfo(sprintf('FFmpeg opens %s', $pathfile));
- $this->pathfile = $pathfile;
+ $streams = $this->ffprobe->streams($pathfile);
- foreach ($this->helpers as $helper) {
- $helper->open($pathfile);
+ if (0 < count($streams->videos())) {
+ return new Video($pathfile, $this->driver, $this->ffprobe);
+ } elseif (0 < count($streams->audios())) {
+ return new Audio($pathfile, $this->driver, $this->ffprobe);
}
- return $this;
+ throw new InvalidArgumentException('Unable to detect file format, only audio and video supported');
}
/**
- * Set a prober
+ * Creates a new FFMpeg instance
*
- * @return \FFMpeg\FFMpeg
+ * @param array|ConfigurationInterface $configuration
+ * @param LoggerInterface $logger
+ * @param FFProbe $probe
+ *
+ * @return FFMpeg
*/
- public function setProber(FFProbe $prober)
+ public static function create($configuration = array(), LoggerInterface $logger = null, FFProbe $probe = null)
{
- $this->prober = $prober;
-
- return $this;
- }
-
- /**
- * Close a file
- *
- * @return \FFMpeg\FFMpeg
- */
- public function close()
- {
- $this->logger->addInfo(sprintf('FFmpeg closes %s', $this->pathfile));
-
- $this->pathfile = null;
-
- return $this;
- }
-
- /**
- * Extract an image from a media file
- *
- * @param integer|string $time The time where to take the snapshot, time could either be in second or in hh:mm:ss[.xxx] form.
- * @param string $output The pathfile where to write
- * @param Boolean $accurate Whether to decode the whole video until position or seek and extract. See -ss option in FFMpeg manual (http://ffmpeg.org/ffmpeg.html#Main-options)
- *
- * @return \FFMpeg\FFMpeg
- *
- * @throws RuntimeException
- * @throws LogicException
- */
- public function extractImage($time, $output, $accurate = false)
- {
- if (!$this->pathfile) {
- throw new LogicException('No file open');
+ if (!$configuration instanceof ConfigurationInterface) {
+ $configuration = new Configuration($configuration);
}
- /**
- * @see http://ffmpeg.org/ffmpeg.html#Main-options
- */
- if (!$accurate) {
- $options = array(
- $this->binary, '-ss', $time,
- '-i', $this->pathfile,
- '-vframes', '1',
- '-f', 'image2', $output
- );
- } else {
- $options = array(
- $this->binary,
- '-i', $this->pathfile,
- '-vframes', '1', '-ss', $time,
- '-f', 'image2', $output
- );
+ $binaries = $configuration->get('ffmpeg.binaries', array('avconv', 'ffmpeg'));
+
+ if (!$configuration->has('timeout')) {
+ $configuration->set('timeout', 300);
}
- $builder = ProcessBuilder::create($options);
- $process = $builder->getProcess();
- $process->setTimeout($this->timeout);
-
- $this->logger->addInfo(sprintf('FFmpeg executes command %s', $process->getCommandline()));
-
- try {
- $process->run(array($this, 'transcodeCallback'));
- } catch (\RuntimeException $e) {
+ $driver = FFMpegDriver::load($binaries, $logger, $configuration);
+ if (null === $probe) {
+ $probe = FFProbe::create($configuration, $logger, null);
}
- if (!$process->isSuccessful()) {
- $this->logger->addError(sprintf('FFmpeg command failed: %s', $process->getErrorOutput()));
-
- $this->cleanupTemporaryFile($output);
-
- throw new RuntimeException('Failed to extract image');
- }
-
- $this->logger->addInfo(sprintf('FFmpeg command successful'));
-
- return $this;
- }
-
- /**
- * Encode the file to the specified format
- *
- * @param AudioInterface $format The output format
- * @param string $outputPathfile The pathfile where to write
- * @return \FFMpeg\FFMpeg
- * @throws RuntimeException
- * @throws LogicException
- */
- public function encode(AudioInterface $format, $outputPathfile)
- {
- if (!$this->pathfile) {
- throw new LogicException('No file open');
- }
-
- switch (true) {
- case $format instanceof VideoInterface:
- $this->encodeVideo($format, $outputPathfile);
- break;
- default:
- case $format instanceof AudioInterface:
- $this->encodeAudio($format, $outputPathfile);
- break;
- }
-
- return $this;
- }
-
- /**
- * Encode to audio
- *
- * @param Audio $format The output format
- * @param string $outputPathfile The pathfile where to write
- * @return \FFMpeg\FFMpeg
- * @throws RuntimeException
- */
- protected function encodeAudio(AudioInterface $format, $outputPathfile)
- {
- $builder = ProcessBuilder::create(array(
- $this->binary,
- '-y', '-i',
- $this->pathfile,
- '-threads', $this->threads,
- '-ab', $format->getKiloBitrate() . 'k ',
- ));
-
- foreach ($format->getExtraParams() as $parameter) {
- $builder->add($parameter);
- }
-
- if ($format instanceof AudioTranscodable) {
- $builder->add('-acodec')->add($format->getAudioCodec());
- }
-
- if ($format instanceof AudioResamplable) {
- $builder->add('-ac')->add(2)->add('-ar')->add($format->getAudioSampleRate());
- }
-
- $builder->add($outputPathfile);
-
- $process = $builder->getProcess();
-
- $process->setTimeout($this->timeout);
-
- $this->logger->addInfo(sprintf('FFmpeg executes command %s', $process->getCommandLine()));
-
- try {
- $process->run(array($this, 'transcodeCallback'));
- } catch (\RuntimeException $e) {
-
- }
-
- if (!$process->isSuccessful()) {
- $this->logger->addInfo(sprintf('FFmpeg command failed'));
- throw new RuntimeException(sprintf('Encoding failed: %s', $process->getErrorOutput()));
- }
-
- $this->logger->addInfo(sprintf('FFmpeg command successful'));
-
- return $this;
- }
-
- /**
- * Encode to video
- *
- * @param VideoInterface $format The output format
- * @param string $outputPathfile The pathfile where to write
- * @return \FFMpeg\FFMpeg
- * @throws RuntimeException
- */
- protected function encodeVideo(VideoInterface $format, $outputPathfile)
- {
- $builder = ProcessBuilder::create(array(
- $this->binary, '-y', '-i',
- $this->pathfile
- ));
-
- foreach ($format->getExtraParams() as $parameter) {
- $builder->add($parameter);
- }
-
- if ($format instanceof VideoResizable) {
- if (!$this->prober) {
- throw new LogicException('You must set a valid prober if you use a resizable format');
- }
-
- $result = json_decode($this->prober->probeStreams($this->pathfile), true);
-
- $originalWidth = $originalHeight = null;
-
- foreach ($result as $stream) {
- foreach ($stream as $name => $value) {
- if ($name == 'width') {
- $originalWidth = $value;
- continue;
- }
- if ($name == 'height') {
- $originalHeight = $value;
- continue;
- }
- }
- }
-
- if ($originalHeight !== null && $originalWidth !== null) {
- $this->logger->addInfo(sprintf('Read dimension for resizing succesful : %s x %s', $originalWidth, $originalHeight));
- } else {
- $this->logger->addInfo(sprintf('Read dimension for resizing failed !'));
- }
-
- if ($originalHeight !== null && $originalWidth !== null) {
- $dimensions = $format->getComputedDimensions($originalWidth, $originalHeight);
- $width = $this->getMultiple($dimensions->getWidth(), $format->getModulus());
- $height = $this->getMultiple($dimensions->getHeight(), $format->getModulus());
-
- $builder->add('-s')->add($width . 'x' . $height);
- }
- }
-
-
- if ($format instanceof VideoResamplable) {
- $builder->add('-r')->add($format->getFrameRate());
-
- /**
- * @see http://sites.google.com/site/linuxencoding/x264-ffmpeg-mapping
- */
- if ($format->supportBFrames()) {
- $builder->add('-b_strategy')
- ->add('1')
- ->add('-bf')
- ->add('3')
- ->add('-g')
- ->add($format->getGOPSize());
- }
- }
-
- if ($format instanceof VideoTranscodable) {
- $builder->add('-vcodec')->add($format->getVideoCodec());
- }
-
- $builder->add('-b:v')->add($format->getKiloBitrate() . 'k')
- ->add('-threads')->add($this->threads)
- ->add('-refs')->add('6')
- ->add('-coder')->add('1')
- ->add('-sc_threshold')->add('40')
- ->add('-flags')->add('+loop')
- ->add('-me_range')->add('16')
- ->add('-subq')->add('7')
- ->add('-i_qfactor')->add('0.71')
- ->add('-qcomp')->add('0.6')
- ->add('-qdiff')->add('4')
- ->add('-trellis')->add('1')
- ->add('-b:a')->add('92k');
-
- if ($format instanceof AudioTranscodable) {
- $builder->add('-acodec')->add($format->getAudioCodec());
- }
-
- $passPrefix = uniqid('pass-');
-
- $pass1 = $builder;
- $pass2 = clone $builder;
-
- $passes[] = $pass1
- ->add('-pass')->add('1')
- ->add('-passlogfile')->add($passPrefix)
- ->add('-an')->add($outputPathfile)
- ->getProcess();
- $passes[] = $pass2
- ->add('-pass')->add('2')
- ->add('-passlogfile')->add($passPrefix)
- ->add('-ac')->add('2')
- ->add('-ar')->add('44100')->add($outputPathfile)
- ->getProcess();
-
- foreach ($passes as $process) {
-
- $process->setTimeout($this->timeout);
-
- $this->logger->addInfo(sprintf('FFmpeg executes command %s', $process->getCommandline()));
-
- try {
- $process->run(array($this, 'transcodeCallback'));
- } catch (\RuntimeException $e) {
- break;
- }
- }
-
- $this->cleanupTemporaryFile(getcwd() . '/' . $passPrefix . '-0.log');
- $this->cleanupTemporaryFile(getcwd() . '/' . $passPrefix . '-0.log');
- $this->cleanupTemporaryFile(getcwd() . '/' . $passPrefix . '-0.log.mbtree');
-
- if (!$process->isSuccessful()) {
- $this->logger->addInfo(sprintf('FFmpeg command failed'));
- throw new RuntimeException(sprintf('Encoding failed : %s', $process->getErrorOutput()));
- }
-
- $this->logger->addInfo(sprintf('FFmpeg command successful'));
-
- return $this;
- }
-
- /**
- * The main transcoding callback, delegates the content to the helpers.
- *
- * @param string $channel (stdio|stderr)
- * @param string $content the current line of the ffmpeg output
- */
- public function transcodeCallback($channel, $content)
- {
- foreach ($this->helpers as $helper) {
- $helper->transcodeCallback($channel, $content);
- }
- }
-
- /**
- * Removes unnecessary file
- *
- * @param string $pathfile
- */
- protected function cleanupTemporaryFile($pathfile)
- {
- if (file_exists($pathfile) && is_writable($pathfile)) {
- unlink($pathfile);
- }
- }
-
- /**
- * Returns the nearest multiple for a value
- *
- * @param integer $value
- * @param integer $multiple
- * @return integer
- */
- protected function getMultiple($value, $multiple)
- {
- while (0 !== $value % $multiple) {
- $value++;
- }
-
- return $value;
- }
-
- /**
- * {@inheritdoc}
- *
- * @return string
- */
- protected static function getBinaryName()
- {
- return array('avconv', 'ffmpeg');
+ return new static($driver, $probe);
}
}
diff --git a/src/FFMpeg/FFMpegServiceProvider.php b/src/FFMpeg/FFMpegServiceProvider.php
index 6060d50..2d1419d 100644
--- a/src/FFMpeg/FFMpegServiceProvider.php
+++ b/src/FFMpeg/FFMpegServiceProvider.php
@@ -11,49 +11,52 @@
namespace FFMpeg;
+use Doctrine\Common\Cache\ArrayCache;
use FFMpeg\FFMpeg;
use FFMpeg\FFProbe;
-use Monolog\Logger;
-use Monolog\Handler\NullHandler;
use Silex\Application;
use Silex\ServiceProviderInterface;
class FFMpegServiceProvider implements ServiceProviderInterface
{
-
public function register(Application $app)
{
- if (isset($app['monolog'])) {
- $app['ffmpeg.logger'] = function() use ($app) {
- return $app['monolog'];
- };
- } else {
- $app['ffmpeg.logger'] = $app->share(function(Application $app) {
- $logger = new Logger('FFMpeg logger');
- $logger->pushHandler(new NullHandler());
+ $app['ffmpeg.configuration'] = array();
+ $app['ffmpeg.default.configuration'] = array(
+ 'ffmpeg.threads' => 4,
+ 'ffmpeg.timeout' => 300,
+ 'ffmpeg.binaries' => array('avconv', 'ffmpeg'),
+ 'ffprobe.timeout' => 30,
+ 'ffprobe.binaries' => array('avprobe', 'ffprobe'),
+ );
+ $app['ffmpeg.logger'] = null;
- return $logger;
- });
- }
+ $app['ffmpeg.configuration.build'] = $app->share(function (Application $app) {
+ return array_replace($app['ffmpeg.default.configuration'], $app['ffmpeg.configuration']);
+ });
- $app['ffmpeg.ffmpeg'] = $app->share(function(Application $app) {
- if (isset($app['ffmpeg.ffmpeg.binary'])) {
- $ffmpeg = new FFMpeg($app['ffmpeg.ffmpeg.binary'], $app['ffmpeg.logger']);
- } else {
- $ffmpeg = FFMpeg::load($app['ffmpeg.logger']);
+ $app['ffmpeg'] = $app['ffmpeg.ffmpeg'] = $app->share(function(Application $app) {
+ $configuration = $app['ffmpeg.configuration.build'];
+
+ if (isset($configuration['ffmpeg.timeout'])) {
+ $configuration['timeout'] = $configuration['ffmpeg.timeout'];
}
- return $ffmpeg
- ->setProber($app['ffmpeg.ffprobe'])
- ->setThreads(isset($app['ffmpeg.threads']) ? $app['ffmpeg.threads'] : 1);
+ return FFMpeg::create($configuration, $app['ffmpeg.logger'], $app['ffmpeg.ffprobe']);
+ });
+
+ $app['ffprobe.cache'] = $app->share(function () {
+ return new ArrayCache();
});
$app['ffmpeg.ffprobe'] = $app->share(function(Application $app) {
- if (isset($app['ffmpeg.ffprobe.binary'])) {
- return new FFProbe($app['ffmpeg.ffprobe.binary'], $app['ffmpeg.logger']);
- } else {
- return FFProbe::load($app['ffmpeg.logger']);
+ $configuration = $app['ffmpeg.configuration.build'];
+
+ if (isset($configuration['ffmpeg.timeout'])) {
+ $configuration['timeout'] = $configuration['ffprobe.timeout'];
}
+
+ return FFProbe::create($configuration, $app['ffmpeg.logger'], $app['ffprobe.cache']);
});
}
diff --git a/src/FFMpeg/FFProbe.php b/src/FFMpeg/FFProbe.php
index 4d428d2..1a9bbfd 100644
--- a/src/FFMpeg/FFProbe.php
+++ b/src/FFMpeg/FFProbe.php
@@ -11,163 +11,258 @@
namespace FFMpeg;
+use Alchemy\BinaryDriver\ConfigurationInterface;
+use Doctrine\Common\Cache\ArrayCache;
+use Doctrine\Common\Cache\Cache;
+use FFMpeg\Driver\FFProbeDriver;
+use FFMpeg\FFProbe\DataMapping\Format;
+use FFMpeg\FFProbe\Mapper;
+use FFMpeg\FFProbe\MapperInterface;
+use FFMpeg\FFProbe\OptionsTester;
+use FFMpeg\FFProbe\OptionsTesterInterface;
+use FFMpeg\FFProbe\OutputParser;
+use FFMpeg\FFProbe\OutputParserInterface;
use FFMpeg\Exception\InvalidArgumentException;
use FFMpeg\Exception\RuntimeException;
-use Symfony\Component\Process\Process;
-use Symfony\Component\Process\ProcessBuilder;
+use Psr\Log\LoggerInterface;
-/**
- * FFProbe driver
- *
- * @author Romain Neutron imprec@gmail.com
- */
-class FFProbe extends Binary
+class FFProbe
{
+ const TYPE_STREAMS = 'streams';
+ const TYPE_FORMAT = 'format';
- protected $cachedFormats = array();
+ /** @var Cache */
+ private $cache;
+ /** @var OptionsTesterInterface */
+ private $optionsTester;
+ /** @var OutputParserInterface */
+ private $parser;
+ /** @var FFProbeDriver */
+ private $ffprobe;
+ /** @var MapperInterface */
+ private $mapper;
+
+ public function __construct(FFProbeDriver $ffprobe, Cache $cache)
+ {
+ $this->ffprobe = $ffprobe;
+ $this->optionsTester = new OptionsTester($ffprobe, $cache);
+ $this->parser = new OutputParser();
+ $this->mapper = new Mapper();
+ $this->cache = $cache;
+ }
/**
+ * @return OutputParserInterface
+ */
+ public function getParser()
+ {
+ return $this->parser;
+ }
+
+ /**
+ * @param OutputParserInterface $parser
+ *
+ * @return FFProbe
+ */
+ public function setParser(OutputParserInterface $parser)
+ {
+ $this->parser = $parser;
+
+ return $this;
+ }
+
+ /**
+ * @return FFProbeDriver
+ */
+ public function getFFProbeDriver()
+ {
+ return $this->ffprobe;
+ }
+
+ /**
+ * @param FFProbeDriver $ffprobe
+ *
+ * @return FFProbe
+ */
+ public function setFFProbeDriver(FFProbeDriver $ffprobe)
+ {
+ $this->ffprobe = $ffprobe;
+
+ return $this;
+ }
+
+ /**
+ * @param OptionsTesterInterface $tester
+ *
+ * @return FFProbe
+ */
+ public function setOptionsTester(OptionsTesterInterface $tester)
+ {
+ $this->optionsTester = $tester;
+
+ return $this;
+ }
+
+ /**
+ * @return OptionsTesterInterface
+ */
+ public function getOptionsTester()
+ {
+ return $this->optionsTester;
+ }
+
+ /**
+ * @param Cache $cache
+ *
+ * @return FFProbe
+ */
+ public function setCache(Cache $cache)
+ {
+ $this->cache = $cache;
+
+ return $this;
+ }
+
+ /**
+ * @return Cache
+ */
+ public function getCache()
+ {
+ return $this->cache;
+ }
+
+ /**
+ * @return MapperInterface
+ */
+ public function getMapper()
+ {
+ return $this->mapper;
+ }
+
+ /**
+ * @param MapperInterface $mapper
+ *
+ * @return FFProbe
+ */
+ public function setMapper(MapperInterface $mapper)
+ {
+ $this->mapper = $mapper;
+
+ return $this;
+ }
+
+ /**
+ * @api
+ *
* Probe the format of a given file
*
- * @param string $pathfile
- * @return string A Json object containing the key/values of the probe output
+ * @param string $pathfile
+ *
+ * @return Format A Format object
*
* @throws InvalidArgumentException
* @throws RuntimeException
*/
- public function probeFormat($pathfile)
+ public function format($pathfile)
{
- if ( ! is_file($pathfile)) {
- throw new InvalidArgumentException($pathfile);
- }
-
- if (isset($this->cachedFormats[$pathfile])) {
- return $this->cachedFormats[$pathfile];
- }
-
- $builder = ProcessBuilder::create(array(
- $this->binary, $pathfile, '-show_format'
- ));
-
- $output = $this->executeProbe($builder->getProcess());
-
- $ret = array();
-
- foreach (explode(PHP_EOL, $output) as $line) {
-
- if (in_array($line, array('[FORMAT]', '[/FORMAT]'))) {
- continue;
- }
-
- $chunks = explode('=', $line);
- $key = array_shift($chunks);
-
- if ('' === trim($key)) {
- continue;
- }
-
- $value = trim(implode('=', $chunks));
-
- if (ctype_digit($value)) {
- $value = (int) $value;
- }
-
- $ret[$key] = $value;
- }
-
- return $this->cachedFormats[$pathfile] = json_encode($ret);
+ return $this->probe($pathfile, '-show_format', static::TYPE_FORMAT);
}
/**
+ * @api
+ *
* Probe the streams contained in a given file
*
- * @param string $pathfile
- * @return array An array of streams array
+ * @param string $pathfile
+ *
+ * @return StreamCollection A collection of streams
*
* @throws InvalidArgumentException
* @throws RuntimeException
*/
- public function probeStreams($pathfile)
+ public function streams($pathfile)
{
- if ( ! is_file($pathfile)) {
- throw new InvalidArgumentException($pathfile);
- }
-
- $builder = ProcessBuilder::create(array(
- $this->binary, $pathfile, '-show_streams'
- ));
-
- $output = explode(PHP_EOL, $this->executeProbe($builder->getProcess()));
-
- $ret = array();
- $n = 0;
-
- foreach ($output as $line) {
-
- if ($line == '[STREAM]') {
- $n ++;
- $ret[$n] = array();
- continue;
- }
- if ($line == '[/STREAM]') {
- continue;
- }
-
- $chunks = explode('=', $line);
- $key = array_shift($chunks);
-
- if ('' === trim($key)) {
- continue;
- }
-
- $value = trim(implode('=', $chunks));
-
- if (ctype_digit($value)) {
- $value = (int) $value;
- }
-
- $ret[$n][$key] = $value;
- }
-
- return json_encode(array_values($ret));
+ return $this->probe($pathfile, '-show_streams', static::TYPE_STREAMS);
}
/**
+ * @api
*
- * @param Process $process
- * @return string
- * @throws RuntimeException
+ * @param array|ConfigurationInterface $configuration
+ * @param LoggerInterface $logger
+ * @param Cache $cache
+ *
+ * @return FFProbe
*/
- protected function executeProbe(Process $process)
+ public static function create($configuration = array(), LoggerInterface $logger = null, Cache $cache = null)
{
- $this->logger->addInfo(sprintf('FFprobe executes command %s', $process->getCommandline()));
-
- try {
- $process->run();
- } catch (\RuntimeException $e) {
- $this->logger->addInfo('FFprobe command failed');
-
- throw new RuntimeException(sprintf('Failed to run the given command %s', $process->getCommandline()));
+ if (null === $cache) {
+ $cache = new ArrayCache();
}
- if ( ! $process->isSuccessful()) {
- $this->logger->addInfo('FFprobe command failed');
-
- throw new RuntimeException(sprintf('Failed to probe %s', $process->getCommandline()));
- }
-
- $this->logger->addInfo('FFprobe command successful');
-
- return $process->getOutput();
+ return new static(FFProbeDriver::create($configuration, $logger), $cache);
}
- /**
- * {@inheritdoc}
- *
- * @return string
- */
- protected static function getBinaryName()
+ private function probe($pathfile, $command, $type, $allowJson = true)
{
- return array('avprobe', 'ffprobe');
+ if (!is_file($pathfile)) {
+ throw new InvalidArgumentException(sprintf(
+ 'Invalid filepath %s, unable to read.', $pathfile
+ ));
+ }
+
+ $id = sprintf('%s-%s', $command, $pathfile);
+
+ if ($this->cache->contains($id)) {
+ return $this->cache->fetch($id);
+ }
+
+ if (!$this->optionsTester->has($command)) {
+ throw new RuntimeException(sprintf(
+ 'This version of ffprobe is too old and '
+ . 'does not support `%s` option, please upgrade', $command
+ ));
+ }
+
+ $commands = array($pathfile, $command);
+
+ $parseIsToDo = false;
+
+ if ($allowJson && $this->optionsTester->has('-print_format')) {
+ $commands[] = '-print_format';
+ $commands[] = 'json';
+ } else {
+ $parseIsToDo = true;
+ }
+
+ $output = $this->ffprobe->command($commands);
+
+ if ($parseIsToDo) {
+ $data = $this->parser->parse($type, $output);
+ } else {
+ try {
+ // Malformed json may be retrieved
+ $data = $this->parseJson($output);
+ } catch (RuntimeException $e) {
+ return $this->probe($pathfile, $command, $type, false);
+ }
+ }
+
+ $ret = $this->mapper->map($type, $data);
+
+ $this->cache->save($id, $ret);
+
+ return $ret;
+ }
+
+ private function parseJson($data)
+ {
+ $ret = @json_decode($data, true);
+
+ if (JSON_ERROR_NONE !== json_last_error()) {
+ throw new RuntimeException(sprintf('Unable to parse json %s', $ret));
+ }
+
+ return $ret;
}
}
diff --git a/src/FFMpeg/FFProbe/DataMapping/AbstractData.php b/src/FFMpeg/FFProbe/DataMapping/AbstractData.php
new file mode 100644
index 0000000..5088b89
--- /dev/null
+++ b/src/FFMpeg/FFProbe/DataMapping/AbstractData.php
@@ -0,0 +1,80 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FFMpeg\FFProbe\DataMapping;
+
+use FFMpeg\Exception\InvalidArgumentException;
+
+abstract class AbstractData implements \Countable
+{
+ private $properties;
+
+ public function __construct(array $properties)
+ {
+ $this->properties = $properties;
+ }
+
+ /**
+ * Returns true if data has property
+ *
+ * @param string $property
+ * @return Boolean
+ */
+ public function has($property)
+ {
+ return isset($this->properties[$property]);
+ }
+
+ /**
+ * Returns the property value given its name
+ *
+ * @param string $property
+ * @return mixed
+ *
+ * @throws InvalidArgumentException In case the data does not have the property
+ */
+ public function get($property)
+ {
+ if (!isset($this->properties[$property])) {
+ throw new InvalidArgumentException(sprintf('Invalid property `%s`.', $property));
+ }
+
+ return $this->properties[$property];
+ }
+
+ /**
+ * Returns all property names
+ *
+ * @return array
+ */
+ public function keys()
+ {
+ return array_keys($this->properties);
+ }
+
+ /**
+ * Returns all properties and their values
+ *
+ * @return array
+ */
+ public function all()
+ {
+ return $this->properties;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function count()
+ {
+ return count($this->properties);
+ }
+}
diff --git a/src/FFMpeg/Exception/LogicException.php b/src/FFMpeg/FFProbe/DataMapping/Format.php
similarity index 68%
rename from src/FFMpeg/Exception/LogicException.php
rename to src/FFMpeg/FFProbe/DataMapping/Format.php
index 9ba8143..4d7caa0 100644
--- a/src/FFMpeg/Exception/LogicException.php
+++ b/src/FFMpeg/FFProbe/DataMapping/Format.php
@@ -9,9 +9,8 @@
* file that was distributed with this source code.
*/
-namespace FFMpeg\Exception;
+namespace FFMpeg\FFProbe\DataMapping;
-class LogicException extends \LogicException implements ExceptionInterface
+class Format extends AbstractData
{
-
}
diff --git a/src/FFMpeg/FFProbe/DataMapping/Stream.php b/src/FFMpeg/FFProbe/DataMapping/Stream.php
new file mode 100644
index 0000000..d3a84c9
--- /dev/null
+++ b/src/FFMpeg/FFProbe/DataMapping/Stream.php
@@ -0,0 +1,35 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FFMpeg\FFProbe\DataMapping;
+
+class Stream extends AbstractData
+{
+ /**
+ * Returns true if the stream is an audio stream
+ *
+ * @return Boolean
+ */
+ public function isAudio()
+ {
+ return $this->has('codec_type') ? 'audio' === $this->get('codec_type') : false;
+ }
+
+ /**
+ * Returns true if the stream is a video stream
+ *
+ * @return Boolean
+ */
+ public function isVideo()
+ {
+ return $this->has('codec_type') ? 'video' === $this->get('codec_type') : false;
+ }
+}
diff --git a/src/FFMpeg/FFProbe/DataMapping/StreamCollection.php b/src/FFMpeg/FFProbe/DataMapping/StreamCollection.php
new file mode 100644
index 0000000..bb730ee
--- /dev/null
+++ b/src/FFMpeg/FFProbe/DataMapping/StreamCollection.php
@@ -0,0 +1,99 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FFMpeg\FFProbe\DataMapping;
+
+class StreamCollection implements \Countable, \IteratorAggregate
+{
+ private $streams;
+
+ public function __construct(array $streams = array())
+ {
+ $this->streams = array_values($streams);
+ }
+
+ /**
+ * Returns the first stream of the collection, null if the collection is
+ * empty.
+ *
+ * @return null|Stream
+ */
+ public function first()
+ {
+ $stream = reset($this->streams);
+
+ return $stream ?: null;
+ }
+
+ /**
+ * Adds a stream to the collection
+ *
+ * @param Stream $stream
+ *
+ * @return StreamCollection
+ */
+ public function add(Stream $stream)
+ {
+ $this->streams[] = $stream;
+
+ return $this;
+ }
+
+ /**
+ * Returns a new StreamCollection with only video streams
+ *
+ * @return StreamCollection
+ */
+ public function videos()
+ {
+ return new static(array_filter($this->streams, function (Stream $stream) {
+ return $stream->isVideo();
+ }));
+ }
+
+ /**
+ * Returns a new StreamCollection with only audio streams
+ *
+ * @return StreamCollection
+ */
+ public function audios()
+ {
+ return new static(array_filter($this->streams, function (Stream $stream) {
+ return $stream->isAudio();
+ }));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function count()
+ {
+ return count($this->streams);
+ }
+
+ /**
+ * Returns the array of contained streams
+ *
+ * @return array
+ */
+ public function all()
+ {
+ return $this->streams;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getIterator()
+ {
+ return new \ArrayIterator($this->streams);
+ }
+}
diff --git a/src/FFMpeg/FFProbe/Mapper.php b/src/FFMpeg/FFProbe/Mapper.php
new file mode 100644
index 0000000..c0bf70f
--- /dev/null
+++ b/src/FFMpeg/FFProbe/Mapper.php
@@ -0,0 +1,54 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FFMpeg\FFProbe;
+
+use FFMpeg\FFProbe;
+use FFMpeg\FFProbe\DataMapping\Format;
+use FFMpeg\FFProbe\DataMapping\StreamCollection;
+use FFMpeg\FFProbe\DataMapping\Stream;
+use FFMpeg\Exception\InvalidArgumentException;
+
+class Mapper implements MapperInterface
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function map($type, $data)
+ {
+ switch ($type) {
+ case FFProbe::TYPE_FORMAT:
+ return $this->mapFormat($data);
+ case FFProbe::TYPE_STREAMS:
+ return $this->mapStreams($data);
+ default:
+ throw new InvalidArgumentException(sprintf(
+ 'Invalid type `%s`.', $type
+ ));
+ }
+ }
+
+ private function mapFormat($data)
+ {
+ return new Format($data['format']);
+ }
+
+ private function mapStreams($data)
+ {
+ $streams = new StreamCollection();
+
+ foreach ($data['streams'] as $properties) {
+ $streams->add(new Stream($properties));
+ }
+
+ return $streams;
+ }
+}
diff --git a/src/FFMpeg/FFProbe/MapperInterface.php b/src/FFMpeg/FFProbe/MapperInterface.php
new file mode 100644
index 0000000..a505784
--- /dev/null
+++ b/src/FFMpeg/FFProbe/MapperInterface.php
@@ -0,0 +1,27 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FFMpeg\FFProbe;
+
+interface MapperInterface
+{
+ /**
+ * Maps data given its type
+ *
+ * @param string $type One of FFProbe::TYPE_* constant
+ * @param string $data The data
+ *
+ * @return Format|Stream
+ *
+ * @throws InvalidArgumentException In case the type is not supported
+ */
+ public function map($type, $data);
+}
diff --git a/src/FFMpeg/FFProbe/OptionsTester.php b/src/FFMpeg/FFProbe/OptionsTester.php
new file mode 100644
index 0000000..f6feb7a
--- /dev/null
+++ b/src/FFMpeg/FFProbe/OptionsTester.php
@@ -0,0 +1,64 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FFMpeg\FFProbe;
+
+use Doctrine\Common\Cache\Cache;
+use FFMpeg\Driver\FFProbeDriver;
+
+class OptionsTester implements OptionsTesterInterface
+{
+ /** @var FFProbeDriver */
+ private $ffprobe;
+ /** @var Cache */
+ private $cache;
+
+ public function __construct(FFProbeDriver $ffprobe, Cache $cache)
+ {
+ $this->ffprobe = $ffprobe;
+ $this->cache = $cache;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function has($name)
+ {
+ $id = sprintf('option-%s', $name);
+
+ if ($this->cache->contains($id)) {
+ return $this->cache->fetch($id);
+ }
+
+ $output = $this->retrieveHelpOutput();
+
+ $ret = (Boolean) preg_match('/^'.$name.'/m', $output);
+
+ $this->cache->save($id, $ret);
+
+ return $ret;
+ }
+
+ private function retrieveHelpOutput()
+ {
+ $id = 'help';
+
+ if ($this->cache->contains($id)) {
+ return $this->cache->fetch($id);
+ }
+
+ $output = $this->ffprobe->command(array('-help', '-loglevel', 'quiet'));
+
+ $this->cache->save($id, $output);
+
+ return $output;
+ }
+}
diff --git a/src/FFMpeg/AdapterInterface.php b/src/FFMpeg/FFProbe/OptionsTesterInterface.php
similarity index 51%
rename from src/FFMpeg/AdapterInterface.php
rename to src/FFMpeg/FFProbe/OptionsTesterInterface.php
index 84fc007..b4d40e3 100644
--- a/src/FFMpeg/AdapterInterface.php
+++ b/src/FFMpeg/FFProbe/OptionsTesterInterface.php
@@ -9,20 +9,16 @@
* file that was distributed with this source code.
*/
-namespace FFMpeg;
+namespace FFMpeg\FFProbe;
-use Monolog\Logger;
-
-/**
- * FFMpeg Adapter interface
- *
- * @author Romain Neutron imprec@gmail.com
- */
-interface AdapterInterface
+interface OptionsTesterInterface
{
-
/**
- * Loads the adapter
+ * Tells if the given option is supported by ffprobe
+ *
+ * @param string $name
+ *
+ * @return Boolean
*/
- public static function load(Logger $logger);
+ public function has($name);
}
diff --git a/src/FFMpeg/FFProbe/OutputParser.php b/src/FFMpeg/FFProbe/OutputParser.php
new file mode 100644
index 0000000..acaa1a0
--- /dev/null
+++ b/src/FFMpeg/FFProbe/OutputParser.php
@@ -0,0 +1,125 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FFMpeg\FFProbe;
+
+use FFMpeg\FFProbe;
+use FFMpeg\Exception\InvalidArgumentException;
+
+class OutputParser implements OutputParserInterface
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function parse($type, $data)
+ {
+ switch ($type) {
+ case FFProbe::TYPE_FORMAT:
+ return $this->parseFormat($data);
+ break;
+ case FFProbe::TYPE_STREAMS:
+ return $this->parseStreams($data);
+ break;
+ default:
+ throw new InvalidArgumentException(sprintf('Unknown data type %s', $type));
+ }
+ }
+
+ private function parseFormat($data)
+ {
+ $ret = array();
+
+ foreach (explode(PHP_EOL, $data) as $line) {
+
+ if (in_array($line, array('[FORMAT]', '[/FORMAT]'))) {
+ continue;
+ }
+
+ $chunks = explode('=', $line);
+ $key = array_shift($chunks);
+
+ if ('' === trim($key)) {
+ continue;
+ }
+
+ $value = trim(implode('=', $chunks));
+
+ if ('nb_streams' === $key) {
+ $value = (int) $value;
+ }
+
+ if (0 === strpos($key, 'TAG:')) {
+ if (!isset($ret['tags'])) {
+ $ret['tags'] = array();
+ }
+ $ret['tags'][substr($key, 4)] = $value;
+ } else {
+ $ret[$key] = $value;
+ }
+ }
+
+ return array('format' => $ret);
+ }
+
+ private function parseStreams($data)
+ {
+ $ret = array();
+ $n = -1;
+
+ foreach (explode(PHP_EOL, $data) as $line) {
+
+ if ($line == '[STREAM]') {
+ $n ++;
+ $ret[$n] = array();
+ continue;
+ }
+ if ($line == '[/STREAM]') {
+ continue;
+ }
+
+ $chunks = explode('=', $line);
+ $key = array_shift($chunks);
+
+ if ('' === trim($key)) {
+ continue;
+ }
+
+ $value = trim(implode('=', $chunks));
+
+ if ('N/A' === $value) {
+ continue;
+ }
+ if ('profile' === $key && 'unknown' === $value) {
+ continue;
+ }
+
+ if (in_array($key, array('index', 'width', 'height', 'channels', 'bits_per_sample', 'has_b_frames', 'level', 'start_pts', 'duration_ts'))) {
+ $value = (int) $value;
+ }
+
+ if (0 === strpos($key, 'TAG:')) {
+ if (!isset($ret[$n]['tags'])) {
+ $ret[$n]['tags'] = array();
+ }
+ $ret[$n]['tags'][substr($key, 4)] = $value;
+ } elseif (0 === strpos($key, 'DISPOSITION:')) {
+ if (!isset($ret[$n]['disposition'])) {
+ $ret[$n]['disposition'] = array();
+ }
+ $ret[$n]['disposition'][substr($key, 12)] = $value;
+ } else {
+ $ret[$n][$key] = $value;
+ }
+ }
+
+ return array('streams' => $ret);
+ }
+}
diff --git a/src/FFMpeg/FFProbe/OutputParserInterface.php b/src/FFMpeg/FFProbe/OutputParserInterface.php
new file mode 100644
index 0000000..a179478
--- /dev/null
+++ b/src/FFMpeg/FFProbe/OutputParserInterface.php
@@ -0,0 +1,27 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FFMpeg\FFProbe;
+
+interface OutputParserInterface
+{
+ /**
+ * Parses ffprobe raw output
+ *
+ * @param string $type One of FFProbe::TYPE_* constant
+ * @param string $data The data
+ *
+ * @return array
+ *
+ * @throws InvalidArgumentException In case the type is not supported
+ */
+ public function parse($type, $data);
+}
diff --git a/src/FFMpeg/Filters/Audio/AudioFilterInterface.php b/src/FFMpeg/Filters/Audio/AudioFilterInterface.php
new file mode 100644
index 0000000..b111cfe
--- /dev/null
+++ b/src/FFMpeg/Filters/Audio/AudioFilterInterface.php
@@ -0,0 +1,29 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FFMpeg\Filters\Audio;
+
+use FFMpeg\Filters\FilterInterface;
+use FFMpeg\Format\AudioInterface;
+use FFMpeg\Media\Audio;
+
+interface AudioFilterInterface extends FilterInterface
+{
+ /**
+ * Applies the filter on the the Audio media given an format.
+ *
+ * @param Audio $audio
+ * @param AudioInterface $format
+ *
+ * @return array An array of arguments
+ */
+ public function apply(Audio $audio, AudioInterface $format);
+}
diff --git a/src/FFMpeg/Filters/Audio/AudioFilters.php b/src/FFMpeg/Filters/Audio/AudioFilters.php
new file mode 100644
index 0000000..39453bb
--- /dev/null
+++ b/src/FFMpeg/Filters/Audio/AudioFilters.php
@@ -0,0 +1,30 @@
+audio = $audio;
+ }
+
+ /**
+ * Resamples the audio file.
+ *
+ * @param Integer $rate
+ *
+ * @return AudioFilters
+ */
+ public function resample($rate)
+ {
+ $this->audio->addFilter(new AudioResamplableFilter($rate));
+
+ return $this;
+ }
+}
diff --git a/src/FFMpeg/Filters/Audio/AudioResamplableFilter.php b/src/FFMpeg/Filters/Audio/AudioResamplableFilter.php
new file mode 100644
index 0000000..2a25d57
--- /dev/null
+++ b/src/FFMpeg/Filters/Audio/AudioResamplableFilter.php
@@ -0,0 +1,43 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FFMpeg\Filters\Audio;
+
+use FFMpeg\Format\AudioInterface;
+use FFMpeg\Media\Audio;
+
+class AudioResamplableFilter implements AudioFilterInterface
+{
+ /** @var string */
+ private $rate;
+
+ public function __construct($rate)
+ {
+ $this->rate = $rate;
+ }
+
+ /**
+ *
+ * @return Integer
+ */
+ public function getRate()
+ {
+ return $this->rate;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function apply(Audio $audio, AudioInterface $format)
+ {
+ return array('-ac', 2, '-ar', $this->rate);
+ }
+}
diff --git a/src/FFMpeg/Filters/FilterInterface.php b/src/FFMpeg/Filters/FilterInterface.php
new file mode 100644
index 0000000..4939fb5
--- /dev/null
+++ b/src/FFMpeg/Filters/FilterInterface.php
@@ -0,0 +1,16 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FFMpeg\Filters;
+
+interface FilterInterface
+{
+}
diff --git a/src/FFMpeg/Filters/FiltersCollection.php b/src/FFMpeg/Filters/FiltersCollection.php
new file mode 100644
index 0000000..43a856b
--- /dev/null
+++ b/src/FFMpeg/Filters/FiltersCollection.php
@@ -0,0 +1,45 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FFMpeg\Filters;
+
+class FiltersCollection implements \Countable, \IteratorAggregate
+{
+ private $filters = array();
+
+ /**
+ * @param FilterInterface $filter
+ *
+ * @return FiltersCollection
+ */
+ public function add(FilterInterface $filter)
+ {
+ $this->filters[] = $filter;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function count()
+ {
+ return count($this->filters);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getIterator()
+ {
+ return new \ArrayIterator($this->filters);
+ }
+}
diff --git a/src/FFMpeg/Filters/Frame/FrameFilterInterface.php b/src/FFMpeg/Filters/Frame/FrameFilterInterface.php
new file mode 100644
index 0000000..db4fdac
--- /dev/null
+++ b/src/FFMpeg/Filters/Frame/FrameFilterInterface.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FFMpeg\Filters\Frame;
+
+use FFMpeg\Filters\FilterInterface;
+use FFMpeg\Media\Frame;
+use FFMpeg\Format\FrameInterface;
+
+interface FrameFilterInterface extends FilterInterface
+{
+ public function apply(Frame $frame, FrameInterface $format);
+}
diff --git a/src/FFMpeg/Filters/Frame/FrameFilters.php b/src/FFMpeg/Filters/Frame/FrameFilters.php
new file mode 100644
index 0000000..85bfde9
--- /dev/null
+++ b/src/FFMpeg/Filters/Frame/FrameFilters.php
@@ -0,0 +1,24 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FFMpeg\Filters\Frame;
+
+use FFMpeg\Media\Frame;
+
+class FrameFilters
+{
+ private $frame;
+
+ public function __construct(Frame $frame)
+ {
+ $this->frame = $frame;
+ }
+}
diff --git a/src/FFMpeg/Filters/Video/ResizeFilter.php b/src/FFMpeg/Filters/Video/ResizeFilter.php
new file mode 100644
index 0000000..c183d57
--- /dev/null
+++ b/src/FFMpeg/Filters/Video/ResizeFilter.php
@@ -0,0 +1,126 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FFMpeg\Filters\Video;
+
+use FFMpeg\Coordinate\Dimension;
+use FFMpeg\Media\Video;
+use FFMpeg\Format\VideoInterface;
+
+class ResizeFilter implements VideoFilterInterface
+{
+ const RESIZEMODE_FIT = 'fit';
+ const RESIZEMODE_INSET = 'inset';
+ const RESIZEMODE_SCALE_WIDTH = 'width';
+ const RESIZEMODE_SCALE_HEIGHT = 'height';
+
+ /** @var Dimension */
+ private $dimension;
+ /** @var string */
+ private $mode;
+ /** @var Boolean */
+ private $forceStandards;
+
+ public function __construct(Dimension $dimension, $mode = self::RESIZEMODE_FIT, $forceStandards = true)
+ {
+ $this->dimension = $dimension;
+ $this->mode = $mode;
+ $this->forceStandards = $forceStandards;
+ }
+
+ /**
+ * @return Dimension
+ */
+ public function getDimension()
+ {
+ return $this->dimension;
+ }
+
+ /**
+ * @return string
+ */
+ public function getMode()
+ {
+ return $this->mode;
+ }
+
+ /**
+ * @return Boolean
+ */
+ public function areStandardsForced()
+ {
+ return $this->forceStandards;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function apply(Video $video, VideoInterface $format)
+ {
+ $originalWidth = $originalHeight = null;
+
+ foreach ($video->getStreams() as $stream) {
+ if ($stream->isVideo()) {
+ if ($stream->has('width')) {
+ $originalWidth = $stream->get('width');
+ }
+ if ($stream->has('height')) {
+ $originalHeight = $stream->get('height');
+ }
+ }
+ }
+
+ $commands = array();
+
+ if ($originalHeight !== null && $originalWidth !== null) {
+ $dimensions = $this->getComputedDimensions(new Dimension($originalWidth, $originalHeight), $format->getModulus());
+
+ $commands[] = '-s';
+ $commands[] = $dimensions->getWidth() . 'x' . $dimensions->getHeight();
+ }
+
+ return $commands;
+ }
+
+ private function getComputedDimensions(Dimension $dimension, $modulus)
+ {
+ $originalRatio = $dimension->getRatio($this->forceStandards);
+
+ switch ($this->mode) {
+ case self::RESIZEMODE_SCALE_WIDTH:
+ $height = $this->dimension->getHeight();
+ $width = $originalRatio->calculateWidth($height, $modulus);
+ break;
+ case self::RESIZEMODE_SCALE_HEIGHT:
+ $width = $this->dimension->getWidth();
+ $height = $originalRatio->calculateHeight($width, $modulus);
+ break;
+ case self::RESIZEMODE_INSET:
+ $targetRatio = $this->dimension->getRatio($this->forceStandards);
+
+ if ($targetRatio->getValue() > $originalRatio->getValue()) {
+ $height = $this->dimension->getHeight();
+ $width = $originalRatio->calculateWidth($height, $modulus);
+ } else {
+ $width = $this->dimension->getWidth();
+ $height = $originalRatio->calculateHeight($width, $modulus);
+ }
+ break;
+ case self::RESIZEMODE_FIT:
+ default:
+ $width = $this->dimension->getWidth();
+ $height = $this->dimension->getHeight();
+ break;
+ }
+
+ return new Dimension($width, $height);
+ }
+}
diff --git a/src/FFMpeg/Filters/Video/SynchronizeFilter.php b/src/FFMpeg/Filters/Video/SynchronizeFilter.php
new file mode 100644
index 0000000..f189d8f
--- /dev/null
+++ b/src/FFMpeg/Filters/Video/SynchronizeFilter.php
@@ -0,0 +1,49 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FFMpeg\Filters\Video;
+
+use FFMpeg\Format\VideoInterface;
+use FFMpeg\Media\Video;
+
+class SynchronizeFilter implements VideoFilterInterface
+{
+ public function apply(Video $video, VideoInterface $format)
+ {
+ $streams = $video->getStreams();
+
+ if (null === $videoStream = $streams->videos()->first()) {
+ return array();
+ }
+ if (!$videoStream->has('start_time')) {
+ return array();
+ }
+
+ $params = array(
+ '-itsoffset',
+ $videoStream->get('start_time'),
+ '-i',
+ $video->getPathfile(),
+ );
+
+ foreach ($streams as $stream) {
+ if ($videoStream === $stream) {
+ $params[] = '-map';
+ $params[] = '1:' . $stream->get('index');
+ } else {
+ $params[] = '-map';
+ $params[] = '0:' . $stream->get('index');
+ }
+ }
+
+ return $params;
+ }
+}
diff --git a/src/FFMpeg/Filters/Video/VideoFilterInterface.php b/src/FFMpeg/Filters/Video/VideoFilterInterface.php
new file mode 100644
index 0000000..f4cbbcb
--- /dev/null
+++ b/src/FFMpeg/Filters/Video/VideoFilterInterface.php
@@ -0,0 +1,29 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FFMpeg\Filters\Video;
+
+use FFMpeg\Filters\FilterInterface;
+use FFMpeg\Format\VideoInterface;
+use FFMpeg\Media\Video;
+
+interface VideoFilterInterface extends FilterInterface
+{
+ /**
+ * Applies the filter on the the Video media given an format.
+ *
+ * @param Video $video
+ * @param VideoInterface $format
+ *
+ * @return array An array of arguments
+ */
+ public function apply(Video $video, VideoInterface $format);
+}
diff --git a/src/FFMpeg/Filters/Video/VideoFilters.php b/src/FFMpeg/Filters/Video/VideoFilters.php
new file mode 100644
index 0000000..393580b
--- /dev/null
+++ b/src/FFMpeg/Filters/Video/VideoFilters.php
@@ -0,0 +1,69 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FFMpeg\Filters\Video;
+
+use FFMpeg\Media\Video;
+use FFMpeg\Coordinate\Dimension;
+use FFMpeg\Coordinate\FrameRate;
+
+class VideoFilters
+{
+ private $video;
+
+ public function __construct(Video $video)
+ {
+ $this->video = $video;
+ }
+
+ /**
+ * Resizes a video to a given dimension
+ *
+ * @param Dimension $dimension
+ * @param string $mode
+ * @param Boolean $forceStandards
+ *
+ * @return VideoFilters
+ */
+ public function resize(Dimension $dimension, $mode = ResizeFilter::RESIZEMODE_FIT, $forceStandards = true)
+ {
+ $this->video->addFilter(new ResizeFilter($dimension, $mode, $forceStandards));
+
+ return $this;
+ }
+
+ /**
+ * Resamples the video to the given framerate.
+ *
+ * @param FrameRate $framerate
+ * @param type $gop
+ *
+ * @return VideoFilters
+ */
+ public function resample(FrameRate $framerate, $gop)
+ {
+ $this->video->addFilter(new VideoResampleFilter($framerate, $gop));
+
+ return $this;
+ }
+
+ /**
+ * Synchronizes audio and video.
+ *
+ * @return VideoFilters
+ */
+ public function synchronize()
+ {
+ $this->video->addFilter(new SynchronizeFilter());
+
+ return $this;
+ }
+}
diff --git a/src/FFMpeg/Filters/Video/VideoResampleFilter.php b/src/FFMpeg/Filters/Video/VideoResampleFilter.php
new file mode 100644
index 0000000..e35b87d
--- /dev/null
+++ b/src/FFMpeg/Filters/Video/VideoResampleFilter.php
@@ -0,0 +1,72 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FFMpeg\Filters\Video;
+
+use FFMpeg\Coordinate\FrameRate;
+use FFMpeg\Media\Video;
+use FFMpeg\Format\VideoInterface;
+
+class VideoResampleFilter implements VideoFilterInterface
+{
+ private $rate;
+ private $gop;
+
+ public function __construct(FrameRate $rate, $gop)
+ {
+ $this->rate = $rate;
+ $this->gop = $gop;
+ }
+
+ /**
+ * Returns the frame rate
+ *
+ * @return FrameRate
+ */
+ public function getFrameRate()
+ {
+ return $this->rate;
+ }
+
+ /**
+ * Returns the GOP size
+ *
+ * @see https://wikipedia.org/wiki/Group_of_pictures
+ *
+ * @return Integer
+ */
+ public function getGOP()
+ {
+ return $this->gop;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function apply(Video $video, VideoInterface $format)
+ {
+ $commands = array('-r', $this->rate->getValue());
+
+ /**
+ * @see http://sites.google.com/site/linuxencoding/x264-ffmpeg-mapping
+ */
+ if ($format->supportBFrames()) {
+ $commands[] = '-b_strategy';
+ $commands[] = '1';
+ $commands[] = '-bf';
+ $commands[] = '3';
+ $commands[] = '-g';
+ $commands[] = $this->gop;
+ }
+
+ return $commands;
+ }
+}
diff --git a/src/FFMpeg/Format/Audio/DefaultAudio.php b/src/FFMpeg/Format/Audio/DefaultAudio.php
index 7eaa5dd..2a3c653 100644
--- a/src/FFMpeg/Format/Audio/DefaultAudio.php
+++ b/src/FFMpeg/Format/Audio/DefaultAudio.php
@@ -11,23 +11,24 @@
namespace FFMpeg\Format\Audio;
+use Evenement\EventEmitter;
use FFMpeg\Exception\InvalidArgumentException;
+use FFMpeg\Format\AudioInterface;
+use FFMpeg\Media\MediaTypeInterface;
+use FFMpeg\Format\ProgressableInterface;
+use FFMpeg\Format\ProgressListener\AudioProgressListener;
+use FFMpeg\FFProbe;
-/**
- * The abstract default Audio format
- *
- * @author Romain Neutron imprec@gmail.com
- */
-abstract class DefaultAudio implements Resamplable, Interactive
+abstract class DefaultAudio extends EventEmitter implements AudioInterface, ProgressableInterface
{
+ /** @var string */
protected $audioCodec;
- protected $audioSampleRate = 44100;
- protected $kiloBitrate = 128;
+
+ /** @var integer */
+ protected $audioKiloBitrate = 128;
/**
- * Returns extra parameters for the encoding
- *
- * @return string
+ * {@inheritdoc}
*/
public function getExtraParams()
{
@@ -43,11 +44,12 @@ abstract class DefaultAudio implements Resamplable, Interactive
}
/**
- * Set the audio codec, Should be in the available ones, otherwise an
+ * Sets the audio codec, Should be in the available ones, otherwise an
* exception is thrown
*
- * @param string $audioCodec
- * @throws \InvalidArgumentException
+ * @param string $audioCodec
+ *
+ * @throws InvalidArgumentException
*/
public function setAudioCodec($audioCodec)
{
@@ -66,24 +68,24 @@ abstract class DefaultAudio implements Resamplable, Interactive
/**
* {@inheritdoc}
*/
- public function getAudioSampleRate()
+ public function getAudioKiloBitrate()
{
- return $this->audioSampleRate;
+ return $this->audioKiloBitrate;
}
/**
- * Set the audio sample rate
+ * Sets the kiloBitrate value
*
- * @param integer $audioSampleRate
- * @throws \InvalidArgumentException
+ * @param integer $kiloBitrate
+ * @throws InvalidArgumentException
*/
- public function setAudioSampleRate($audioSampleRate)
+ public function setAudioKiloBitrate($kiloBitrate)
{
- if ($audioSampleRate < 1) {
- throw new InvalidArgumentException('Wrong audio sample rate value');
+ if ($kiloBitrate < 1) {
+ throw new InvalidArgumentException('Wrong kiloBitrate value');
}
- $this->audioSampleRate = (int) $audioSampleRate;
+ $this->audioKiloBitrate = (int) $kiloBitrate;
return $this;
}
@@ -91,25 +93,14 @@ abstract class DefaultAudio implements Resamplable, Interactive
/**
* {@inheritdoc}
*/
- public function getKiloBitrate()
+ public function createProgressListener(MediaTypeInterface $media, FFProbe $ffprobe, $pass, $total)
{
- return $this->kiloBitrate;
- }
+ $format = $this;
+ $listener = new AudioProgressListener($ffprobe, $media->getPathfile(), $pass, $total);
+ $listener->on('progress', function () use ($media, $format) {
+ $format->emit('progress', array_merge(array($media, $format), func_get_args()));
+ });
- /**
- * Set the kiloBitrate value
- *
- * @param int integer $kiloBitrate
- * @throws \InvalidArgumentException
- */
- public function setKiloBitrate($kiloBitrate)
- {
- if ($kiloBitrate < 1) {
- throw new InvalidArgumentException('Wrong kiloBitrate value');
- }
-
- $this->kiloBitrate = (int) $kiloBitrate;
-
- return $this;
+ return array($listener);
}
}
diff --git a/src/FFMpeg/Format/Audio/Flac.php b/src/FFMpeg/Format/Audio/Flac.php
index 579ffc6..28a7197 100644
--- a/src/FFMpeg/Format/Audio/Flac.php
+++ b/src/FFMpeg/Format/Audio/Flac.php
@@ -13,12 +13,13 @@ namespace FFMpeg\Format\Audio;
/**
* The Flac audio format
- *
- * @author Romain Neutron imprec@gmail.com
*/
class Flac extends DefaultAudio
{
- protected $audioCodec = 'flac';
+ public function __construct()
+ {
+ $this->audioCodec = 'flac';
+ }
/**
* {@inheritDoc}
diff --git a/src/FFMpeg/Format/Audio/Interactive.php b/src/FFMpeg/Format/Audio/Interactive.php
deleted file mode 100644
index eb0254a..0000000
--- a/src/FFMpeg/Format/Audio/Interactive.php
+++ /dev/null
@@ -1,30 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace FFMpeg\Format\Audio;
-
-/**
- * The interactive audio interface. This provide a method to list available
- * codecs. This is usefull to build interactive development and switch between
- * different codecs
- *
- * @author Romain Neutron imprec@gmail.com
- */
-interface Interactive extends Transcodable
-{
-
- /**
- * Returns the list of available audio codecs for this format
- *
- * @return array
- */
- public function getAvailableAudioCodecs();
-}
diff --git a/src/FFMpeg/Format/Audio/Mp3.php b/src/FFMpeg/Format/Audio/Mp3.php
index c7d45f3..4cd86f5 100644
--- a/src/FFMpeg/Format/Audio/Mp3.php
+++ b/src/FFMpeg/Format/Audio/Mp3.php
@@ -13,12 +13,13 @@ namespace FFMpeg\Format\Audio;
/**
* The MP3 audio format
- *
- * @author Romain Neutron imprec@gmail.com
*/
class Mp3 extends DefaultAudio
{
- protected $audioCodec = 'libmp3lame';
+ public function __construct()
+ {
+ $this->audioCodec = 'libmp3lame';
+ }
/**
* {@inheritDoc}
diff --git a/src/FFMpeg/Format/Audio/Resamplable.php b/src/FFMpeg/Format/Audio/Resamplable.php
deleted file mode 100644
index 2f8aa81..0000000
--- a/src/FFMpeg/Format/Audio/Resamplable.php
+++ /dev/null
@@ -1,32 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace FFMpeg\Format\Audio;
-
-use FFMpeg\Format\AudioInterface;
-
-/**
- * The resamplable audio interface
- *
- * This provide a method to define the AudiosampleRate
- *
- * @author Romain Neutron imprec@gmail.com
- */
-interface Resamplable extends AudioInterface
-{
-
- /**
- * Get the audio sample rate
- *
- * @return integer
- */
- public function getAudioSampleRate();
-}
diff --git a/src/FFMpeg/Format/AudioInterface.php b/src/FFMpeg/Format/AudioInterface.php
index 22eac12..47ed5d6 100644
--- a/src/FFMpeg/Format/AudioInterface.php
+++ b/src/FFMpeg/Format/AudioInterface.php
@@ -8,23 +8,16 @@
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
-
namespace FFMpeg\Format;
-/**
- * The base audio interface
- *
- * @author Romain Neutron imprec@gmail.com
- */
-interface AudioInterface
+interface AudioInterface extends FormatInterface
{
-
/**
- * Get the kiloBitrate value
+ * Get the audio kiloBitrate value
*
* @return integer
*/
- public function getKiloBitrate();
+ public function getAudioKiloBitrate();
/**
* Return an array of extra parameters to add to ffmpeg commandline
@@ -33,4 +26,17 @@ interface AudioInterface
*/
public function getExtraParams();
+ /**
+ * Returns the audio codec
+ *
+ * @return string
+ */
+ public function getAudioCodec();
+
+ /**
+ * Returns the list of available audio codecs for this format
+ *
+ * @return array
+ */
+ public function getAvailableAudioCodecs();
}
diff --git a/src/FFMpeg/Exception/BinaryNotFoundException.php b/src/FFMpeg/Format/FormatInterface.php
similarity index 66%
rename from src/FFMpeg/Exception/BinaryNotFoundException.php
rename to src/FFMpeg/Format/FormatInterface.php
index aa16b8c..4495f7a 100644
--- a/src/FFMpeg/Exception/BinaryNotFoundException.php
+++ b/src/FFMpeg/Format/FormatInterface.php
@@ -8,10 +8,8 @@
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
+namespace FFMpeg\Format;
-namespace FFMpeg\Exception;
-
-class BinaryNotFoundException extends \Exception implements ExceptionInterface
+interface FormatInterface
{
-
}
diff --git a/src/FFMpeg/Format/FrameInterface.php b/src/FFMpeg/Format/FrameInterface.php
new file mode 100644
index 0000000..77a27ab
--- /dev/null
+++ b/src/FFMpeg/Format/FrameInterface.php
@@ -0,0 +1,16 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FFMpeg\Format;
+
+interface FrameInterface extends FormatInterface
+{
+}
diff --git a/src/FFMpeg/Format/ProgressListener/AbstractProgressListener.php b/src/FFMpeg/Format/ProgressListener/AbstractProgressListener.php
new file mode 100644
index 0000000..6066535
--- /dev/null
+++ b/src/FFMpeg/Format/ProgressListener/AbstractProgressListener.php
@@ -0,0 +1,238 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FFMpeg\Format\ProgressListener;
+
+use Alchemy\BinaryDriver\Listeners\ListenerInterface;
+use Evenement\EventEmitter;
+use FFMpeg\FFProbe;
+use FFMpeg\Exception\RuntimeException;
+
+/**
+ * @author Robert Gruendler
+ */
+abstract class AbstractProgressListener extends EventEmitter implements ListenerInterface
+{
+ /** @var integer */
+ private $duration;
+
+ /** @var integer */
+ private $totalSize;
+
+ /** @var integer */
+ private $currentSize;
+
+ /** @var integer */
+ private $currentTime;
+
+ /** @var double */
+ private $lastOutput = null;
+
+ /** @var FFProbe */
+ private $ffprobe;
+
+ /** @var string */
+ private $pathfile;
+
+ /** @var Boolean */
+ private $initialized = false;
+
+ /** @var integer */
+ private $currentPass;
+
+ /** @var integer */
+ private $totalPass;
+
+ /**
+ * Transcoding rate in kb/s
+ *
+ * @var integer
+ */
+ private $rate;
+
+ /**
+ * Percentage of transcoding progress (0 - 100)
+ *
+ * @var integer
+ */
+ private $percent = 0;
+
+ /**
+ * Time remaining (seconds)
+ *
+ * @var integer
+ */
+ private $remaining = null;
+
+ /**
+ * @param FFProbe $ffprobe
+ * @param string $pathfile
+ *
+ * @throws RuntimeException
+ */
+ public function __construct(FFProbe $ffprobe, $pathfile, $currentPass, $totalPass)
+ {
+ $this->ffprobe = $ffprobe;
+ $this->pathfile = $pathfile;
+ $this->currentPass = $currentPass;
+ $this->totalPass = $totalPass;
+ }
+
+ /**
+ * @return FFProbe
+ */
+ public function getFFProbe()
+ {
+ return $this->ffprobe;
+ }
+
+ /**
+ * @return string
+ */
+ public function getPathfile()
+ {
+ return $this->pathfile;
+ }
+
+ /**
+ * @return integer
+ */
+ public function getCurrentPass()
+ {
+ return $this->currentPass;
+ }
+
+ /**
+ * @return integer
+ */
+ public function getTotalPass()
+ {
+ return $this->totalPass;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function handle($type, $data)
+ {
+ if (null !== $progress = $this->parseProgress($data)) {
+ $this->emit('progress', array_values($progress));
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function forwardedEvents()
+ {
+ return array();
+ }
+
+ /**
+ * Get the regex pattern to match a ffmpeg stderr status line
+ */
+ abstract protected function getPattern();
+
+ /**
+ * @param string $progress A ffmpeg stderr progress output
+ *
+ * @return array the progressinfo array or null if there's no progress available yet.
+ */
+ private function parseProgress($progress)
+ {
+ if (!$this->initialized) {
+ $this->initialize();
+ }
+
+ $matches = array();
+
+ if (preg_match($this->getPattern(), $progress, $matches) !== 1) {
+ return null;
+ }
+
+ $currentDuration = $this->convertDuration($matches[2]);
+ $currentTime = microtime(true);
+ $currentSize = trim(str_replace('kb', '', strtolower(($matches[1]))));
+ $percent = max(0, min(1, $currentDuration / $this->duration));
+
+ if ($this->lastOutput !== null) {
+ $delta = $currentTime - $this->lastOutput;
+ $deltaSize = $currentSize - $this->currentSize;
+ $rate = $deltaSize * $delta;
+ if ($rate > 0) {
+ $totalDuration = $this->totalSize / $rate;
+ $this->remaining = floor($totalDuration - ($totalDuration * $percent));
+ $this->rate = floor($rate);
+ } else {
+ $this->remaining = 0;
+ $this->rate = 0;
+ }
+ }
+
+ $percent = $percent / $this->totalPass + ($this->currentPass - 1) / $this->totalPass;
+
+ $this->percent = floor($percent * 100);
+ $this->lastOutput = $currentTime;
+ $this->currentSize = (int) $currentSize;
+ $this->currentTime = $currentDuration;
+
+ return $this->getProgressInfo();
+ }
+
+ /**
+ *
+ * @param string $rawDuration in the format 00:00:00.00
+ * @return number
+ */
+ private function convertDuration($rawDuration)
+ {
+ $ar = array_reverse(explode(":", $rawDuration));
+ $duration = floatval($ar[0]);
+ if (!empty($ar[1])) {
+ $duration += intval($ar[1]) * 60;
+ }
+ if (!empty($ar[2])) {
+ $duration += intval($ar[2]) * 60 * 60;
+ }
+
+ return $duration;
+ }
+
+ /**
+ * @return array
+ */
+ private function getProgressInfo()
+ {
+ if ($this->remaining === null) {
+ return null;
+ }
+
+ return array(
+ 'percent' => $this->percent,
+ 'remaining' => $this->remaining,
+ 'rate' => $this->rate
+ );
+ }
+
+ private function initialize()
+ {
+ $format = $this->ffprobe->format($this->pathfile);
+
+ if (false === $format->has('size') || false === $format->has('duration')) {
+ throw new RuntimeException(sprintf('Unable to probe format for %s', $this->pathfile));
+ }
+
+ $this->totalSize = $format->get('size') / 1024;
+ $this->duration = $format->get('duration');
+
+ $this->initialized = true;
+ }
+}
diff --git a/src/FFMpeg/Helper/AudioProgressHelper.php b/src/FFMpeg/Format/ProgressListener/AudioProgressListener.php
similarity index 83%
rename from src/FFMpeg/Helper/AudioProgressHelper.php
rename to src/FFMpeg/Format/ProgressListener/AudioProgressListener.php
index 5044469..7fbb0bb 100644
--- a/src/FFMpeg/Helper/AudioProgressHelper.php
+++ b/src/FFMpeg/Format/ProgressListener/AudioProgressListener.php
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
-namespace FFMpeg\Helper;
+namespace FFMpeg\Format\ProgressListener;
/**
* Parses ffmpeg stderr progress information. An example:
@@ -20,7 +20,7 @@ namespace FFMpeg\Helper;
*
* @author Robert Gruendler
*/
-class AudioProgressHelper extends ProgressHelper
+class AudioProgressListener extends AbstractProgressListener
{
public function getPattern()
{
diff --git a/src/FFMpeg/Helper/VideoProgressHelper.php b/src/FFMpeg/Format/ProgressListener/VideoProgressListener.php
similarity index 84%
rename from src/FFMpeg/Helper/VideoProgressHelper.php
rename to src/FFMpeg/Format/ProgressListener/VideoProgressListener.php
index 7322abf..6bfa4a1 100644
--- a/src/FFMpeg/Helper/VideoProgressHelper.php
+++ b/src/FFMpeg/Format/ProgressListener/VideoProgressListener.php
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
-namespace FFMpeg\Helper;
+namespace FFMpeg\Format\ProgressListener;
/**
* Parses ffmpeg stderr progress information for video files. An example:
@@ -20,7 +20,7 @@ namespace FFMpeg\Helper;
*
* @author Robert Gruendler
*/
-class VideoProgressHelper extends ProgressHelper
+class VideoProgressListener extends AbstractProgressListener
{
public function getPattern()
{
diff --git a/src/FFMpeg/Format/ProgressableInterface.php b/src/FFMpeg/Format/ProgressableInterface.php
new file mode 100644
index 0000000..7318d65
--- /dev/null
+++ b/src/FFMpeg/Format/ProgressableInterface.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FFMpeg\Format;
+
+use Evenement\EventEmitterInterface;
+use FFMpeg\FFProbe;
+use FFMpeg\Media\MediaTypeInterface;
+
+interface ProgressableInterface extends EventEmitterInterface
+{
+ /**
+ * Creates the progress listener
+ *
+ * @param MediaTypeInterface $media
+ * @param FFProbe $ffprobe
+ * @param Integer $pass The current pas snumber
+ * @param Integer $total The total pass number
+ *
+ * @return array An array of listeners
+ */
+ public function createProgressListener(MediaTypeInterface $media, FFProbe $ffprobe, $pass, $total);
+}
diff --git a/src/FFMpeg/Format/Video/DefaultVideo.php b/src/FFMpeg/Format/Video/DefaultVideo.php
index 47b0210..7427175 100644
--- a/src/FFMpeg/Format/Video/DefaultVideo.php
+++ b/src/FFMpeg/Format/Video/DefaultVideo.php
@@ -11,171 +11,48 @@
namespace FFMpeg\Format\Video;
-use FFMpeg\Format\Audio\DefaultAudio;
-use FFMpeg\Format\Dimension;
+use FFMpeg\FFProbe;
use FFMpeg\Exception\InvalidArgumentException;
+use FFMpeg\Format\Audio\DefaultAudio;
+use FFMpeg\Format\VideoInterface;
+use FFMpeg\Media\MediaTypeInterface;
+use FFMpeg\Format\ProgressListener\VideoProgressListener;
/**
* The abstract default Video format
- *
- * @author Romain Neutron imprec@gmail.com
*/
-abstract class DefaultVideo extends DefaultAudio implements Interactive, Resamplable, Resizable
+abstract class DefaultVideo extends DefaultAudio implements VideoInterface
{
- const RESIZEMODE_FIT = 'fit';
- const RESIZEMODE_INSET = 'inset';
- const RESIZEMODE_SCALE_WIDTH = 'width';
- const RESIZEMODE_SCALE_HEIGHT = 'height';
-
- protected $width;
- protected $height;
- protected $frameRate = 25;
- protected $resizeMode = self::RESIZEMODE_FIT;
+ /** @var string */
protected $videoCodec;
- protected $GOPsize = 25;
+
+ /** @var Integer */
protected $kiloBitrate = 1000;
+
+ /** @var Integer */
protected $modulus = 16;
- /**
- * Returns the width setting.
- * The return of this method should not depend on a media file size
- *
- * @return integer
- */
- public function getWidth()
- {
- return $this->width;
- }
-
- /**
- * Returns the height setting
- * The return of this method should not depend on a media file size
- *
- * @return integer
- */
- public function getHeight()
- {
- return $this->height;
- }
-
- /**
- * Set the dimensions
- *
- * @param integer $width The heigth
- * @param integer $height The width
- * @throws \InvalidArgumentException
- */
- public function setDimensions($width, $height)
- {
- if ($width < 1) {
- throw new InvalidArgumentException('Wrong width value');
- }
- if ($height < 1) {
- throw new InvalidArgumentException('Wrong height value');
- }
-
- $this->width = $width;
- $this->height = $height;
-
- return $this;
- }
-
- /**
- * {@inheritdoc)
- */
- public function getComputedDimensions($originalWidth, $originalHeight)
- {
- $originalRatio = $originalWidth / $originalHeight;
-
- switch ($this->getResizeMode()) {
- case self::RESIZEMODE_SCALE_WIDTH:
- $height = $this->height;
- $width = round($originalRatio * $this->height);
- break;
- case self::RESIZEMODE_SCALE_HEIGHT:
- $width = $this->width;
- $height = round($this->width / $originalRatio);
- break;
- case self::RESIZEMODE_INSET:
- $targetRatio = $this->width / $this->height;
-
- if ($targetRatio > $originalRatio) {
- $height = $this->height;
- $width = round($originalRatio * $this->height);
- } else {
- $width = $this->width;
- $height = round($this->width / $originalRatio);
- }
- break;
- case self::RESIZEMODE_FIT:
- default:
- if (null !== $this->width && null !== $this->height) {
- $width = $this->width;
- $height = $this->height;
- } else {
- $width = $originalWidth;
- $height = $originalHeight;
- }
- break;
- }
-
- return new Dimension($width, $height);
- }
-
- /**
- * Set the resize mode
- *
- * @param string $mode The mode, one of the self::RESIZEMODE_* constants
- *
- * @throws InvalidArgumentException
- */
- public function setResizeMode($mode)
- {
- if ( ! in_array($mode, array(self::RESIZEMODE_FIT, self::RESIZEMODE_INSET, self::RESIZEMODE_SCALE_WIDTH, self::RESIZEMODE_SCALE_HEIGHT))) {
- throw new InvalidArgumentException(
- 'Resize mode `%s` is not valid , avalaible values are %s',
- $mode,
- implode(', ', array(self::RESIZEMODE_FIT, self::RESIZEMODE_INSET, self::RESIZEMODE_SCALE_WIDTH, self::RESIZEMODE_SCALE_HEIGHT))
- );
- }
-
- $this->resizeMode = $mode;
-
- return $this;
- }
-
- /**
- * Get the current resize mode name
- *
- * @return string
- */
- public function getResizeMode()
- {
- return $this->resizeMode;
- }
-
/**
* {@inheritdoc}
*/
- public function getFrameRate()
+ public function getKiloBitrate()
{
- return $this->frameRate;
+ return $this->kiloBitrate;
}
/**
- * Set the framerate
+ * Sets the kiloBitrate value
*
- * @param integer $frameRate
- *
- * @throws \InvalidArgumentException
+ * @param integer $kiloBitrate
+ * @throws InvalidArgumentException
*/
- public function setFrameRate($frameRate)
+ public function setKiloBitrate($kiloBitrate)
{
- if ($frameRate < 1) {
- throw new InvalidArgumentException('Wrong framerate value');
+ if ($kiloBitrate < 1) {
+ throw new InvalidArgumentException('Wrong kiloBitrate value');
}
- $this->frameRate = (int) $frameRate;
+ $this->kiloBitrate = (int) $kiloBitrate;
return $this;
}
@@ -189,11 +66,11 @@ abstract class DefaultVideo extends DefaultAudio implements Interactive, Resampl
}
/**
- * Set the video codec, Should be in the available ones, otherwise an
+ * Sets the video codec, Should be in the available ones, otherwise an
* exception is thrown
*
- * @param string $videoCodec
- * @throws \InvalidArgumentException
+ * @param string $videoCodec
+ * @throws InvalidArgumentException
*/
public function setVideoCodec($videoCodec)
{
@@ -209,32 +86,6 @@ abstract class DefaultVideo extends DefaultAudio implements Interactive, Resampl
return $this;
}
- /**
- * {@inheritdoc}
- */
- public function getGOPsize()
- {
- return $this->GOPsize;
- }
-
- /**
- * Set the GOP size
- *
- * @param integer $GOPsize
- *
- * @throws \InvalidArgumentException
- */
- public function setGOPsize($GOPsize)
- {
- if ($GOPsize < 1) {
- throw new InvalidArgumentException('Wrong GOP size value');
- }
-
- $this->GOPsize = (int) $GOPsize;
-
- return $this;
- }
-
/**
* {@inheritDoc}
*/
@@ -244,24 +95,27 @@ abstract class DefaultVideo extends DefaultAudio implements Interactive, Resampl
}
/**
- * Used to determine what resolutions sizes are valid.
- *
- * @param int $value
- */
- public function setModulus($value)
- {
- if(!in_array($value, array(2, 4, 8, 16))){
- throw new InvalidArgumentException('Wrong modulus division value. Valid values are 2, 4, 8 or 16');
- }
-
- $this->modulus = $value;
- }
-
- /**
- * @return int
+ * @return integer
*/
public function getModulus()
{
return $this->modulus;
}
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createProgressListener(MediaTypeInterface $media, FFProbe $ffprobe, $pass, $total)
+ {
+ $format = $this;
+ $listeners = array(new VideoProgressListener($ffprobe, $media->getPathfile(), $pass, $total));
+
+ foreach ($listeners as $listener) {
+ $listener->on('progress', function () use ($format, $media) {
+ $format->emit('progress', array_merge(array($media, $format), func_get_args()));
+ });
+ }
+
+ return $listeners;
+ }
}
diff --git a/src/FFMpeg/Format/Video/Interactive.php b/src/FFMpeg/Format/Video/Interactive.php
deleted file mode 100644
index 4641d69..0000000
--- a/src/FFMpeg/Format/Video/Interactive.php
+++ /dev/null
@@ -1,30 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace FFMpeg\Format\Video;
-
-/**
- * The interactive video interface. This provide a method to list available
- * codecs. This is usefull to build interactive development and switch between
- * different codecs
- *
- * @author Romain Neutron imprec@gmail.com
- */
-interface Interactive extends Transcodable
-{
-
- /**
- * Returns the list of available video codecs for this format
- *
- * @return array
- */
- public function getAvailableVideoCodecs();
-}
diff --git a/src/FFMpeg/Format/Video/Ogg.php b/src/FFMpeg/Format/Video/Ogg.php
index a1aca8c..b67fd68 100644
--- a/src/FFMpeg/Format/Video/Ogg.php
+++ b/src/FFMpeg/Format/Video/Ogg.php
@@ -13,13 +13,15 @@ namespace FFMpeg\Format\Video;
/**
* The Ogg video format
- *
- * @author Romain Neutron imprec@gmail.com
*/
class Ogg extends DefaultVideo
{
- protected $audioCodec = 'libvorbis';
- protected $videoCodec = 'libtheora';
+ public function __construct($audioCodec = 'libvorbis', $videoCodec = 'libtheora')
+ {
+ $this
+ ->setAudioCodec($audioCodec)
+ ->setVideoCodec($videoCodec);
+ }
/**
* {@inheritDoc}
diff --git a/src/FFMpeg/Format/Video/Resamplable.php b/src/FFMpeg/Format/Video/Resamplable.php
deleted file mode 100644
index dd00ebc..0000000
--- a/src/FFMpeg/Format/Video/Resamplable.php
+++ /dev/null
@@ -1,50 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace FFMpeg\Format\Video;
-
-use FFMpeg\Format\VideoInterface;
-
-/**
- * The resamplable video interface
- *
- * This interface provides frame rate and GOP size settings for video encoding
- *
- * @author Romain Neutron imprec@gmail.com
- */
-interface Resamplable extends VideoInterface
-{
-
- /**
- * Returns the frame rate
- *
- * @return integer
- */
- public function getFrameRate();
-
- /**
- * Returns true if the current format supports B-Frames
- *
- * @see https://wikipedia.org/wiki/Video_compression_picture_types
- *
- * @return Boolean
- */
- public function supportBFrames();
-
- /**
- * Returns the GOP size
- *
- * @see https://wikipedia.org/wiki/Group_of_pictures
- *
- * @return integer
- */
- public function getGOPSize();
-}
diff --git a/src/FFMpeg/Format/Video/Resizable.php b/src/FFMpeg/Format/Video/Resizable.php
deleted file mode 100644
index 335c85f..0000000
--- a/src/FFMpeg/Format/Video/Resizable.php
+++ /dev/null
@@ -1,48 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace FFMpeg\Format\Video;
-
-use FFMpeg\Format\VideoInterface;
-use FFMpeg\Format\Dimension;
-
-/**
- * The resizable video interface
- *
- * This interface provides methods for video resizing.
- *
- * @author Romain Neutron imprec@gmail.com
- */
-interface Resizable extends VideoInterface
-{
-
- /**
- * Returns the computed dimensions for the resize, after operation.
- * This method return the actual dimensions that FFmpeg will use.
- *
- * @param integer $originalWidth
- * @param integer $originalHeight
- * @return Dimension A dimension
- */
- public function getComputedDimensions($originalWidth, $originalHeight);
-
- /**
- * Returns the modulus used by the Resizable video.
- *
- * This used to calculate the target dimensions while maintaining the best
- * aspect ratio.
- *
- * @see http://www.undeadborn.net/tools/rescalculator.php
- *
- * @return integer
- */
- public function getModulus();
-}
diff --git a/src/FFMpeg/Format/Video/Transcodable.php b/src/FFMpeg/Format/Video/Transcodable.php
deleted file mode 100644
index 2c6a92a..0000000
--- a/src/FFMpeg/Format/Video/Transcodable.php
+++ /dev/null
@@ -1,28 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace FFMpeg\Format\Video;
-
-use FFMpeg\Format\VideoInterface;
-
-/**
- * @author Romain Neutron imprec@gmail.com
- */
-interface Transcodable extends VideoInterface
-{
-
- /**
- * Returns the video codec
- *
- * @return string
- */
- public function getVideoCodec();
-}
diff --git a/src/FFMpeg/Format/Video/WMV.php b/src/FFMpeg/Format/Video/WMV.php
index ae57ecd..56c170e 100644
--- a/src/FFMpeg/Format/Video/WMV.php
+++ b/src/FFMpeg/Format/Video/WMV.php
@@ -13,13 +13,15 @@ namespace FFMpeg\Format\Video;
/**
* The WMV video format
- *
- * @author Romain Neutron imprec@gmail.com
*/
class WMV extends DefaultVideo
{
- protected $audioCodec = 'wmav2';
- protected $videoCodec = 'wmv2';
+ public function __construct($audioCodec = 'wmav2', $videoCodec = 'wmv2')
+ {
+ $this
+ ->setAudioCodec($audioCodec)
+ ->setVideoCodec($videoCodec);
+ }
/**
* {@inheritDoc}
diff --git a/src/FFMpeg/Format/Video/WMV3.php b/src/FFMpeg/Format/Video/WMV3.php
index a534b52..575936e 100644
--- a/src/FFMpeg/Format/Video/WMV3.php
+++ b/src/FFMpeg/Format/Video/WMV3.php
@@ -13,13 +13,15 @@ namespace FFMpeg\Format\Video;
/**
* The WMV video format
- *
- * @author Romain Neutron imprec@gmail.com
*/
class WMV3 extends DefaultVideo
{
- protected $audioCodec = 'wmav3';
- protected $videoCodec = 'wmv3';
+ public function __construct($audioCodec = 'wmav3', $videoCodec = 'wmv3')
+ {
+ $this
+ ->setAudioCodec($audioCodec)
+ ->setVideoCodec($videoCodec);
+ }
/**
* {@inheritDoc}
diff --git a/src/FFMpeg/Format/Video/WebM.php b/src/FFMpeg/Format/Video/WebM.php
index fbb3abc..e055c6a 100644
--- a/src/FFMpeg/Format/Video/WebM.php
+++ b/src/FFMpeg/Format/Video/WebM.php
@@ -13,13 +13,15 @@ namespace FFMpeg\Format\Video;
/**
* The WebM video format
- *
- * @author Romain Neutron imprec@gmail.com
*/
class WebM extends DefaultVideo
{
- protected $audioCodec = 'libvorbis';
- protected $videoCodec = 'libvpx';
+ public function __construct($audioCodec = 'libvorbis', $videoCodec = 'libvpx')
+ {
+ $this
+ ->setAudioCodec($audioCodec)
+ ->setVideoCodec($videoCodec);
+ }
/**
* {@inheritDoc}
diff --git a/src/FFMpeg/Format/Video/X264.php b/src/FFMpeg/Format/Video/X264.php
index f2d4018..40010fd 100644
--- a/src/FFMpeg/Format/Video/X264.php
+++ b/src/FFMpeg/Format/Video/X264.php
@@ -13,13 +13,15 @@ namespace FFMpeg\Format\Video;
/**
* The X264 video format
- *
- * @author Romain Neutron imprec@gmail.com
*/
class X264 extends DefaultVideo
{
- protected $audioCodec = 'libmp3lame';
- protected $videoCodec = 'libx264';
+ public function __construct($audioCodec = 'libfaac', $videoCodec = 'libx264')
+ {
+ $this
+ ->setAudioCodec($audioCodec)
+ ->setVideoCodec($videoCodec);
+ }
/**
* {@inheritDoc}
diff --git a/src/FFMpeg/Format/VideoInterface.php b/src/FFMpeg/Format/VideoInterface.php
index 6e16a5a..d1bbcf6 100644
--- a/src/FFMpeg/Format/VideoInterface.php
+++ b/src/FFMpeg/Format/VideoInterface.php
@@ -11,17 +11,54 @@
namespace FFMpeg\Format;
-/**
- * The base video interface
- *
- * @author Romain Neutron imprec@gmail.com
- */
interface VideoInterface extends AudioInterface
{
+ /**
+ * Get the kiloBitrate value
+ *
+ * @return integer
+ */
+ public function getKiloBitrate();
+
/**
* Returns the number of passes
*
* @return string
*/
public function getPasses();
+
+ /**
+ * Returns the modulus used by the Resizable video.
+ *
+ * This used to calculate the target dimensions while maintaining the best
+ * aspect ratio.
+ *
+ * @see http://www.undeadborn.net/tools/rescalculator.php
+ *
+ * @return integer
+ */
+ public function getModulus();
+
+ /**
+ * Returns the video codec
+ *
+ * @return string
+ */
+ public function getVideoCodec();
+
+ /**
+ * Returns true if the current format supports B-Frames
+ *
+ * @see https://wikipedia.org/wiki/Video_compression_picture_types
+ *
+ * @return Boolean
+ */
+ public function supportBFrames();
+
+ /**
+ * Returns the list of available video codecs for this format
+ *
+ * @return array
+ */
+ public function getAvailableVideoCodecs();
}
diff --git a/src/FFMpeg/Helper/HelperInterface.php b/src/FFMpeg/Helper/HelperInterface.php
deleted file mode 100644
index e7778c0..0000000
--- a/src/FFMpeg/Helper/HelperInterface.php
+++ /dev/null
@@ -1,42 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace FFMpeg\Helper;
-
-use FFMpeg\FFProbe;
-
-/**
- * @author Robert Gruendler
- */
-interface HelperInterface
-{
- /**
- * The callback from the ffmpeg process.
- *
- * @param string $channel (stdio|stderr)
- * @param string $content the current line of the ffmpeg output
- */
- function transcodeCallback($channel, $content);
-
- /**
- * The helper has access to a prober instance if available.
- *
- * @param FFProbe $prober
- */
- function setProber(FFProbe $prober);
-
- /**
- * Called when the input file is opened.
- *
- * @param string $pathfile
- */
- function open($pathfile);
-}
diff --git a/src/FFMpeg/Helper/ProgressHelper.php b/src/FFMpeg/Helper/ProgressHelper.php
deleted file mode 100644
index bf4fa1f..0000000
--- a/src/FFMpeg/Helper/ProgressHelper.php
+++ /dev/null
@@ -1,218 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace FFMpeg\Helper;
-
-use FFMpeg\FFProbe;
-
-/**
- * @author Robert Gruendler
- */
-abstract class ProgressHelper implements HelperInterface
-{
- /**
- * @var number
- */
- protected $duration = null;
-
- /**
- * transcoding rate in kb/s
- *
- * @var number
- */
- protected $rate;
-
- /**
- * @var array
- */
- protected $format;
-
- /**
- * @var number
- */
- protected $totalSize;
-
- /**
- * @var number
- */
- protected $currentSize;
-
- /**
- * @var number
- */
- protected $currentTime;
-
- /**
- * @var double
- */
- protected $lastOutput = null;
-
- /**
- * Percentage of transcoding progress (0 - 100)
- *
- * @var number
- */
- protected $percent = 0;
-
- /**
- * Time remaining (seconds)
- *
- * @var number
- */
- protected $remaining = null;
-
- /**
- * @var FFProbe
- */
- protected $prober;
-
- /**
- * @var Closure|string|array
- */
- protected $callback;
-
- /**
- * @param mixed $callback
- */
- public function __construct($callback)
- {
- $this->callback = $callback;
- }
-
- /**
- * Used to ease testing.
- *
- * @param number $duration
- */
- public function setDuration($duration)
- {
- $this->duration = $duration;
- }
-
- /*
- * {@inheritdoc}
- */
- public function transcodeCallback($channel, $content)
- {
- $progress = $this->parseProgress($content);
-
- if (is_array($progress)) {
- call_user_func_array($this->callback, $progress);
- }
- }
-
- /*
- * {@inheritdoc}
- */
- public function setProber(FFProbe $prober)
- {
- $this->prober = $prober;
- }
-
- /*
- * {@inheritdoc}
- */
- public function open($pathfile)
- {
- if ($this->prober === null) {
- throw new \RuntimeException('Unable to report audio progress without a prober');
- }
-
- $format = json_decode($this->prober->probeFormat($pathfile), true);
-
- if ($format === null || count($format) === 0 || isset($format['size']) === false) {
- throw new \RuntimeException('Unable to probe format for ' . $pathfile);
- }
-
- $this->format = $format;
- $this->totalSize = $format['size'] / 1024;
- $this->duration = $format['duration'];
- }
-
- /**
- * @param string $progress A ffmpeg stderr progress output
- * @return array the progressinfo array or null if there's no progress available yet.
- */
- public function parseProgress($progress)
- {
- $matches = array();
-
- if (preg_match($this->getPattern(), $progress, $matches) !== 1) {
- return null;
- }
-
- $currentDuration = $this->convertDuration($matches[2]);
- $currentTime = microtime(true);
- $currentSize = trim(str_replace('kb', '', strtolower(($matches[1]))));
- $percent = max(0, min(1, $currentDuration / $this->duration));
-
- if ($this->lastOutput !== null) {
- $delta = $currentTime - $this->lastOutput;
- $deltaSize = $currentSize - $this->currentSize;
- $rate = $deltaSize * $delta;
- if ($rate > 0) {
- $totalDuration = $this->totalSize / $rate;
- $this->remaining = floor($totalDuration - ($totalDuration * $percent));
- $this->rate = floor($rate);
- } else {
- $this->remaining = 0;
- $this->rate = 0;
- }
- }
-
- $this->percent = floor($percent * 100);
- $this->lastOutput = $currentTime;
- $this->currentSize = (int) $currentSize;
- $this->currentTime = $currentDuration;
-
- return $this->getProgressInfo();
- }
-
- /**
- *
- * @param string $rawDuration in the format 00:00:00.00
- * @return number
- */
- protected function convertDuration($rawDuration)
- {
- $ar = array_reverse(explode(":", $rawDuration));
- $duration = floatval($ar[0]);
- if (!empty($ar[1])) {
- $duration += intval($ar[1]) * 60;
- }
- if (!empty($ar[2])) {
- $duration += intval($ar[2]) * 60 * 60;
- }
-
- return $duration;
- }
-
- /**
- * @return array
- */
- public function getProgressInfo()
- {
- if ($this->remaining === null) {
- return null;
- }
-
- return array(
- 'percent' => $this->percent,
- 'remaining' => $this->remaining,
- 'rate' => $this->rate
- );
- }
-
- /**
- * Get the regex pattern to match a ffmpeg stderr status line
- */
- abstract function getPattern();
-}
diff --git a/src/FFMpeg/Media/AbstractMediaType.php b/src/FFMpeg/Media/AbstractMediaType.php
new file mode 100644
index 0000000..4cb3345
--- /dev/null
+++ b/src/FFMpeg/Media/AbstractMediaType.php
@@ -0,0 +1,126 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FFMpeg\Media;
+
+use FFMpeg\Driver\FFMpegDriver;
+use FFMpeg\Exception\InvalidArgumentException;
+use FFMpeg\FFProbe;
+use FFMpeg\Filters\FiltersCollection;
+use FFMpeg\Media\MediaTypeInterface;
+
+abstract class AbstractMediaType implements MediaTypeInterface
+{
+ /** @var string */
+ protected $pathfile;
+ /** @var FFMpegDriver */
+ protected $driver;
+ /** @var FFProbe */
+ protected $ffprobe;
+ /** @var FiltersCollection */
+ protected $filters;
+
+ public function __construct($pathfile, FFMpegDriver $driver, FFProbe $ffprobe)
+ {
+ $this->ensureFileIsPresent($pathfile);
+
+ $this->pathfile = $pathfile;
+ $this->driver = $driver;
+ $this->ffprobe = $ffprobe;
+ $this->filters = new FiltersCollection();
+ }
+
+ /**
+ * @return FFMpegDriver
+ */
+ public function getFFMpegDriver()
+ {
+ return $this->driver;
+ }
+
+ /**
+ * @param FFMpegDriver $driver
+ *
+ * @return MediaTypeInterface
+ */
+ public function setFFMpegDriver(FFMpegDriver $driver)
+ {
+ $this->driver = $driver;
+
+ return $this;
+ }
+
+ /**
+ * @return FFProbe
+ */
+ public function getFFProbe()
+ {
+ return $this->ffprobe;
+ }
+
+ /**
+ * @param FFProbe $ffprobe
+ *
+ * @return MediaTypeInterface
+ */
+ public function setFFProbe(FFProbe $ffprobe)
+ {
+ $this->ffprobe = $ffprobe;
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getPathfile()
+ {
+ return $this->pathfile;
+ }
+
+ /**
+ * @param FiltersCollection $filters
+ *
+ * @return MediaTypeInterface
+ */
+ public function setFiltersCollection(FiltersCollection $filters)
+ {
+ $this->filters = $filters;
+
+ return $this;
+ }
+
+ /**
+ * @return MediaTypeInterface
+ */
+ public function getFiltersCollection()
+ {
+ return $this->filters;
+ }
+
+ protected function ensureFileIsPresent($filename)
+ {
+ if (!is_file($filename) || !is_readable($filename)) {
+ throw new InvalidArgumentException(sprintf(
+ '%s is not present or not readable', $filename
+ ));
+ }
+ }
+
+ protected function cleanupTemporaryFile($filename)
+ {
+ if (file_exists($filename) && is_writable($filename)) {
+ unlink($filename);
+ }
+
+ return $this;
+ }
+}
diff --git a/src/FFMpeg/Media/AbstractStreamableMedia.php b/src/FFMpeg/Media/AbstractStreamableMedia.php
new file mode 100644
index 0000000..5458015
--- /dev/null
+++ b/src/FFMpeg/Media/AbstractStreamableMedia.php
@@ -0,0 +1,34 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FFMpeg\Media;
+
+use FFMpeg\FFProbe\DataMapping\Stream;
+use FFMpeg\FFProbe\DataMapping\StreamCollection;
+
+abstract class AbstractStreamableMedia extends AbstractMediaType
+{
+ /**
+ * @return StreamCollection
+ */
+ public function getStreams()
+ {
+ return $this->ffprobe->streams($this->pathfile);
+ }
+
+ /**
+ * @return Stream
+ */
+ public function getFormat()
+ {
+ return $this->ffprobe->format($this->pathfile);
+ }
+}
diff --git a/src/FFMpeg/Media/Audio.php b/src/FFMpeg/Media/Audio.php
new file mode 100644
index 0000000..2ba0304
--- /dev/null
+++ b/src/FFMpeg/Media/Audio.php
@@ -0,0 +1,92 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FFMpeg\Media;
+
+use Alchemy\BinaryDriver\Exception\ExecutionFailureException;
+use FFMpeg\Filters\Audio\AudioFilters;
+use FFMpeg\Format\FormatInterface;
+use FFMpeg\Exception\RuntimeException;
+use FFMpeg\Filters\Audio\AudioFilterInterface;
+use FFMpeg\Format\ProgressableInterface;
+
+class Audio extends AbstractStreamableMedia
+{
+ /**
+ * {@inheritdoc}
+ *
+ * @return AudioFilters
+ */
+ public function filters()
+ {
+ return new AudioFilters($this);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return Audio
+ */
+ public function addFilter(AudioFilterInterface $filter)
+ {
+ $this->filters->add($filter);
+
+ return $this;
+ }
+
+ /**
+ * Export the audio in the desired format, applies registered filters
+ *
+ * @param FormatInterface $format
+ * @param string $outputPathfile
+ *
+ * @return Audio
+ *
+ * @throws RuntimeException
+ */
+ public function save(FormatInterface $format, $outputPathfile)
+ {
+ $listeners = null;
+
+ if ($format instanceof ProgressableInterface) {
+ $listeners = $format->createProgressListener($this, $this->ffprobe, 1, 1);
+ }
+
+ $commands = array_merge(array('-y', '-i', $this->pathfile), $format->getExtraParams());
+
+ foreach ($this->filters as $filter) {
+ $commands = array_merge($commands, $filter->apply($this, $format));
+ }
+
+ if ($this->driver->getConfiguration()->has('ffmpeg.threads')) {
+ $commands[] = '-threads';
+ $commands[] = $this->driver->getConfiguration()->get('ffmpeg.threads');
+ }
+
+ if (null !== $format->getAudioCodec()) {
+ $commands[] = '-acodec';
+ $commands[] = $format->getAudioCodec();
+ }
+
+ $commands[] = '-b:a';
+ $commands[] = $format->getAudioKiloBitrate() . 'k';
+ $commands[] = $outputPathfile;
+
+ try {
+ $this->driver->command($commands, false, $listeners);
+ } catch (ExecutionFailureException $e) {
+ $this->cleanupTemporaryFile($outputPathfile);
+ throw new RuntimeException('Encoding failed', $e->getCode(), $e);
+ }
+
+ return $this;
+ }
+}
diff --git a/src/FFMpeg/Media/Frame.php b/src/FFMpeg/Media/Frame.php
new file mode 100644
index 0000000..9ffb38d
--- /dev/null
+++ b/src/FFMpeg/Media/Frame.php
@@ -0,0 +1,104 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FFMpeg\Media;
+
+use Alchemy\BinaryDriver\Exception\ExecutionFailureException;
+use FFMpeg\Filters\Frame\FrameFilterInterface;
+use FFMpeg\Filters\Frame\FrameFilters;
+use FFMpeg\Driver\FFMpegDriver;
+use FFMpeg\FFProbe;
+use FFMpeg\Exception\RuntimeException;
+use FFMpeg\Coordinate\TimeCode;
+
+class Frame extends AbstractMediaType
+{
+ /** @var TimeCode */
+ private $timecode;
+
+ public function __construct($pathfile, FFMpegDriver $driver, FFProbe $ffprobe, TimeCode $timecode)
+ {
+ parent::__construct($pathfile, $driver, $ffprobe);
+ $this->timecode = $timecode;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return FrameFilters
+ */
+ public function filters()
+ {
+ return new FrameFilters($this);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return Frame
+ */
+ public function addFilter(FrameFilterInterface $filter)
+ {
+ $this->filters->add($filter);
+
+ return $this;
+ }
+
+ /**
+ * @return TimeCode
+ */
+ public function getTimeCode()
+ {
+ return $this->timecode;
+ }
+
+ /**
+ * Saves the frame in the given filename.
+ *
+ * Uses the `unaccurate method by default.`
+ *
+ * @param string $pathfile
+ * @param Boolean $accurate
+ *
+ * @return Frame
+ *
+ * @throws RuntimeException
+ */
+ public function saveAs($pathfile, $accurate = false)
+ {
+ /**
+ * @see http://ffmpeg.org/ffmpeg.html#Main-options
+ */
+ if (!$accurate) {
+ $commands = array(
+ '-ss', (string) $this->timecode,
+ '-i', $this->pathfile,
+ '-vframes', '1',
+ '-f', 'image2', $pathfile
+ );
+ } else {
+ $commands = array(
+ '-i', $this->pathfile,
+ '-vframes', '1', '-ss', (string) $this->timecode,
+ '-f', 'image2', $pathfile
+ );
+ }
+
+ try {
+ $this->driver->command($commands);
+ } catch (ExecutionFailureException $e) {
+ $this->cleanupTemporaryFile($pathfile);
+ throw new RuntimeException('Unable to save frame', $e->getCode(), $e);
+ }
+
+ return $this;
+ }
+}
diff --git a/src/FFMpeg/Format/Audio/Transcodable.php b/src/FFMpeg/Media/MediaTypeInterface.php
similarity index 52%
rename from src/FFMpeg/Format/Audio/Transcodable.php
rename to src/FFMpeg/Media/MediaTypeInterface.php
index 3b85ecc..eca7f52 100644
--- a/src/FFMpeg/Format/Audio/Transcodable.php
+++ b/src/FFMpeg/Media/MediaTypeInterface.php
@@ -9,20 +9,17 @@
* file that was distributed with this source code.
*/
-namespace FFMpeg\Format\Audio;
+namespace FFMpeg\Media;
-use FFMpeg\Format\AudioInterface;
-
-/**
- * @author Romain Neutron imprec@gmail.com
- */
-interface Transcodable extends AudioInterface
+interface MediaTypeInterface
{
+ /**
+ * Returns the available filters
+ */
+ public function filters();
/**
- * Returns the audio codec
- *
* @return string
*/
- public function getAudioCodec();
+ public function getPathfile();
}
diff --git a/src/FFMpeg/Media/Video.php b/src/FFMpeg/Media/Video.php
new file mode 100644
index 0000000..b368497
--- /dev/null
+++ b/src/FFMpeg/Media/Video.php
@@ -0,0 +1,166 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FFMpeg\Media;
+
+use Alchemy\BinaryDriver\Exception\ExecutionFailureException;
+use FFMpeg\Coordinate\TimeCode;
+use FFMpeg\Exception\RuntimeException;
+use FFMpeg\Filters\Video\VideoFilters;
+use FFMpeg\Filters\Video\VideoFilterInterface;
+use FFMpeg\Format\VideoInterface;
+use FFMpeg\Format\ProgressableInterface;
+use FFMpeg\Media\Frame;
+
+class Video extends AbstractStreamableMedia
+{
+ /**
+ * {@inheritdoc}
+ *
+ * @return VideoFilters
+ */
+ public function filters()
+ {
+ return new VideoFilters($this);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return Video
+ */
+ public function addFilter(VideoFilterInterface $filter)
+ {
+ $this->filters->add($filter);
+
+ return $this;
+ }
+
+ /**
+ * Export the video in the desired format, applies registered filters
+ *
+ * @param FormatInterface $format
+ * @param string $outputPathfile
+ *
+ * @return Video
+ *
+ * @throws RuntimeException
+ */
+ public function save(VideoInterface $format, $outputPathfile)
+ {
+ $commands = array_merge(array('-y', '-i', $this->pathfile), $format->getExtraParams());
+
+ foreach ($this->filters as $filter) {
+ $commands = array_merge($commands, $filter->apply($this, $format));
+ }
+
+ if ($this->driver->getConfiguration()->has('ffmpeg.threads')) {
+ $commands[] = '-threads';
+ $commands[] = $this->driver->getConfiguration()->get('ffmpeg.threads');
+ }
+
+ if (null !== $format->getVideoCodec()) {
+ $commands[] = '-vcodec';
+ $commands[] = $format->getVideoCodec();
+ }
+ if (null !== $format->getAudioCodec()) {
+ $commands[] = '-acodec';
+ $commands[] = $format->getAudioCodec();
+ }
+
+ $commands[] = '-b:v';
+ $commands[] = $format->getKiloBitrate() . 'k';
+ $commands[] = '-refs';
+ $commands[] = '6';
+ $commands[] = '-coder';
+ $commands[] = '1';
+ $commands[] = '-sc_threshold';
+ $commands[] = '40';
+ $commands[] = '-flags';
+ $commands[] = '+loop';
+ $commands[] = '-me_range';
+ $commands[] = '16';
+ $commands[] = '-subq';
+ $commands[] = '7';
+ $commands[] = '-i_qfactor';
+ $commands[] = '0.71';
+ $commands[] = '-qcomp';
+ $commands[] = '0.6';
+ $commands[] = '-qdiff';
+ $commands[] = '4';
+ $commands[] = '-trellis';
+ $commands[] = '1';
+ $commands[] = '-b:a';
+ $commands[] = $format->getAudioKiloBitrate() . 'k';
+
+ $passPrefix = uniqid('pass-');
+
+ $pass1 = $commands;
+ $pass2 = $commands;
+
+ $pass1[] = '-pass';
+ $pass1[] = '1';
+ $pass1[] = '-passlogfile';
+ $pass1[] = $passPrefix;
+ $pass1[] = '-an';
+ $pass1[] = $outputPathfile;
+
+ $pass2[] = '-pass';
+ $pass2[] = '2';
+ $pass2[] = '-passlogfile';
+ $pass2[] = $passPrefix;
+ $pass2[] = '-ac';
+ $pass2[] = '2';
+ $pass2[] = '-ar';
+ $pass2[] = '44100';
+ $pass2[] = $outputPathfile;
+
+ $failure = null;
+
+ foreach (array($pass1, $pass2) as $pass => $passCommands) {
+ try {
+ /** add listeners here */
+ $listeners = null;
+
+ if ($format instanceof ProgressableInterface) {
+ $listeners = $format->createProgressListener($this, $this->ffprobe, $pass + 1, 2);
+ }
+
+ $this->driver->command($passCommands, false, $listeners);
+ } catch (ExecutionFailureException $e) {
+ $failure = $e;
+ break;
+ }
+ }
+
+ $this
+ ->cleanupTemporaryFile(getcwd() . '/' . $passPrefix . '-0.log')
+ ->cleanupTemporaryFile(getcwd() . '/' . $passPrefix . '-0.log')
+ ->cleanupTemporaryFile(getcwd() . '/' . $passPrefix . '-0.log.mbtree');
+
+ if (null !== $failure) {
+ throw new RuntimeException('Encoding failed', $failure->getCode(), $failure);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the frame at timecode
+ *
+ * @param Timecode $at
+ * @return Frame
+ */
+ public function frame(Timecode $at)
+ {
+ return new Frame($this->pathfile, $this->driver, $this->ffprobe, $at);
+ }
+}
diff --git a/tests/FFMpeg/Functional/FunctionalTestCase.php b/tests/FFMpeg/Functional/FunctionalTestCase.php
new file mode 100644
index 0000000..062bf68
--- /dev/null
+++ b/tests/FFMpeg/Functional/FunctionalTestCase.php
@@ -0,0 +1,13 @@
+ 300));
+ }
+}
diff --git a/tests/FFMpeg/Functional/VideoTranscodeTest.php b/tests/FFMpeg/Functional/VideoTranscodeTest.php
new file mode 100644
index 0000000..2b9ff24
--- /dev/null
+++ b/tests/FFMpeg/Functional/VideoTranscodeTest.php
@@ -0,0 +1,38 @@
+getFFMpeg();
+ $video = $ffmpeg->open(__DIR__ . '/../../files/Test.ogv');
+
+ $this->assertInstanceOf('FFMpeg\Media\Video', $video);
+
+ $lastPercentage = null;
+ $phpunit = $this;
+
+ $codec = new X264('libvo_aacenc');
+ $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);
+ }
+}
diff --git a/tests/FFMpeg/Functional/output/.placeholder b/tests/FFMpeg/Functional/output/.placeholder
new file mode 100644
index 0000000..e69de29
diff --git a/tests/FFMpeg/Tests/Coordinate/AspectRatioTest.php b/tests/FFMpeg/Tests/Coordinate/AspectRatioTest.php
new file mode 100644
index 0000000..49e703c
--- /dev/null
+++ b/tests/FFMpeg/Tests/Coordinate/AspectRatioTest.php
@@ -0,0 +1,83 @@
+assertEquals($expected, $ratio->getValue());
+
+ $this->assertEquals($calculatedHeight, $ratio->calculateHeight(240, $modulus));
+ $this->assertEquals($calculatedWidth, $ratio->calculateWidth(320, $modulus));
+ }
+
+ public function provideDimensionsAndExpectedratio()
+ {
+ return array(
+ //AR_5_4
+ array(720, 576, false, 5/4, 400, 192),
+ array(720, 577, false, 5/4, 400, 192),
+ array(720, 620, false, 720/620, 372, 206),
+ array(720, 576, true, 5/4, 400, 192),
+ //AR_ROTATED_4_5
+ array(576, 720, false, 4/5, 256, 300),
+ array(576, 720, true, 4/5, 256, 300),
+ //AR_4_3
+ array(320, 240, false, 4/3, 426, 180),
+ array(320, 240, true, 4/3, 426, 180),
+ //AR_ROTATED_3_4
+ array(240, 320, false, 3/4, 240, 320),
+ array(240, 320, true, 3/4, 240, 320),
+ //AR_16_9
+ array(1920, 1080, false, 16/9, 568, 136),
+ array(1920, 1080, true, 16/9, 568, 136),
+ array(1280, 720, false, 16/9, 568, 136),
+ array(1280, 720, true, 16/9, 568, 136),
+ array(3840, 2160, false, 16/9, 568, 136),
+ array(3840, 2160, true, 16/9, 568, 136),
+ // modulus 4
+ array(1920, 1080, false, 16/9, 568, 136, 4),
+ array(1920, 1080, true, 16/9, 568, 136, 4),
+ array(1280, 720, false, 16/9, 568, 136, 4),
+ array(1280, 720, true, 16/9, 568, 136, 4),
+ array(3840, 2160, false, 16/9, 568, 136, 4),
+ array(3840, 2160, true, 16/9, 568, 136, 4),
+ // modulus 16
+ array(1920, 1080, false, 16/9, 576, 128, 16),
+ array(1920, 1080, true, 16/9, 576, 128, 16),
+ array(1280, 720, false, 16/9, 576, 128, 16),
+ array(1280, 720, true, 16/9, 576, 128, 16),
+ array(3840, 2160, false, 16/9, 576, 128, 16),
+ array(3840, 2160, true, 16/9, 576, 128, 16),
+ //AR_ROTATED_9_16
+ array(1080, 1920, false, 9/16, 180, 426),
+ array(1080, 1920, true, 9/16, 180, 426),
+ array(720, 1280, false, 9/16, 180, 426),
+ array(720, 1280, true, 9/16, 180, 426),
+ array(2160, 3840, false, 9/16, 180, 426),
+ array(2160, 3840, true, 9/16, 180, 426),
+ //AR_3_2
+ array(360, 240, false, 3/2, 480, 160),
+ array(360, 240, true, 3/2, 480, 160),
+ //AR_ROTATED_2_3
+ array(240, 360, false, 2/3, 214, 360),
+ array(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
+ );
+ }
+}
diff --git a/tests/FFMpeg/Tests/Coordinate/DimensionTest.php b/tests/FFMpeg/Tests/Coordinate/DimensionTest.php
new file mode 100644
index 0000000..2b3e14b
--- /dev/null
+++ b/tests/FFMpeg/Tests/Coordinate/DimensionTest.php
@@ -0,0 +1,38 @@
+assertEquals(320, $dimension->getWidth());
+ $this->assertEquals(240, $dimension->getHeight());
+ }
+}
diff --git a/tests/FFMpeg/Tests/Coordinate/FrameRateTest.php b/tests/FFMpeg/Tests/Coordinate/FrameRateTest.php
new file mode 100644
index 0000000..51bfbff
--- /dev/null
+++ b/tests/FFMpeg/Tests/Coordinate/FrameRateTest.php
@@ -0,0 +1,31 @@
+assertEquals(23.997, $fr->getValue());
+ }
+
+ /**
+ * @dataProvider provideInvalidFrameRates
+ * @expectedException FFMpeg\Exception\InvalidArgumentException
+ */
+ public function testInvalidFrameRate($value)
+ {
+ new FrameRate($value);
+ }
+
+ public function provideInvalidFrameRates()
+ {
+ return array(
+ array(0), array(-1.5), array(-2),
+ );
+ }
+}
diff --git a/tests/FFMpeg/Tests/Coordinate/PointTest.php b/tests/FFMpeg/Tests/Coordinate/PointTest.php
new file mode 100644
index 0000000..33e3d37
--- /dev/null
+++ b/tests/FFMpeg/Tests/Coordinate/PointTest.php
@@ -0,0 +1,16 @@
+assertEquals(4, $point->getX());
+ $this->assertEquals(25, $point->getY());
+ }
+}
diff --git a/tests/FFMpeg/Tests/Coordinate/TimeCodeTest.php b/tests/FFMpeg/Tests/Coordinate/TimeCodeTest.php
new file mode 100644
index 0000000..241ee47
--- /dev/null
+++ b/tests/FFMpeg/Tests/Coordinate/TimeCodeTest.php
@@ -0,0 +1,38 @@
+assertEquals((string) $tc, $expected);
+ }
+
+ public function provideTimeCodes()
+ {
+ return array(
+ array('1:02:04:05:20', '26:04:05.20'),
+ array('1:02:04:05.20', '26:04:05.20'),
+ array('02:04:05:20', '02:04:05.20'),
+ array('02:04:05.20', '02:04:05.20'),
+ array('00:00:05.20', '00:00:05.20'),
+ array('00:00:00.00', '00:00:00.00'),
+ );
+ }
+
+ /**
+ * @expectedException FFMpeg\Exception\InvalidArgumentException
+ */
+ public function testFromInvalidString()
+ {
+ TimeCode::fromString('lalali lala');
+ }
+}
diff --git a/tests/FFMpeg/Tests/Driver/FFMpegDriverTest.php b/tests/FFMpeg/Tests/Driver/FFMpegDriverTest.php
new file mode 100644
index 0000000..3463c81
--- /dev/null
+++ b/tests/FFMpeg/Tests/Driver/FFMpegDriverTest.php
@@ -0,0 +1,43 @@
+find($name)) {
+ $found = true;
+ break;
+ }
+ }
+
+ if (!$found) {
+ $this->markTestSkipped('Neither ffmpeg or avconv found');
+ }
+ }
+
+ public function testCreate()
+ {
+ $logger = $this->getLoggerMock();
+ $ffmpeg = FFMpegDriver::create($logger, array());
+ $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());
+ }
+}
diff --git a/tests/FFMpeg/Tests/Driver/FFProbeDriverTest.php b/tests/FFMpeg/Tests/Driver/FFProbeDriverTest.php
new file mode 100644
index 0000000..3ec3de5
--- /dev/null
+++ b/tests/FFMpeg/Tests/Driver/FFProbeDriverTest.php
@@ -0,0 +1,43 @@
+find($name)) {
+ $found = true;
+ break;
+ }
+ }
+
+ if (!$found) {
+ $this->markTestSkipped('Neither ffprobe or avprobe found');
+ }
+ }
+
+ public function testCreate()
+ {
+ $logger = $this->getLoggerMock();
+ $ffprobe = FFProbeDriver::create(array(), $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());
+ }
+}
diff --git a/tests/FFMpeg/Tests/FFMpegServiceProviderTest.php b/tests/FFMpeg/Tests/FFMpegServiceProviderTest.php
new file mode 100644
index 0000000..371133e
--- /dev/null
+++ b/tests/FFMpeg/Tests/FFMpegServiceProviderTest.php
@@ -0,0 +1,69 @@
+register(new FFMpegServiceProvider(), array(
+ 'ffmpeg.configuration' => array(
+ 'ffmpeg.threads' => 12,
+ 'ffmpeg.timeout' => 10666,
+ 'ffprobe.timeout' => 4242,
+ )
+ ));
+
+ $this->assertInstanceOf('FFMpeg\FFMpeg', $app['ffmpeg']);
+ $this->assertSame($app['ffmpeg'], $app['ffmpeg.ffmpeg']);
+ $this->assertInstanceOf('FFMpeg\FFProbe', $app['ffmpeg.ffprobe']);
+
+ $this->assertEquals(12, $app['ffmpeg']->getFFMpegDriver()->getConfiguration()->get('ffmpeg.threads'));
+ $this->assertEquals(10666, $app['ffmpeg']->getFFMpegDriver()->getProcessBuilderFactory()->getTimeout());
+ $this->assertEquals(4242, $app['ffmpeg.ffprobe']->getFFProbeDriver()->getProcessBuilderFactory()->getTimeout());
+ }
+
+ public function testWithoutConfig()
+ {
+ $app = new Application();
+ $app->register(new FFMpegServiceProvider());
+
+ $this->assertInstanceOf('FFMpeg\FFMpeg', $app['ffmpeg']);
+ $this->assertSame($app['ffmpeg'], $app['ffmpeg.ffmpeg']);
+ $this->assertInstanceOf('FFMpeg\FFProbe', $app['ffmpeg.ffprobe']);
+
+ $this->assertEquals(4, $app['ffmpeg']->getFFMpegDriver()->getConfiguration()->get('ffmpeg.threads'));
+ $this->assertEquals(300, $app['ffmpeg']->getFFMpegDriver()->getProcessBuilderFactory()->getTimeout());
+ $this->assertEquals(30, $app['ffmpeg.ffprobe']->getFFProbeDriver()->getProcessBuilderFactory()->getTimeout());
+ }
+
+ public function testWithFFMpegBinaryConfig()
+ {
+ $app = new Application();
+ $app->register(new FFMpegServiceProvider(), array(
+ 'ffmpeg.configuration' => array(
+ 'ffmpeg.binaries' => '/path/to/ffmpeg',
+ )
+ ));
+
+ $this->setExpectedException('Alchemy\BinaryDriver\Exception\ExecutableNotFoundException', 'Executable not found, proposed : /path/to/ffmpeg');
+ $app['ffmpeg'];
+ }
+
+ public function testWithFFMprobeBinaryConfig()
+ {
+ $app = new Application();
+ $app->register(new FFMpegServiceProvider(), array(
+ 'ffmpeg.configuration' => array(
+ 'ffprobe.binaries' => '/path/to/ffprobe',
+ )
+ ));
+
+ $this->setExpectedException('Alchemy\BinaryDriver\Exception\ExecutableNotFoundException', 'Executable not found, proposed : /path/to/ffprobe');
+ $app['ffmpeg.ffprobe'];
+ }
+}
diff --git a/tests/FFMpeg/Tests/FFMpegTest.php b/tests/FFMpeg/Tests/FFMpegTest.php
new file mode 100644
index 0000000..6695df0
--- /dev/null
+++ b/tests/FFMpeg/Tests/FFMpegTest.php
@@ -0,0 +1,112 @@
+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(array(new Stream(array())))));
+ $streams->expects($this->once())
+ ->method('videos')
+ ->will($this->returnValue(array()));
+
+ $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(array(new Stream(array())))));
+ $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__));
+ }
+
+ /**
+ * @expectedException FFMpeg\Exception\InvalidArgumentException
+ */
+ public function testOpenUnknown()
+ {
+ $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(array('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());
+ }
+}
diff --git a/tests/FFMpeg/Tests/FFProbe/DataMapping/AbstractDataTest.php b/tests/FFMpeg/Tests/FFProbe/DataMapping/AbstractDataTest.php
new file mode 100644
index 0000000..d2a4d63
--- /dev/null
+++ b/tests/FFMpeg/Tests/FFProbe/DataMapping/AbstractDataTest.php
@@ -0,0 +1,56 @@
+ '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(array('key1' => 'value1', 'key2' => 'value2'));
+
+ $this->assertEquals('value1', $imp->get('key1'));
+ $this->assertEquals('value2', $imp->get('key2'));
+ }
+
+ /**
+ * @expectedException FFMpeg\Exception\InvalidArgumentException
+ */
+ public function testGetInvalid()
+ {
+ $imp = new Implementation(array('key1' => 'value1', 'key2' => 'value2'));
+
+ $imp->get('key3');
+ }
+
+ public function testKeys()
+ {
+ $imp = new Implementation(array('key1' => 'value1', 'key2' => 'value2'));
+
+ $this->assertEquals(array('key1', 'key2'), $imp->keys());
+ }
+
+ public function testAll()
+ {
+ $values = array('key1' => 'value1', 'key2' => 'value2');
+ $imp = new Implementation($values);
+
+ $this->assertEquals($values, $imp->all());
+ }
+}
+
+class Implementation extends AbstractData
+{
+}
diff --git a/tests/FFMpeg/Tests/FFProbe/DataMapping/StreamCollectionTest.php b/tests/FFMpeg/Tests/FFProbe/DataMapping/StreamCollectionTest.php
new file mode 100644
index 0000000..e010f1a
--- /dev/null
+++ b/tests/FFMpeg/Tests/FFProbe/DataMapping/StreamCollectionTest.php
@@ -0,0 +1,89 @@
+getStreamMock();
+
+ $collection = new StreamCollection();
+ $this->assertEquals(array(), $collection->all());
+ $collection->add($stream);
+ $this->assertEquals(array($stream), $collection->all());
+ $collection->add($stream);
+ $this->assertEquals(array($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(array($audio, $video));
+ $videos = $collection->videos();
+
+ $this->assertInstanceOf('FFMpeg\FFProbe\DataMapping\StreamCollection', $videos);
+ $this->assertCount(1, $videos);
+ $this->assertEquals(array($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(array($audio, $video));
+ $audios = $collection->audios();
+
+ $this->assertInstanceOf('FFMpeg\FFProbe\DataMapping\StreamCollection', $audios);
+ $this->assertCount(1, $audios);
+ $this->assertEquals(array($audio), $audios->all());
+ }
+
+ public function testCount()
+ {
+ $stream = $this->getStreamMock();
+
+ $collection = new StreamCollection(array($stream));
+ $this->assertCount(1, $collection);
+ }
+
+ public function testGetIterator()
+ {
+ $audio = $this->getStreamMock();
+ $video = $this->getStreamMock();
+
+ $collection = new StreamCollection(array($audio, $video));
+ $this->assertInstanceOf('\Iterator', $collection->getIterator());
+ $this->assertCount(2, $collection->getIterator());
+ }
+
+ public function testFirst()
+ {
+ $stream1 = $this->getStreamMock();
+ $stream2 = $this->getStreamMock();
+
+ $coll = new StreamCollection(array($stream1, $stream2));
+
+ $this->assertSame($stream1, $coll->first());
+ }
+}
diff --git a/tests/FFMpeg/Tests/FFProbe/DataMapping/StreamTest.php b/tests/FFMpeg/Tests/FFProbe/DataMapping/StreamTest.php
new file mode 100644
index 0000000..e1ea7b6
--- /dev/null
+++ b/tests/FFMpeg/Tests/FFProbe/DataMapping/StreamTest.php
@@ -0,0 +1,43 @@
+assertTrue($isAudio === $stream->isAudio());
+ }
+
+ public function provideAudioCases()
+ {
+ return array(
+ array(true, array('codec_type' => 'audio')),
+ array(false, array('codec_type' => 'video')),
+ );
+ }
+
+ /**
+ * @dataProvider provideVideoCases
+ */
+ public function testIsVideo($isVideo, $properties)
+ {
+ $stream = new Stream($properties);
+ $this->assertTrue($isVideo === $stream->isVideo());
+ }
+
+ public function provideVideoCases()
+ {
+ return array(
+ array(true, array('codec_type' => 'video')),
+ array(false, array('codec_type' => 'audio')),
+ );
+ }
+}
diff --git a/tests/FFMpeg/Tests/FFProbe/MapperTest.php b/tests/FFMpeg/Tests/FFProbe/MapperTest.php
new file mode 100644
index 0000000..519e1b4
--- /dev/null
+++ b/tests/FFMpeg/Tests/FFProbe/MapperTest.php
@@ -0,0 +1,44 @@
+assertEquals($expected, $mapper->map($type, $data));
+ }
+
+ /**
+ * @expectedException FFMpeg\Exception\InvalidArgumentException
+ */
+ public function testMapInvalidArgument()
+ {
+ $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 array(
+ array(FFProbe::TYPE_FORMAT, $format, new Format($format['format'])),
+ array(FFProbe::TYPE_STREAMS, $streams, new StreamCollection(array_map(function ($streamData) {
+ return new Stream($streamData);
+ }, $streams['streams']))),
+ );
+ }
+}
diff --git a/tests/FFMpeg/Tests/FFProbe/OptionsTesterTest.php b/tests/FFMpeg/Tests/FFProbe/OptionsTesterTest.php
new file mode 100644
index 0000000..b743a2c
--- /dev/null
+++ b/tests/FFMpeg/Tests/FFProbe/OptionsTesterTest.php
@@ -0,0 +1,101 @@
+getCacheMock();
+
+ $cache->expects($this->never())
+ ->method('fetch');
+
+ $cache->expects($this->exactly(2))
+ ->method('contains')
+ ->will($this->returnValue(false));
+
+ $cache->expects($this->exactly(2))
+ ->method('save');
+
+ $ffprobe = $this->getFFProbeDriverMock();
+ $ffprobe->expects($this->once())
+ ->method('command')
+ ->with(array('-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 array(
+ array(true, $data, '-print_format'),
+ array(false, $data, '-another_print_format'),
+ );
+ }
+
+ /**
+ * @dataProvider provideOptions
+ */
+ public function testHasOptionWithHelpCacheLoaded($isPresent, $data, $optionName)
+ {
+ $cache = $this->getCacheMock();
+
+ $cache->expects($this->once())
+ ->method('fetch')
+ ->will($this->returnValue($data));
+
+ $cache->expects($this->at(0))
+ ->method('contains')
+ ->will($this->returnValue(false));
+
+ $cache->expects($this->at(1))
+ ->method('contains')
+ ->will($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();
+
+ $cache->expects($this->once())
+ ->method('fetch')
+ ->with('option-' . $optionName)
+ ->will($this->returnValue($isPresent));
+
+ $cache->expects($this->once())
+ ->method('contains')
+ ->with('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));
+ }
+}
diff --git a/tests/FFMpeg/Tests/FFProbe/OutputParserTest.php b/tests/FFMpeg/Tests/FFProbe/OutputParserTest.php
new file mode 100644
index 0000000..96e90af
--- /dev/null
+++ b/tests/FFMpeg/Tests/FFProbe/OutputParserTest.php
@@ -0,0 +1,42 @@
+assertEquals($expectedOutput, $parser->parse($type, $data));
+ }
+
+ /**
+ * @expectedException FFMpeg\Exception\InvalidArgumentException
+ */
+ public function testParseWithInvalidArgument()
+ {
+ $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 array(
+ array(FFProbe::TYPE_FORMAT, $rawFormat, $expectedFormat),
+ array(FFProbe::TYPE_STREAMS, $rawStreams, $expectedStreams),
+ );
+ }
+}
diff --git a/tests/FFMpeg/Tests/FFProbeTest.php b/tests/FFMpeg/Tests/FFProbeTest.php
new file mode 100644
index 0000000..bf0ca3c
--- /dev/null
+++ b/tests/FFMpeg/Tests/FFProbeTest.php
@@ -0,0 +1,308 @@
+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 array(
+ array($stream, 'streams', array('-show_streams', '-print_format'), FFProbe::TYPE_STREAMS, array(__FILE__, '-show_streams', '-print_format', 'json'), false),
+ array($format, 'format', array('-show_format', '-print_format'), FFProbe::TYPE_FORMAT, array(__FILE__, '-show_format', '-print_format', 'json'), false),
+ array($stream, 'streams', array('-show_streams'), FFProbe::TYPE_STREAMS, array(__FILE__, '-show_streams'), true),
+ array($format, 'format', array('-show_format'), FFProbe::TYPE_FORMAT, array(__FILE__, '-show_format'), true),
+ );
+ }
+
+ /**
+ * @dataProvider provideDataWhitoutCache
+ */
+ public function testProbeWithoutCache($output, $method, $commands, $type, $caughtCommands, $isRaw)
+ {
+ $pathfile = __FILE__;
+ $data = array('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('contains')
+ ->will($this->returnValue(false));
+ $cache->expects($this->never())
+ ->method('fetch');
+ $cache->expects($this->once())
+ ->method('save')
+ ->with($this->anything(), $output);
+
+ $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(array($ffprobe, $method), $pathfile));
+ }
+
+ public function provideDataForInvalidJson()
+ {
+ $stream = $this->getStreamMock();
+ $format = $this->getFormatMock();
+
+ return array(
+ array($stream, 'streams', array('-show_streams', '-print_format'), FFProbe::TYPE_STREAMS, array(__FILE__, '-show_streams', '-print_format', 'json')),
+ array($format, 'format', array('-show_format', '-print_format'), FFProbe::TYPE_FORMAT, array(__FILE__, '-show_format', '-print_format', 'json')),
+ );
+ }
+
+ /**
+ * @dataProvider provideDataForInvalidJson
+ */
+ public function testProbeWithWrongJson($output, $method, $commands, $type, $caughtCommands)
+ {
+ $pathfile = __FILE__;
+ $data = array('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('contains')
+ ->will($this->returnValue(false));
+ $cache->expects($this->never())
+ ->method('fetch');
+
+ $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(array($ffprobe, $method), $pathfile));
+ }
+
+ public function provideProbingDataWithCache()
+ {
+ $stream = $this->getStreamMock();
+ $format = $this->getFormatMock();
+
+ return array(
+ array($stream, 'streams'),
+ array($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();
+
+ $cache = $this->getCacheMock();
+ $cache->expects($this->once())
+ ->method('contains')
+ ->will($this->returnValue(true));
+ $cache->expects($this->once())
+ ->method('fetch')
+ ->will($this->returnValue($output));
+ $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(array($ffprobe, $method), $pathfile));
+ }
+
+ /**
+ * @expectedException FFMpeg\Exception\InvalidArgumentException
+ * @dataProvider provideProbeMethod
+ */
+ public function testProbeWithInvalidFile($method)
+ {
+ $pathfile = '/path/to/nofile';
+
+ $ffprobe = new FFProbe($this->getFFProbeDriverMock(), $this->getCacheMock());
+ call_user_func(array($ffprobe, $method), $pathfile);
+ }
+
+ public function provideProbeMethod()
+ {
+ return array(
+ array('streams'),
+ array('format'),
+ );
+ }
+
+ /**
+ * @expectedException FFMpeg\Exception\RuntimeException
+ * @dataProvider provideProbeMethod
+ */
+ public function testProbeWithoutShowStreamsAvailable($method)
+ {
+ $pathfile = __FILE__;
+
+ $ffprobe = new FFProbe($this->getFFProbeDriverMock(), $this->getCacheMock());
+ $ffprobe->setOptionsTester($this->getFFProbeOptionsTesterMock());
+ call_user_func(array($ffprobe, $method), $pathfile);
+ }
+
+ /**
+ * @dataProvider provideCreateOptions
+ */
+ public function testCreate($logger, $conf, $cache)
+ {
+ $finder = new ExecutableFinder();
+
+ $found = false;
+ foreach (array('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 array(
+ array(null, array('key' => 'value'), null),
+ array($this->getLoggerMock(), array('key' => 'value'), null),
+ array(null, new Configuration(), null),
+ array(null, array('key' => 'value'), $this->getCacheMock()),
+ );
+ }
+}
diff --git a/tests/FFMpeg/Tests/Filters/Audio/AudioFiltersTest.php b/tests/FFMpeg/Tests/Filters/Audio/AudioFiltersTest.php
new file mode 100644
index 0000000..930cca9
--- /dev/null
+++ b/tests/FFMpeg/Tests/Filters/Audio/AudioFiltersTest.php
@@ -0,0 +1,26 @@
+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());
+ }
+}
diff --git a/tests/FFMpeg/Tests/Filters/Audio/AudioResamplableFilterTest.php b/tests/FFMpeg/Tests/Filters/Audio/AudioResamplableFilterTest.php
new file mode 100644
index 0000000..fbed42c
--- /dev/null
+++ b/tests/FFMpeg/Tests/Filters/Audio/AudioResamplableFilterTest.php
@@ -0,0 +1,24 @@
+assertEquals(500, $filter->getRate());
+ }
+
+ public function testApply()
+ {
+ $audio = $this->getAudioMock();
+ $format = $this->getMock('FFMpeg\Format\AudioInterface');
+
+ $filter = new AudioResamplableFilter(500);
+ $this->assertEquals(array('-ac', 2, '-ar', 500), $filter->apply($audio, $format));
+ }
+}
diff --git a/tests/FFMpeg/Tests/Filters/FiltersCollectionTest.php b/tests/FFMpeg/Tests/Filters/FiltersCollectionTest.php
new file mode 100644
index 0000000..6ac820e
--- /dev/null
+++ b/tests/FFMpeg/Tests/Filters/FiltersCollectionTest.php
@@ -0,0 +1,30 @@
+assertCount(0, $coll);
+
+ $coll->add($this->getMock('FFMpeg\Filters\FilterInterface'));
+ $this->assertCount(1, $coll);
+
+ $coll->add($this->getMock('FFMpeg\Filters\FilterInterface'));
+ $this->assertCount(2, $coll);
+ }
+
+ public function testIterator()
+ {
+ $coll = new FiltersCollection();
+ $coll->add($this->getMock('FFMpeg\Filters\FilterInterface'));
+ $coll->add($this->getMock('FFMpeg\Filters\FilterInterface'));
+
+ $this->assertInstanceOf('\ArrayIterator', $coll->getIterator());
+ $this->assertCount(2, $coll->getIterator());
+ }
+}
diff --git a/tests/FFMpeg/Tests/Filters/Video/ResizeFilterTest.php b/tests/FFMpeg/Tests/Filters/Video/ResizeFilterTest.php
new file mode 100644
index 0000000..09b58a6
--- /dev/null
+++ b/tests/FFMpeg/Tests/Filters/Video/ResizeFilterTest.php
@@ -0,0 +1,75 @@
+getVideoMock();
+ $pathfile = '/path/to/file'.mt_rand();
+
+ $format = $this->getMock('FFMpeg\Format\VideoInterface');
+ $format->expects($this->any())
+ ->method('getModulus')
+ ->will($this->returnValue($modulus));
+
+ $streams = new StreamCollection(array(
+ new Stream(array(
+ 'codec_type' => 'video',
+ '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 array(
+ array(new Dimension(320, 240), ResizeFilter::RESIZEMODE_FIT, 640, 480, 2, array('-s', '320x240')),
+ array(new Dimension(320, 240), ResizeFilter::RESIZEMODE_INSET, 640, 480, 2, array('-s', '320x240')),
+ array(new Dimension(320, 240), ResizeFilter::RESIZEMODE_SCALE_HEIGHT, 640, 480, 2, array('-s', '320x240')),
+ array(new Dimension(320, 240), ResizeFilter::RESIZEMODE_SCALE_WIDTH, 640, 480, 2, array('-s', '320x240')),
+
+ array(new Dimension(640, 480), ResizeFilter::RESIZEMODE_FIT, 320, 240, 2, array('-s', '640x480')),
+ array(new Dimension(640, 480), ResizeFilter::RESIZEMODE_INSET, 320, 240, 2, array('-s', '640x480')),
+ array(new Dimension(640, 480), ResizeFilter::RESIZEMODE_SCALE_HEIGHT, 320, 240, 2, array('-s', '640x480')),
+ array(new Dimension(640, 480), ResizeFilter::RESIZEMODE_SCALE_WIDTH, 320, 240, 2, array('-s', '640x480')),
+
+ array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_FIT, 1280, 720, 2, array('-s', '640x360')),
+ array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_INSET, 1280, 720, 2, array('-s', '640x360')),
+ array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_SCALE_HEIGHT, 1280, 720, 2, array('-s', '640x360')),
+ array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_SCALE_WIDTH, 1280, 720, 2, array('-s', '640x360')),
+
+ array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_FIT, 1280, 720, 2, array('-s', '640x360')),
+ array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_INSET, 1280, 720, 2, array('-s', '640x360')),
+ array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_SCALE_HEIGHT, 1280, 720, 2, array('-s', '640x360')),
+ array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_SCALE_WIDTH, 1280, 720, 2, array('-s', '640x360')),
+
+ // test non standard dimension
+ array(new Dimension(700, 150), ResizeFilter::RESIZEMODE_INSET, 123, 456, 2, array('-s', '62x150'), true),
+ array(new Dimension(700, 150), ResizeFilter::RESIZEMODE_INSET, 123, 456, 2, array('-s', '40x150'), false),
+
+ array(new Dimension(320, 320), ResizeFilter::RESIZEMODE_FIT, 640, 480, 2, array('-s', '320x320')),
+ array(new Dimension(320, 320), ResizeFilter::RESIZEMODE_INSET, 640, 480, 2, array('-s', '320x240')),
+ array(new Dimension(320, 320), ResizeFilter::RESIZEMODE_SCALE_HEIGHT, 640, 480, 2, array('-s', '320x240')),
+ array(new Dimension(320, 320), ResizeFilter::RESIZEMODE_SCALE_WIDTH, 640, 480, 2, array('-s', '426x320')),
+ );
+ }
+}
diff --git a/tests/FFMpeg/Tests/Filters/Video/SynchronizeFilterTest.php b/tests/FFMpeg/Tests/Filters/Video/SynchronizeFilterTest.php
new file mode 100644
index 0000000..8a50f05
--- /dev/null
+++ b/tests/FFMpeg/Tests/Filters/Video/SynchronizeFilterTest.php
@@ -0,0 +1,58 @@
+getVideoMock();
+ $format = $this->getMock('FFMpeg\Format\VideoInterface');
+ $video->expects($this->once())
+ ->method('getStreams')
+ ->will($this->returnValue($streams));
+ $video->expects($this->any())
+ ->method('getPathfile')
+ ->will($this->returnValue(__FILE__));
+
+ $filter = new SynchronizeFilter();
+ $this->assertEquals($expected, $filter->apply($video, $format));
+ }
+
+ public function provideStreams()
+ {
+ $audio = new StreamCollection(array(new Stream(array(
+ 'index' => 0,
+ 'codec_type' => 'audio',
+ ))));
+ $synced = new StreamCollection(array(new Stream(array(
+ 'index' => 0,
+ 'codec_type' => 'video',
+ )), new Stream(array(
+ 'index' => 1,
+ 'codec_type' => 'audio',
+ ))));
+ $video = new StreamCollection(array(new Stream(array(
+ 'index' => 0,
+ 'codec_type' => 'video',
+ 'start_time' => '0.123456',
+ )), new Stream(array(
+ 'index' => 1,
+ 'codec_type' => 'audio',
+ ))));
+
+ return array(
+ array($audio, array()),
+ array($synced, array()),
+ array($video, array('-itsoffset', '0.123456', '-i', __FILE__, '-map', '1:0', '-map', '0:1')),
+ );
+ }
+}
diff --git a/tests/FFMpeg/Tests/Filters/Video/VideoFiltersTest.php b/tests/FFMpeg/Tests/Filters/Video/VideoFiltersTest.php
new file mode 100644
index 0000000..3524d6f
--- /dev/null
+++ b/tests/FFMpeg/Tests/Filters/Video/VideoFiltersTest.php
@@ -0,0 +1,79 @@
+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 array(
+ array(ResizeFilter::RESIZEMODE_FIT, true),
+ array(ResizeFilter::RESIZEMODE_SCALE_WIDTH, true),
+ array(ResizeFilter::RESIZEMODE_SCALE_HEIGHT, false),
+ array(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\VideoResampleFilter'))
+ ->will($this->returnCallback(function ($filter) use (&$capturedFilter) {
+ $capturedFilter = $filter;
+ }));
+
+ $filters->resample($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();
+ }
+}
diff --git a/tests/FFMpeg/Tests/Filters/Video/VideoResampleFilterTest.php b/tests/FFMpeg/Tests/Filters/Video/VideoResampleFilterTest.php
new file mode 100644
index 0000000..dc6634c
--- /dev/null
+++ b/tests/FFMpeg/Tests/Filters/Video/VideoResampleFilterTest.php
@@ -0,0 +1,44 @@
+getVideoMock();
+ $format = $this->getMock('FFMpeg\Format\VideoInterface');
+ $format->expects($this->any())
+ ->method('supportBFrames')
+ ->will($this->returnValue(true));
+
+ $expected = array('-r', 54, '-b_strategy', '1', '-bf', '3', '-g', 42);
+
+ $filter = new VideoResampleFilter($framerate, $gop);
+ $this->assertEquals($expected, $filter->apply($video, $format));
+ }
+
+ public function testApplyWithAFormatThatDoesNotSupportsBFrames()
+ {
+ $framerate = new FrameRate(54);
+ $gop = 42;
+
+ $video = $this->getVideoMock();
+ $format = $this->getMock('FFMpeg\Format\VideoInterface');
+ $format->expects($this->any())
+ ->method('supportBFrames')
+ ->will($this->returnValue(false));
+
+ $expected = array('-r', 54);
+
+ $filter = new VideoResampleFilter($framerate, $gop);
+ $this->assertEquals($expected, $filter->apply($video, $format));
+ }
+}
diff --git a/tests/FFMpeg/Tests/Format/Audio/AudioTestCase.php b/tests/FFMpeg/Tests/Format/Audio/AudioTestCase.php
new file mode 100644
index 0000000..ff75478
--- /dev/null
+++ b/tests/FFMpeg/Tests/Format/Audio/AudioTestCase.php
@@ -0,0 +1,96 @@
+getFormat()->getExtraParams() 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());
+ }
+ }
+
+ /**
+ * @expectedException FFMpeg\Exception\InvalidArgumentException
+ */
+ public function testSetInvalidAudioCodec()
+ {
+ $this->getFormat()->setAudioCodec('invalid-random-audio-codec');
+ }
+
+ public function testGetAvailableAudioCodecs()
+ {
+ $this->assertGreaterThan(0, count($this->getFormat()->getAvailableAudioCodecs()));
+ }
+
+ public function testGetAudioKiloBitrate()
+ {
+ $this->assertInternalType('integer', $this->getFormat()->getAudioKiloBitrate());
+ }
+
+ public function testSetAudioKiloBitrate()
+ {
+ $format = $this->getFormat();
+ $format->setAudioKiloBitrate(256);
+ $this->assertEquals(256, $format->getAudioKiloBitrate());
+ }
+
+ /**
+ * @expectedException FFMpeg\Exception\InvalidArgumentException
+ */
+ public function testSetInvalidKiloBitrate()
+ {
+ $this->getFormat()->setAudioKiloBitrate(0);
+ }
+
+ /**
+ * @expectedException FFMpeg\Exception\InvalidArgumentException
+ */
+ public function testSetNegativeKiloBitrate()
+ {
+ $this->getFormat()->setAudioKiloBitrate(-10);
+ }
+
+ public function testCreateProgressListener()
+ {
+ $media = $this->getMock('FFMpeg\Media\MediaTypeInterface');
+ $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();
+}
diff --git a/tests/FFMpeg/Tests/Format/Audio/FlacTest.php b/tests/FFMpeg/Tests/Format/Audio/FlacTest.php
new file mode 100644
index 0000000..6b45226
--- /dev/null
+++ b/tests/FFMpeg/Tests/Format/Audio/FlacTest.php
@@ -0,0 +1,13 @@
+getFFProbeMock();
+ $ffprobe->expects($this->once())
+ ->method('format')
+ ->with(__FILE__)
+ ->will($this->returnValue(new Format(array(
+ '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 array(
+ array(
+ 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
+ ),
+ array(
+ 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
+ )
+ );
+ }
+}
diff --git a/tests/FFMpeg/Tests/Format/ProgressListener/VideoProgressListenerTest.php b/tests/FFMpeg/Tests/Format/ProgressListener/VideoProgressListenerTest.php
new file mode 100644
index 0000000..1e6663d
--- /dev/null
+++ b/tests/FFMpeg/Tests/Format/ProgressListener/VideoProgressListenerTest.php
@@ -0,0 +1,87 @@
+getFFProbeMock();
+ $ffprobe->expects($this->once())
+ ->method('format')
+ ->with(__FILE__)
+ ->will($this->returnValue(new Format(array(
+ 'size' => $size,
+ 'duration' => $duration,
+ ))));
+
+ $listener = new VideoProgressListener($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 + 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 array(
+ array(
+ 147073958,
+ 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
+ ),
+ array(
+ 147073958,
+ 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
+ )
+ );
+ }
+}
diff --git a/tests/FFMpeg/Tests/Format/Video/OggTest.php b/tests/FFMpeg/Tests/Format/Video/OggTest.php
new file mode 100644
index 0000000..16bb2ae
--- /dev/null
+++ b/tests/FFMpeg/Tests/Format/Video/OggTest.php
@@ -0,0 +1,13 @@
+assertScalar($this->getFormat()->getVideoCodec());
+ $this->assertContains($this->getFormat()->getVideoCodec(), $this->getFormat()->getAvailableVideoCodecs());
+ }
+
+ public function testSupportBFrames()
+ {
+ $this->assertInternalType('boolean', $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->assertInternalType('integer', $this->getFormat()->getKiloBitrate());
+ }
+
+ public function testSetKiloBitrate()
+ {
+ $format = $this->getFormat();
+ $format->setKiloBitrate(2560);
+ $this->assertEquals(2560, $format->getKiloBitrate());
+ }
+
+ /**
+ * @expectedException FFMpeg\Exception\InvalidArgumentException
+ */
+ public function testSetInvalidVideoCodec()
+ {
+ $this->getFormat()->setVideoCodec('invalid-random-video-codec');
+ }
+
+ public function testGetAvailableVideoCodecs()
+ {
+ $this->assertGreaterThan(0, count($this->getFormat()->getAvailableVideoCodecs()));
+ }
+
+ public function testCreateProgressListener()
+ {
+ $media = $this->getMock('FFMpeg\Media\MediaTypeInterface');
+ $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->assertInternalType('integer', $this->getFormat()->getPasses());
+ $this->assertGreaterThan(0, $this->getFormat()->getPasses());
+ }
+
+ public function testGetModulus()
+ {
+ $this->assertInternalType('integer', $this->getFormat()->getModulus());
+ $this->assertGreaterThan(0, $this->getFormat()->getModulus());
+ $this->assertEquals(0, $this->getFormat()->getModulus() % 2);
+ }
+}
diff --git a/tests/FFMpeg/Tests/Format/Video/WMV3Test.php b/tests/FFMpeg/Tests/Format/Video/WMV3Test.php
new file mode 100644
index 0000000..c035c60
--- /dev/null
+++ b/tests/FFMpeg/Tests/Format/Video/WMV3Test.php
@@ -0,0 +1,13 @@
+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();
+}
diff --git a/tests/FFMpeg/Tests/Media/AudioTest.php b/tests/FFMpeg/Tests/Media/AudioTest.php
new file mode 100644
index 0000000..79f5653
--- /dev/null
+++ b/tests/FFMpeg/Tests/Media/AudioTest.php
@@ -0,0 +1,268 @@
+getFFMpegDriverMock();
+ $ffprobe = $this->getFFProbeMock();
+
+ new Audio('/no/file', $driver, $ffprobe);
+ }
+
+ 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->getMock('FFMpeg\Filters\Audio\AudioFilterInterface');
+
+ $filters->expects($this->once())
+ ->method('add')
+ ->with($filter);
+
+ $audio->addFilter($filter);
+ }
+
+ public function testSaveWithFailure()
+ {
+ $driver = $this->getFFMpegDriverMock();
+ $ffprobe = $this->getFFProbeMock();
+ $outputPathfile = '/target/file';
+
+ $format = $this->getMock('FFMpeg\Format\AudioInterface');
+ $format->expects($this->any())
+ ->method('getExtraParams')
+ ->will($this->returnValue(array()));
+
+ $configuration = $this->getMock('Alchemy\BinaryDriver\ConfigurationInterface');
+
+ $driver->expects($this->any())
+ ->method('getConfiguration')
+ ->will($this->returnValue($configuration));
+
+ $failure = new ExecutionFailureException('failed to encode');
+ $driver->expects($this->once())
+ ->method('command')
+ ->will($this->throwException($failure));
+
+ $audio = new Audio(__FILE__, $driver, $ffprobe);
+ $this->setExpectedException('FFMpeg\Exception\RuntimeException');
+ $audio->save($format, $outputPathfile);
+ }
+
+ public function testSaveAppliesFilters()
+ {
+ $driver = $this->getFFMpegDriverMock();
+ $ffprobe = $this->getFFProbeMock();
+ $outputPathfile = '/target/file';
+ $format = $this->getMock('FFMpeg\Format\AudioInterface');
+ $format->expects($this->any())
+ ->method('getExtraParams')
+ ->will($this->returnValue(array()));
+
+ $configuration = $this->getMock('Alchemy\BinaryDriver\ConfigurationInterface');
+
+ $driver->expects($this->any())
+ ->method('getConfiguration')
+ ->will($this->returnValue($configuration));
+
+ $audio = new Audio(__FILE__, $driver, $ffprobe);
+
+ $filter = $this->getMock('FFMpeg\Filters\Audio\AudioFilterInterface');
+ $filter->expects($this->once())
+ ->method('apply')
+ ->with($audio, $format)
+ ->will($this->returnValue(array('extra-filter-command')));
+
+ $capturedCommands = array();
+
+ $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->getMock('Alchemy\BinaryDriver\ConfigurationInterface');
+
+ $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->getMock('FFMpeg\Format\AudioInterface');
+ $format->expects($this->any())
+ ->method('getExtraParams')
+ ->will($this->returnValue(array()));
+ $format->expects($this->any())
+ ->method('getAudioKiloBitrate')
+ ->will($this->returnValue(663));
+
+ $audioFormat = $this->getMock('FFMpeg\Format\AudioInterface');
+ $audioFormat->expects($this->any())
+ ->method('getExtraParams')
+ ->will($this->returnValue(array()));
+ $audioFormat->expects($this->any())
+ ->method('getAudioKiloBitrate')
+ ->will($this->returnValue(664));
+ $audioFormat->expects($this->any())
+ ->method('getAudioCodec')
+ ->will($this->returnValue('patati-patata-audio'));
+
+ $formatExtra = $this->getMock('FFMpeg\Format\AudioInterface');
+ $formatExtra->expects($this->any())
+ ->method('getExtraParams')
+ ->will($this->returnValue(array('extra', 'param')));
+ $formatExtra->expects($this->any())
+ ->method('getAudioKiloBitrate')
+ ->will($this->returnValue(665));
+
+ $listeners = array($this->getMock('Alchemy\BinaryDriver\Listeners\ListenerInterface'));
+
+ $progressableFormat = $this->getMockBuilder('FFMpeg\Tests\Media\AudioProg')
+ ->disableOriginalConstructor()->getMock();
+ $progressableFormat->expects($this->any())
+ ->method('getExtraParams')
+ ->will($this->returnValue(array()));
+ $progressableFormat->expects($this->any())
+ ->method('createProgressListener')
+ ->will($this->returnValue($listeners));
+ $progressableFormat->expects($this->any())
+ ->method('getAudioKiloBitrate')
+ ->will($this->returnValue(666));
+
+ return array(
+ array(false, array(
+ '-y', '-i', __FILE__,
+ '-b:a', '663k',
+ '/target/file',
+ ), null, $format),
+ array(false, array(
+ '-y', '-i', __FILE__,
+ '-acodec', 'patati-patata-audio',
+ '-b:a', '664k',
+ '/target/file',
+ ), null, $audioFormat),
+ array(false, array(
+ '-y', '-i', __FILE__,
+ 'extra', 'param',
+ '-b:a', '665k',
+ '/target/file',
+ ), null, $formatExtra),
+ array(true, array(
+ '-y', '-i', __FILE__,
+ '-threads', 24,
+ '-b:a', '663k',
+ '/target/file',
+ ), null, $format),
+ array(true, array(
+ '-y', '-i', __FILE__,
+ 'extra', 'param',
+ '-threads', 24,
+ '-b:a', '665k',
+ '/target/file',
+ ), null, $formatExtra),
+ array(false, array(
+ '-y', '-i', __FILE__,
+ '-b:a', '666k',
+ '/target/file',
+ ), $listeners, $progressableFormat),
+ array(true, array(
+ '-y', '-i', __FILE__,
+ '-threads', 24,
+ '-b:a', '666k',
+ '/target/file',
+ ), $listeners, $progressableFormat),
+ );
+ }
+
+ public function getClassName()
+ {
+ return 'FFMpeg\Media\Audio';
+ }
+}
+
+abstract class AudioProg implements ProgressableInterface, AudioInterface
+{
+}
diff --git a/tests/FFMpeg/Tests/Media/FrameTest.php b/tests/FFMpeg/Tests/Media/FrameTest.php
new file mode 100644
index 0000000..7d0b8e4
--- /dev/null
+++ b/tests/FFMpeg/Tests/Media/FrameTest.php
@@ -0,0 +1,98 @@
+getFFMpegDriverMock(), $this->getFFProbeMock(), $this->getTimeCodeMock());
+ }
+
+ public function testGetTimeCode()
+ {
+ $driver = $this->getFFMpegDriverMock();
+ $ffprobe = $this->getFFProbeMock();
+ $timecode = $this->getTimeCodeMock();
+
+ $frame = new Frame(__FILE__, $driver, $ffprobe, $timecode);
+ $this->assertSame($timecode, $frame->getTimeCode());
+ }
+
+ public function testFiltersReturnFilters()
+ {
+ $driver = $this->getFFMpegDriverMock();
+ $ffprobe = $this->getFFProbeMock();
+ $timecode = $this->getTimeCodeMock();
+
+ $frame = new Frame(__FILE__, $driver, $ffprobe, $timecode);
+ $this->assertInstanceOf('FFMpeg\Filters\Frame\FrameFilters', $frame->filters());
+ }
+
+ public function testAddFiltersAddsAFilter()
+ {
+ $driver = $this->getFFMpegDriverMock();
+ $ffprobe = $this->getFFProbeMock();
+ $timecode = $this->getTimeCodeMock();
+
+ $filters = $this->getMockBuilder('FFMpeg\Filters\FiltersCollection')
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $filter = $this->getMock('FFMpeg\Filters\Frame\FrameFilterInterface');
+
+ $filters->expects($this->once())
+ ->method('add')
+ ->with($filter);
+
+ $frame = new Frame(__FILE__, $driver, $ffprobe, $timecode);
+ $frame->setFiltersCollection($filters);
+ $frame->addFilter($filter);
+ }
+
+ /**
+ * @dataProvider provideSaveAsOptions
+ */
+ public function testSaveAs($accurate, $commands)
+ {
+ $driver = $this->getFFMpegDriverMock();
+ $ffprobe = $this->getFFProbeMock();
+ $timecode = $this->getTimeCodeMock();
+ $timecode->expects($this->once())
+ ->method('__toString')
+ ->will($this->returnValue('timecode'));
+
+ $pathfile = '/target/destination';
+
+ array_push($commands, $pathfile);
+
+ $driver->expects($this->once())
+ ->method('command')
+ ->with($commands);
+
+ $frame = new Frame(__FILE__, $driver, $ffprobe, $timecode);
+ $this->assertSame($frame, $frame->saveAs($pathfile, $accurate));
+ }
+
+ public function provideSaveAsOptions()
+ {
+ return array(
+ array(false, array(
+ '-ss', 'timecode',
+ '-i', __FILE__,
+ '-vframes', '1',
+ '-f', 'image2')
+ ),
+ array(true, array(
+ '-i', __FILE__,
+ '-vframes', '1', '-ss', 'timecode',
+ '-f', 'image2'
+ )),
+ );
+ }
+}
diff --git a/tests/FFMpeg/Tests/Media/VideoTest.php b/tests/FFMpeg/Tests/Media/VideoTest.php
new file mode 100644
index 0000000..14f290d
--- /dev/null
+++ b/tests/FFMpeg/Tests/Media/VideoTest.php
@@ -0,0 +1,394 @@
+getFFMpegDriverMock();
+ $ffprobe = $this->getFFProbeMock();
+
+ new Video('/no/file', $driver, $ffprobe);
+ }
+
+ public function testFiltersReturnsVideoFilters()
+ {
+ $driver = $this->getFFMpegDriverMock();
+ $ffprobe = $this->getFFProbeMock();
+
+ $video = new Video(__FILE__, $driver, $ffprobe);
+ $this->assertInstanceOf('FFMpeg\Filters\Video\VideoFilters', $video->filters());
+ }
+
+ public function testAddFiltersAddsAFilter()
+ {
+ $driver = $this->getFFMpegDriverMock();
+ $ffprobe = $this->getFFProbeMock();
+
+ $filters = $this->getMockBuilder('FFMpeg\Filters\FiltersCollection')
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $video = new Video(__FILE__, $driver, $ffprobe);
+ $video->setFiltersCollection($filters);
+
+ $filter = $this->getMock('FFMpeg\Filters\Video\VideoFilterInterface');
+
+ $filters->expects($this->once())
+ ->method('add')
+ ->with($filter);
+
+ $video->addFilter($filter);
+ }
+
+ public function testFrameShouldReturnAFrame()
+ {
+ $driver = $this->getFFMpegDriverMock();
+ $ffprobe = $this->getFFProbeMock();
+
+ $at = $this->getTimeCodeMock();
+
+ $video = new Video(__FILE__, $driver, $ffprobe);
+ $frame = $video->frame($at);
+
+ $this->assertInstanceOf('FFMpeg\Media\Frame', $frame);
+ $this->assertSame($at, $frame->getTimeCode());
+ $this->assertSame(__FILE__, $frame->getPathfile());
+ }
+
+ public function testSaveWithFailure()
+ {
+ $driver = $this->getFFMpegDriverMock();
+ $ffprobe = $this->getFFProbeMock();
+ $outputPathfile = '/target/file';
+ $format = $this->getMock('FFMpeg\Format\VideoInterface');
+ $format->expects($this->any())
+ ->method('getExtraParams')
+ ->will($this->returnValue(array()));
+
+ $configuration = $this->getMock('Alchemy\BinaryDriver\ConfigurationInterface');
+
+ $driver->expects($this->any())
+ ->method('getConfiguration')
+ ->will($this->returnValue($configuration));
+
+ $failure = new ExecutionFailureException('failed to encode');
+ $driver->expects($this->once())
+ ->method('command')
+ ->will($this->throwException($failure));
+
+ $video = new Video(__FILE__, $driver, $ffprobe);
+ $this->setExpectedException('FFMpeg\Exception\RuntimeException');
+ $video->save($format, $outputPathfile);
+ }
+
+ public function testSaveAppliesFilters()
+ {
+ $driver = $this->getFFMpegDriverMock();
+ $ffprobe = $this->getFFProbeMock();
+ $outputPathfile = '/target/file';
+ $format = $this->getMock('FFMpeg\Format\VideoInterface');
+ $format->expects($this->any())
+ ->method('getExtraParams')
+ ->will($this->returnValue(array()));
+
+ $configuration = $this->getMock('Alchemy\BinaryDriver\ConfigurationInterface');
+
+ $driver->expects($this->any())
+ ->method('getConfiguration')
+ ->will($this->returnValue($configuration));
+
+ $video = new Video(__FILE__, $driver, $ffprobe);
+
+ $filter = $this->getMock('FFMpeg\Filters\Video\VideoFilterInterface');
+ $filter->expects($this->once())
+ ->method('apply')
+ ->with($video, $format)
+ ->will($this->returnValue(array('extra-filter-command')));
+
+ $capturedCommands = array();
+
+ $driver->expects($this->exactly(2))
+ ->method('command')
+ ->with($this->isType('array'), false, $this->anything())
+ ->will($this->returnCallback(function ($commands, $errors, $listeners) use (&$capturedCommands) {
+ $capturedCommands[] = $commands;
+ }));
+
+ $video->addFilter($filter);
+ $video->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->getMock('Alchemy\BinaryDriver\ConfigurationInterface');
+
+ $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');
+ }
+
+ $capturedCommands = array();
+ $capturedListeners = null;
+
+ $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;
+ $capturedListeners = $listeners;
+ }));
+
+ $outputPathfile = '/target/file';
+
+ $video = new Video(__FILE__, $driver, $ffprobe);
+ $video->save($format, $outputPathfile);
+
+ $prefix = null;
+
+ foreach ($capturedCommands as $passKey => $pass) {
+ foreach ($pass as $command) {
+ if (0 === strpos($command, 'pass-')) {
+ $prefix = $command;
+ break;
+ }
+ }
+
+ if (null === $prefix) {
+ $this->fail('Unable to find pass prefix command.');
+ }
+
+ $found = false;
+ foreach ($pass as $key => $command) {
+ if ($command === $prefix) {
+ $found = true;
+ unset($capturedCommands[$passKey][$key]);
+ $capturedCommands[$passKey] = array_values($capturedCommands[$passKey]);
+ break;
+ }
+ }
+
+ if (!$found) {
+ $this->fail('Unable to find pass prefix command back.');
+ }
+ }
+
+ $this->assertEquals($expectedCommands, $capturedCommands);
+ $this->assertEquals($expectedListeners, $capturedListeners);
+ }
+
+ public function provideSaveData()
+ {
+ $format = $this->getMock('FFMpeg\Format\VideoInterface');
+ $format->expects($this->any())
+ ->method('getExtraParams')
+ ->will($this->returnValue(array()));
+ $format->expects($this->any())
+ ->method('getKiloBitrate')
+ ->will($this->returnValue(663));
+ $format->expects($this->any())
+ ->method('getAudioKiloBitrate')
+ ->will($this->returnValue(92));
+
+ $audioVideoFormat = $this->getMock('FFMpeg\Format\VideoInterface');
+ $audioVideoFormat->expects($this->any())
+ ->method('getExtraParams')
+ ->will($this->returnValue(array()));
+ $audioVideoFormat->expects($this->any())
+ ->method('getVideoCodec')
+ ->will($this->returnValue('gloubi-boulga-video'));
+ $audioVideoFormat->expects($this->any())
+ ->method('getAudioCodec')
+ ->will($this->returnValue('patati-patata-audio'));
+ $audioVideoFormat->expects($this->any())
+ ->method('getKiloBitrate')
+ ->will($this->returnValue(664));
+ $audioVideoFormat->expects($this->any())
+ ->method('getAudioKiloBitrate')
+ ->will($this->returnValue(92));
+
+ $formatExtra = $this->getMock('FFMpeg\Format\VideoInterface');
+ $formatExtra->expects($this->any())
+ ->method('getExtraParams')
+ ->will($this->returnValue(array('extra', 'param')));
+ $formatExtra->expects($this->any())
+ ->method('getKiloBitrate')
+ ->will($this->returnValue(665));
+ $formatExtra->expects($this->any())
+ ->method('getAudioKiloBitrate')
+ ->will($this->returnValue(92));
+
+ $listeners = array($this->getMock('Alchemy\BinaryDriver\Listeners\ListenerInterface'));
+
+ $progressableFormat = $this->getMockBuilder('FFMpeg\Tests\Media\Prog')
+ ->disableOriginalConstructor()->getMock();
+ $progressableFormat->expects($this->any())
+ ->method('getExtraParams')
+ ->will($this->returnValue(array()));
+ $progressableFormat->expects($this->any())
+ ->method('createProgressListener')
+ ->will($this->returnValue($listeners));
+ $progressableFormat->expects($this->any())
+ ->method('getKiloBitrate')
+ ->will($this->returnValue(666));
+ $progressableFormat->expects($this->any())
+ ->method('getAudioKiloBitrate')
+ ->will($this->returnValue(92));
+
+ return array(
+ array(false, array(array(
+ '-y', '-i', __FILE__, '-b:v', '663k',
+ '-refs', '6', '-coder', '1', '-sc_threshold', '40', '-flags', '+loop',
+ '-me_range', '16', '-subq', '7', '-i_qfactor', '0.71', '-qcomp', '0.6',
+ '-qdiff', '4', '-trellis', '1', '-b:a', '92k', '-pass', '1', '-passlogfile',
+ '-an', '/target/file',
+ ), array(
+ '-y', '-i', __FILE__,
+ '-b:v', '663k',
+ '-refs', '6', '-coder', '1', '-sc_threshold', '40', '-flags', '+loop',
+ '-me_range', '16', '-subq', '7', '-i_qfactor', '0.71', '-qcomp', '0.6',
+ '-qdiff', '4', '-trellis', '1', '-b:a', '92k', '-pass', '2', '-passlogfile',
+ '-ac', '2', '-ar', '44100', '/target/file',
+ )), null, $format),
+ array(false, array(array(
+ '-y', '-i', __FILE__,
+ '-vcodec', 'gloubi-boulga-video',
+ '-acodec', 'patati-patata-audio', '-b:v', '664k',
+ '-refs', '6', '-coder', '1', '-sc_threshold', '40', '-flags', '+loop',
+ '-me_range', '16', '-subq', '7', '-i_qfactor', '0.71', '-qcomp', '0.6',
+ '-qdiff', '4', '-trellis', '1', '-b:a', '92k', '-pass', '1', '-passlogfile',
+ '-an', '/target/file',
+ ), array(
+ '-y', '-i', __FILE__,
+ '-vcodec', 'gloubi-boulga-video',
+ '-acodec', 'patati-patata-audio',
+ '-b:v', '664k',
+ '-refs', '6', '-coder', '1', '-sc_threshold', '40', '-flags', '+loop',
+ '-me_range', '16', '-subq', '7', '-i_qfactor', '0.71', '-qcomp', '0.6',
+ '-qdiff', '4', '-trellis', '1', '-b:a', '92k', '-pass', '2', '-passlogfile',
+ '-ac', '2', '-ar', '44100', '/target/file',
+ )), null, $audioVideoFormat),
+ array(false, array(array(
+ '-y', '-i', __FILE__,
+ 'extra', 'param','-b:v', '665k',
+ '-refs', '6', '-coder', '1', '-sc_threshold', '40', '-flags', '+loop',
+ '-me_range', '16', '-subq', '7', '-i_qfactor', '0.71', '-qcomp', '0.6',
+ '-qdiff', '4', '-trellis', '1', '-b:a', '92k', '-pass', '1', '-passlogfile',
+ '-an', '/target/file',
+ ), array(
+ '-y', '-i', __FILE__,
+ 'extra', 'param', '-b:v', '665k',
+ '-refs', '6', '-coder', '1', '-sc_threshold', '40', '-flags', '+loop',
+ '-me_range', '16', '-subq', '7', '-i_qfactor', '0.71', '-qcomp', '0.6',
+ '-qdiff', '4', '-trellis', '1', '-b:a', '92k', '-pass', '2', '-passlogfile',
+ '-ac', '2', '-ar', '44100', '/target/file',
+ )), null, $formatExtra),
+ array(true, array(array(
+ '-y', '-i', __FILE__,
+ '-threads', 24, '-b:v', '663k',
+ '-refs', '6', '-coder', '1', '-sc_threshold', '40', '-flags', '+loop',
+ '-me_range', '16', '-subq', '7', '-i_qfactor', '0.71', '-qcomp', '0.6',
+ '-qdiff', '4', '-trellis', '1', '-b:a', '92k', '-pass', '1', '-passlogfile',
+ '-an', '/target/file',
+ ), array(
+ '-y', '-i', __FILE__,
+ '-threads', 24,
+ '-b:v', '663k',
+ '-refs', '6', '-coder', '1', '-sc_threshold', '40', '-flags', '+loop',
+ '-me_range', '16', '-subq', '7', '-i_qfactor', '0.71', '-qcomp', '0.6',
+ '-qdiff', '4', '-trellis', '1', '-b:a', '92k', '-pass', '2', '-passlogfile',
+ '-ac', '2', '-ar', '44100', '/target/file',
+ )), null, $format),
+ array(true, array(array(
+ '-y', '-i', __FILE__,
+ 'extra', 'param', '-threads', 24, '-b:v', '665k',
+ '-refs', '6', '-coder', '1', '-sc_threshold', '40', '-flags', '+loop',
+ '-me_range', '16', '-subq', '7', '-i_qfactor', '0.71', '-qcomp', '0.6',
+ '-qdiff', '4', '-trellis', '1', '-b:a', '92k', '-pass', '1', '-passlogfile',
+ '-an', '/target/file',
+ ), array(
+ '-y', '-i', __FILE__,
+ 'extra', 'param', '-threads', 24, '-b:v', '665k',
+ '-refs', '6', '-coder', '1', '-sc_threshold', '40', '-flags', '+loop',
+ '-me_range', '16', '-subq', '7', '-i_qfactor', '0.71', '-qcomp', '0.6',
+ '-qdiff', '4', '-trellis', '1', '-b:a', '92k', '-pass', '2', '-passlogfile',
+ '-ac', '2', '-ar', '44100', '/target/file',
+ )), null, $formatExtra),
+ array(false, array(array(
+ '-y', '-i', __FILE__, '-b:v', '666k',
+ '-refs', '6', '-coder', '1', '-sc_threshold', '40', '-flags', '+loop',
+ '-me_range', '16', '-subq', '7', '-i_qfactor', '0.71', '-qcomp', '0.6',
+ '-qdiff', '4', '-trellis', '1', '-b:a', '92k', '-pass', '1', '-passlogfile',
+ '-an', '/target/file',
+ ), array(
+ '-y', '-i', __FILE__,
+ '-b:v', '666k',
+ '-refs', '6', '-coder', '1', '-sc_threshold', '40', '-flags', '+loop',
+ '-me_range', '16', '-subq', '7', '-i_qfactor', '0.71', '-qcomp', '0.6',
+ '-qdiff', '4', '-trellis', '1', '-b:a', '92k', '-pass', '2', '-passlogfile',
+ '-ac', '2', '-ar', '44100', '/target/file',
+ )), $listeners, $progressableFormat),
+ array(true, array(array(
+ '-y', '-i', __FILE__,
+ '-threads', 24, '-b:v', '666k',
+ '-refs', '6', '-coder', '1', '-sc_threshold', '40', '-flags', '+loop',
+ '-me_range', '16', '-subq', '7', '-i_qfactor', '0.71', '-qcomp', '0.6',
+ '-qdiff', '4', '-trellis', '1', '-b:a', '92k', '-pass', '1', '-passlogfile',
+ '-an', '/target/file',
+ ), array(
+ '-y', '-i', __FILE__,
+ '-threads', 24,
+ '-b:v', '666k',
+ '-refs', '6', '-coder', '1', '-sc_threshold', '40', '-flags', '+loop',
+ '-me_range', '16', '-subq', '7', '-i_qfactor', '0.71', '-qcomp', '0.6',
+ '-qdiff', '4', '-trellis', '1', '-b:a', '92k', '-pass', '2', '-passlogfile',
+ '-ac', '2', '-ar', '44100', '/target/file',
+ )), $listeners, $progressableFormat),
+ );
+ }
+
+ public function getClassName()
+ {
+ return 'FFMpeg\Media\Video';
+ }
+}
+
+abstract class Prog implements ProgressableInterface, VideoInterface
+{
+}
diff --git a/tests/FFMpeg/Tests/TestCase.php b/tests/FFMpeg/Tests/TestCase.php
new file mode 100644
index 0000000..d347acd
--- /dev/null
+++ b/tests/FFMpeg/Tests/TestCase.php
@@ -0,0 +1,131 @@
+assertTrue(is_scalar($value));
+ }
+
+ public function getLoggerMock()
+ {
+ return $this->getMock('Psr\Log\LoggerInterface');
+ }
+
+ public function getCacheMock()
+ {
+ return $this->getMock('Doctrine\Common\Cache\Cache');
+ }
+
+ public function getTimeCodeMock()
+ {
+ return $this->getMockBuilder('FFMpeg\Coordinate\TimeCode')
+ ->disableOriginalConstructor()
+ ->getMock();
+ }
+
+ public function getDimensionMock()
+ {
+ return $this->getMockBuilder('FFMpeg\Coordinate\Dimension')
+ ->disableOriginalConstructor()
+ ->getMock();
+ }
+
+ public function getFramerateMock()
+ {
+ return $this->getMockBuilder('FFMpeg\Coordinate\Framerate')
+ ->disableOriginalConstructor()
+ ->getMock();
+ }
+
+ public function getFFMpegDriverMock()
+ {
+ return $this->getMockBuilder('FFMpeg\Driver\FFMpegDriver')
+ ->disableOriginalConstructor()
+ ->getMock();
+ }
+
+ public function getFFProbeDriverMock()
+ {
+ return $this->getMockBuilder('FFMpeg\Driver\FFProbeDriver')
+ ->disableOriginalConstructor()
+ ->getMock();
+ }
+
+ public function getFFProbeMock()
+ {
+ return $this->getMockBuilder('FFMpeg\FFProbe')
+ ->disableOriginalConstructor()
+ ->getMock();
+ }
+
+ public function getStreamMock()
+ {
+ return $this->getMockBuilder('FFMpeg\FFProbe\DataMapping\Stream')
+ ->disableOriginalConstructor()
+ ->getMock();
+ }
+
+ public function getFFProbeParserMock()
+ {
+ return $this->getMock('FFMpeg\FFProbe\OutputParserInterface');
+ }
+
+ public function getFFProbeOptionsTesterMock()
+ {
+ return $this->getMock('FFMpeg\FFProbe\OptionsTesterInterface');
+ }
+
+ public function getFFProbeMapperMock()
+ {
+ return $this->getMock('FFMpeg\FFProbe\MapperInterface');
+ }
+
+ public function getFFProbeOptionsTesterMockWithOptions(array $options)
+ {
+ $tester = $this->getFFProbeOptionsTesterMock();
+
+ $tester->expects($this->any())
+ ->method('has')
+ ->will($this->returnCallback(function ($option) use ($options) {
+ return in_array($option, $options);
+ }));
+
+ return $tester;
+ }
+
+ public function getConfigurationMock()
+ {
+ return $this->getMock('Alchemy\BinaryDriver\ConfigurationInterface');
+ }
+
+ public function getFormatMock()
+ {
+ return $this->getMockBuilder('FFMpeg\FFProbe\DataMapping\Format')
+ ->disableOriginalConstructor()
+ ->getMock();
+ }
+
+ public function getStreamCollectionMock()
+ {
+ return $this->getMockBuilder('FFMpeg\FFProbe\DataMapping\StreamCollection')
+ ->disableOriginalConstructor()
+ ->getMock();
+ }
+
+ protected function getAudioMock()
+ {
+ return $this->getMockBuilder('FFMpeg\Media\Audio')
+ ->disableOriginalConstructor()
+ ->getMock();
+ }
+
+ protected function getVideoMock()
+ {
+ return $this->getMockBuilder('FFMpeg\Media\Video')
+ ->disableOriginalConstructor()
+ ->getMock();
+ }
+}
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
new file mode 100644
index 0000000..a6f006d
--- /dev/null
+++ b/tests/bootstrap.php
@@ -0,0 +1,5 @@
+add('FFMpeg\Tests', __DIR__);
+$loader->add('FFMpeg\Functional', __DIR__);
diff --git a/tests/fixtures/ffprobe/help.raw b/tests/fixtures/ffprobe/help.raw
new file mode 100644
index 0000000..fbcc8ba
--- /dev/null
+++ b/tests/fixtures/ffprobe/help.raw
@@ -0,0 +1,512 @@
+Simple multimedia streams analyzer
+usage: ffprobe [OPTIONS] [INPUT_FILE]
+
+Main options:
+-L show license
+-h topic show help
+-? topic show help
+-help topic show help
+--help topic show help
+-version show version
+-formats show available formats
+-codecs show available codecs
+-decoders show available decoders
+-encoders show available encoders
+-bsfs show available bit stream filters
+-protocols show available protocols
+-filters show available filters
+-pix_fmts show available pixel formats
+-layouts show standard channel layouts
+-sample_fmts show available audio sample formats
+-loglevel loglevel set libav* logging level
+-v loglevel set libav* logging level
+-report generate a report
+-max_alloc bytes set maximum size of a single allocated block
+-cpuflags flags force specific cpu flags
+-f format force format
+-unit show unit of the displayed values
+-prefix use SI prefixes for the displayed values
+-byte_binary_prefix use binary prefixes for byte units
+-sexagesimal use sexagesimal format HOURS:MM:SS.MICROSECONDS for time units
+-pretty prettify the format of displayed values, make it more human readable
+-print_format format set the output printing format (available formats are: default, compact, csv, flat, ini, json, xml)
+-of format alias for -print_format
+-select_streams stream_specifier select the specified streams
+-sections print sections structure and section information, and exit
+-show_data show packets data
+-show_error show probing error
+-show_format show format/container info
+-show_frames show frames info
+-show_format_entry entry show a particular entry from the format/container info
+-show_entries entry_list show a set of specified entries
+-show_packets show packets info
+-show_streams show streams info
+-count_frames count the number of frames per stream
+-count_packets count the number of packets per stream
+-show_program_version show ffprobe version
+-show_library_versions show library versions
+-show_versions show program and library versions
+-show_private_data show private data
+-private same as show_private_data
+-bitexact force bitexact output
+-default generic catch all option
+-i input_file read specified file
+
+
+AVFormatContext AVOptions:
+-avioflags ED....
+ direct ED.... reduce buffering
+-probesize .D.... set probing size (from 32 to INT_MAX)
+-fflags ED....
+ ignidx .D.... ignore index
+ genpts .D.... generate pts
+ nofillin .D.... do not fill in missing values that can be exactly calculated
+ noparse .D.... disable AVParsers, this needs nofillin too
+ igndts .D.... ignore dts
+ discardcorrupt .D.... discard corrupted frames
+ sortdts .D.... try to interleave outputted packets by dts
+ keepside .D.... dont merge side data
+ nobuffer .D.... reduce the latency introduced by optional buffering
+-seek2any .D.... forces seeking to enable seek to any mode (from 0 to 1)
+-analyzeduration .D.... specify how many microseconds are analyzed to probe the input (from 0 to INT_MAX)
+-cryptokey .D.... decryption key
+-indexmem .D.... max memory used for timestamp index (per stream) (from 0 to INT_MAX)
+-rtbufsize .D.... max memory used for buffering real-time frames (from 0 to INT_MAX)
+-fdebug ED.... print specific debug info
+ ts ED....
+-max_delay ED.... maximum muxing or demuxing delay in microseconds (from -1 to INT_MAX)
+-fpsprobesize .D.... number of frames used to probe fps (from -1 to 2.14748e+09)
+-f_err_detect .D.... set error detection flags (deprecated; use err_detect, save via avconv)
+ crccheck .D.... verify embedded CRCs
+ bitstream .D.... detect bitstream specification deviations
+ buffer .D.... detect improper bitstream length
+ explode .D.... abort decoding on minor error detection
+ careful .D.... consider things that violate the spec and have not been seen in the wild as errors
+ compliant .D.... consider all spec non compliancies as errors
+ aggressive .D.... consider things that a sane encoder shouldnt do as an error
+-err_detect .D.... set error detection flags
+ crccheck .D.... verify embedded CRCs
+ bitstream .D.... detect bitstream specification deviations
+ buffer .D.... detect improper bitstream length
+ explode .D.... abort decoding on minor error detection
+ careful .D.... consider things that violate the spec and have not been seen in the wild as errors
+ compliant .D.... consider all spec non compliancies as errors
+ aggressive .D.... consider things that a sane encoder shouldnt do as an error
+-use_wallclock_as_timestamps .D.... use wallclock as timestamps (from 0 to 2.14748e+09)
+-skip_initial_bytes .D.... skip initial bytes (from 0 to 2.14748e+09)
+-correct_ts_overflow .D.... correct single timestamp overflows (from 0 to 1)
+
+AVIOContext AVOptions:
+
+URLContext AVOptions:
+
+crypto AVOptions:
+-key .D.... AES decryption key
+-iv .D.... AES decryption initialization vector
+
+ffrtmphttp AVOptions:
+-ffrtmphttp_tls .D.... Use a HTTPS tunneling connection (RTMPTS). (from 0 to 1)
+
+file AVOptions:
+
+http AVOptions:
+-seekable .D.... control seekability of connection (from -1 to 1)
+-headers ED.... set custom HTTP headers, can override built in default headers
+-content_type ED.... force a content type
+-user-agent .D.... override User-Agent header
+-multiple_requests ED.... use persistent connections (from 0 to 1)
+-post_data ED.... set custom HTTP post data
+-timeout ED.... set timeout of socket I/O operations (from -1 to INT_MAX)
+
+rtmp AVOptions:
+-rtmp_app ED.... Name of application to connect to on the RTMP server
+-rtmp_buffer ED.... Set buffer time in milliseconds. The default is 3000. (from 0 to INT_MAX)
+-rtmp_conn ED.... Append arbitrary AMF data to the Connect message
+-rtmp_flashver ED.... Version of the Flash plugin used to run the SWF player.
+-rtmp_live .D.... Specify that the media is a live stream. (from INT_MIN to INT_MAX)
+ any .D.... both
+ live .D.... live stream
+ recorded .D.... recorded stream
+-rtmp_pageurl .D.... URL of the web page in which the media was embedded. By default no value will be sent.
+-rtmp_playpath ED.... Stream identifier to play or to publish
+-rtmp_subscribe .D.... Name of live stream to subscribe to. Defaults to rtmp_playpath.
+-rtmp_swfhash .D.... SHA256 hash of the decompressed SWF file (32 bytes).
+-rtmp_swfsize .D.... Size of the decompressed SWF file, required for SWFVerification. (from 0 to INT_MAX)
+-rtmp_swfurl ED.... URL of the SWF player. By default no value will be sent
+-rtmp_swfverify .D.... URL to player swf file, compute hash/size automatically.
+-rtmp_tcurl ED.... URL of the target stream. Defaults to proto://host[:port]/app.
+-rtmp_listen .D.... Listen for incoming rtmp connections (from INT_MIN to INT_MAX)
+-timeout .D.... Maximum timeout (in seconds) to wait for incoming connections. -1 is infinite. Implies -rtmp_listen 1 (from INT_MIN to INT_MAX)
+
+rtmpt AVOptions:
+-rtmp_app ED.... Name of application to connect to on the RTMP server
+-rtmp_buffer ED.... Set buffer time in milliseconds. The default is 3000. (from 0 to INT_MAX)
+-rtmp_conn ED.... Append arbitrary AMF data to the Connect message
+-rtmp_flashver ED.... Version of the Flash plugin used to run the SWF player.
+-rtmp_live .D.... Specify that the media is a live stream. (from INT_MIN to INT_MAX)
+ any .D.... both
+ live .D.... live stream
+ recorded .D.... recorded stream
+-rtmp_pageurl .D.... URL of the web page in which the media was embedded. By default no value will be sent.
+-rtmp_playpath ED.... Stream identifier to play or to publish
+-rtmp_subscribe .D.... Name of live stream to subscribe to. Defaults to rtmp_playpath.
+-rtmp_swfhash .D.... SHA256 hash of the decompressed SWF file (32 bytes).
+-rtmp_swfsize .D.... Size of the decompressed SWF file, required for SWFVerification. (from 0 to INT_MAX)
+-rtmp_swfurl ED.... URL of the SWF player. By default no value will be sent
+-rtmp_swfverify .D.... URL to player swf file, compute hash/size automatically.
+-rtmp_tcurl ED.... URL of the target stream. Defaults to proto://host[:port]/app.
+-rtmp_listen .D.... Listen for incoming rtmp connections (from INT_MIN to INT_MAX)
+-timeout .D.... Maximum timeout (in seconds) to wait for incoming connections. -1 is infinite. Implies -rtmp_listen 1 (from INT_MIN to INT_MAX)
+
+srtp AVOptions:
+
+tcp AVOptions:
+-listen ED.... listen on port instead of connecting (from 0 to 1)
+-timeout ED.... timeout of socket i/o operations (from 0 to INT_MAX)
+-listen_timeout ED.... connection awaiting timeout (from -1 to INT_MAX)
+
+udp AVOptions:
+-buffer_size ED.... Socket buffer size in bytes (from 0 to INT_MAX)
+-localport ED.... Set local port to bind to (from 0 to INT_MAX)
+-localaddr ED.... Choose local IP address
+-pkt_size ED.... Set size of UDP packets (from 0 to INT_MAX)
+-reuse ED.... Explicitly allow or disallow reusing UDP sockets (from 0 to 1)
+-connect ED.... Should connect() be called on socket (from 0 to 1)
+-fifo_size .D.... Set the UDP receiving circular buffer size, expressed as a number of packets with size of 188 bytes (from 0 to INT_MAX)
+-overrun_nonfatal .D.... Survive in case of UDP receiving circular buffer overrun (from 0 to 1)
+-timeout .D.... In read mode: if no data arrived in more than this time interval, raise error (from 0 to INT_MAX)
+
+Artworx Data Format demuxer AVOptions:
+-linespeed .D.... set simulated line speed (bytes per second) (from 1 to INT_MAX)
+-video_size .D.... set video size, such as 640x480 or hd720.
+-framerate .D.... set framerate (frames per second)
+
+aqtdec AVOptions:
+-subfps .D...S set the movie frame rate (from 0 to INT_MAX)
+
+asf demuxer AVOptions:
+-no_resync_search .D.... Don't try to resynchronize by looking for a certain optional start code (from 0 to 1)
+
+avi AVOptions:
+-use_odml .D.... use odml index (from -1 to 1)
+
+Binary text demuxer AVOptions:
+-linespeed .D.... set simulated line speed (bytes per second) (from 1 to INT_MAX)
+-video_size .D.... set video size, such as 640x480 or hd720.
+-framerate .D.... set framerate (frames per second)
+
+cavsvideo demuxer AVOptions:
+-framerate .D....
+
+CDXL demuxer AVOptions:
+-sample_rate .D.... (from 1 to INT_MAX)
+-framerate .D....
+
+concat demuxer AVOptions:
+-safe .D.... enable safe mode (from -1 to 1)
+
+dirac demuxer AVOptions:
+-framerate .D....
+
+dnxhd demuxer AVOptions:
+-framerate .D....
+
+flvdec AVOptions:
+-flv_metadata .D.V.. Allocate streams according the onMetaData array (from 0 to 1)
+
+g729 demuxer AVOptions:
+-bit_rate .D.... (from 0 to INT_MAX)
+
+GIF demuxer AVOptions:
+-min_delay .D.... minimum valid delay between frames (in hundredths of second) (from 0 to 6000)
+-default_delay .D.... default delay between frames (in hundredths of second) (from 0 to 6000)
+
+gsm demuxer AVOptions:
+-sample_rate .D.... (from 1 to 6.50753e+07)
+
+h261 demuxer AVOptions:
+-framerate .D....
+
+h263 demuxer AVOptions:
+-framerate .D....
+
+h264 demuxer AVOptions:
+-framerate .D....
+
+iCE Draw File demuxer AVOptions:
+-linespeed .D.... set simulated line speed (bytes per second) (from 1 to INT_MAX)
+-video_size .D.... set video size, such as 640x480 or hd720.
+-framerate .D.... set framerate (frames per second)
+
+image2 demuxer AVOptions:
+-framerate .D.... set the video framerate
+-loop .D.... force loop over input file sequence (from 0 to 1)
+-pattern_type .D.... set pattern type (from 0 to INT_MAX)
+ glob_sequence .D.... select glob/sequence pattern type
+ glob .D.... select glob pattern type
+ sequence .D.... select sequence pattern type
+-pixel_format .D.... set video pixel format
+-start_number .D.... set first number in the sequence (from 0 to INT_MAX)
+-start_number_range .D.... set range for looking at the first sequence number (from 1 to INT_MAX)
+-video_size .D.... set video size
+-frame_size .D.... force frame size in bytes (from 0 to INT_MAX)
+
+image2pipe demuxer AVOptions:
+-framerate .D.... set the video framerate
+-loop .D.... force loop over input file sequence (from 0 to 1)
+-pattern_type .D.... set pattern type (from 0 to INT_MAX)
+ glob_sequence .D.... select glob/sequence pattern type
+ glob .D.... select glob pattern type
+ sequence .D.... select sequence pattern type
+-pixel_format .D.... set video pixel format
+-start_number .D.... set first number in the sequence (from 0 to INT_MAX)
+-start_number_range .D.... set range for looking at the first sequence number (from 1 to INT_MAX)
+-video_size .D.... set video size
+-frame_size .D.... force frame size in bytes (from 0 to INT_MAX)
+
+ingenient demuxer AVOptions:
+-framerate .D....
+
+m4v demuxer AVOptions:
+-framerate .D....
+
+mjpeg demuxer AVOptions:
+-framerate .D....
+
+mov,mp4,m4a,3gp,3g2,mj2 AVOptions:
+-use_absolute_path .D.V.. allow using absolute path when opening alias, this is a possible security issue (from 0 to 1)
+-ignore_editlist .D.V.. (from 0 to 1)
+
+mpegtsraw demuxer AVOptions:
+-compute_pcr .D.... Compute exact PCR for each transport stream packet. (from 0 to 1)
+
+mpegvideo demuxer AVOptions:
+-framerate .D....
+
+alaw demuxer AVOptions:
+-sample_rate .D.... (from 0 to INT_MAX)
+-channels .D.... (from 0 to INT_MAX)
+
+mulaw demuxer AVOptions:
+-sample_rate .D.... (from 0 to INT_MAX)
+-channels .D.... (from 0 to INT_MAX)
+
+f64be demuxer AVOptions:
+-sample_rate .D.... (from 0 to INT_MAX)
+-channels .D.... (from 0 to INT_MAX)
+
+f64le demuxer AVOptions:
+-sample_rate .D.... (from 0 to INT_MAX)
+-channels .D.... (from 0 to INT_MAX)
+
+f32be demuxer AVOptions:
+-sample_rate .D.... (from 0 to INT_MAX)
+-channels .D.... (from 0 to INT_MAX)
+
+f32le demuxer AVOptions:
+-sample_rate .D.... (from 0 to INT_MAX)
+-channels .D.... (from 0 to INT_MAX)
+
+s32be demuxer AVOptions:
+-sample_rate .D.... (from 0 to INT_MAX)
+-channels .D.... (from 0 to INT_MAX)
+
+s32le demuxer AVOptions:
+-sample_rate .D.... (from 0 to INT_MAX)
+-channels .D.... (from 0 to INT_MAX)
+
+s24be demuxer AVOptions:
+-sample_rate .D.... (from 0 to INT_MAX)
+-channels .D.... (from 0 to INT_MAX)
+
+s24le demuxer AVOptions:
+-sample_rate .D.... (from 0 to INT_MAX)
+-channels .D.... (from 0 to INT_MAX)
+
+s16be demuxer AVOptions:
+-sample_rate .D.... (from 0 to INT_MAX)
+-channels .D.... (from 0 to INT_MAX)
+
+s16le demuxer AVOptions:
+-sample_rate .D.... (from 0 to INT_MAX)
+-channels .D.... (from 0 to INT_MAX)
+
+s8 demuxer AVOptions:
+-sample_rate .D.... (from 0 to INT_MAX)
+-channels .D.... (from 0 to INT_MAX)
+
+u32be demuxer AVOptions:
+-sample_rate .D.... (from 0 to INT_MAX)
+-channels .D.... (from 0 to INT_MAX)
+
+u32le demuxer AVOptions:
+-sample_rate .D.... (from 0 to INT_MAX)
+-channels .D.... (from 0 to INT_MAX)
+
+u24be demuxer AVOptions:
+-sample_rate .D.... (from 0 to INT_MAX)
+-channels .D.... (from 0 to INT_MAX)
+
+u24le demuxer AVOptions:
+-sample_rate .D.... (from 0 to INT_MAX)
+-channels .D.... (from 0 to INT_MAX)
+
+u16be demuxer AVOptions:
+-sample_rate .D.... (from 0 to INT_MAX)
+-channels .D.... (from 0 to INT_MAX)
+
+u16le demuxer AVOptions:
+-sample_rate .D.... (from 0 to INT_MAX)
+-channels .D.... (from 0 to INT_MAX)
+
+u8 demuxer AVOptions:
+-sample_rate .D.... (from 0 to INT_MAX)
+-channels .D.... (from 0 to INT_MAX)
+
+rawvideo demuxer AVOptions:
+-video_size .D.... set frame size
+-pixel_format .D.... set pixel format
+-framerate .D.... set frame rate
+
+RTP demuxer AVOptions:
+-rtp_flags .D.... RTP flags
+ filter_src .D.... Only receive packets from the negotiated peer IP
+ listen .D.... Wait for incoming connections
+-reorder_queue_size .D.... Number of packets to buffer for handling of reordered packets (from -1 to INT_MAX)
+
+RTSP demuxer AVOptions:
+-initial_pause .D.... Don't start playing the stream immediately (from 0 to 1)
+-rtsp_transport ED.... RTSP transport protocols
+ udp ED.... UDP
+ tcp ED.... TCP
+ udp_multicast .D.... UDP multicast
+ http .D.... HTTP tunneling
+-rtsp_flags .D.... RTSP flags
+ filter_src .D.... Only receive packets from the negotiated peer IP
+ listen .D.... Wait for incoming connections
+-allowed_media_types .D.... Media types to accept from the server
+ video .D.... Video
+ audio .D.... Audio
+ data .D.... Data
+-min_port ED.... Minimum local UDP port (from 0 to 65535)
+-max_port ED.... Maximum local UDP port (from 0 to 65535)
+-timeout .D.... Maximum timeout (in seconds) to wait for incoming connections. -1 is infinite. Implies flag listen (from INT_MIN to INT_MAX)
+-reorder_queue_size .D.... Number of packets to buffer for handling of reordered packets (from -1 to INT_MAX)
+
+sbg_demuxer AVOptions:
+-sample_rate .D.... (from 0 to INT_MAX)
+-frame_size .D.... (from 0 to INT_MAX)
+-max_file_size .D.... (from 0 to INT_MAX)
+
+SDP demuxer AVOptions:
+-sdp_flags .D.... SDP flags
+ filter_src .D.... Only receive packets from the negotiated peer IP
+ listen .D.... Wait for incoming connections
+ custom_io .D.... Use custom IO
+-allowed_media_types .D.... Media types to accept from the server
+ video .D.... Video
+ audio .D.... Audio
+ data .D.... Data
+-reorder_queue_size .D.... Number of packets to buffer for handling of reordered packets (from -1 to INT_MAX)
+
+tedcaptions_demuxer AVOptions:
+-start_time .D...S set the start time (offset) of the subtitles, in ms (from I64_MIN to I64_MAX)
+
+TTY demuxer AVOptions:
+-chars_per_frame .D.... (from 1 to INT_MAX)
+-video_size .D.... A string describing frame size, such as 640x480 or hd720.
+-framerate .D....
+
+vc1 demuxer AVOptions:
+-framerate .D....
+
+WAV demuxer AVOptions:
+-ignore_length .D.... Ignore length (from 0 to 1)
+
+eXtended BINary text (XBIN) demuxer AVOptions:
+-linespeed .D.... set simulated line speed (bytes per second) (from 1 to INT_MAX)
+-video_size .D.... set video size, such as 640x480 or hd720.
+-framerate .D.... set framerate (frames per second)
+
+lavfi indev AVOptions:
+-graph .D.... set libavfilter graph
+-graph_file .D.... set libavfilter graph filename
+-dumpgraph .D.... dump graph to stderr
+
+AIFF muxer AVOptions:
+
+AST muxer AVOptions:
+
+f4v muxer AVOptions:
+
+GIF muxer AVOptions:
+
+hls muxer AVOptions:
+
+image2 muxer AVOptions:
+
+ipod muxer AVOptions:
+
+ismv muxer AVOptions:
+
+LATM/LOAS muxer AVOptions:
+
+mov muxer AVOptions:
+
+MP3 muxer AVOptions:
+
+mp4 muxer AVOptions:
+
+mpeg muxer AVOptions:
+
+vcd muxer AVOptions:
+
+dvd muxer AVOptions:
+
+svcd muxer AVOptions:
+
+vob muxer AVOptions:
+
+MPEGTS muxer AVOptions:
+
+Ogg muxer AVOptions:
+
+psp muxer AVOptions:
+
+RTP muxer AVOptions:
+
+RTSP muxer AVOptions:
+-initial_pause .D.... Don't start playing the stream immediately (from 0 to 1)
+-rtsp_transport ED.... RTSP transport protocols
+ udp ED.... UDP
+ tcp ED.... TCP
+ udp_multicast .D.... UDP multicast
+ http .D.... HTTP tunneling
+-rtsp_flags .D.... RTSP flags
+ filter_src .D.... Only receive packets from the negotiated peer IP
+ listen .D.... Wait for incoming connections
+-allowed_media_types .D.... Media types to accept from the server
+ video .D.... Video
+ audio .D.... Audio
+ data .D.... Data
+-min_port ED.... Minimum local UDP port (from 0 to 65535)
+-max_port ED.... Maximum local UDP port (from 0 to 65535)
+-timeout .D.... Maximum timeout (in seconds) to wait for incoming connections. -1 is infinite. Implies flag listen (from INT_MIN to INT_MAX)
+-reorder_queue_size .D.... Number of packets to buffer for handling of reordered packets (from -1 to INT_MAX)
+
+segment muxer AVOptions:
+
+stream_segment muxer AVOptions:
+
+smooth streaming muxer AVOptions:
+
+spdif AVOptions:
+
+tg2 muxer AVOptions:
+
+tgp muxer AVOptions:
+
+WAV muxer AVOptions:
+
+caca_outdev AVOptions:
+
diff --git a/tests/fixtures/ffprobe/show_format.json b/tests/fixtures/ffprobe/show_format.json
new file mode 100644
index 0000000..de962fa
--- /dev/null
+++ b/tests/fixtures/ffprobe/show_format.json
@@ -0,0 +1,20 @@
+{
+ "format": {
+ "filename": "Interview5mm4500.mp4",
+ "nb_streams": 2,
+ "format_name": "mov,mp4,m4a,3gp,3g2,mj2",
+ "format_long_name": "QuickTime / MOV",
+ "start_time": "0.000000",
+ "duration": "300.011000",
+ "size": "173833712",
+ "bit_rate": "4635395",
+ "tags": {
+ "major_brand": "isom",
+ "minor_version": "512",
+ "compatible_brands": "isomiso2avc1mp41",
+ "creation_time": "1970-01-01 00:00:00",
+ "title": "5mm 4500.mp4",
+ "encoder": "Lavf52.108.0"
+ }
+ }
+}
diff --git a/tests/fixtures/ffprobe/show_format.raw b/tests/fixtures/ffprobe/show_format.raw
new file mode 100644
index 0000000..1e857e5
--- /dev/null
+++ b/tests/fixtures/ffprobe/show_format.raw
@@ -0,0 +1,16 @@
+[FORMAT]
+filename=Interview5mm4500.mp4
+nb_streams=2
+format_name=mov,mp4,m4a,3gp,3g2,mj2
+format_long_name=QuickTime / MOV
+start_time=0.000000
+duration=300.011000
+size=173833712
+bit_rate=4635395
+TAG:major_brand=isom
+TAG:minor_version=512
+TAG:compatible_brands=isomiso2avc1mp41
+TAG:creation_time=1970-01-01 00:00:00
+TAG:title=5mm 4500.mp4
+TAG:encoder=Lavf52.108.0
+[/FORMAT]
diff --git a/tests/fixtures/ffprobe/show_streams.json b/tests/fixtures/ffprobe/show_streams.json
new file mode 100644
index 0000000..decdc36
--- /dev/null
+++ b/tests/fixtures/ffprobe/show_streams.json
@@ -0,0 +1,88 @@
+{
+ "streams": [
+ {
+ "index": 0,
+ "codec_name": "h264",
+ "codec_long_name": "H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10",
+ "profile": "High",
+ "codec_type": "video",
+ "codec_time_base": "1/100",
+ "codec_tag_string": "avc1",
+ "codec_tag": "0x31637661",
+ "width": 960,
+ "height": 720,
+ "has_b_frames": 2,
+ "sample_aspect_ratio": "1:1",
+ "display_aspect_ratio": "4:3",
+ "pix_fmt": "yuv420p",
+ "level": 32,
+ "r_frame_rate": "50/1",
+ "avg_frame_rate": "50/1",
+ "time_base": "1/50000",
+ "start_pts": 0,
+ "start_time": "0.000000",
+ "duration_ts": 15000000,
+ "duration": "300.000000",
+ "bit_rate": "4499198",
+ "nb_frames": "15000",
+ "disposition": {
+ "default": 0,
+ "dub": 0,
+ "original": 0,
+ "comment": 0,
+ "lyrics": 0,
+ "karaoke": 0,
+ "forced": 0,
+ "hearing_impaired": 0,
+ "visual_impaired": 0,
+ "clean_effects": 0,
+ "attached_pic": 0
+ },
+ "tags": {
+ "creation_time": "1970-01-01 00:00:00",
+ "language": "und",
+ "handler_name": "VideoHandler"
+ }
+ },
+ {
+ "index": 1,
+ "codec_name": "aac",
+ "codec_long_name": "AAC (Advanced Audio Coding)",
+ "codec_type": "audio",
+ "codec_time_base": "1/48000",
+ "codec_tag_string": "mp4a",
+ "codec_tag": "0x6134706d",
+ "sample_fmt": "fltp",
+ "sample_rate": "48000",
+ "channels": 2,
+ "bits_per_sample": 0,
+ "r_frame_rate": "0/0",
+ "avg_frame_rate": "0/0",
+ "time_base": "1/48000",
+ "start_pts": 0,
+ "start_time": "0.000000",
+ "duration_ts": 14400512,
+ "duration": "300.010667",
+ "bit_rate": "127973",
+ "nb_frames": "14063",
+ "disposition": {
+ "default": 0,
+ "dub": 0,
+ "original": 0,
+ "comment": 0,
+ "lyrics": 0,
+ "karaoke": 0,
+ "forced": 0,
+ "hearing_impaired": 0,
+ "visual_impaired": 0,
+ "clean_effects": 0,
+ "attached_pic": 0
+ },
+ "tags": {
+ "creation_time": "1970-01-01 00:00:00",
+ "language": "und",
+ "handler_name": "SoundHandler"
+ }
+ }
+ ]
+}
diff --git a/tests/fixtures/ffprobe/show_streams.raw b/tests/fixtures/ffprobe/show_streams.raw
new file mode 100644
index 0000000..6ce06c9
--- /dev/null
+++ b/tests/fixtures/ffprobe/show_streams.raw
@@ -0,0 +1,84 @@
+[STREAM]
+index=0
+codec_name=h264
+codec_long_name=H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10
+profile=High
+codec_type=video
+codec_time_base=1/100
+codec_tag_string=avc1
+codec_tag=0x31637661
+width=960
+height=720
+has_b_frames=2
+sample_aspect_ratio=1:1
+display_aspect_ratio=4:3
+pix_fmt=yuv420p
+level=32
+timecode=N/A
+id=N/A
+r_frame_rate=50/1
+avg_frame_rate=50/1
+time_base=1/50000
+start_pts=0
+start_time=0.000000
+duration_ts=15000000
+duration=300.000000
+bit_rate=4499198
+nb_frames=15000
+nb_read_frames=N/A
+nb_read_packets=N/A
+DISPOSITION:default=0
+DISPOSITION:dub=0
+DISPOSITION:original=0
+DISPOSITION:comment=0
+DISPOSITION:lyrics=0
+DISPOSITION:karaoke=0
+DISPOSITION:forced=0
+DISPOSITION:hearing_impaired=0
+DISPOSITION:visual_impaired=0
+DISPOSITION:clean_effects=0
+DISPOSITION:attached_pic=0
+TAG:creation_time=1970-01-01 00:00:00
+TAG:language=und
+TAG:handler_name=VideoHandler
+[/STREAM]
+[STREAM]
+index=1
+codec_name=aac
+codec_long_name=AAC (Advanced Audio Coding)
+profile=unknown
+codec_type=audio
+codec_time_base=1/48000
+codec_tag_string=mp4a
+codec_tag=0x6134706d
+sample_fmt=fltp
+sample_rate=48000
+channels=2
+bits_per_sample=0
+id=N/A
+r_frame_rate=0/0
+avg_frame_rate=0/0
+time_base=1/48000
+start_pts=0
+start_time=0.000000
+duration_ts=14400512
+duration=300.010667
+bit_rate=127973
+nb_frames=14063
+nb_read_frames=N/A
+nb_read_packets=N/A
+DISPOSITION:default=0
+DISPOSITION:dub=0
+DISPOSITION:original=0
+DISPOSITION:comment=0
+DISPOSITION:lyrics=0
+DISPOSITION:karaoke=0
+DISPOSITION:forced=0
+DISPOSITION:hearing_impaired=0
+DISPOSITION:visual_impaired=0
+DISPOSITION:clean_effects=0
+DISPOSITION:attached_pic=0
+TAG:creation_time=1970-01-01 00:00:00
+TAG:language=und
+TAG:handler_name=SoundHandler
+[/STREAM]
diff --git a/tests/src/FFMpeg/BinaryTest.php b/tests/src/FFMpeg/BinaryTest.php
deleted file mode 100644
index 5863da0..0000000
--- a/tests/src/FFMpeg/BinaryTest.php
+++ /dev/null
@@ -1,103 +0,0 @@
-logger = new Logger('tests');
- $this->logger->pushHandler(new NullHandler());
- }
-
- /**
- * @covers FFMpeg\Binary::__construct
- * @expectedException \FFMpeg\Exception\BinaryNotFoundException
- */
- public function testConstruct()
- {
- $binary = new BinaryTester('pretty_binary', $this->logger);
- }
-
- public function testTimeout()
- {
- $tester = BinaryTester::load($this->logger, 200);
- $this->assertEquals(200, $tester->getTimeout());
- }
-
- public function testDefaultTimeout()
- {
- $tester = BinaryTester::load($this->logger);
- $this->assertEquals(60, $tester->getTimeout());
- }
-
- public function testNoTimeout()
- {
- $tester = BinaryTester::load($this->logger, 0);
- $this->assertEquals(0, $tester->getTimeout());
- }
-
- public function testSetTimeout()
- {
- $tester = BinaryTester::load($this->logger);
- $tester->setTimeout(200);
- $this->assertEquals(200, $tester->getTimeout());
- }
-
- /**
- * @expectedException \FFMpeg\Exception\InvalidArgumentException
- */
- public function testSetInvalidTimeout()
- {
- $tester = BinaryTester::load($this->logger);
- $tester->setTimeout(-1);
- }
-
- /**
- * @covers FFMpeg\Binary::load
- */
- public function testLoad()
- {
- BinaryTester::load($this->logger);
- }
-
- /**
- * @covers FFMpeg\Binary::load
- * @expectedException \FFMpeg\Exception\BinaryNotFoundException
- */
- public function testLoadWrongBinary()
- {
- BinaryTesterWrongBinary::load($this->logger);
- }
-
-}
-
-class BinaryTester extends Binary
-{
-
- protected static function getBinaryName()
- {
- return array('php');
- }
-
-}
-
-class BinaryTesterWrongBinary extends Binary
-{
-
- protected static function getBinaryName()
- {
- return array('');
- }
-
-}
diff --git a/tests/src/FFMpeg/FFMpegServiceProviderTest.php b/tests/src/FFMpeg/FFMpegServiceProviderTest.php
deleted file mode 100644
index 34ae282..0000000
--- a/tests/src/FFMpeg/FFMpegServiceProviderTest.php
+++ /dev/null
@@ -1,83 +0,0 @@
-getApplication();
-
- $app->register(new FFMpegServiceProvider());
-
- $this->assertInstanceOf('\\FFMpeg\\FFProbe', $app['ffmpeg.ffprobe']);
- $this->assertInstanceOf('\\FFMpeg\\FFMpeg', $app['ffmpeg.ffmpeg']);
- }
-
- /**
- * @expectedException FFMpeg\Exception\BinaryNotFoundException
- * @covers FFMpeg\FFMpegServiceProvider::register
- */
- public function testRegisterFFMpegFails()
- {
- $app = $this->getApplication();
- $app->register(new FFMpegServiceProvider(), array('ffmpeg.ffmpeg.binary' => '/path/to/no/ffmpeg'));
-
- $app['ffmpeg.ffmpeg'];
- }
-
- /**
- * @expectedException FFMpeg\Exception\BinaryNotFoundException
- * @covers FFMpeg\FFMpegServiceProvider::register
- */
- public function testRegisterFFProbeFails()
- {
- $app = $this->getApplication();
- $app->register(new FFMpegServiceProvider(), array('ffmpeg.ffprobe.binary' => '/path/to/no/ffprobe'));
-
- $app['ffmpeg.ffprobe'];
- }
-
- /**
- * @covers FFMpeg\FFMpegServiceProvider::register
- */
- public function testRegisterCustomLogger()
- {
- $app = $this->getApplication();
- $app['logger'] = $app->share(function(Application $app){
- $logger = new Logger('tests');
- $logger->pushHandler(new NullHandler());
-
- return $logger;
- });
- $app->register(new FFMpegServiceProvider());
-
- $this->assertInstanceOf('\\FFMpeg\\FFProbe', $app['ffmpeg.ffprobe']);
- $this->assertInstanceOf('\\FFMpeg\\FFMpeg', $app['ffmpeg.ffmpeg']);
- }
-
- /**
- * @covers FFMpeg\FFMpegServiceProvider::register
- */
- public function testCustomThreadsFFMpeg()
- {
- $app = $this->getApplication();
- $app->register(new FFMpegServiceProvider(), array('ffmpeg.threads'=>18));
-
- $this->assertEquals(18, $app['ffmpeg.ffmpeg']->getThreads());
- }
-}
-
diff --git a/tests/src/FFMpeg/FFMpegTest.php b/tests/src/FFMpeg/FFMpegTest.php
deleted file mode 100644
index 33ff70c..0000000
--- a/tests/src/FFMpeg/FFMpegTest.php
+++ /dev/null
@@ -1,238 +0,0 @@
-logger = new Logger('tests');
- $this->logger->pushHandler(new NullHandler());
-
- $this->object = FFMpeg::load($this->logger);
- $this->probe = FFProbe::load($this->logger);
- $this->object->setProber($this->probe);
- }
-
- /**
- * @covers FFMpeg\FFMpeg::open
- * @expectedException \InvalidArgumentException
- */
- public function testOpenInvalid()
- {
- $this->object->open(__DIR__ . '/invalid.files');
- }
-
- /**
- * @covers FFMpeg\FFMpeg::open
- */
- public function testOpen()
- {
- $this->object->open(__DIR__ . '/../../files/Test.ogv');
- }
-
- /**
- * @covers FFMpeg\FFMpeg::extractImage
- */
- public function testExtractImage()
- {
- $dest = __DIR__ . '/../../files/extract_Test.jpg';
-
- $this->object->open(__DIR__ . '/../../files/Test.ogv');
- $this->object->extractImage(2, $dest);
-
- $this->probe->probeFormat($dest);
-
- unlink($dest);
- }
-
- /**
- * @covers FFMpeg\FFMpeg::extractImage
- */
- public function testExtractImagePng()
- {
- $dest = __DIR__ . '/../../files/extract_Test.png';
-
- $this->object->open(__DIR__ . '/../../files/Test.ogv');
- $this->object->extractImage(2, $dest);
-
- $this->probe->probeFormat($dest);
-
- unlink($dest);
- }
-
- /**
- * @covers FFMpeg\FFMpeg::extractImage
- */
- public function testExtractImageGif()
- {
- $dest = __DIR__ . '/../../files/extract_Test.gif';
-
- $this->object->open(__DIR__ . '/../../files/Test.ogv');
- $this->object->extractImage(2, $dest);
-
- $this->probe->probeFormat($dest);
-
- unlink($dest);
- }
-
- /**
- * @covers FFMpeg\FFMpeg::extractImage
- * @expectedException \FFMpeg\Exception\LogicException
- */
- public function testExtractImageNoMovie()
- {
- $this->object->extractImage(2, 'Path');
- }
-
- /**
- * @covers FFMpeg\FFMpeg::encode
- * @expectedException \FFMpeg\Exception\LogicException
- */
- public function testEncode()
- {
- $format = new Format\Video\WebM();
- $format-> setDimensions(32, 32);
- $this->object->encode($format, './invalid.file');
- }
-
- /**
- * @covers FFMpeg\FFMpeg::encode
- * @expectedException FFMpeg\Exception\BinaryNotFoundException
- */
- public function testWrongBinary()
- {
- $logger = new \Monolog\Logger('test');
- $logger->pushHandler(new \Monolog\Handler\NullHandler());
-
- $ffmpeg = new FFMpeg('wrongbinary', $logger);
- }
-
- /**
- * @covers FFMpeg\FFMpeg::encode
- * @covers FFMpeg\FFMpeg::encodeAudio
- */
- public function testEncodeMp3()
- {
- $dest = __DIR__ . '/../../files/encode_test.mp3';
-
- $this->object->open(__DIR__ . '/../../files/Audio.mp3');
- $this->object->encode(new Format\Audio\Mp3(), $dest);
-
- $this->probe->probeFormat($dest);
-
- unlink($dest);
- }
-
- /**
- * @covers FFMpeg\FFMpeg::encode
- * @covers FFMpeg\FFMpeg::encodeAudio
- */
- public function testEncodeFlac()
- {
- $dest = __DIR__ . '/../../files/encode_test.flac';
-
- $this->object->open(__DIR__ . '/../../files/Audio.mp3');
- $this->object->encode(new Format\Audio\Flac(), $dest);
-
- $this->probe->probeFormat($dest);
-
- unlink($dest);
- }
-
- /**
- * @covers FFMpeg\FFMpeg::encode
- * @covers FFMpeg\FFMpeg::encodeVideo
- */
- public function testEncodeWebm()
- {
- $dest = __DIR__ . '/../../files/encode_test.webm';
-
- $format = new Format\Video\WebM();
- $format-> setDimensions(32, 32);
-
- $this->object->open(__DIR__ . '/../../files/Test.ogv');
- $this->object->encode($format, $dest);
-
- $this->probe->probeFormat($dest);
-
- unlink($dest);
- }
-
- /**
- * @covers FFMpeg\FFMpeg::encode
- * @covers FFMpeg\FFMpeg::encodeVideo
- */
- public function testEncodeOgg()
- {
- $dest = __DIR__ . '/../../files/encode_test.ogv';
-
- $format = new Format\Video\Ogg();
- $format->setDimensions(32, 32);
-
- $this->object->open(__DIR__ . '/../../files/Test.ogv');
- $this->object->encode($format, $dest);
-
- $this->probe->probeFormat($dest);
-
- unlink($dest);
- }
-
- /**
- * @covers FFMpeg\FFMpeg::encode
- * @covers FFMpeg\FFMpeg::encodeVideo
- */
- public function testEncodeX264()
- {
- $dest = __DIR__ . '/../../files/encode_test.mp4';
-
- $format = new Format\Video\X264();
- $format-> setDimensions(32, 32);
-
- $this->object->open(__DIR__ . '/../../files/Test.ogv');
- $this->object->encode($format, $dest);
-
- $this->probe->probeFormat($dest);
-
- unlink($dest);
- }
-
- /**
- * @covers FFMpeg\FFMpeg::getMultiple
- */
- public function testGetMultiple()
- {
- $object = FFMpegTester::load($this->logger);
- $this->assertEquals(336, $object->getMultipleTester(321, 16));
- $this->assertEquals(322, $object->getMultipleTester(321, 2));
- $this->assertEquals(324, $object->getMultipleTester(321, 4));
- $this->assertEquals(328, $object->getMultipleTester(321, 8));
- $this->assertEquals(320, $object->getMultipleTester(319, 16));
- $this->assertEquals(320, $object->getMultipleTester(313, 16));
- $this->assertEquals(320, $object->getMultipleTester(312, 16));
- $this->assertEquals(336, $object->getMultipleTester(329, 16));
- $this->assertEquals(16, $object->getMultipleTester(8, 16));
- }
-}
-
-class FFMpegTester extends FFMpeg
-{
- public function getMultipleTester($value, $multiple)
- {
- return parent::getMultiple($value, $multiple);
- }
-}
diff --git a/tests/src/FFMpeg/FFProbeTest.php b/tests/src/FFMpeg/FFProbeTest.php
deleted file mode 100644
index 26df48c..0000000
--- a/tests/src/FFMpeg/FFProbeTest.php
+++ /dev/null
@@ -1,77 +0,0 @@
-logger = new Logger('tests');
- $this->logger->pushHandler(new NullHandler());
-
- $this->object = FFProbe::load($this->logger);
- }
-
- /**
- * @covers FFMpeg\FFProbe::probeFormat
- * @covers FFMpeg\FFProbe::probeStreams
- * @covers FFMpeg\FFProbe::executeProbe
- */
- public function testProbe()
- {
- $this->object->probeFormat(__DIR__ . '/../../files/Test.ogv');
- $this->object->probeStreams(__DIR__ . '/../../files/Test.ogv');
- }
-
- /**
- * @covers FFMpeg\FFProbe::probeFormat
- * @covers FFMpeg\FFProbe::executeProbe
- * @expectedException \RuntimeException
- */
- public function testProbeInvalidFile()
- {
- $this->object->probeFormat(__DIR__ . '/../../files/WrongFile.mp4');
- }
-
- /**
- * @covers FFMpeg\FFProbe::probeStreams
- * @covers FFMpeg\FFProbe::executeProbe
- * @expectedException \FFMpeg\Exception\RuntimeException
- */
- public function testProbeStreamsInvalidFile()
- {
- $this->object->probeStreams(__DIR__ . '/../../files/WrongFile.mp4');
- }
-
- /**
- * @covers FFMpeg\FFProbe::probeStreams
- * @covers FFMpeg\FFProbe::executeProbe
- * @expectedException \InvalidArgumentException
- */
- public function testProbeStreamsInvalidPathFile()
- {
- $this->object->probeStreams(__DIR__ . '/../../files/unknown.file');
- }
-
- /**
- * @covers FFMpeg\FFProbe::probeFormat
- * @covers FFMpeg\FFProbe::executeProbe
- * @expectedException \InvalidArgumentException
- */
- public function testProbeFormatInvalidPathFile()
- {
- $this->object->probeFormat(__DIR__ . '/../../files/unknown.file');
- }
-
-}
diff --git a/tests/src/FFMpeg/Format/Audio/DefaultAudioTest.php b/tests/src/FFMpeg/Format/Audio/DefaultAudioTest.php
deleted file mode 100644
index d24ecaa..0000000
--- a/tests/src/FFMpeg/Format/Audio/DefaultAudioTest.php
+++ /dev/null
@@ -1,135 +0,0 @@
-object = new DefaultAudioTester();
- }
-
- /**
- * @covers FFMpeg\Format\Audio\DefaultAudio::getExtraParams
- */
- public function testGetExtraParams()
- {
- $this->assertEquals('-f format', $this->object->getExtraParams());
- }
-
- /**
- * @covers FFMpeg\Format\Audio\DefaultAudio::getAudioCodec
- */
- public function testGetAudioCodec()
- {
- $this->assertEquals('audiocodec1', $this->object->getAudioCodec());
- }
-
- /**
- * @covers FFMpeg\Format\Audio\DefaultAudio::setAudioCodec
- */
- public function testSetAudioCodec()
- {
- $this->object->setAudioCodec('audiocodec2');
- $this->assertEquals('audiocodec2', $this->object->getAudioCodec());
- $this->object->setAudioCodec('audiocodec1');
- $this->assertEquals('audiocodec1', $this->object->getAudioCodec());
- }
-
- /**
- * @covers FFMpeg\Format\Audio\DefaultAudio::setAudioCodec
- * @expectedException \InvalidArgumentException
- */
- public function testSetWrongAudioCodec()
- {
- $this->object->setAudioCodec('audiocodec4');
- }
-
- /**
- * @covers FFMpeg\Format\Audio\DefaultAudio::getAudioSampleRate
- */
- public function testGetAudioSampleRate()
- {
- $this->assertEquals(44100, $this->object->getAudioSampleRate());
- }
-
- /**
- * @covers FFMpeg\Format\Audio\DefaultAudio::setAudioSampleRate
- */
- public function testSetAudioSampleRate()
- {
- $this->object->setAudioSampleRate(22050);
- $this->assertEquals(22050, $this->object->getAudioSampleRate());
- }
-
- /**
- * @covers FFMpeg\Format\Audio\DefaultAudio::setAudioSampleRate
- * @expectedException \InvalidArgumentException
- * @dataProvider getWrongAudioSampleRate
- */
- public function testSetWrongAudioSampleRate($samplerate)
- {
- $this->object->setAudioSampleRate($samplerate);
- }
-
- public function getWrongAudioSampleRate()
- {
- return array(array(-5), array(0));
- }
-
- /**
- * @covers FFMpeg\Format\Audio\DefaultAudio::getKiloBitrate
- */
- public function testGetKiloBitrate()
- {
- $this->assertEquals(128, $this->object->getKiloBitrate());
- }
-
- /**
- * @covers FFMpeg\Format\Audio\DefaultAudio::setKiloBitrate
- */
- public function testSetKiloBitrate()
- {
- $this->object->setKiloBitrate(500);
- $this->assertEquals(500, $this->object->getKiloBitrate());
- }
-
- /**
- * @covers FFMpeg\Format\Audio\DefaultAudio::setKiloBitrate
- * @dataProvider getWrongKiloBitrate
- * @expectedException \InvalidArgumentException
- */
- public function testSetWrongKiloBitrate($kbrate)
- {
- $this->object->setKiloBitrate($kbrate);
- }
-
- public function getWrongKiloBitrate()
- {
- return array(array(-5), array(0));
- }
-
-}
-
-class DefaultAudioTester extends DefaultAudio
-{
-
- protected $audioCodec = 'audiocodec1';
-
- public function getAvailableAudioCodecs()
- {
- return array('audiocodec1', 'audiocodec2', 'audiocodec3');
- }
-
- public function getExtraParams()
- {
- return '-f format';
- }
-
-}
diff --git a/tests/src/FFMpeg/Format/Video/DefaultVideoTest.php b/tests/src/FFMpeg/Format/Video/DefaultVideoTest.php
deleted file mode 100644
index 8ee49eb..0000000
--- a/tests/src/FFMpeg/Format/Video/DefaultVideoTest.php
+++ /dev/null
@@ -1,201 +0,0 @@
-object = new DefaultVideoTester();
- }
-
- /**
- * @covers FFMpeg\Format\Video\DefaultVideo::setDimensions
- * @covers FFMpeg\Format\Video\DefaultVideo::getWidth
- * @covers FFMpeg\Format\Video\DefaultVideo::getHeight
- */
- public function testSetDimensions()
- {
- $this->object->setDimensions(240, 640);
- $this->assertEquals(240, $this->object->getWidth());
- $this->assertEquals(640, $this->object->getHeight());
-
- $this->object->setDimensions(242, 638);
- $this->assertEquals(242, $this->object->getWidth());
- $this->assertEquals(638, $this->object->getHeight());
- }
-
- /**
- * @covers FFMpeg\Format\Video\DefaultVideo::setDimensions
- * @dataProvider getWrongDimensions
- * @expectedException \InvalidArgumentException
- */
- public function testWrongDimensions($width, $height)
- {
- $this->object->setDimensions($width, $height);
- }
-
- /**
- * Data provider for testWrongDimensions
- *
- * @return array
- */
- public function getWrongDimensions()
- {
- return array(
- array(0, 240),
- array(240, 0),
- array(-5, 240),
- array(240, -5),
- array(-5, -5),
- array(0, 0)
- );
- }
-
- /**
- * @covers FFMpeg\Format\Video\DefaultVideo::getFrameRate
- */
- public function testGetFrameRate()
- {
- $this->assertEquals(25, $this->object->getFrameRate());
- }
-
- /**
- * @covers FFMpeg\Format\Video\DefaultVideo::setFrameRate
- */
- public function testSetFrameRate()
- {
- $this->object->setFrameRate(12);
- $this->assertEquals(12, $this->object->getFrameRate());
- }
-
- /**
- * @covers FFMpeg\Format\Video\DefaultVideo::setFrameRate
- * @dataProvider getWrongFrameRates
- * @expectedException \InvalidArgumentException
- */
- public function testSetWrongFrameRates($framerate)
- {
- $this->object->setFrameRate($framerate);
- }
-
- /**
- * Data provider for testWrongFrameRates
- *
- * @return array
- */
- public function getWrongFramerates()
- {
- return array(array(-5), array(0));
- }
-
- /**
- * @covers FFMpeg\Format\Video\DefaultVideo::getVideoCodec
- */
- public function testGetVideoCodec()
- {
- $this->assertEquals('videocodec2', $this->object->getVideoCodec());
- }
-
- /**
- * @covers FFMpeg\Format\Video\DefaultVideo::setVideoCodec
- */
- public function testSetVideoCodec()
- {
- $this->object->setVideoCodec('videocodec2');
- $this->assertEquals('videocodec2', $this->object->getVideoCodec());
- $this->object->setVideoCodec('videocodec1');
- $this->assertEquals('videocodec1', $this->object->getVideoCodec());
- }
-
- /**
- * @covers FFMpeg\Format\Video\DefaultVideo::setVideoCodec
- * @expectedException \InvalidArgumentException
- */
- public function testSetWrongVideoCodec()
- {
- $this->object->setVideoCodec('videocodec4');
- }
-
- /**
- * @covers FFMpeg\Format\Video\DefaultVideo::getGOPsize
- */
- public function testGetGOPsize()
- {
- $this->assertEquals(25, $this->object->getGOPsize());
- }
-
- /**
- * @covers FFMpeg\Format\Video\DefaultVideo::setGOPsize
- */
- public function testSetGOPsize()
- {
- $this->object->setGOPsize(100);
- $this->assertEquals(100, $this->object->getGOPsize());
- }
-
- /**
- * @covers FFMpeg\Format\Video\DefaultVideo::setGOPsize
- * @dataProvider getWrongGOPsize
- * @expectedException \InvalidArgumentException
- */
- public function testSetWrongGOPSize($GOP)
- {
- $this->object->setGOPsize($GOP);
- }
-
- public function getWrongGOPsize()
- {
- return array(array(-5), array(0));
- }
-
- /**
- * @covers FFMpeg\Format\Video\DefaultVideo::getKiloBitrate
- */
- public function testGetKiloBitrate()
- {
- $this->assertEquals(1000, $this->object->getKiloBitrate());
- }
-
- /**
- * @covers FFMpeg\Format\Video\DefaultVideo::getExtraParams
- */
- public function testGetExtraParams()
- {
- $this->assertTrue(is_array($this->object->getExtraParams()));
- }
-}
-
-class DefaultVideoTester extends DefaultVideo
-{
-
- protected $audioCodec = 'audiocodec1';
- protected $videoCodec = 'videocodec2';
-
- public function supportBFrames()
- {
- return true;
- }
-
- public function getAvailableAudioCodecs()
- {
- return array('audiocodec1', 'audiocodec2', 'audiocodec3');
- }
-
- public function getAvailableVideoCodecs()
- {
- return array('videocodec1', 'videocodec2');
- }
-
- public function getExtraParams()
- {
- return array('-f', 'format');
- }
-
-}
diff --git a/tests/src/FFMpeg/Format/Video/OggTest.php b/tests/src/FFMpeg/Format/Video/OggTest.php
deleted file mode 100644
index 60160d4..0000000
--- a/tests/src/FFMpeg/Format/Video/OggTest.php
+++ /dev/null
@@ -1,35 +0,0 @@
-object = new Ogg();
- $this->object->setDimensions(320, 320);
- }
-
- /**
- * @covers FFMpeg\Format\Video\Ogg::getAvailableAudioCodecs
- */
- public function testGetAvailableAudioCodecs()
- {
- $this->object->setAudioCodec('libvorbis');
- }
-
- /**
- * @covers FFMpeg\Format\Video\Ogg::getAvailableVideoCodecs
- */
- public function testGetAvailableVideoCodecs()
- {
- $this->object->setVideoCodec('libtheora');
- }
-
-}
diff --git a/tests/src/FFMpeg/Format/Video/WebMTest.php b/tests/src/FFMpeg/Format/Video/WebMTest.php
deleted file mode 100644
index bf7cc88..0000000
--- a/tests/src/FFMpeg/Format/Video/WebMTest.php
+++ /dev/null
@@ -1,43 +0,0 @@
-object = new WebM();
- $this->object->setDimensions(320, 320);
- }
-
- /**
- * @covers FFMpeg\Format\Video\WebM::getAvailableAudioCodecs
- */
- public function testGetAvailableAudioCodecs()
- {
- $this->object->setAudioCodec('libvorbis');
- }
-
- /**
- * @covers FFMpeg\Format\Video\WebM::getAvailableVideoCodecs
- */
- public function testGetAvailableVideoCodecs()
- {
- $this->object->setVideoCodec('libvpx');
- }
-
- /**
- * @covers FFMpeg\Format\Video\WebM::getExtraParams
- */
- public function testGetExtraParams()
- {
- $this->assertTrue(is_array($this->object->getExtraParams()));
- }
-
-}
diff --git a/tests/src/FFMpeg/Format/Video/X264Test.php b/tests/src/FFMpeg/Format/Video/X264Test.php
deleted file mode 100644
index e107418..0000000
--- a/tests/src/FFMpeg/Format/Video/X264Test.php
+++ /dev/null
@@ -1,35 +0,0 @@
-object = new X264();
- $this->object->setDimensions(320, 320);
- }
-
- /**
- * @covers FFMpeg\Format\Video\X264::getAvailableAudioCodecs
- */
- public function testGetAvailableAudioCodecs()
- {
- $this->object->setAudioCodec('libmp3lame');
- }
-
- /**
- * @covers FFMpeg\Format\Video\X264::getAvailableVideoCodecs
- */
- public function testGetAvailableVideoCodecs()
- {
- $this->object->setVideoCodec('libx264');
- }
-
-}
diff --git a/tests/src/FFMpeg/Progress/ProgressTest.php b/tests/src/FFMpeg/Progress/ProgressTest.php
deleted file mode 100644
index 3881314..0000000
--- a/tests/src/FFMpeg/Progress/ProgressTest.php
+++ /dev/null
@@ -1,93 +0,0 @@
-logger = new Logger('tests');
- $this->logger->pushHandler(new NullHandler());
-
- $this->object = FFMpeg::load($this->logger);
- $this->probe = FFProbe::load($this->logger);
- $this->object->setProber($this->probe);
- }
-
- /**
- * @covers FFMpeg\Helper\ProgressHelper::parseProgress
- * @covers FFMpeg\Helper\ProgressHelper::convertDuration
- * @covers FFMpeg\Helper\ProgressHelper::getProgressInfo
- * @covers FFMpeg\Helper\AudioProgressHelper::getPattern
- */
- public function testProgressHelper()
- {
- $progressInfo = array();
-
- $audioProgress = new AudioProgressHelper(function($percent, $remaining, $rate) use ($progressInfo ) {
- $progressInfo[] = $percent;
- });
-
- $dest = __DIR__ . '/../../../files/encode_test.mp3';
-
- $this->object->open(__DIR__ . '/../../../files/Audio.mp3');
- $this->object->attachHelper($audioProgress);
- $this->object->encode(new Mp3(), $dest);
-
- $this->assertGreaterThanOrEqual(3, $progressInfo);
- }
-
- /**
- * @covers FFMpeg\Helper\AudioProgressHelper::getPattern
- */
- public function testAudioProgressHelper()
- {
- $audioProgress = new AudioProgressHelper(function($percent, $remaining, $rate) { });
- $audioProgress->setDuration(500);
-
- $line = "size= 712kB time=00:00:45.50 bitrate= 128.1kbits/s";
- $audioProgress->parseProgress($line);
-
- sleep(1);
-
- $line = "size= 4712kB time=00:01:45.50 bitrate= 128.1kbits/s";
- $progress = $audioProgress->parseProgress($line);
-
- $this->assertEquals('21.0', $progress['percent']);
- }
-
- /**
- * @covers FFMpeg\Helper\VideoProgressHelper::getPattern
- */
- public function testVideoProgress()
- {
- $videoProgress = new VideoProgressHelper(function($percent, $remaining, $rate) {});
- $videoProgress->setDuration(500);
-
- $line = "frame= 206 fps=202 q=10.0 size= 571kB time=00:00:07.12 bitrate= 656.8kbits/s dup=9 drop=0";
- $videoProgress->parseProgress($line);
-
- sleep(1);
-
- $line = "frame= 854 fps=113 q=20.0 size= 4430kB time=00:00:33.04 bitrate=1098.5kbits/s dup=36 drop=0";
- $progress = $videoProgress->parseProgress($line);
-
- $this->assertEquals('6.0', $progress['percent']);
- }
-}