Skip to content

Commit 2b5d18f

Browse files
authored
Creation of a Gif Media to be able to extract gif animation based on video sequences (PHP-FFMpeg#285)
* Creation of a media to extract video sequences into gif files * We add a gif method to the class Video to be able to use the Gif Media. * Parameters where missing in the declaration of the gif function * One parameter was badly defined in the gif method * We use the proper media in the method gif * We add a missing declaration in the Video class * Update of the README file * Modification of the README file * We remove an empty class
1 parent 40f8eda commit 2b5d18f

File tree

6 files changed

+327
-0
lines changed

6 files changed

+327
-0
lines changed

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,22 @@ $frame->save('target.jpg');
403403
This method has a second optional boolean parameter. Set it to true to get
404404
accurate images ; it takes more time to execute.
405405

406+
#### Gif
407+
408+
A gif is an animated image extracted from a sequence of the video ;.
409+
410+
You can save gif files using the `FFMpeg\Media\Gif::save` method.
411+
412+
```php
413+
$video = $ffmpeg->open( '/path/to/video' );
414+
$video
415+
->gif(FFMpeg\Coordinate\TimeCode::fromSeconds(2), new FFMpeg\Coordinate\Dimension(640, 480), 3)
416+
->save($new_file);
417+
```
418+
419+
This method has a third optional boolean parameter, which is the duration of the animation.
420+
If you don't set it, you will get a fixed gif image.
421+
406422
#### Formats
407423

408424
A format implements `FFMpeg\Format\FormatInterface`. To save to a video file,
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
/*
4+
* This file is part of PHP-FFmpeg.
5+
*
6+
* (c) Strime <contact@strime.io>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace FFMpeg\Filters\Gif;
13+
14+
use FFMpeg\Filters\FilterInterface;
15+
use FFMpeg\Media\Gif;
16+
17+
interface GifFilterInterface extends FilterInterface
18+
{
19+
public function apply(Gif $gif);
20+
}

src/FFMpeg/Filters/Gif/GifFilters.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
/*
4+
* This file is part of PHP-FFmpeg.
5+
*
6+
* (c) Strime <contact@strime.io>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace FFMpeg\Filters\Gif;
13+
14+
use FFMpeg\Media\Gif;
15+
16+
class GifFilters
17+
{
18+
private $gif;
19+
20+
public function __construct(Gif $gif)
21+
{
22+
$this->gif = $gif;
23+
}
24+
}

src/FFMpeg/Media/Gif.php

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
<?php
2+
3+
/*
4+
* This file is part of PHP-FFmpeg.
5+
*
6+
* (c) Strime <contact@strime.io>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace FFMpeg\Media;
13+
14+
use Alchemy\BinaryDriver\Exception\ExecutionFailureException;
15+
use FFMpeg\Filters\Gif\GifFilterInterface;
16+
use FFMpeg\Filters\Gif\GifFilters;
17+
use FFMpeg\Driver\FFMpegDriver;
18+
use FFMpeg\FFProbe;
19+
use FFMpeg\Exception\RuntimeException;
20+
use FFMpeg\Coordinate\TimeCode;
21+
use FFMpeg\Coordinate\Dimension;
22+
23+
class Gif extends AbstractMediaType
24+
{
25+
/** @var TimeCode */
26+
private $timecode;
27+
/** @var Dimension */
28+
private $dimension;
29+
/** @var integer */
30+
private $duration;
31+
/** @var Video */
32+
private $video;
33+
34+
public function __construct(Video $video, FFMpegDriver $driver, FFProbe $ffprobe, TimeCode $timecode, Dimension $dimension, $duration = null)
35+
{
36+
parent::__construct($video->getPathfile(), $driver, $ffprobe);
37+
$this->timecode = $timecode;
38+
$this->dimension = $dimension;
39+
$this->duration = $duration;
40+
$this->video = $video;
41+
}
42+
43+
/**
44+
* Returns the video related to the gif.
45+
*
46+
* @return Video
47+
*/
48+
public function getVideo()
49+
{
50+
return $this->video;
51+
}
52+
53+
/**
54+
* {@inheritdoc}
55+
*
56+
* @return GifFilters
57+
*/
58+
public function filters()
59+
{
60+
return new GifFilters($this);
61+
}
62+
63+
/**
64+
* {@inheritdoc}
65+
*
66+
* @return Gif
67+
*/
68+
public function addFilter(GifFilterInterface $filter)
69+
{
70+
$this->filters->add($filter);
71+
72+
return $this;
73+
}
74+
75+
/**
76+
* @return TimeCode
77+
*/
78+
public function getTimeCode()
79+
{
80+
return $this->timecode;
81+
}
82+
83+
/**
84+
* @return Dimension
85+
*/
86+
public function getDimension()
87+
{
88+
return $this->dimension;
89+
}
90+
91+
/**
92+
* Saves the gif in the given filename.
93+
*
94+
* @param string $pathfile
95+
*
96+
* @return Gif
97+
*
98+
* @throws RuntimeException
99+
*/
100+
public function save($pathfile)
101+
{
102+
/**
103+
* @see http://ffmpeg.org/ffmpeg.html#Main-options
104+
*/
105+
$commands = array(
106+
'-ss', (string)$this->timecode
107+
);
108+
109+
if(null !== $this->duration) {
110+
$commands[] = '-t';
111+
$commands[] = (string)$this->duration;
112+
}
113+
114+
$commands[] = '-i';
115+
$commands[] = $this->pathfile;
116+
$commands[] = '-vf';
117+
$commands[] = 'scale=' . $this->dimension->getWidth() . ':-1';
118+
$commands[] = '-gifflags';
119+
$commands[] = '+transdiff';
120+
$commands[] = '-y';
121+
122+
foreach ($this->filters as $filter) {
123+
$commands = array_merge($commands, $filter->apply($this));
124+
}
125+
126+
$commands = array_merge($commands, array($pathfile));
127+
128+
try {
129+
$this->driver->command($commands);
130+
} catch (ExecutionFailureException $e) {
131+
$this->cleanupTemporaryFile($pathfile);
132+
throw new RuntimeException('Unable to save gif', $e->getCode(), $e);
133+
}
134+
135+
return $this;
136+
}
137+
}

