Merge branch '0.4'
This commit is contained in:
commit
4db04f70e4
16 changed files with 176 additions and 54 deletions
|
|
@ -1,6 +1,12 @@
|
||||||
CHANGELOG
|
CHANGELOG
|
||||||
---------
|
---------
|
||||||
|
|
||||||
|
* 0.4.2 (xx-xx-xx)
|
||||||
|
|
||||||
|
* Add Rotate filter.
|
||||||
|
* Remove time_start metadata when using synchronize filter
|
||||||
|
* Remove restriction on filesystem resources.
|
||||||
|
|
||||||
* 0.4.1 (11-26-2013)
|
* 0.4.1 (11-26-2013)
|
||||||
|
|
||||||
* Add Clip filter (@guimeira)
|
* Add Clip filter (@guimeira)
|
||||||
|
|
|
||||||
27
README.md
27
README.md
|
|
@ -75,8 +75,17 @@ $ffmpeg = FFMpeg\FFMpeg::create(array(
|
||||||
|
|
||||||
### Manipulate media
|
### Manipulate media
|
||||||
|
|
||||||
`FFMpeg\FFMpeg` creates media based on file paths. To open a file path, use the
|
`FFMpeg\FFMpeg` creates media based on URIs. URIs could be either a pointer to a
|
||||||
`FFMpeg\FFMpeg::open` method.
|
local filesystem resource, an HTTP resource or any resource supported by FFmpeg.
|
||||||
|
|
||||||
|
**Note** : To list all supported resource type of your FFmpeg build, use the
|
||||||
|
`-protocols` command :
|
||||||
|
|
||||||
|
```
|
||||||
|
ffmpeg -protocols
|
||||||
|
```
|
||||||
|
|
||||||
|
To open a resource, use the `FFMpeg\FFMpeg::open` method.
|
||||||
|
|
||||||
```php
|
```php
|
||||||
$ffmpeg->open('video.mpeg');
|
$ffmpeg->open('video.mpeg');
|
||||||
|
|
@ -145,6 +154,20 @@ $video
|
||||||
->synchronize();
|
->synchronize();
|
||||||
```
|
```
|
||||||
|
|
||||||
|
###### Rotate
|
||||||
|
|
||||||
|
Rotates a video to a given angle.
|
||||||
|
|
||||||
|
```php
|
||||||
|
$video->filters()->rotate($angle);
|
||||||
|
```
|
||||||
|
|
||||||
|
The `$angle` parameter must be one of the following constants :
|
||||||
|
|
||||||
|
- `FFMpeg\Filters\Video\RotateFilter::ROTATE_90` : 90° clockwise
|
||||||
|
- `FFMpeg\Filters\Video\RotateFilter::ROTATE_180` : 180°
|
||||||
|
- `FFMpeg\Filters\Video\RotateFilter::ROTATE_270` : 90° counterclockwise
|
||||||
|
|
||||||
###### Resize
|
###### Resize
|
||||||
|
|
||||||
Resizes a video to a given size.
|
Resizes a video to a given size.
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ namespace FFMpeg;
|
||||||
use Alchemy\BinaryDriver\ConfigurationInterface;
|
use Alchemy\BinaryDriver\ConfigurationInterface;
|
||||||
use FFMpeg\Driver\FFMpegDriver;
|
use FFMpeg\Driver\FFMpegDriver;
|
||||||
use FFMpeg\Exception\InvalidArgumentException;
|
use FFMpeg\Exception\InvalidArgumentException;
|
||||||
|
use FFMpeg\Exception\RuntimeException;
|
||||||
use FFMpeg\Media\Audio;
|
use FFMpeg\Media\Audio;
|
||||||
use FFMpeg\Media\Video;
|
use FFMpeg\Media\Video;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
@ -88,12 +89,10 @@ class FFMpeg
|
||||||
*/
|
*/
|
||||||
public function open($pathfile)
|
public function open($pathfile)
|
||||||
{
|
{
|
||||||
if (!file_exists($pathfile)) {
|
if (null === $streams = $this->ffprobe->streams($pathfile)) {
|
||||||
throw new InvalidArgumentException(sprintf('File %s does not exists', $pathfile));
|
throw new RuntimeException(sprintf('Unable to probe "%s".', $pathfile));
|
||||||
}
|
}
|
||||||
|
|
||||||
$streams = $this->ffprobe->streams($pathfile);
|
|
||||||
|
|
||||||
if (0 < count($streams->videos())) {
|
if (0 < count($streams->videos())) {
|
||||||
return new Video($pathfile, $this->driver, $this->ffprobe);
|
return new Video($pathfile, $this->driver, $this->ffprobe);
|
||||||
} elseif (0 < count($streams->audios())) {
|
} elseif (0 < count($streams->audios())) {
|
||||||
|
|
|
||||||
73
src/FFMpeg/Filters/Video/RotateFilter.php
Normal file
73
src/FFMpeg/Filters/Video/RotateFilter.php
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of PHP-FFmpeg.
|
||||||
|
*
|
||||||
|
* (c) Alchemy <dev.team@alchemy.fr>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace FFMpeg\Filters\Video;
|
||||||
|
|
||||||
|
use FFMpeg\Coordinate\Dimension;
|
||||||
|
use FFMpeg\Exception\InvalidArgumentException;
|
||||||
|
use FFMpeg\Exception\RuntimeException;
|
||||||
|
use FFMpeg\Media\Video;
|
||||||
|
use FFMpeg\Format\VideoInterface;
|
||||||
|
|
||||||
|
class RotateFilter implements VideoFilterInterface
|
||||||
|
{
|
||||||
|
const ROTATE_90 = 'transpose=1';
|
||||||
|
const ROTATE_180 = 'hflip,vflip';
|
||||||
|
const ROTATE_270 = 'transpose=2';
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $angle;
|
||||||
|
/** @var integer */
|
||||||
|
private $priority;
|
||||||
|
|
||||||
|
public function __construct($angle, $priority = 0)
|
||||||
|
{
|
||||||
|
$this->setAngle($angle);
|
||||||
|
$this->priority = (int) $priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getPriority()
|
||||||
|
{
|
||||||
|
return $this->priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Dimension
|
||||||
|
*/
|
||||||
|
public function getAngle()
|
||||||
|
{
|
||||||
|
return $this->angle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function apply(Video $video, VideoInterface $format)
|
||||||
|
{
|
||||||
|
return array('-vf', $this->angle, '-metadata:s:v:0', 'rotate=0');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function setAngle($angle)
|
||||||
|
{
|
||||||
|
switch ($angle) {
|
||||||
|
case self::ROTATE_90:
|
||||||
|
case self::ROTATE_180:
|
||||||
|
case self::ROTATE_270:
|
||||||
|
$this->angle = $angle;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new InvalidArgumentException('Invalid angle value.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -39,6 +39,6 @@ class SynchronizeFilter implements VideoFilterInterface
|
||||||
*/
|
*/
|
||||||
public function apply(Video $video, VideoInterface $format)
|
public function apply(Video $video, VideoInterface $format)
|
||||||
{
|
{
|
||||||
return array('-async', '1');
|
return array('-async', '1', '-metadata:s:v:0', 'start_time=0');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -95,4 +95,11 @@ class VideoFilters extends AudioFilters
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function rotate($angle, $priority = 0)
|
||||||
|
{
|
||||||
|
$this->media->addFilter(new RotateFilter($angle, $priority));
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -154,6 +154,10 @@ abstract class AbstractProgressListener extends EventEmitter implements Listener
|
||||||
$this->initialize();
|
$this->initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (null === $this->totalSize || null === $this->duration) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$matches = array();
|
$matches = array();
|
||||||
|
|
||||||
if (preg_match($this->getPattern(), $progress, $matches) !== 1) {
|
if (preg_match($this->getPattern(), $progress, $matches) !== 1) {
|
||||||
|
|
@ -226,9 +230,14 @@ abstract class AbstractProgressListener extends EventEmitter implements Listener
|
||||||
|
|
||||||
private function initialize()
|
private function initialize()
|
||||||
{
|
{
|
||||||
$format = $this->ffprobe->format($this->pathfile);
|
try {
|
||||||
|
$format = $this->ffprobe->format($this->pathfile);
|
||||||
|
} catch (RuntimeException $e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (false === $format->has('size') || false === $format->has('duration')) {
|
if (false === $format->has('size') || false === $format->has('duration')) {
|
||||||
|
return;
|
||||||
throw new RuntimeException(sprintf('Unable to probe format for %s', $this->pathfile));
|
throw new RuntimeException(sprintf('Unable to probe format for %s', $this->pathfile));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,6 @@ abstract class AbstractMediaType implements MediaTypeInterface
|
||||||
|
|
||||||
public function __construct($pathfile, FFMpegDriver $driver, FFProbe $ffprobe)
|
public function __construct($pathfile, FFMpegDriver $driver, FFProbe $ffprobe)
|
||||||
{
|
{
|
||||||
$this->ensureFileIsPresent($pathfile);
|
|
||||||
|
|
||||||
$this->pathfile = $pathfile;
|
$this->pathfile = $pathfile;
|
||||||
$this->driver = $driver;
|
$this->driver = $driver;
|
||||||
$this->ffprobe = $ffprobe;
|
$this->ffprobe = $ffprobe;
|
||||||
|
|
@ -106,15 +104,6 @@ abstract class AbstractMediaType implements MediaTypeInterface
|
||||||
return $this->filters;
|
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)
|
protected function cleanupTemporaryFile($filename)
|
||||||
{
|
{
|
||||||
if (file_exists($filename) && is_writable($filename)) {
|
if (file_exists($filename) && is_writable($filename)) {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
namespace FFMpeg\Functional;
|
namespace FFMpeg\Functional;
|
||||||
|
|
||||||
use FFMpeg\Format\Video\X264;
|
use FFMpeg\Format\Video\X264;
|
||||||
|
use FFMpeg\Media\Video;
|
||||||
|
|
||||||
class VideoTranscodeTest extends FunctionalTestCase
|
class VideoTranscodeTest extends FunctionalTestCase
|
||||||
{
|
{
|
||||||
|
|
@ -35,4 +36,22 @@ class VideoTranscodeTest extends FunctionalTestCase
|
||||||
$this->assertFileExists($filename);
|
$this->assertFileExists($filename);
|
||||||
unlink($filename);
|
unlink($filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \FFMpeg\Exception\RuntimeException
|
||||||
|
*/
|
||||||
|
public function testTranscodeInvalidFile()
|
||||||
|
{
|
||||||
|
$ffmpeg = $this->getFFMpeg();
|
||||||
|
$ffmpeg->open(__DIR__ . '/../../files/UnknownFileTest.ogv');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSaveInvalidForgedVideo()
|
||||||
|
{
|
||||||
|
$ffmpeg = $this->getFFMpeg();
|
||||||
|
$video = new Video(__DIR__ . '/../../files/UnknownFileTest.ogv', $ffmpeg->getFFMpegDriver(), $ffmpeg->getFFProbe());
|
||||||
|
|
||||||
|
$this->setExpectedException('FFMpeg\Exception\RuntimeException');
|
||||||
|
$video->save(new X264('libvo_aacenc'), __DIR__ . '/output/output-x264.mp4');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,8 @@ use FFMpeg\FFProbe\DataMapping\Stream;
|
||||||
class FFMpegTest Extends TestCase
|
class FFMpegTest Extends TestCase
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @expectedException FFMpeg\Exception\InvalidArgumentException
|
* @expectedException \FFMpeg\Exception\RuntimeException
|
||||||
|
* @expectedExceptionMessage Unable to probe "/path/to/unknown/file".
|
||||||
*/
|
*/
|
||||||
public function testOpenInvalid()
|
public function testOpenInvalid()
|
||||||
{
|
{
|
||||||
|
|
@ -58,7 +59,7 @@ class FFMpegTest Extends TestCase
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @expectedException FFMpeg\Exception\InvalidArgumentException
|
* @expectedException \FFMpeg\Exception\InvalidArgumentException
|
||||||
*/
|
*/
|
||||||
public function testOpenUnknown()
|
public function testOpenUnknown()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
27
tests/FFMpeg/Tests/Filters/Video/RotateFilterTest.php
Normal file
27
tests/FFMpeg/Tests/Filters/Video/RotateFilterTest.php
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace FFMpeg\Tests\Filters\Video;
|
||||||
|
|
||||||
|
use FFMpeg\Filters\Video\RotateFilter;
|
||||||
|
use FFMpeg\Tests\TestCase;
|
||||||
|
|
||||||
|
class RotateFilterTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testApply()
|
||||||
|
{
|
||||||
|
$video = $this->getVideoMock();
|
||||||
|
$format = $this->getMock('FFMpeg\Format\VideoInterface');
|
||||||
|
|
||||||
|
$filter = new RotateFilter(RotateFilter::ROTATE_90);
|
||||||
|
$this->assertEquals(array('-vf', RotateFilter::ROTATE_90, '-metadata:s:v:0', 'rotate=0'), $filter->apply($video, $format));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \FFMpeg\Exception\InvalidArgumentException
|
||||||
|
* @expectedExceptionMessage Invalid angle value.
|
||||||
|
*/
|
||||||
|
public function testApplyInvalidAngle()
|
||||||
|
{
|
||||||
|
new RotateFilter('90');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -13,6 +13,6 @@ class SynchronizeFilterTest extends TestCase
|
||||||
$format = $this->getMock('FFMpeg\Format\VideoInterface');
|
$format = $this->getMock('FFMpeg\Format\VideoInterface');
|
||||||
|
|
||||||
$filter = new SynchronizeFilter();
|
$filter = new SynchronizeFilter();
|
||||||
$this->assertEquals(array('-async', '1'), $filter->apply($video, $format));
|
$this->assertEquals(array('-async', '1', '-metadata:s:v:0', 'start_time=0'), $filter->apply($video, $format));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,5 +6,4 @@ use FFMpeg\Tests\TestCase;
|
||||||
|
|
||||||
abstract class AbstractMediaTestCase extends TestCase
|
abstract class AbstractMediaTestCase extends TestCase
|
||||||
{
|
{
|
||||||
abstract public function testWithInvalidFile();
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,17 +9,6 @@ use FFMpeg\Format\AudioInterface;
|
||||||
|
|
||||||
class AudioTest extends AbstractStreamableTestCase
|
class AudioTest extends AbstractStreamableTestCase
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* @expectedException FFMpeg\Exception\InvalidArgumentException
|
|
||||||
*/
|
|
||||||
public function testWithInvalidFile()
|
|
||||||
{
|
|
||||||
$driver = $this->getFFMpegDriverMock();
|
|
||||||
$ffprobe = $this->getFFProbeMock();
|
|
||||||
|
|
||||||
new Audio('/no/file', $driver, $ffprobe);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testFiltersReturnsAudioFilters()
|
public function testFiltersReturnsAudioFilters()
|
||||||
{
|
{
|
||||||
$driver = $this->getFFMpegDriverMock();
|
$driver = $this->getFFMpegDriverMock();
|
||||||
|
|
|
||||||
|
|
@ -6,14 +6,6 @@ use FFMpeg\Media\Frame;
|
||||||
|
|
||||||
class FrameTest extends AbstractMediaTestCase
|
class FrameTest extends AbstractMediaTestCase
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* @expectedException FFMpeg\Exception\InvalidArgumentException
|
|
||||||
*/
|
|
||||||
public function testWithInvalidFile()
|
|
||||||
{
|
|
||||||
new Frame($this->getVideoMock('/No/file'), $this->getFFMpegDriverMock(), $this->getFFProbeMock(), $this->getTimeCodeMock());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testGetTimeCode()
|
public function testGetTimeCode()
|
||||||
{
|
{
|
||||||
$driver = $this->getFFMpegDriverMock();
|
$driver = $this->getFFMpegDriverMock();
|
||||||
|
|
|
||||||
|
|
@ -9,17 +9,6 @@ use FFMpeg\Format\VideoInterface;
|
||||||
|
|
||||||
class VideoTest extends AbstractStreamableTestCase
|
class VideoTest extends AbstractStreamableTestCase
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* @expectedException FFMpeg\Exception\InvalidArgumentException
|
|
||||||
*/
|
|
||||||
public function testWithInvalidFile()
|
|
||||||
{
|
|
||||||
$driver = $this->getFFMpegDriverMock();
|
|
||||||
$ffprobe = $this->getFFProbeMock();
|
|
||||||
|
|
||||||
new Video('/no/file', $driver, $ffprobe);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testFiltersReturnsVideoFilters()
|
public function testFiltersReturnsVideoFilters()
|
||||||
{
|
{
|
||||||
$driver = $this->getFFMpegDriverMock();
|
$driver = $this->getFFMpegDriverMock();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue