diff --git a/.gitignore b/.gitignore index c85f162..2b7fb77 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ composer.phar composer.lock phpunit.xml +sami.phar diff --git a/README.md b/README.md index c204cd8..825afeb 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Check another amazing repo : [PHP FFMpeg extras](https://github.com/alchemy-fr/P This library requires a working FFMpeg install. You will need both FFMpeg and FFProbe binaries to use it. Be sure that these binaries can be located with system PATH to get the benefit of the binary detection, -otherwise you should have to explicitely give the binaries path on load. +otherwise you should have to explicitly give the binaries path on load. For Windows users : Please find the binaries at http://ffmpeg.zeranoe.com/builds/. @@ -28,12 +28,8 @@ appear in latest ffmpeg version. The recommended way to install PHP-FFMpeg is through [Composer](https://getcomposer.org). -```json -{ - "require": { - "php-ffmpeg/php-ffmpeg": "~0.5" - } -} +```bash +$ composer require php-ffmpeg/php-ffmpeg ``` ## Basic Usage @@ -69,7 +65,7 @@ $ffmpeg = FFMpeg\FFMpeg::create(); ``` FFMpeg will autodetect ffmpeg and ffprobe binaries. If you want to give binary -paths explicitely, you can pass an array as configuration. A `Psr\Logger\LoggerInterface` +paths explicitly, you can pass an array as configuration. A `Psr\Logger\LoggerInterface` can also be passed to log binary executions. ```php @@ -233,12 +229,28 @@ Resizes a video to a given size. $video->filters()->resize($dimension, $mode, $useStandards); ``` -The resize filter takes three parameters : +The resize filter takes three parameters: - `$dimension`, an instance of `FFMpeg\Coordinate\Dimension` - `$mode`, one of the constants `FFMpeg\Filters\Video\ResizeFilter::RESIZEMODE_*` constants - `$useStandards`, a boolean to force the use of the nearest aspect ratio standard. +If you want a video in a non-standard ratio, you can use the padding filter to resize your video in the desired size, and wrap it into black bars. + +```php +$video->filters()->pad($dimension); +``` + +The pad filter takes one parameter: + +- `$dimension`, an instance of `FFMpeg\Coordinate\Dimension` + +Don't forget to save it afterwards. + +```php +$video->save(new FFMpeg\Format\Video\X264(), $new_file); +``` + ###### Watermark Watermark a video with a given image. @@ -344,6 +356,27 @@ method. It only accepts audio filters. You can build your own filters and some are bundled in PHP-FFMpeg - they are accessible through the `FFMpeg\Media\Audio::filters` method. +###### Metadata + +Add metadata to audio files. Just pass an array of key=value pairs of all +metadata you would like to add. If no arguments are passed into the filter +all metadata will be removed from input file. Currently supported data is +title, artist, album, artist, composer, track, year, description, artwork + +```php +$audio->filters()->addMetadata(["title" => "Some Title", "track" => 1]); + +//remove all metadata and video streams from audio file +$audio->filters()->addMetadata(); +``` + +Add artwork to the audio file +```php +$audio->filters()->addMetadata(["artwork" => "/path/to/image/file.jpg"]); +``` +NOTE: at present ffmpeg (version 3.2.2) only supports artwork output for .mp3 +files + ###### Resample Resamples an audio file. diff --git a/composer.json b/composer.json index 7e2babf..c7a79b4 100644 --- a/composer.json +++ b/composer.json @@ -19,6 +19,11 @@ "name": "Patrik Karisch", "email": "patrik@karisch.guru", "homepage": "http://www.karisch.guru" + }, + { + "name": "Romain Biard", + "email": "romain.biard@gmail.com", + "homepage": "https://www.strime.io/" } ], "require": { diff --git a/src/FFMpeg/Filters/Audio/AddMetadataFilter.php b/src/FFMpeg/Filters/Audio/AddMetadataFilter.php new file mode 100644 index 0000000..aa1207b --- /dev/null +++ b/src/FFMpeg/Filters/Audio/AddMetadataFilter.php @@ -0,0 +1,45 @@ +metaArr = $data; + $this->priority = $priority; + } + + public function getPriority() + { + //must be of high priority in case theres a second input stream (artwork) to register with audio + return $this->priority; + } + + public function apply(Audio $audio, AudioInterface $format) + { + if (is_null($this->metaArr)) + return ['-map_metadata', '-1', '-vn']; + + $metadata = []; + + if (array_key_exists("artwork", $this->metaArr)) { + array_push($metadata, "-i", $this->metaArr['artwork'], "-map", "0", "-map", "1"); + unset($this->metaArr['artwork']); + } + + foreach ($this->metaArr as $k => $v) { + array_push($metadata, "-metadata", "$k=$v"); + } + + return $metadata; + } +} diff --git a/src/FFMpeg/Filters/Audio/AudioFilters.php b/src/FFMpeg/Filters/Audio/AudioFilters.php index fe328c5..0eca6f7 100644 --- a/src/FFMpeg/Filters/Audio/AudioFilters.php +++ b/src/FFMpeg/Filters/Audio/AudioFilters.php @@ -2,6 +2,7 @@ namespace FFMpeg\Filters\Audio; +use FFMpeg\Filters\Audio\AddMetadataFilter; use FFMpeg\Media\Audio; class AudioFilters @@ -26,4 +27,25 @@ class AudioFilters return $this; } + + /** + * Add metadata to an audio file. If no arguments are given then filter + * will remove all metadata from the audio file + * @param Array|Null $data If array must contain one of these key/value pairs: + * - "title": Title metadata + * - "artist": Artist metadata + * - "composer": Composer metadata + * - "album": Album metadata + * - "track": Track metadata + * - "artwork": Song artwork. String of file path + * - "year": Year metadata + * - "genre": Genre metadata + * - "description": Description metadata + */ + public function addMetadata($data = null) + { + $this->media->addFilter(new AddMetadataFilter($data)); + + return $this; + } } diff --git a/src/FFMpeg/Filters/Video/PadFilter.php b/src/FFMpeg/Filters/Video/PadFilter.php new file mode 100644 index 0000000..df19a2d --- /dev/null +++ b/src/FFMpeg/Filters/Video/PadFilter.php @@ -0,0 +1,60 @@ + + * + * 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\RuntimeException; +use FFMpeg\Media\Video; +use FFMpeg\Format\VideoInterface; + +class PadFilter implements VideoFilterInterface +{ + /** @var Dimension */ + private $dimension; + /** @var integer */ + private $priority; + + public function __construct(Dimension $dimension, $priority = 0) + { + $this->dimension = $dimension; + $this->priority = $priority; + } + + /** + * {@inheritdoc} + */ + public function getPriority() + { + return $this->priority; + } + + /** + * @return Dimension + */ + public function getDimension() + { + return $this->dimension; + } + + /** + * {@inheritdoc} + */ + public function apply(Video $video, VideoInterface $format) + { + $commands = array(); + + $commands[] = '-vf'; + $commands[] = 'scale=iw*min(' . $this->dimension->getWidth() . '/iw\,' . $this->dimension->getHeight() .'/ih):ih*min(' . $this->dimension->getWidth() . '/iw\,' . $this->dimension->getHeight() .'/ih),pad=' . $this->dimension->getWidth() . ':' . $this->dimension->getHeight() . ':(' . $this->dimension->getWidth() . '-iw)/2:(' . $this->dimension->getHeight() .'-ih)/2'; + + return $commands; + } +} diff --git a/src/FFMpeg/Filters/Video/VideoFilters.php b/src/FFMpeg/Filters/Video/VideoFilters.php index 0f33b4e..c00243d 100644 --- a/src/FFMpeg/Filters/Video/VideoFilters.php +++ b/src/FFMpeg/Filters/Video/VideoFilters.php @@ -113,6 +113,20 @@ class VideoFilters extends AudioFilters return $this; } + /** + * Adds padding (black bars) to a video. + * + * @param Dimension $dimension + * + * @return VideoFilters + */ + public function pad(Dimension $dimension) + { + $this->media->addFilter(new PadFilter($dimension)); + + return $this; + } + public function rotate($angle) { $this->media->addFilter(new RotateFilter($angle, 30)); @@ -147,4 +161,18 @@ class VideoFilters extends AudioFilters return $this; } + + /** + * Applies a custom filter: -vf foo bar + * + * @param string $parameters + * + * @return VideoFilters + */ + public function custom($parameters) + { + $this->media->addFilter(new CustomFilter($parameters)); + + return $this; + } } diff --git a/tests/Unit/Filters/Audio/AudioMetadataTest.php b/tests/Unit/Filters/Audio/AudioMetadataTest.php new file mode 100644 index 0000000..33eb470 --- /dev/null +++ b/tests/Unit/Filters/Audio/AudioMetadataTest.php @@ -0,0 +1,63 @@ +getAudioMock(); + $audio->expects($this->once()) + ->method('addFilter') + ->with($this->isInstanceOf('FFMpeg\Filters\Audio\AddMetadataFilter')) + ->will($this->returnCallback(function ($filter) use (&$capturedFilter) { + $capturedFilter = $filter; + })); + $format = $this->getMock('FFMpeg\Format\AudioInterface'); + + $filters = new AudioFilters($audio); + $filters->addMetadata(array('title' => "Hello World")); + $this->assertEquals(array(0 => "-metadata", 1 => "title=Hello World"), $capturedFilter->apply($audio, $format)); + } + + public function testAddArtwork() + { + $capturedFilter = null; + + $audio = $this->getAudioMock(); + $audio->expects($this->once()) + ->method('addFilter') + ->with($this->isInstanceOf('FFMpeg\Filters\Audio\AddMetadataFilter')) + ->will($this->returnCallback(function ($filter) use (&$capturedFilter) { + $capturedFilter = $filter; + })); + $format = $this->getMock('FFMpeg\Format\AudioInterface'); + + $filters = new AudioFilters($audio); + $filters->addMetadata(array('genre' => 'Some Genre', 'artwork' => "/path/to/file.jpg")); + $this->assertEquals(array(0 => "-i", 1 => "/path/to/file.jpg", 2 => "-map", 3 => "0", 4 => "-map", 5 => "1", 6 => "-metadata", 7 => "genre=Some Genre"), $capturedFilter->apply($audio, $format)); + } + + public function testRemoveMetadata() + { + $capturedFilter = null; + + $audio = $this->getAudioMock(); + $audio->expects($this->once()) + ->method('addFilter') + ->with($this->isInstanceOf('FFMpeg\Filters\Audio\AddMetadataFilter')) + ->will($this->returnCallback(function ($filter) use (&$capturedFilter) { + $capturedFilter = $filter; + })); + $format = $this->getMock('FFMpeg\Format\AudioInterface'); + + $filters = new AudioFilters($audio); + $filters->addMetadata(); + $this->assertEquals(array(0 => "-map_metadata", 1 => "-1", 2 => "-vn"), $capturedFilter->apply($audio, $format)); + } +} diff --git a/tests/Unit/Filters/Video/PadFilterTest.php b/tests/Unit/Filters/Video/PadFilterTest.php new file mode 100644 index 0000000..1d7c85a --- /dev/null +++ b/tests/Unit/Filters/Video/PadFilterTest.php @@ -0,0 +1,44 @@ +getVideoMock(); + $pathfile = '/path/to/file'.mt_rand(); + + $format = $this->getMock('FFMpeg\Format\VideoInterface'); + + $streams = new StreamCollection(array( + new Stream(array( + 'codec_type' => 'video', + 'width' => $width, + 'height' => $height, + )) + )); + + $filter = new PadFilter($dimension); + $this->assertEquals($expected, $filter->apply($video, $format)); + } + + public function provideDimensions() + { + return array( + array(new Dimension(1000, 800), 640, 480, array('-vf', 'scale=iw*min(1000/iw\,800/ih):ih*min(1000/iw\,800/ih),pad=1000:800:(1000-iw)/2:(800-ih)/2')), + array(new Dimension(300, 600), 640, 480, array('-vf', 'scale=iw*min(300/iw\,600/ih):ih*min(300/iw\,600/ih),pad=300:600:(300-iw)/2:(600-ih)/2')), + array(new Dimension(100, 900), 640, 480, array('-vf', 'scale=iw*min(100/iw\,900/ih):ih*min(100/iw\,900/ih),pad=100:900:(100-iw)/2:(900-ih)/2')), + array(new Dimension(1200, 200), 640, 480, array('-vf', 'scale=iw*min(1200/iw\,200/ih):ih*min(1200/iw\,200/ih),pad=1200:200:(1200-iw)/2:(200-ih)/2')), + ); + } +}