Version 0.3

This commit is contained in:
Romain Neutron 2013-06-25 10:03:20 +02:00
commit ad3a5af623
130 changed files with 7283 additions and 2627 deletions

View file

@ -0,0 +1,126 @@
<?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\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;
}
}

View file

@ -0,0 +1,34 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Media;
use 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);
}
}

View file

@ -0,0 +1,92 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Media;
use Alchemy\BinaryDriver\Exception\ExecutionFailureException;
use FFMpeg\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;
}
}

104
src/FFMpeg/Media/Frame.php Normal file
View file

@ -0,0 +1,104 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Media;
use Alchemy\BinaryDriver\Exception\ExecutionFailureException;
use FFMpeg\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;
}
}

View file

@ -0,0 +1,25 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Media;
interface MediaTypeInterface
{
/**
* Returns the available filters
*/
public function filters();
/**
* @return string
*/
public function getPathfile();
}

166
src/FFMpeg/Media/Video.php Normal file
View file

@ -0,0 +1,166 @@
<?php
/*
* This file is part of PHP-FFmpeg.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FFMpeg\Media;
use Alchemy\BinaryDriver\Exception\ExecutionFailureException;
use FFMpeg\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);
}
}