Skip to content

Commit 7f5a0c4

Browse files
adding animated gif and png support
1 parent f19ecc7 commit 7f5a0c4

File tree

10 files changed

+116
-72
lines changed

10 files changed

+116
-72
lines changed

examples/draw-animated-gif.php

Lines changed: 31 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
<?php
22
/**
33
* This file is part of PHP-FFmpeg-Extensions library.
4-
*
54
* (c) Alexander Sharapov <alexander@sharapov.biz>
65
* http://sharapov.biz/
7-
*
86
*/
97

108
use \Sharapov\FFMpegExtensions\Input\File as InputFile;
@@ -15,39 +13,45 @@
1513
require_once 'init.php';
1614

1715
// Open source video
18-
$video = $ffmpeg->open( new InputFile( 'source/demo_video_720p_HD.mp4' ) );
16+
$video = $ffmpeg->open(new InputFile('source/demo_video_720p_HD.mp4'));
1917

2018
// Apply filter options to video
2119
$video
22-
->filters()
23-
->complex( new FilterComplexOptions\OptionsCollection( [
24-
( ( new FilterComplexOptions\OptionOverlay() )
25-
// Set z-index property. Greater value is always in front
26-
->setZIndex( 260 )
27-
// You can use fade-in and fade-out effects. Set time in seconds
28-
->setFadeIn( 4 )
29-
->setFadeOut( 4 )
30-
// Set image path
31-
->setExtraInputStream( new InputFile( 'source/funny-cat-gif-3.gif' ) )
32-
// Coordinates where the text should be rendered. Accepts positive integer or
33-
// constants "(w-tw)/2", "(h-th)/2" to handle auto-horizontal, auto-vertical values
34-
->setCoordinates( new Coordinate\Point( 230, 200 ) )
35-
// Set image dimensions
36-
->setDimensions( new Coordinate\Dimension( 450, 200 ) )
37-
// Set timings (start, stop) in seconds. Accepts float values as well
38-
->setTimeLine( new Coordinate\TimeLine( 3, 13 ) ) )
39-
] ) );
20+
->filters()
21+
->complex(
22+
new FilterComplexOptions\OptionsCollection([
23+
((new FilterComplexOptions\OptionOverlay())
24+
// Set z-index property. Greater value is always in front
25+
->setZIndex(260)
26+
// You can use fade-in and fade-out effects. Set time in seconds
27+
->setFadeIn(2)
28+
->setFadeOut(2)
29+
// Set image path
30+
->setExtraInputStream(new InputFile('source/animated.gif'))
31+
// Coordinates where the text should be rendered. Accepts positive integer or
32+
// constants "(w-tw)/2", "(h-th)/2" to handle auto-horizontal, auto-vertical values
33+
->setCoordinates(new Coordinate\Point(230, 200))
34+
// Set image dimensions
35+
//->setDimensions(new Coordinate\Dimension(450, 200))
36+
// Set timings (start, stop) in seconds. Accepts float values as well
37+
->setTimeLine(new Coordinate\TimeLine(3, 13)))
38+
]));
4039

4140
// Run render
42-
$format = new \FFMpeg\Format\Video\X264( 'libmp3lame' );
43-
$format->on( 'progress', function ( $video, $format, $percentage ) {
41+
$format = new \FFMpeg\Format\Video\X264('libmp3lame');
42+
$format->on('progress', function($video, $format, $percentage) {
4443
echo "$percentage% transcoded\n";
45-
} );
44+
});
4645

4746
try {
4847
$video
49-
->save( $format, 'output/output.mp4' );
48+
->save($format, 'output/output.mp4');
5049
print 'Done!';
51-
} catch ( ExecutionFailureException $e ) {
50+
} catch (ExecutionFailureException $e) {
5251
print $e->getMessage();
53-
}
52+
}
53+
54+
/*
55+
* -loop 1
56+
"ffmpeg-20170915-6743351-win64-static/bin/ffmpeg.exe" -y -i "source/demo_video_720p_HD.mp4" -ignore_loop 0 -i "source/animated.gif" -filter_complex "[1:v]format=yuva420p,scale=450:200,fade=t=in:st=0:d=4:alpha='1',fade=t=out:st=9:d=4:alpha='1'[t1],[0:v][t1]overlay=230:200:shortest='1':enable='between(t,3,13)'" -threads 12 -vcodec libx264 -acodec libmp3lame -b:v 1000k -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 128k "output/output.mp4"
57+
*/

