Skip to content

Commit 6dd31fb

Browse files
adding concatenation feature
1 parent 879b7c1 commit 6dd31fb

File tree

10 files changed

+133
-51
lines changed

10 files changed

+133
-51
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ vendor
33
composer.lock
44
examples/source
55
examples/output
6-
examples/ffmpeg-20170915-6743351-win64-static
6+
data/tmp/*
7+
!data/tmp/.gitkeep

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@
3030
],
3131
"require": {
3232
"php": ">=5.4",
33-
"php-ffmpeg/php-ffmpeg": "0.9.5"
33+
"php-ffmpeg/php-ffmpeg": "0.9.5",
34+
"neutron/temporary-filesystem": "^2.1.1"
3435
},
3536
"suggest": {
3637
"php-ffmpeg/extras": "A compilation of common audio & video drivers for PHP-FFMpeg"

data/tmp/.gitkeep

Whitespace-only changes.

examples/concatenate.php

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,29 +7,34 @@
77
* Time: 23:46
88
*/
99

10+
chdir( dirname( __DIR__ ) );
1011

11-
ini_set('display_errors', 1);
12-
date_default_timezone_set('UTC');
13-
require_once dirname(__FILE__) . '/../vendor/autoload.php';
12+
ini_set( 'display_errors', 1 );
13+
date_default_timezone_set( 'UTC' );
14+
require_once dirname( __FILE__ ) . '/../vendor/autoload.php';
1415

1516
// Init ffmpeg library
16-
$ffmpeg = \Sharapov\FFMpegExtensions\FFMpeg::create([
17-
'ffmpeg.binaries' => dirname(__FILE__).'/../../../ffmpeg-static/ffmpeg-3.4.2-64bit-static/ffmpeg',
18-
'ffprobe.binaries' => dirname(__FILE__).'/../../../ffmpeg-static/ffmpeg-3.4.2-64bit-static/ffprobe',
19-
'timeout' => 3600, // The timeout for the underlying process
20-
'ffmpeg.threads' => 12, // The number of threads that FFMpeg should use
21-
]);
22-
23-
// $ffmpeg->open('source/ez/spokesperson-clip-01.mp4')
17+
$ffmpeg = \Sharapov\FFMpegExtensions\FFMpeg::create( [
18+
'ffmpeg.binaries' => getcwd() . '/../../ffmpeg-static/ffmpeg-3.4.2-64bit-static/ffmpeg',
19+
'ffprobe.binaries' => getcwd() . '/../../ffmpeg-static/ffmpeg-3.4.2-64bit-static/ffprobe',
20+
'timeout' => 3600,
21+
// The timeout for the underlying process
22+
'ffmpeg.threads' => 12,
23+
// The number of threads that FFMpeg should use
24+
] );
2425

2526
$collection = new \Sharapov\FFMpegExtensions\Media\VideoCollection();
2627

27-
2828
$collection
29-
->add($ffmpeg->open('source/ez/spokesperson-clip-01.mp4'))
30-
->add($ffmpeg->open('source/ez/spokesperson-clip-02.mp4'));
29+
->add( $ffmpeg->open( new \Sharapov\FFMpegExtensions\Input\File( getcwd() . '/examples/source/demo_video_720p_HD.mp4' ) ) )
30+
->add( $ffmpeg->open( new \Sharapov\FFMpegExtensions\Input\File( getcwd() . '/examples/source/intro_720p_muted.mp4' ) ) );
31+
32+
$format = new \Sharapov\FFMpegExtensions\Format\Video\X264();
33+
$format->on( 'progress', function ( $item, $format, $percentage ) {
34+
print 'Done: ' . $percentage . "%\n";
35+
} );
3136

32-
$result = $ffmpeg->concatenate($collection);
37+
$result = $ffmpeg->concatenate( $format, $collection, getcwd() . '/examples/merged.mp4' );
3338

3439

3540

src/php-ffmpeg-extensions/FFMpeg.php

Lines changed: 79 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,29 @@
1010
namespace Sharapov\FFMpegExtensions;
1111

