diff --git a/src/FFMpeg/Filters/Video/ResizeFilter.php b/src/FFMpeg/Filters/Video/ResizeFilter.php index c5f96cb..ab8ee03 100644 --- a/src/FFMpeg/Filters/Video/ResizeFilter.php +++ b/src/FFMpeg/Filters/Video/ResizeFilter.php @@ -98,8 +98,10 @@ class ResizeFilter implements VideoFilterInterface if (null !== $dimensions) { $dimensions = $this->getComputedDimensions($dimensions, $format->getModulus()); - $commands[] = '-s'; - $commands[] = $dimensions->getWidth() . 'x' . $dimensions->getHeight(); + // Using Filter to have ordering + $commands[] = '-vf'; + $commands[] = '[in]scale=' . $dimensions->getWidth() . ':' . $dimensions->getHeight() . ' [out]'; + } return $commands; diff --git a/src/FFMpeg/Format/Audio/DefaultAudio.php b/src/FFMpeg/Format/Audio/DefaultAudio.php index 4226bff..d0d1030 100644 --- a/src/FFMpeg/Format/Audio/DefaultAudio.php +++ b/src/FFMpeg/Format/Audio/DefaultAudio.php @@ -46,6 +46,31 @@ abstract class DefaultAudio extends EventEmitter implements AudioInterface, Prog return $this->audioCodec; } + /** + * {@inheritdoc} + */ + public function getAdditionalParameters() + { + return $this->additionalParameters; + } + + /** + * Sets additional parameters. + * + * @param array $additionalParameters + * @throws InvalidArgumentException + */ + public function setAdditionalParameters($additionalParameters) + { + if (!is_array($additionalParameters)) { + throw new InvalidArgumentException('Wrong additionalParamaters value'); + } + + $this->additionalParameters = $additionalParameters; + + return $this; + } + /** * Sets the audio codec, Should be in the available ones, otherwise an * exception is thrown. diff --git a/src/FFMpeg/Format/AudioInterface.php b/src/FFMpeg/Format/AudioInterface.php index 1af29c0..9a8a927 100644 --- a/src/FFMpeg/Format/AudioInterface.php +++ b/src/FFMpeg/Format/AudioInterface.php @@ -25,6 +25,13 @@ interface AudioInterface extends FormatInterface * @return integer */ public function getAudioChannels(); + + /** + * Returns the list of available video codecs for this format. + * + * @return array + */ + public function getAdditionalParameters(); /** * Returns the audio codec. diff --git a/src/FFMpeg/Format/Profile.php b/src/FFMpeg/Format/Profile.php new file mode 100644 index 0000000..32a65a7 --- /dev/null +++ b/src/FFMpeg/Format/Profile.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FFMpeg\Format; + +interface Profile { + + const HIGH = 'high'; + + const MAIN = 'main'; + + const BASELINE = 'baseline'; + +} \ No newline at end of file diff --git a/src/FFMpeg/Format/Video/DefaultVideo.php b/src/FFMpeg/Format/Video/DefaultVideo.php index 1abfa67..dc7d898 100644 --- a/src/FFMpeg/Format/Video/DefaultVideo.php +++ b/src/FFMpeg/Format/Video/DefaultVideo.php @@ -16,6 +16,7 @@ use FFMpeg\Exception\InvalidArgumentException; use FFMpeg\Format\Audio\DefaultAudio; use FFMpeg\Format\VideoInterface; use FFMpeg\Media\MediaTypeInterface; +use FFMpeg\Format\Profile; use FFMpeg\Format\ProgressListener\VideoProgressListener; /** @@ -32,8 +33,56 @@ abstract class DefaultVideo extends DefaultAudio implements VideoInterface /** @var Integer */ protected $modulus = 16; - /** @var Array */ - protected $additionalParamaters; + /** @var string */ + private $profile = Profile::MAIN; + + /** @var float */ + private $level = 3.1; + + /** @var string[] */ + protected $additionalParameters; + + /** + * Sets the profile of this video + * @var string $profile must be one of `baseline`, `main` or `high` + * @throws \InvalidArgumentException + */ + public function setProfile(string $profile) { + switch($profile) { + case Profile::BASELINE: + case Profile::MAIN: + case Profile::HIGH: + // these are fine + break; + default: + throw new \InvalidArgumentException('Invalid profile given! Must be one of `baseline`, `main` or `high`!'); + } + $this->profile = $profile; + return $this; + } + + /** + * @inheritDoc + */ + public function getProfile() { + return $this->profile; + } + + /** + * Sets the given level + * @param float $level The level(for example: 3.0, 3.1, 4.0, 4.1) + */ + public function setLevel(float $level) { + $this->level = $level; + return $this; + } + + /** + * @inheritDoc + */ + public function getLevel() { + return $this->level; + } /** * {@inheritdoc} @@ -97,31 +146,6 @@ abstract class DefaultVideo extends DefaultAudio implements VideoInterface return $this->modulus; } - /** - * {@inheritdoc} - */ - public function getAdditionalParameters() - { - return $this->additionalParamaters; - } - - /** - * Sets additional parameters. - * - * @param array $additionalParamaters - * @throws InvalidArgumentException - */ - public function setAdditionalParameters($additionalParamaters) - { - if (!is_array($additionalParamaters)) { - throw new InvalidArgumentException('Wrong additionalParamaters value'); - } - - $this->additionalParamaters = $additionalParamaters; - - return $this; - } - /** * {@inheritdoc} */ diff --git a/src/FFMpeg/Format/VideoInterface.php b/src/FFMpeg/Format/VideoInterface.php index 329bff2..71bb659 100644 --- a/src/FFMpeg/Format/VideoInterface.php +++ b/src/FFMpeg/Format/VideoInterface.php @@ -56,9 +56,14 @@ interface VideoInterface extends AudioInterface public function getAvailableVideoCodecs(); /** - * Returns the list of available video codecs for this format. - * - * @return array + * Returns the current profile + * @return string */ - public function getAdditionalParameters(); + public function getProfile(); + + /** + * Returns the level + * @return float + */ + public function getLevel(); } diff --git a/src/FFMpeg/Media/Audio.php b/src/FFMpeg/Media/Audio.php index 19f3478..4b5c84b 100644 --- a/src/FFMpeg/Media/Audio.php +++ b/src/FFMpeg/Media/Audio.php @@ -91,6 +91,13 @@ class Audio extends AbstractStreamableMedia $commands[] = '-ac'; $commands[] = $format->getAudioChannels(); } + // If the user passed some additional parameters + if (null !== $format->getAdditionalParameters()) { + foreach ($format->getAdditionalParameters() as $additionalParameter) { + $commands[] = $additionalParameter; + } + } + $commands[] = $outputPathfile; try { diff --git a/src/FFMpeg/Media/Video.php b/src/FFMpeg/Media/Video.php index 1a01953..7005e72 100644 --- a/src/FFMpeg/Media/Video.php +++ b/src/FFMpeg/Media/Video.php @@ -72,6 +72,8 @@ class Video extends Audio if ($format instanceof VideoInterface) { if (null !== $format->getVideoCodec()) { $filters->add(new SimpleFilter(array('-vcodec', $format->getVideoCodec()))); + $filters->add(new SimpleFilter(array('-vprofile', $format->getProfile()))); + $filters->add(new SimpleFilter(array('-level', $format->getLevel()))); } } if ($format instanceof AudioInterface) { @@ -121,14 +123,62 @@ class Video extends Audio } // If the user passed some additional parameters - if ($format instanceof VideoInterface) { - if (null !== $format->getAdditionalParameters()) { - foreach ($format->getAdditionalParameters() as $additionalParameter) { - $commands[] = $additionalParameter; - } + if (null !== $format->getAdditionalParameters()) { + foreach ($format->getAdditionalParameters() as $additionalParameter) { + $commands[] = $additionalParameter; } } + // Merge Filters into one command + $videoFilterVars = $videoFilterProcesses = []; + for($i=0;$i $process) { + $command = '[' . $lastInput .']'; + $command .= $process; + $lastInput = 'p' . $i; + if ( $i == count($videoFilterProcesses) - 1 ) { + $command .= '[out]'; + } else { + $command .= '[' . $lastInput . ']'; + } + + $videoFilterCommands[] = $command; + } + $videoFilterCommand = implode(";", $videoFilterCommands); + + if ( $videoFilterCommand ) { + $commands[] = '-vf'; + $commands[] = $videoFilterCommand; + } + $fs = FsManager::create(); $fsId = uniqid('ffmpeg-passes'); $passPrefix = $fs->createTemporaryDirectory(0777, 50, $fsId) . '/' . uniqid('pass-'); diff --git a/tests/Unit/Filters/Video/ResizeFilterTest.php b/tests/Unit/Filters/Video/ResizeFilterTest.php index ab92241..c87a3cb 100644 --- a/tests/Unit/Filters/Video/ResizeFilterTest.php +++ b/tests/Unit/Filters/Video/ResizeFilterTest.php @@ -42,34 +42,34 @@ class ResizeFilterTest extends TestCase public function provideDimensions() { return array( - array(new Dimension(320, 240), ResizeFilter::RESIZEMODE_FIT, 640, 480, 2, array('-s', '320x240')), - array(new Dimension(320, 240), ResizeFilter::RESIZEMODE_INSET, 640, 480, 2, array('-s', '320x240')), - array(new Dimension(320, 240), ResizeFilter::RESIZEMODE_SCALE_HEIGHT, 640, 480, 2, array('-s', '320x240')), - array(new Dimension(320, 240), ResizeFilter::RESIZEMODE_SCALE_WIDTH, 640, 480, 2, array('-s', '320x240')), + array(new Dimension(320, 240), ResizeFilter::RESIZEMODE_FIT, 640, 480, 2, array('-vf', '[in]scale=320:240 [out]')), + array(new Dimension(320, 240), ResizeFilter::RESIZEMODE_INSET, 640, 480, 2, array('-vf', '[in]scale=320:240 [out]')), + array(new Dimension(320, 240), ResizeFilter::RESIZEMODE_SCALE_HEIGHT, 640, 480, 2, array('-vf', '[in]scale=320:240 [out]')), + array(new Dimension(320, 240), ResizeFilter::RESIZEMODE_SCALE_WIDTH, 640, 480, 2, array('-vf', '[in]scale=320:240 [out]')), - array(new Dimension(640, 480), ResizeFilter::RESIZEMODE_FIT, 320, 240, 2, array('-s', '640x480')), - array(new Dimension(640, 480), ResizeFilter::RESIZEMODE_INSET, 320, 240, 2, array('-s', '640x480')), - array(new Dimension(640, 480), ResizeFilter::RESIZEMODE_SCALE_HEIGHT, 320, 240, 2, array('-s', '640x480')), - array(new Dimension(640, 480), ResizeFilter::RESIZEMODE_SCALE_WIDTH, 320, 240, 2, array('-s', '640x480')), + array(new Dimension(640, 480), ResizeFilter::RESIZEMODE_FIT, 320, 240, 2, array('-vf', '[in]scale=640:480 [out]')), + array(new Dimension(640, 480), ResizeFilter::RESIZEMODE_INSET, 320, 240, 2, array('-vf', '[in]scale=640:480 [out]')), + array(new Dimension(640, 480), ResizeFilter::RESIZEMODE_SCALE_HEIGHT, 320, 240, 2, array('-vf', '[in]scale=640:480 [out]')), + array(new Dimension(640, 480), ResizeFilter::RESIZEMODE_SCALE_WIDTH, 320, 240, 2, array('-vf', '[in]scale=640:480 [out]')), - array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_FIT, 1280, 720, 2, array('-s', '640x360')), - array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_INSET, 1280, 720, 2, array('-s', '640x360')), - array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_SCALE_HEIGHT, 1280, 720, 2, array('-s', '640x360')), - array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_SCALE_WIDTH, 1280, 720, 2, array('-s', '640x360')), + array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_FIT, 1280, 720, 2, array('-vf', '[in]scale=640:360 [out]')), + array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_INSET, 1280, 720, 2, array('-vf', '[in]scale=640:360 [out]')), + array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_SCALE_HEIGHT, 1280, 720, 2, array('-vf', '[in]scale=640:360 [out]')), + array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_SCALE_WIDTH, 1280, 720, 2, array('-vf', '[in]scale=640:360 [out]')), - array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_FIT, 1280, 720, 2, array('-s', '640x360')), - array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_INSET, 1280, 720, 2, array('-s', '640x360')), - array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_SCALE_HEIGHT, 1280, 720, 2, array('-s', '640x360')), - array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_SCALE_WIDTH, 1280, 720, 2, array('-s', '640x360')), + array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_FIT, 1280, 720, 2, array('-vf', '[in]scale=640:360 [out]')), + array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_INSET, 1280, 720, 2, array('-vf', '[in]scale=640:360 [out]')), + array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_SCALE_HEIGHT, 1280, 720, 2, array('-vf', '[in]scale=640:360 [out]')), + array(new Dimension(640, 360), ResizeFilter::RESIZEMODE_SCALE_WIDTH, 1280, 720, 2, array('-vf', '[in]scale=640:360 [out]')), // test non standard dimension - array(new Dimension(700, 150), ResizeFilter::RESIZEMODE_INSET, 123, 456, 2, array('-s', '62x150'), true), - array(new Dimension(700, 150), ResizeFilter::RESIZEMODE_INSET, 123, 456, 2, array('-s', '40x150'), false), + array(new Dimension(700, 150), ResizeFilter::RESIZEMODE_INSET, 123, 456, 2, array('-vf', '[in]scale=62:150 [out]'), true), + array(new Dimension(700, 150), ResizeFilter::RESIZEMODE_INSET, 123, 456, 2, array('-vf', '[in]scale=40:150 [out]'), false), - array(new Dimension(320, 320), ResizeFilter::RESIZEMODE_FIT, 640, 480, 2, array('-s', '320x320')), - array(new Dimension(320, 320), ResizeFilter::RESIZEMODE_INSET, 640, 480, 2, array('-s', '320x240')), - array(new Dimension(320, 320), ResizeFilter::RESIZEMODE_SCALE_HEIGHT, 640, 480, 2, array('-s', '320x240')), - array(new Dimension(320, 320), ResizeFilter::RESIZEMODE_SCALE_WIDTH, 640, 480, 2, array('-s', '426x320')), + array(new Dimension(320, 320), ResizeFilter::RESIZEMODE_FIT, 640, 480, 2, array('-vf', '[in]scale=320:320 [out]')), + array(new Dimension(320, 320), ResizeFilter::RESIZEMODE_INSET, 640, 480, 2, array('-vf', '[in]scale=320:240 [out]')), + array(new Dimension(320, 320), ResizeFilter::RESIZEMODE_SCALE_HEIGHT, 640, 480, 2, array('-vf', '[in]scale=320:240 [out]')), + array(new Dimension(320, 320), ResizeFilter::RESIZEMODE_SCALE_WIDTH, 640, 480, 2, array('-vf', '[in]scale=426:320 [out]')), ); } } diff --git a/tests/Unit/Media/VideoTest.php b/tests/Unit/Media/VideoTest.php index 009a2f6..322ba53 100644 --- a/tests/Unit/Media/VideoTest.php +++ b/tests/Unit/Media/VideoTest.php @@ -449,60 +449,81 @@ class VideoTest extends AbstractStreamableTestCase return array( array(false, array(array( - '-y', '-i', __FILE__, '-b:v', '663k', - '-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', '92k', '-ac', 2, 'foo', 'bar', '-pass', 1, '-passlogfile', - '/target/file', - ), array( - '-y', '-i', __FILE__, + '-y', + '-i', __FILE__, '-b:v', '663k', - '-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', '92k', '-ac', 2, 'foo', 'bar', '-pass', 2, '-passlogfile', - '/target/file', + '-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', '92k', + '-ac', 2, + 'foo', 'bar', + '-pass', 1, + '-passlogfile', '/target/file', + ), array( + '-y', + '-i', __FILE__, + '-b:v', '663k', + '-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', '92k', + '-ac', 2, + 'foo', 'bar', + '-pass', 2, + '-passlogfile', '/target/file' )), null, $format), array(false, array(array( - '-y', '-i', __FILE__, - '-vcodec', 'gloubi-boulga-video', - '-acodec', 'patati-patata-audio', '-b:v', '664k', - '-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', '92k', '-ac', '2', '-pass', '1', '-passlogfile', - '/target/file', + '-y', + '-i', __FILE__, + 'extra', 'param', + '-b:v', '665k', + '-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', '92k', + '-ac', '2', + '-pass', '1', + '-passlogfile', '/target/file', ), array( '-y', '-i', __FILE__, - '-vcodec', 'gloubi-boulga-video', - '-acodec', 'patati-patata-audio', - '-b:v', '664k', - '-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', '92k', '-ac', '2', '-pass', '2', '-passlogfile', - '/target/file', - )), null, $audioVideoFormat), - array(false, array(array( - '-y', '-i', __FILE__, - '-vcodec', 'gloubi-boulga-video', - '-acodec', 'patati-patata-audio', '-b:v', '664k', - '-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', '92k', '-ac', '2', - '/target/file', - )), null, $audioVideoFormatSinglePass), - array(false, array(array( - '-y', '-i', __FILE__, - 'extra', 'param','-b:v', '665k', - '-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', '92k', '-ac', '2', '-pass', '1', '-passlogfile', - '/target/file', - ), array( - '-y', '-i', __FILE__, - 'extra', 'param', '-b:v', '665k', - '-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', '92k', '-ac', '2', '-pass', '2', '-passlogfile', - '/target/file', + 'extra', 'param', + '-b:v', '665k', + '-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', '92k', + '-ac', '2', + '-pass', '2', + '-passlogfile', '/target/file', )), null, $formatExtra), array(true, array(array( '-y', '-i', __FILE__,