examples/draw-overlay-image.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
require_once 'init.php';
1414

1515
// Open source video
16-
$video = $ffmpeg->open(new InputFile('source/Coast - 1270.mp4'));
16+
$video = $ffmpeg->open(new InputFile('source/demo_video_720p_HD.mp4'));
1717

1818
// Create complex filter collection
1919
$options = new FilterComplexOptions\OptionsCollection();
111 KB
Binary file not shown.

examples/source/animated.gif

787 KB
Loading

examples/source/static.gif

41.4 KB
Loading

examples/source/static.png

131 KB
Loading

src/php-ffmpeg-extensions/Filters/Video/ComplexFilter.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,10 +132,16 @@ public function apply(Video $video, VideoInterface $format) {
132132
// We need to save last stream id to apply next options in the correct order
133133
$lastStreamId = 's' . $stm;
134134
// For image overlay we have to add -loop 1 before input
135-
if($option->isImage()) {
135+
if($option->isStaticImage()) {
136136
$this->_extraInputs[] = '-loop';
137137
$this->_extraInputs[] = '1';
138138
}
139+
140+
if($option->isAnimatedGif() or $option->isAnimatedPng()) {
141+
$this->_extraInputs[] = '-ignore_loop';
142+
$this->_extraInputs[] = '0';
143+
}
144+
139145
// Pass input paths to the separate array
140146
$this->setExtraInput($option->getExtraInputStream());
141147
$imn++;

src/php-ffmpeg-extensions/Filters/Video/FilterComplexOptions/CoordinatesTrait.php

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,13 @@ trait CoordinatesTrait {
1515

1616
/**
1717
* Returns coordinates object.
18-
* @return mixed
18+
*
19+
* @param bool $returnDefault
20+
*
21+
* @return \Sharapov\FFMpegExtensions\Coordinate\Point|string
1922
*/
20-
public function getCoordinates() {
21-
if(!$this->_coordinates instanceof Point) {
22-
throw new InvalidArgumentException('Coordinates are empty.');
23-
}
24-
25-
return $this->_coordinates;
23+
public function getCoordinates($returnDefault = false) {
24+
return ($this->_coordinates instanceof Point) ? $this->_coordinates : (($returnDefault) ? : $this->getDefaultCoordinates());
2625
}
2726

2827
/**
@@ -37,4 +36,12 @@ public function setCoordinates(Point $point) {
3736

3837
return $this;
3938
}
39+
40+
/**
41+
* Gets default coordinates
42+
* @return string
43+
*/
44+
public function getDefaultCoordinates() {
45+
return '0:0';
46+
}
4047
}

src/php-ffmpeg-extensions/Filters/Video/FilterComplexOptions/DimensionsTrait.php

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,13 @@ trait DimensionsTrait {
1616

1717
/**
1818
* Returns dimensions object.
19-
* @return mixed
19+
*
20+
* @param bool $returnDefault
21+
*
22+
* @return \Sharapov\FFMpegExtensions\Coordinate\Dimension|string
2023
*/
21-
public function getDimensions() {
22-
if(!$this->_dimensions instanceof Dimension) {
23-
throw new InvalidArgumentException('Dimensions are empty.');
24-
}
25-
26-
return $this->_dimensions;
24+
public function getDimensions($returnDefault = false) {
25+
return ($this->_dimensions instanceof Dimension) ? $this->_dimensions : (($returnDefault) ? : $this->getDefaultDimensions());
2726
}
2827

2928
/**
@@ -38,4 +37,21 @@ public function setDimensions(Dimension $dimension) {
3837

3938
return $this;
4039
}
40+
41+
/**
42+
* Gets default dimensions
43+
* @return string
44+
*/
45+
public function getDefaultDimensions() {
46+
if($this->isImage()) {
47+
$dimensions = @getimagesize($this->getExtraInputStream()->getPath());
48+
if($dimensions) {
49+
return sprintf('%s:%s', $dimensions[0], $dimensions[1]);
50+
}
51+
} else {
52+
throw new InvalidArgumentException('Due to performance issues we cannot automatically detect non-image overlay dimensions. Please set them before encoding.');
53+
}
54+
55+
return '0:0';
56+
}
4157
}

src/php-ffmpeg-extensions/Filters/Video/FilterComplexOptions/OptionOverlay.php

Lines changed: 40 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
namespace Sharapov\FFMpegExtensions\Filters\Video\FilterComplexOptions;
99

10+
use Sharapov\FFMpegExtensions\Coordinate\Dimension;
1011
use Sharapov\FFMpegExtensions\Coordinate\Point;
1112
use Sharapov\FFMpegExtensions\Coordinate\TimeLine;
1213
use Sharapov\FFMpegExtensions\Filters\ExtraInputStreamInterface;
@@ -43,30 +44,6 @@ public function getFormat() {
4344
return $this->getProbe()->format($this->getExtraInputStream()->getPath());
4445
}
4546

46-
function isAnimatedGif($filename) {
47-
return (bool)preg_match('#(\x00\x21\xF9\x04.{4}\x00\x2C.*){2,}#s', file_get_contents($filename));
48-
}
49-
50-
function identify_apng($filename) {
51-
$img_bytes = file_get_contents($filename);
52-
if($img_bytes) {
53-
if(strpos(substr($img_bytes, 0, strpos($img_bytes, 'IDAT')),
54-
'acTL') !== false) {
55-
return true;
56-
}
57-
}
58-
59-
return false;
60-
}
61-
62-
# Identifies APNGs
63-
# Written by Coda, functionified by Foone/Popcorn Mariachi#!9i78bPeIxI
64-
# This code is in the public domain
65-
# identify_apng returns:
66-
# true if the file is an APNG
67-
# false if it is any other sort of file (it is not checked for PNG validity)
68-
# takes on argument, a filename.
69-
7047
/**
7148
* Returns a command string.
7249
* @return string
@@ -80,8 +57,6 @@ public function __toString() {
8057
* @return string
8158
*/
8259
public function getCommand() {
83-
$coordinates = ($this->getCoordinates() instanceof Point) ? (string)$this->getCoordinates() : '0:0';
84-
8560
if($this->getTimeLine() instanceof TimeLine) {
8661
$timeLine = sprintf(":%s", (string)$this->getTimeLine());
8762
} else {
@@ -108,17 +83,53 @@ public function getCommand() {
10883
}
10984

11085
// We have to add shortest=1 for images to stop the overlay when the main video ends
111-
if($this->isImage()) {
86+
if($this->isStaticImage() or $this->isAnimatedGif() or $this->isAnimatedPng()) {
11287
$shortest = ":shortest='1'";
11388
} else {
11489
$shortest = '';
11590
}
11691

117-
return sprintf("[%s]format=yuva420p,scale=%s%s[%s],[%s][%s]overlay=%s%s%s[%s]", ':s1', (string)$this->getDimensions(), $fadeTime, ':s2', ':s3', ':s4', $coordinates, $shortest, $timeLine, ':s5');
92+
return sprintf("[%s]format=yuva420p,scale=%s%s[%s],[%s][%s]overlay=%s%s%s[%s]", ':s1', (string)$this->getDimensions(), $fadeTime, ':s2', ':s3', ':s4', (string)$this->getCoordinates(), $shortest, $timeLine, ':s5');
93+
}
94+
95+
/**
96+
* Checks if overlay is static image (not animated gif or png).
97+
* @return bool
98+
*/
99+
public function isStaticImage() {
100+
return true === $this->isImage() and false === $this->isAnimatedGif() and false === $this->isAnimatedPng();
101+
}
102+
103+
/**
104+
* Detect animated gif
105+
* @return bool
106+
*/
107+
public function isAnimatedGif() {
108+
return (bool)preg_match('#(\x00\x21\xF9\x04.{4}\x00\x2C.*){2,}#s', @file_get_contents($this->getExtraInputStream()->getPath()));
109+
}
110+
111+
/**
112+
* Identifies APNGs
113+
* Written by Coda, functionified by Foone/Popcorn Mariachi#!9i78bPeIxI
114+
* This code is in the public domain
115+
* Returns: true if the file is an APNG, false if it is any other sort of file (it is not checked for PNG validity)
116+
* takes on argument, a filename.
117+
* @return bool
118+
*/
119+
public function isAnimatedPng() {
120+
$img_bytes = @file_get_contents($this->getExtraInputStream()->getPath());
121+
if($img_bytes) {
122+
if(strpos(substr($img_bytes, 0, strpos($img_bytes, 'IDAT')),
123+
'acTL') !== false) {
124+
return true;
125+
}
126+
}
127+
128+
return false;
118129
}
119130

120131
/**
121-
* Checks if overlay is image.
132+
* Checks if overlay is image (both static or animated)
122133
* @return bool
123134
*/
124135
public function isImage() {

0 commit comments

Comments
 (0)