|
10 | 10 | namespace Sharapov\FFMpegExtensions; |
11 | 11 |
|
12 | 12 | use Alchemy\BinaryDriver\ConfigurationInterface; |
| 13 | +use Alchemy\BinaryDriver\Exception\ExecutionFailureException; |
13 | 14 | use FFMpeg\Exception\InvalidArgumentException; |
14 | 15 | use FFMpeg\Exception\RuntimeException; |
15 | | -use Psr\Log\LoggerInterface; |
16 | 16 | use FFMpeg\Driver\FFMpegDriver; |
| 17 | +use FFMpeg\Format\FormatInterface; |
| 18 | +use Psr\Log\LoggerInterface; |
17 | 19 | use Sharapov\FFMpegExtensions\Input\FileInterface; |
18 | 20 | use Sharapov\FFMpegExtensions\Media\Audio; |
| 21 | +use Sharapov\FFMpegExtensions\Media\CollectionInterface; |
19 | 22 | use Sharapov\FFMpegExtensions\Media\Video; |
20 | | -use Sharapov\FFMpegExtensions\Media\VideoCollection; |
21 | 23 |
|
22 | 24 | class FFMpeg { |
23 | 25 | /** @var FFMpegDriver */ |
24 | 26 | private $driver; |
25 | 27 | /** @var FFProbe */ |
26 | 28 | private $ffprobe; |
| 29 | + /** @var string */ |
| 30 | + private $tmpDir; |
27 | 31 |
|
28 | 32 | public function __construct( FFMpegDriver $ffmpeg, FFProbe $ffprobe ) { |
29 | 33 | $this->driver = $ffmpeg; |
30 | 34 | $this->ffprobe = $ffprobe; |
| 35 | + $this->tmpDir = getcwd() . '/data/tmp/'; |
31 | 36 | } |
32 | 37 |
|
33 | 38 | /** |
@@ -101,20 +106,84 @@ public function open( $file ) { |
101 | 106 | } |
102 | 107 |
|
103 | 108 | /** |
104 | | - * Opens a collection to be processed. |
| 109 | + * Concatenate two or more streams |
105 | 110 | * |
106 | | - * @param $file |
107 | | - * @return Audio|Video |
| 111 | + * @param \Sharapov\FFMpegExtensions\Media\CollectionInterface $collection |
| 112 | + * @param string $outputPathFile |
| 113 | + * @param FormatInterface $format |
108 | 114 | * |
109 | | - * @throws InvalidArgumentException |
110 | | - * @throws RuntimeException |
| 115 | + * @return string |
111 | 116 | */ |
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 | + } |
113 | 133 |
|
| 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 | + } |
114 | 145 |
|
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 | + } |
116 | 168 |
|
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; |
118 | 187 | } |
119 | 188 |
|
120 | 189 | /** |
|
0 commit comments