diff --git a/README.md b/README.md index a6603ee..45d4673 100644 --- a/README.md +++ b/README.md @@ -576,8 +576,8 @@ Then saves audios from the original videos into the 4 different formats and save As you can see, you can take multiple input sources, perform the complicated processing for them and produce multiple output files in the same time, in the one ffmpeg command. -#### Just give me map! -You do not have to use `-filter_complex`. For example, just extract the audio from the video: +#### Just give me a map! +You do not have to use `-filter_complex`. You can use only `-map` options. For example, just extract the audio from the video: ```php $complexMedia = $ffmpeg->openComplex(array('video.mp4')); diff --git a/src/FFMpeg/Media/ComplexMedia.php b/src/FFMpeg/Media/ComplexMedia.php index bc92b61..a199086 100644 --- a/src/FFMpeg/Media/ComplexMedia.php +++ b/src/FFMpeg/Media/ComplexMedia.php @@ -14,11 +14,12 @@ use FFMpeg\Filters\FiltersCollection; use FFMpeg\Format\AudioInterface; use FFMpeg\Format\FormatInterface; use FFMpeg\Format\ProgressableInterface; +use FFMpeg\Format\ProgressListener\AbstractProgressListener; use FFMpeg\Format\VideoInterface; /** * Complex media may have multiple inputs and multiple outputs. - * This class accepts only filters for -filter_complex. + * This class accepts only filters for -filter_complex option. * But you can set initial and additional parameters of the ffmpeg command. * * @see http://trac.ffmpeg.org/wiki/Creating%20multiple%20outputs @@ -46,7 +47,7 @@ class ComplexMedia extends AbstractMediaType private $mapCommands; /** - * @var array + * @var AbstractProgressListener[] */ private $listeners; @@ -88,6 +89,8 @@ class ComplexMedia extends AbstractMediaType } /** + * Add complex filter. + * * @param string $in * @param ComplexCompatibleFilter $filter * @param string $out @@ -117,7 +120,6 @@ class ComplexMedia extends AbstractMediaType /** * @return string[] - * @return void */ public function getInitialParameters() { @@ -137,7 +139,6 @@ class ComplexMedia extends AbstractMediaType /** * @return string[] - * @return void */ public function getAdditionalParameters() { @@ -180,18 +181,21 @@ class ComplexMedia extends AbstractMediaType } /** - * @param string[] $outs Output labels of the -filter_complex part. - * @param FormatInterface $format - * @param string $outputPathfile + * Select the streams for output. + * + * @param string[] $outs Output labels of the -filter_complex part. + * @param FormatInterface $format Format of the output file. + * @param string $outputFilename Output filename. * @param bool $forceDisableAudio * @param bool $forceDisableVideo * * @return $this + * @see https://ffmpeg.org/ffmpeg.html#Manual-stream-selection */ public function map( array $outs, FormatInterface $format, - $outputPathfile, + $outputFilename, $forceDisableAudio = false, $forceDisableVideo = false ) { @@ -202,12 +206,13 @@ class ComplexMedia extends AbstractMediaType } // Apply format params. - $commands = array_merge($commands, $this->applyFormatParams($format, $forceDisableAudio, $forceDisableVideo)); + $commands = array_merge($commands, + $this->applyFormatParams($format, $forceDisableAudio, $forceDisableVideo)); // Set output file. - $commands[] = $outputPathfile; + $commands[] = $outputFilename; - // Create listener. + // Create a listener. if ($format instanceof ProgressableInterface) { $listener = $format->createProgressListener($this, $this->ffprobe, 1, 1, 0); $this->listeners = array_merge($this->listeners, $listener); @@ -220,7 +225,7 @@ class ComplexMedia extends AbstractMediaType /** * Apply added filters and execute ffmpeg command. * - * @return ComplexMedia + * @return void * @throws RuntimeException */ public function save() @@ -233,8 +238,6 @@ class ComplexMedia extends AbstractMediaType } catch (ExecutionFailureException $e) { throw new RuntimeException('Encoding failed', $e->getCode(), $e); } - - return $this; } /** @@ -256,7 +259,7 @@ class ComplexMedia extends AbstractMediaType $commands[] = '-vcodec'; $commands[] = $format->getVideoCodec(); } - // If the user passed some additional parameters. + // If the user passed some additional format parameters. if ($format->getAdditionalParameters() !== null) { $commands = array_merge($commands, $format->getAdditionalParameters()); } @@ -348,13 +351,9 @@ class ComplexMedia extends AbstractMediaType */ protected function buildCommand() { - $commands = array('-y'); - if ($this->driver->getConfiguration()->has('ffmpeg.threads')) { - $commands[] = '-threads'; - $commands[] = $this->driver->getConfiguration()->get('ffmpeg.threads'); - } - - return array_merge($commands, + $globalOptions = array('threads', 'filter_threads', 'filter_complex_threads'); + return array_merge(array('-y'), + $this->buildConfiguredGlobalOptions($globalOptions), $this->getInitialParameters(), $this->buildInputsPart($this->inputs), $this->buildComplexFilterPart($this->filters), @@ -363,6 +362,26 @@ class ComplexMedia extends AbstractMediaType ); } + /** + * @param string[] $optionNames + * + * @return array + */ + private function buildConfiguredGlobalOptions($optionNames) + { + $commands = array(); + foreach ($optionNames as $optionName) { + if (!$this->driver->getConfiguration()->has('ffmpeg.' . $optionName)) { + continue; + } + + $commands[] = '-' . $optionName; + $commands[] = $this->driver->getConfiguration()->get('ffmpeg.' . $optionName); + } + + return $commands; + } + /** * Build inputs part of the ffmpeg command. * diff --git a/tests/Functional/ComplexMediaTest.php b/tests/Functional/ComplexMediaTest.php index b7ad40e..fe11a2b 100644 --- a/tests/Functional/ComplexMediaTest.php +++ b/tests/Functional/ComplexMediaTest.php @@ -13,14 +13,14 @@ class ComplexMediaTest extends FunctionalTestCase /** * Path prefix to avoid conflicts with another tests. */ - const PATH_PREFIX = 'complex_media_'; + const OUTPUT_PATH_PREFIX = 'output/complex_media_'; public function testRunWithoutComplexFilterTestExtractAudio() { $ffmpeg = $this->getFFMpeg(); $inputs = array(realpath(__DIR__ . '/../files/Test.ogv')); $format = new Mp3(); - $output = __DIR__ . '/output/' . self::PATH_PREFIX . 'extracted_with_map.mp3'; + $output = __DIR__ . '/' . self::OUTPUT_PATH_PREFIX . 'extracted_with_map.mp3'; // You can run it without -filter_complex, just using -map. $complexMedia = $ffmpeg->openComplex($inputs); @@ -40,7 +40,7 @@ class ComplexMediaTest extends FunctionalTestCase $inputs = array(realpath(__DIR__ . '/../files/Audio.mp3')); $format = new Mp3(); $format->setAudioKiloBitrate(30); - $output = __DIR__ . '/output/' . self::PATH_PREFIX . 'audio_test.mp3'; + $output = __DIR__ . '/' . self::OUTPUT_PATH_PREFIX . 'audio_test.mp3'; $complexMedia = $ffmpeg->openComplex($inputs); $complexMedia @@ -61,7 +61,7 @@ class ComplexMediaTest extends FunctionalTestCase realpath(__DIR__ . '/../files/portrait.MOV') ); $format = new X264('aac', 'libx264'); - $output = __DIR__ . '/output/' . self::PATH_PREFIX . 'multiple_inputs_test.mp4'; + $output = __DIR__ . '/' . self::OUTPUT_PATH_PREFIX . 'multiple_inputs_test.mp4'; $complexMedia = $ffmpeg->openComplex($inputs); $complexMedia->filters() @@ -88,9 +88,9 @@ class ComplexMediaTest extends FunctionalTestCase $formatX264 = new X264('aac', 'libx264'); $formatMp3 = new Mp3(); - $outputMp3 = __DIR__ . '/output/' . self::PATH_PREFIX . 'test_multiple_outputs.mp3'; - $outputVideo1 = __DIR__ . '/output/' . self::PATH_PREFIX . 'test_multiple_outputs_v1.mp4'; - $outputVideo2 = __DIR__ . '/output/' . self::PATH_PREFIX . 'test_multiple_outputs_v2.mp4'; + $outputMp3 = __DIR__ . '/' . self::OUTPUT_PATH_PREFIX . 'test_multiple_outputs.mp3'; + $outputVideo1 = __DIR__ . '/' . self::OUTPUT_PATH_PREFIX . 'test_multiple_outputs_v1.mp4'; + $outputVideo2 = __DIR__ . '/' . self::OUTPUT_PATH_PREFIX . 'test_multiple_outputs_v2.mp4'; $complexMedia = $ffmpeg->openComplex($inputs); $complexMedia->filters() @@ -131,7 +131,7 @@ class ComplexMediaTest extends FunctionalTestCase $ffmpeg = $this->getFFMpeg(); $inputs = array(realpath(__DIR__ . '/../files/Test.ogv')); $format = new X264('aac', 'libx264'); - $output = __DIR__ . '/output/' . self::PATH_PREFIX . 'testsrc.mp4'; + $output = __DIR__ . '/' . self::OUTPUT_PATH_PREFIX . 'testsrc.mp4'; $complexMedia = $ffmpeg->openComplex($inputs); $complexMedia->filters() @@ -167,7 +167,7 @@ class ComplexMediaTest extends FunctionalTestCase $inputs = array(realpath(__DIR__ . '/../files/Test.ogv')); $format = new X264('aac', 'libx264'); - $output = __DIR__ . '/output/' . self::PATH_PREFIX . 'xstack_test.mp4'; + $output = __DIR__ . '/' . self::OUTPUT_PATH_PREFIX . 'xstack_test.mp4'; $complexMedia = $ffmpeg->openComplex($inputs); $complexMedia->filters() @@ -194,7 +194,7 @@ class ComplexMediaTest extends FunctionalTestCase $inputs = array(realpath(__DIR__ . '/../files/Test.ogv')); $watermark = realpath(__DIR__ . '/../files/watermark.png'); $format = new X264('aac', 'libx264'); - $output = __DIR__ . '/output/' . self::PATH_PREFIX . 'test_of_compatibility_with_existed_filters.mp4'; + $output = __DIR__ . '/' . self::OUTPUT_PATH_PREFIX . 'test_of_compatibility_with_existed_filters.mp4'; $complexMedia = $ffmpeg->openComplex($inputs); $complexMedia->filters() @@ -247,4 +247,22 @@ class ComplexMediaTest extends FunctionalTestCase 'outputFile.mp4', false, true); $this->assertStringNotContainsString('vcodec', $complexMedia2->getFinalCommand()); } + + public function testGlobalOptions() + { + $configuration = array( + 'ffmpeg.threads' => 3, + 'ffmpeg.filter_threads' => 13, + 'ffmpeg.filter_complex_threads' => 24, + ); + + $ffmpeg = $this->getFFMpeg($configuration); + $complexMedia = $ffmpeg->openComplex(array(__FILE__)); + $command = $complexMedia->getFinalCommand(); + + foreach ($configuration as $optionName => $optionValue) { + $optionName = str_replace('ffmpeg.', '', $optionName); + $this->assertStringContainsString('-' . $optionName . ' ' . $optionValue, $command); + } + } } diff --git a/tests/Functional/FunctionalTestCase.php b/tests/Functional/FunctionalTestCase.php index 15fcc26..6a2656a 100644 --- a/tests/Functional/FunctionalTestCase.php +++ b/tests/Functional/FunctionalTestCase.php @@ -8,10 +8,12 @@ use Tests\FFMpeg\BaseTestCase; abstract class FunctionalTestCase extends BaseTestCase { /** + * @param array $configuration + * * @return FFMpeg */ - public function getFFMpeg() + public function getFFMpeg($configuration = array()) { - return FFMpeg::create(array('timeout' => 300)); + return FFMpeg::create(array_merge(array('timeout' => 300), $configuration)); } }