src/FFMpeg/Media/Video.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Alchemy\BinaryDriver\Exception\ExecutionFailureException;
1515
use FFMpeg\Coordinate\TimeCode;
16+
use FFMpeg\Coordinate\Dimension;
1617
use FFMpeg\Filters\Audio\SimpleFilter;
1718
use FFMpeg\Exception\InvalidArgumentException;
1819
use FFMpeg\Exception\RuntimeException;
@@ -181,4 +182,17 @@ public function frame(TimeCode $at)
181182
{
182183
return new Frame($this, $this->driver, $this->ffprobe, $at);
183184
}
185+
186+
/**
187+
* Extracts a gif from a sequence of the video.
188+
*
189+
* @param TimeCode $at
190+
* @param Dimension $dimension
191+
* @param integer $duration
192+
* @return Gif
193+
*/
194+
public function gif(TimeCode $at, Dimension $dimension, $duration = null)
195+
{
196+
return new Gif($this, $this->driver, $this->ffprobe, $at, $dimension, $duration);
197+
}
184198
}

tests/Unit/Media/GifTest.php

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
<?php
2+
3+
namespace Tests\FFMpeg\Unit\Media;
4+
5+
use FFMpeg\Media\Gif;
6+
use FFMpeg\Coordinate\Dimension;
7+
8+
class GifTest extends AbstractMediaTestCase
9+
{
10+
public function testGetTimeCode()
11+
{
12+
$driver = $this->getFFMpegDriverMock();
13+
$ffprobe = $this->getFFProbeMock();
14+
$timecode = $this->getTimeCodeMock();
15+
$dimension = $this->getDimensionMock();
16+
17+
$gif = new Gif($this->getVideoMock(__FILE__), $driver, $ffprobe, $timecode, $dimension);
18+
$this->assertSame($timecode, $gif->getTimeCode());
19+
}
20+
21+
public function testGetDimension()
22+
{
23+
$driver = $this->getFFMpegDriverMock();
24+
$ffprobe = $this->getFFProbeMock();
25+
$timecode = $this->getTimeCodeMock();
26+
$dimension = $this->getDimensionMock();
27+
28+
$gif = new Gif($this->getVideoMock(__FILE__), $driver, $ffprobe, $timecode, $dimension);
29+
$this->assertSame($dimension, $gif->getDimension());
30+
}
31+
32+
public function testFiltersReturnFilters()
33+
{
34+
$driver = $this->getFFMpegDriverMock();
35+
$ffprobe = $this->getFFProbeMock();
36+
$timecode = $this->getTimeCodeMock();
37+
$dimension = $this->getDimensionMock();
38+
39+
$gif = new Gif($this->getVideoMock(__FILE__), $driver, $ffprobe, $timecode, $dimension);
40+
$this->assertInstanceOf('FFMpeg\Filters\Gif\GifFilters', $gif->filters());
41+
}
42+
43+
public function testAddFiltersAddsAFilter()
44+
{
45+
$driver = $this->getFFMpegDriverMock();
46+
$ffprobe = $this->getFFProbeMock();
47+
$timecode = $this->getTimeCodeMock();
48+
$dimension = $this->getDimensionMock();
49+
50+
$filters = $this->getMockBuilder('FFMpeg\Filters\FiltersCollection')
51+
->disableOriginalConstructor()
52+
->getMock();
53+
54+
$filter = $this->getMock('FFMpeg\Filters\Gif\GifFilterInterface');
55+
56+
$filters->expects($this->once())
57+
->method('add')
58+
->with($filter);
59+
60+
$gif = new Gif($this->getVideoMock(__FILE__), $driver, $ffprobe, $timecode, $dimension);
61+
$gif->setFiltersCollection($filters);
62+
$gif->addFilter($filter);
63+
}
64+
65+
/**
66+
* @dataProvider provideSaveOptions
67+
*/
68+
public function testSave($dimension, $duration, $commands)
69+
{
70+
$driver = $this->getFFMpegDriverMock();
71+
$ffprobe = $this->getFFProbeMock();
72+
$timecode = $this->getTimeCodeMock();
73+
74+
$timecode->expects($this->once())
75+
->method('__toString')
76+
->will($this->returnValue('timecode'));
77+
78+
$pathfile = '/target/destination';
79+
80+
array_push($commands, $pathfile);
81+
82+
$driver->expects($this->once())
83+
->method('command')
84+
->with($commands);
85+
86+
$gif = new Gif($this->getVideoMock(__FILE__), $driver, $ffprobe, $timecode, $dimension, $duration);
87+
$this->assertSame($gif, $gif->save($pathfile));
88+
}
89+
90+
public function provideSaveOptions()
91+
{
92+
return array(
93+
array(
94+
new Dimension(320, 240), 3,
95+
array(
96+
'-ss', 'timecode',
97+
'-t', '3',
98+
'-i', __FILE__,
99+
'-vf',
100+
'scale=320:-1', '-gifflags',
101+
'+transdiff', '-y'
102+
),
103+
),
104+
array(
105+
new Dimension(320, 240), null,
106+
array(
107+
'-ss', 'timecode',
108+
'-i', __FILE__,
109+
'-vf',
110+
'scale=320:-1', '-gifflags',
111+
'+transdiff', '-y'
112+
)
113+
),
114+
);
115+
}
116+
}

0 commit comments

Comments
 (0)