1212
use Alchemy\BinaryDriver\ConfigurationInterface;
13+
use Alchemy\BinaryDriver\Exception\ExecutionFailureException;
1314
use FFMpeg\Exception\InvalidArgumentException;
1415
use FFMpeg\Exception\RuntimeException;
15-
use Psr\Log\LoggerInterface;
1616
use FFMpeg\Driver\FFMpegDriver;
17+
use FFMpeg\Format\FormatInterface;
18+
use Psr\Log\LoggerInterface;
1719
use Sharapov\FFMpegExtensions\Input\FileInterface;
1820
use Sharapov\FFMpegExtensions\Media\Audio;
21+
use Sharapov\FFMpegExtensions\Media\CollectionInterface;
1922
use Sharapov\FFMpegExtensions\Media\Video;
20-
use Sharapov\FFMpegExtensions\Media\VideoCollection;
2123

2224
class FFMpeg {
2325
/** @var FFMpegDriver */
2426
private $driver;
2527
/** @var FFProbe */
2628
private $ffprobe;
29+
/** @var string */
30+
private $tmpDir;
2731

2832
public function __construct( FFMpegDriver $ffmpeg, FFProbe $ffprobe ) {
2933
$this->driver = $ffmpeg;
3034
$this->ffprobe = $ffprobe;
35+
$this->tmpDir = getcwd() . '/data/tmp/';
3136
}
3237

3338
/**
@@ -101,20 +106,84 @@ public function open( $file ) {
101106
}
102107

103108
/**
104-
* Opens a collection to be processed.
109+
* Concatenate two or more streams
105110
*
106-
* @param $file
107-
* @return Audio|Video
111+
* @param \Sharapov\FFMpegExtensions\Media\CollectionInterface $collection
112+
* @param string $outputPathFile
113+
* @param FormatInterface $format
108114
*
109-
* @throws InvalidArgumentException
110-
* @throws RuntimeException
115+
* @return string
111116
*/
112-
public function concatenate(\IteratorAggregate $collection) {
117+
public function concatenate( FormatInterface $format, CollectionInterface $collection, $outputPathFile ) {
118+
if ( $collection->count() == 0 ) {
119+
throw new InvalidArgumentException( 'Collection is empty' );
120+
}
121+
122+
/**
123+
* @see https://ffmpeg.org/ffmpeg-formats.html#concat
124+
* @see https://trac.ffmpeg.org/wiki/Concatenate
125+
*/
126+
$sourcesFile = $this->tmpDir . uniqid( 'ffmpeg-concat-' );
127+
// Set the content of this file
128+
$fileStream = @fopen( $sourcesFile, 'w' );
129+
130+
if ( $fileStream === false ) {
131+
throw new ExecutionFailureException( 'Cannot open the temporary file.' );
132+
}
113133

134+
$sources = [];
135+
// Pre-encode each clip in collection
136+
foreach ( $collection as $i => $item ) {
137+
if ( $item instanceof Video ) {
138+
$tmpFile = 'ffmpeg-' . uniqid( md5( time() ) . '-' ) . '.mp4';
139+
$item
140+
->save( $format, $this->tmpDir . $tmpFile );
141+
$sources[] = $this->tmpDir . $tmpFile;
142+
fwrite( $fileStream, "file " . $tmpFile . "\n" );
143+
}
144+
}
114145

115-
print_r($collection);
146+
fclose( $fileStream );
147+
148+
// Execute the command
149+
try {
150+
$this->driver->command( [
151+
'-y',
152+
'-f',
153+
'concat',
154+
'-safe',
155+
'0',
156+
'-i',
157+
$sourcesFile,
158+
'-c',
159+
'copy',
160+
$outputPathFile
161+
] );
162+
} catch ( ExecutionFailureException $e ) {
163+
$this->_cleanupTemporaryFile( $outputPathFile );
164+
$this->_cleanupTemporaryFile( $sourcesFile );
165+
$this->_cleanupTemporaryFile( $sources );
166+
throw new RuntimeException( 'Unable to save concatenated video', $e->getCode(), $e );
167+
}
116168

117-
return 'done';
169+
$this->_cleanupTemporaryFile( $sources );
170+
$this->_cleanupTemporaryFile( $sourcesFile );
171+
172+
return $outputPathFile;
173+
}
174+
175+
private function _cleanupTemporaryFile( $filename ) {
176+
if ( is_array( $filename ) ) {
177+
foreach ( $filename as $file ) {
178+
$this->_cleanupTemporaryFile( $file );
179+
}
180+
} else {
181+
if ( file_exists( $filename ) && is_writable( $filename ) ) {
182+
unlink( $filename );
183+
}
184+
}
185+
186+
return $this;
118187
}
119188

120189
/**

src/php-ffmpeg-extensions/Format/Video/X264.php

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,13 @@
1111

1212
/**
1313
* The X264 video format
14+
*
15+
* Uses aac audio codec by default. libfaac is is considered non-free
16+
* and most of unix systems doesn't provide ffmpeg with it.
17+
* Also that's a crappy encoder, there are better alternatives.
1418
*/
1519
class X264 extends \FFMpeg\Format\Video\X264 {
16-
public function __construct( $audioCodec = 'libfaac', $videoCodec = 'libx264' ) {
20+
public function __construct( $audioCodec = 'aac', $videoCodec = 'libx264' ) {
1721
parent::__construct( $audioCodec, $videoCodec );
1822
}
19-
20-
/**
21-
* {@inheritDoc}
22-
*/
23-
public function getAvailableAudioCodecs() {
24-
return [ 'libvo_aacenc', 'libfaac', 'libmp3lame', 'libfdk_aac', 'copy' ];
25-
}
26-
27-
/**
28-
* {@inheritDoc}
29-
*/
30-
public function getPasses() {
31-
return 1;
32-
}
3323
}

src/php-ffmpeg-extensions/Media/Audio.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,13 @@ public function addFilter( FilterInterface $filter ) {
5151
* Exports the audio in the desired format, applies registered filters.
5252
*
5353
* @param FormatInterface $format
54-
* @param string $outputPathfile
54+
* @param string $outputPathFile
5555
*
5656
* @return Audio
5757
*
5858
* @throws RuntimeException
5959
*/
60-
public function save( FormatInterface $format, $outputPathfile ) {
60+
public function save( FormatInterface $format, $outputPathFile ) {
6161
$commands = [ '-y', '-i', $this->pathfile ];
6262

6363
$filters = clone $this->filters;
@@ -100,7 +100,7 @@ public function save( FormatInterface $format, $outputPathfile ) {
100100
$commands[] = '-ac';
101101
$commands[] = $format->getAudioChannels();
102102
}
103-
$commands[] = $outputPathfile;
103+
$commands[] = $outputPathFile;
104104

105105
try {
106106
$listeners = null;
@@ -111,7 +111,7 @@ public function save( FormatInterface $format, $outputPathfile ) {
111111

112112
$this->driver->command( $commands, false, $listeners );
113113
} catch ( ExecutionFailureException $e ) {
114-
$this->cleanupTemporaryFile( $outputPathfile );
114+
$this->cleanupTemporaryFile( $outputPathFile );
115115
throw new RuntimeException( 'Encoding failed', $e->getCode(), $e );
116116
}
117117

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
/**
3+
* This file is part of PHP-FFmpeg-Extensions library.
4+
*
5+
* (c) Alexander Sharapov <alexander@sharapov.biz>
6+
* http://sharapov.biz/
7+
*
8+
*/
9+
10+
namespace Sharapov\FFMpegExtensions\Media;
11+
12+
interface CollectionInterface {
13+
public function all();
14+
15+
public function count();
16+
}

src/php-ffmpeg-extensions/Media/Video.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,13 @@ public function filters() {
3636
* Exports the video in the desired format, applies registered filters.
3737
*
3838
* @param FormatInterface $format
39-
* @param string $outputPathfile
39+
* @param string $outputPathFile
4040
*
4141
* @return Video
4242
*
4343
* @throws RuntimeException
4444
*/
45-
public function save( FormatInterface $format, $outputPathfile ) {
45+
public function save( FormatInterface $format, $outputPathFile ) {
4646
$commands = [ '-y', '-i', $this->pathfile ];
4747

4848
$filters = clone $this->filters;
@@ -140,7 +140,7 @@ public function save( FormatInterface $format, $outputPathfile ) {
140140
$pass[] = $passPrefix;
141141
}
142142

143-
$pass[] = $outputPathfile;
143+
$pass[] = $outputPathFile;
144144

145145
$passes[] = $pass;
146146
}

src/php-ffmpeg-extensions/Media/VideoCollection.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111

1212
/**
1313
* Video collection
14-
* @package Sharapov\FFMpegExtensions\Filters\Video\FilterComplexOptions
14+
* @package Sharapov\FFMpegExtensions\Media
1515
*/
16-
class VideoCollection implements \Countable, \IteratorAggregate {
16+
class VideoCollection implements CollectionInterface, \Countable, \IteratorAggregate {
1717
private $_streams;
1818

1919
public function __construct( array $options = [] ) {

0 commit comments

Comments
 (0)