/vendor/php-ffmpeg/php-ffmpeg/src/FFMpeg/Media/Concat.php |
@@ -0,0 +1,264 @@ |
<?php |
|
/* |
* This file is part of PHP-FFmpeg. |
* |
* (c) Strime <contact@strime.io> |
* |
* For the full copyright and license information, please view the LICENSE |
* file that was distributed with this source code. |
*/ |
|
namespace FFMpeg\Media; |
|
use Alchemy\BinaryDriver\Exception\ExecutionFailureException; |
use Alchemy\BinaryDriver\Exception\InvalidArgumentException; |
use FFMpeg\Filters\Concat\ConcatFilterInterface; |
use FFMpeg\Filters\Concat\ConcatFilters; |
use FFMpeg\Driver\FFMpegDriver; |
use FFMpeg\FFProbe; |
use FFMpeg\Filters\Audio\SimpleFilter; |
use FFMpeg\Exception\RuntimeException; |
use FFMpeg\Format\FormatInterface; |
use FFMpeg\Filters\FilterInterface; |
use FFMpeg\Format\ProgressableInterface; |
use FFMpeg\Format\AudioInterface; |
use FFMpeg\Format\VideoInterface; |
use Neutron\TemporaryFilesystem\Manager as FsManager; |
|
class Concat extends AbstractMediaType |
{ |
/** @var array */ |
private $sources; |
|
public function __construct($sources, FFMpegDriver $driver, FFProbe $ffprobe) |
{ |
parent::__construct($sources, $driver, $ffprobe); |
$this->sources = $sources; |
} |
|
/** |
* Returns the path to the sources. |
* |
* @return string |
*/ |
public function getSources() |
{ |
return $this->sources; |
} |
|
/** |
* {@inheritdoc} |
* |
* @return ConcatFilters |
*/ |
public function filters() |
{ |
return new ConcatFilters($this); |
} |
|
/** |
* {@inheritdoc} |
* |
* @return Concat |
*/ |
public function addFilter(ConcatFilterInterface $filter) |
{ |
$this->filters->add($filter); |
|
return $this; |
} |
|
/** |
* Saves the concatenated video in the given array, considering that the sources videos are all encoded with the same codec. |
* |
* @param array $outputPathfile |
* @param string $streamCopy |
* |
* @return Concat |
* |
* @throws RuntimeException |
*/ |
public function saveFromSameCodecs($outputPathfile, $streamCopy = TRUE) |
{ |
/** |
* @see https://ffmpeg.org/ffmpeg-formats.html#concat |
* @see https://trac.ffmpeg.org/wiki/Concatenate |
*/ |
|
// Create the file which will contain the list of videos |
$fs = FsManager::create(); |
$sourcesFile = $fs->createTemporaryFile('ffmpeg-concat'); |
|
// Set the content of this file |
$fileStream = @fopen($sourcesFile, 'w'); |
|
if($fileStream === false) { |
throw new ExecutionFailureException('Cannot open the temporary file.'); |
} |
|
$count_videos = 0; |
if(is_array($this->sources) && (count($this->sources) > 0)) { |
foreach ($this->sources as $videoPath) { |
$line = ""; |
|
if($count_videos != 0) |
$line .= "\n"; |
|
$line .= "file ".$videoPath; |
|
fwrite($fileStream, $line); |
|
$count_videos++; |
} |
} |
else { |
throw new InvalidArgumentException('The list of videos is not a valid array.'); |
} |
fclose($fileStream); |
|
|
$commands = array( |
'-f', 'concat', '-safe', '0', |
'-i', $sourcesFile |
); |
|
// Check if stream copy is activated |
if($streamCopy === TRUE) { |
$commands[] = '-c'; |
$commands[] = 'copy'; |
} |
|
// If not, we can apply filters |
else { |
foreach ($this->filters as $filter) { |
$commands = array_merge($commands, $filter->apply($this)); |
} |
} |
|
// Set the output file in the command |
$commands = array_merge($commands, array($outputPathfile)); |
|
// Execute the command |
try { |
$this->driver->command($commands); |
} catch (ExecutionFailureException $e) { |
$this->cleanupTemporaryFile($outputPathfile); |
$this->cleanupTemporaryFile($sourcesFile); |
throw new RuntimeException('Unable to save concatenated video', $e->getCode(), $e); |
} |
|
$this->cleanupTemporaryFile($sourcesFile); |
return $this; |
} |
|
/** |
* Saves the concatenated video in the given filename, considering that the sources videos are all encoded with the same codec. |
* |
* @param string $outputPathfile |
* |
* @return Concat |
* |
* @throws RuntimeException |
*/ |
public function saveFromDifferentCodecs(FormatInterface $format, $outputPathfile) |
{ |
/** |
* @see https://ffmpeg.org/ffmpeg-formats.html#concat |
* @see https://trac.ffmpeg.org/wiki/Concatenate |
*/ |
|
// Check the validity of the parameter |
if(!is_array($this->sources) || (count($this->sources) == 0)) { |
throw new InvalidArgumentException('The list of videos is not a valid array.'); |
} |
|
// Create the commands variable |
$commands = array(); |
|
// Prepare the parameters |
$nbSources = 0; |
$files = array(); |
|
// For each source, check if this is a legit file |
// and prepare the parameters |
foreach ($this->sources as $videoPath) { |
$files[] = '-i'; |
$files[] = $videoPath; |
$nbSources++; |
} |
|
$commands = array_merge($commands, $files); |
|
// Set the parameters of the request |
$commands[] = '-filter_complex'; |
|
$complex_filter = ''; |
for($i=0; $i<$nbSources; $i++) { |
$complex_filter .= '['.$i.':v:0] ['.$i.':a:0] '; |
} |
$complex_filter .= 'concat=n='.$nbSources.':v=1:a=1 [v] [a]'; |
|
$commands[] = $complex_filter; |
$commands[] = '-map'; |
$commands[] = '[v]'; |
$commands[] = '-map'; |
$commands[] = '[a]'; |
|
// Prepare the filters |
$filters = clone $this->filters; |
$filters->add(new SimpleFilter($format->getExtraParams(), 10)); |
|
if ($this->driver->getConfiguration()->has('ffmpeg.threads')) { |
$filters->add(new SimpleFilter(array('-threads', $this->driver->getConfiguration()->get('ffmpeg.threads')))); |
} |
if ($format instanceof VideoInterface) { |
if (null !== $format->getVideoCodec()) { |
$filters->add(new SimpleFilter(array('-vcodec', $format->getVideoCodec()))); |
} |
} |
if ($format instanceof AudioInterface) { |
if (null !== $format->getAudioCodec()) { |
$filters->add(new SimpleFilter(array('-acodec', $format->getAudioCodec()))); |
} |
} |
|
// Add the filters |
foreach ($this->filters as $filter) { |
$commands = array_merge($commands, $filter->apply($this)); |
} |
|
if ($format instanceof AudioInterface) { |
if (null !== $format->getAudioKiloBitrate()) { |
$commands[] = '-b:a'; |
$commands[] = $format->getAudioKiloBitrate() . 'k'; |
} |
if (null !== $format->getAudioChannels()) { |
$commands[] = '-ac'; |
$commands[] = $format->getAudioChannels(); |
} |
} |
|
// If the user passed some additional parameters |
if ($format instanceof VideoInterface) { |
if (null !== $format->getAdditionalParameters()) { |
foreach ($format->getAdditionalParameters() as $additionalParameter) { |
$commands[] = $additionalParameter; |
} |
} |
} |
|
// Set the output file in the command |
$commands[] = $outputPathfile; |
|
$failure = null; |
|
try { |
$this->driver->command($commands); |
} catch (ExecutionFailureException $e) { |
throw new RuntimeException('Encoding failed', $e->getCode(), $e); |
} |
|
return $this; |
} |
} |