✔ VideoConverter
The VideoConverter service acts as a wrapper over ffmpeg and helps with video conversions, clipping, filters, scaling...
It exposes an immutable api for conversion parameters and attempt to make debugging easier with clean exceptions. You can also inject any psr-3 compatible logger if you don't want to log issues by yourself.
<?php
use Soluble\MediaTools\Video\Config\FFMpegConfig;
use Soluble\MediaTools\Video\Exception\ConverterExceptionInterface;
use Soluble\MediaTools\Video\{VideoConverter, VideoConvertParams};
$converter = new VideoConverter(new FFMpegConfig('/path/to/ffmpeg'));
$params = (new VideoConvertParams())
->withVideoCodec('libx264')
->withStreamable(true)
->withCrf(24);
try {
$converter->convert(
'/path/inputFile.mov',
'/path/outputFile.mp4',
$params
);
} catch(ConverterExceptionInterface $e) {
// See chapter about exception !!!
}
Requirements¶
You'll need to have ffmpeg installed on your system.
Initialization¶
The VideoConverter
requires an FFMpegConfig
object as first parameter.
This is where you set the location of the ffmpeg binary, the number of threads you allow for conversions
and the various timeouts if needed. The second parameter can be used to inject any psr-3 compatible logger.
<?php
use Soluble\MediaTools\Video\Config\{FFMpegConfig, FFMpegConfigInterface};
use Soluble\MediaTools\Video\VideoConverter;
$converter = new VideoConverter(
// @param FFMpegConfigInterface
new FFMpegConfig(
// (?string) - path to ffmpeg binary (default: ffmpeg/ffmpeg.exe)
$binary = null,
// (?int) - ffmpeg default threads (null: single-thread)
$threads = null,
// (?float) - max time in seconds for ffmpeg process (null: disable)
$timeout = null,
// (?float) - max idle time in seconds for ffmpeg process
$idleTimeout = null,
// (array) - additional environment variables
$env = []
),
// @param ?\Psr\Log\LoggerInterface - Default to `\Psr\Log\NullLogger`.
$logger = null
);
Tip: initialize in a container (psr-11)
It's a good idea to register services in a container.
Depending on available framework integrations, you may have a look to the VideoConverterFactory
and/or FFMpegConfigFactory
to get an example based on a psr-11 compatible container.
See also the provided default configuration file.
Usage¶
Conversion¶
Typically you'll use the VideoConverter::convert()
method in which you specify the input/output files
as well as the conversion params.
<?php
$conversionService->convert(
'/path/inputFile.mov',
// Output file will be automatically 'shell' escaped,
'/path/outputFile.mp4',
(new VideoConvertParams())->withVideoCodec('libx264')
);
The convert()
method will automatically set the process timeouts, logger... as specified during
service initialization.
What if I need more control over the process ? (advanced usage)
You can use the VideoConverter::getSymfonyProcess(string $inputFile, string $outputFile, VideoConvertParamsInterface $convertParams, ?ProcessParamsInterface $processParams = null): Process
to get more control on the conversion process.
<?php
$process = $conversionService->getSymfonyProcess(
'/path/inputFile.mov',
'/path/outputFile.mp4',
(new VideoConvertParams())->withVideoCodec('libx264')
);
$process->start();
foreach ($process as $type => $data) {
if ($process::OUT === $type) {
echo "\nRead from stdout: ".$data;
} else { // $process::ERR === $type
echo "\nRead from stderr: ".$data;
}
}
Parameters¶
The Video\VideoConvertParams
exposes an immutable api that attempt to mimic ffmpeg params.
<?php
use Soluble\MediaTools\Video\VideoConvertParams;
$params = (new VideoConvertParams())
->withVideoCodec('libx264')
->withStreamable(true)
->withCrf(24)
->withPreset('fast')
->withAudioCodec('aac')
->withAudioBitrate('128k');
Immutable api, what does it change for me ? (vs fluent)
VideoConvertParams exposes an immutable style api (->withXXX()
, like PSR-7 for example).
It means that the original object is never touched, the withXXX()
methods will return a newly
created object.
Please be aware of it especially if you're used to fluent interfaces as both expose chainable methods... your primary reflexes might cause pain:
<?php
$params = (new VideoConvertParams());
$newParams = $params->withVideoCodec('libx264');
// $params used here are empty (incorrect usage)
$converter->convert('i.mov', 'output', $params);
// $newParams have been initialized with video codec (correct)
$converter->convert('i.mov', 'output', $newParams);
Here's a list of categorized built-in methods you can use. See the ffmpeg doc for more information.
- Video options:
Method | FFmpeg arg(s) | Example(s) | Note(s) |
---|---|---|---|
withVideoCodec(string) |
-c:v ◌ | libx264… | any supported ffmpeg codec |
withVideoBitrate(string) |
-b:v ◌ | 750k,2M… | constant bit rate |
withVideoMinBitrate(string) |
-minrate ◌ | 750k,2M… | min variable bitrate |
withVideoMaxBitrate(string) |
-maxrate ◌ | 750k,2M… | max variable bitrate |
withCrf(int) |
-crf ◌ | 32,… | constant rate factor |
withStreamable() |
-movflags +faststart | mp4 container only | |
withPixFmt(string) |
-pix_fmt ◌ | yuv420p | Default 'no change' |
withQuality(string) |
-quality ◌ | good,medium… | |
withPreset(string) |
-preset ◌ | fast… | |
withTune(string) |
-tune ◌ | film… | |
withVideoQualityScale(int) |
-qscale:v ◌ | ||
withTileColumns(int) |
-tile-columns ◌ | 10… | vp9 related |
withKeyframeSpacing(int) |
-g ◌ | 240… | vp9 related |
withFrameParallel(int) |
-frame-parallel ◌ | 2… | vp9 related |
withLagInFrames(int) |
-lag-in-frames ◌ | 25 | vp9, use with autoAltRef |
withAutoAltRef(int) |
-auto-alt-ref ◌ | 1 | vp9, use with lagInFrames |
- Audio options:
Method | FFmpeg arg(s) | Example(s) | Note(s) |
---|---|---|---|
withAudioCodec(string) |
-c:a ◌ | aac,mp3… | webm requires vorbis/opus |
withAudioBitrate(string) |
-b:a ◌ | 128k… | |
withNoAudio() |
-an | removes all audio tracks |
- Seeking/clipping options:
Method | FFmpeg arg(s) | Example(s) | Note(s) |
---|---|---|---|
withSeekStart(SeekTime) |
-ss ◌ | SeekTime::createFromHms('0:00:01.9') |
|
withSeekEnd(SeekTime) |
-to ◌ | new SeekTime(120.456) |
|
withVideoFrames(int) |
-frames:v ◌ | 1000… | Only ◌ frames |
- Filter related:
Method | FFmpeg arg(s) | Example(s) | Note(s) |
---|---|---|---|
withFilter(VideoFilterInterface) |
-filter:v ◌ | See doc section about filters |
- General process options:
Method | FFmpeg arg(s) | Example(s) | Note(s) |
---|---|---|---|
withSpeed(int) |
-speed ◌ | 1,2,3… | for vp9 or multipass |
withThreads(int) |
-threads ◌ | 0,1,2… | by default uses FFMpegConfig |
withOutputFormat(string) |
-format ◌ | mp4,webm… | file extension (if not provided) |
withOverwrite() |
-y | by default. overwrite if file exists | |
withNoOverwrite() |
throw exception if output exists |
- Multipass related
Method | FFmpeg arg(s) | Example(s) | Note(s) |
---|---|---|---|
withPassLogFile(string) |
-passlogfile ◌ | Ex: `tempnam(sys_get_temp_dir(), 'ffmpeg-log') | |
withPass(int) |
-pass ◌ | 1 or 2 |
- Other methods:
Method | Note(s) |
---|---|
withConvertParam(VideoConvertParamInterface) |
With extra VideoConvertParams (will be merged) |
withBuiltInParam(string, mixed) |
With any supported built-in param, see constants. |
withoutParam(string) |
Without the specified parameter. |
getParam(string $param): mixed |
Return the param calue or throw UnsetParamExeption if not set. |
hasParam(string $param): bool |
Whether the param has been set. |
toArray(): array |
Return the object as array. |
To get the latest list of built-ins, see the VideoConvertParamsInterface and FFMpegAdapter sources.
Filters¶
Video filters can be set to the VideoConvertParams through the ->withVideoFilter(VideoFilterInterface $videoFilter)
method:
<?php
use Soluble\MediaTools\Video\Filter;
$params = (new VideoConvertParams())
->withVideoFilter(
new Filter\VideoFilterChain([
// A scaling filter
new Filter\ScaleFilter(800, 600),
// A denoise filter
new Filter\Hqdn3DVideoFilter()
])
);
See the complete video filters doc here
Exceptions¶
All conversion exceptions implements Soluble\MediaTools\VideoException\ConverterExceptionInterface
, interface.,
alternatively you can also :
<?php
use Soluble\MediaTools\Video\{VideoConverter, VideoConvertParams};
use Soluble\MediaTools\Video\Exception as VE;
/** @var VideoConverter $converter */
$params = (new VideoConvertParams())->withVideoCodec('xxx');
try {
$converter->convert('i.mov', 'o.mp4', $params);
// All exception below implements VE\ConverterExceptionInterface
} catch(VE\MissingInputFileException $e) {
// 'i.mov does not exists
echo $e->getMessage();
} catch (
// The following 3 exceptions are linked to process
// failure 'ffmpeg exit code != 0) and implements
//
// - `VE\ConversionProcessExceptionInterface`
// (* which extends Mediatools\Common\Exception\ProcessExceptionInterface)
//
// you can catch all them at once or separately:
VE\ProcessFailedException
| VE\ProcessSignaledException
| VE\ProcessTimedOutException $e)
{
echo $e->getMessage();
// Because they implement ProcessExceptionInterface
// we can get a reference to the executed (symfony) process:
$process = $e->getProcess();
echo $process->getExitCode();
echo $process->getErrorOutput();
} catch(VE\ConverterExceptionInterface $e) {
// Other exceptions can be
//
// - VE\RuntimeException
// - VE\InvalidParamException (should not happen)
}
Notes¶
Compression¶
Achieving a good level of compression while preserving quality is not that easy.
Compression techniques will depend on the codec (h264, av1, vp9), the purpose (archive, streaming, vod...)
and the size (and fps) of the original content. The mediatools VideoConverter
is agnostic
and does not offer any help, you'll need to set up your own set of parameters.
There's a lot of ffmpeg recipes on internet that you can easily port, some interesting sources:
- FFMpeg VP9 encoding
- FFMpeg H.264 encoding
- Google VOD VP9 setting
Variable or Constant Bitrate ? Or both ?
tl;dr: VBR and CBR can be set together to ensure max quality within a target bitrate (streamability++).
Variable Bitrate
Variable bitrate (VBR) ensure that you’d achieve the lowest possible file size at the highest possible quality under the given constraints.
Use the VideoConvertParams::withBitrate()
, withMaxBitrate()
and withMinBitrate()
methods
to set what you want to achieve. But be warned bitrates must not be set blindly, to be effective
they must be choosen in respect to video dimensions and fps. See VOD VP9 setting.
Constant Bitrate
The VideoConvertParams::withCrf()
will set the Constant Rate Factor (CRF) setting
for the x264, x265 and vp9 encoders.
-
h26x: You can set the values between 0 and 51, where lower values would result in better quality, at the expense of higher file sizes. Higher values mean more compression, but at some point you will notice the quality degradation. For x264, sane values are between 18 and 28. The default is 23, so you can use this as a starting point.
-
vpx: The CRF value can be from 0–63. Lower values mean better quality. Recommended values range from 15–35, with 31 being recommended for 1080p HD video
Please also be sure to understand what rate control modes are (you can see here and here and how to choose the one you need.
Performance¶
Conversions are heavy dudes, things that can help:
- Increasing the FfmpegConfig threads parameter can help for some tasks.
- Order of parameters can help. i.e: if you need to clip, makes it before applying filters.
Recipes¶
Transcode to mp4/x264/aac
¶
See the official H264 doc.
<?php
use Soluble\MediaTools\Video\{Exception, VideoConvertParams};
use Soluble\MediaTools\Video\VideoConverterInterface;
$params = (new VideoConvertParams())
->withVideoCodec('libx264')
->withAudioCodec('aac')
->withAudioBitrate('128k')
->withStreamable(true) // Add streamable options (movflags & faststart)
->withCrf(24) // Level of compression: better size / less visual quality
->withPreset('fast'); // Optional: see presets
try {
/** @var VideoConverterInterface $converter */
$converter->convert(
'/path/inputFile.mov',
'/path/outputFile.mp4',
$params
);
} catch(Exception\ConverterExceptionInterface $e) {
// See chapters about exception !!!
}
Transcode to webm/vp9/opus
¶
See the official ffmpeg VP9 docs and have a look at the google vp9 VOD guidelines
<?php
use Soluble\MediaTools\Video\{Exception, VideoConvertParams};
use Soluble\MediaTools\Video\VideoConverterInterface;
$params = (new VideoConvertParams())
->withVideoCodec('libvpx-vp9')
->withVideoBitrate('750k')
->withQuality('good')
->withCrf(33)
->withAudioCodec('libopus')
->withAudioBitrate('128k')
/**
* It is recommended to allow up to 240 frames of video between keyframes (8 seconds for 30fps content).
* Keyframes are video frames which are self-sufficient; they don't rely upon any other frames to render
* but they tend to be larger than other frame types.
* For web and mobile playback, generous spacing between keyframes allows the encoder to choose the best
* placement of keyframes to maximize quality.
*/
->withKeyframeSpacing(240)
// Most of the current VP9 decoders use tile-based, multi-threaded decoding.
// In order for the decoders to take advantage of multiple cores,
// the encoder must set tile-columns and frame-parallel.
->withTileColumns(2)
->withFrameParallel(1)
->withSpeed(1)
// Optional: Use videoprobe to be sure of color conversions if any needed
// ->withPixFmt('yuv420p')
->withOutputFormat('webm');
try {
/** @var VideoConverterInterface $converter */
$converter->convert(
'/path/inputFile.mov',
'/path/outputFile.webm',
$params
);
} catch(Exception\ConverterExceptionInterface $e) {
// see chapter about exceptions
}
Video scaling¶
See also ffmpeg doc
<?php
use Soluble\MediaTools\Video\{Exception, VideoConvertParams, SeekTime};
use Soluble\MediaTools\Video\Filter\ScaleFilter;
$params = (new VideoConvertParams())
->withVideoFilter(
new ScaleFilter(
// $width: as an int or any ffmpeg supported placeholder: iw*0.5, ...
800,
// $height: as an int or any ffmpeg supported placeholder: ih*0.5, ...
'ih*0.5',
// $aspect_ratio_mode (increase or decrease)
ScaleFilter::OPTION_ASPECT_RATIO_INCREASE
)
);
try {
/** @var \Soluble\MediaTools\Video\VideoConverterInterface $videoConverter */
$videoConverter->convert(
'/path/inputFile.mp4',
'/path/outputFile.mp4',
$params
);
} catch(Exception\ConverterExceptionInterface $e) {
// see chapter about exceptions
}
Video clipping¶
See the official ffmpeg docs
<?php
use Soluble\MediaTools\Video\{Exception, VideoConvertParams, SeekTime};
$params = (new VideoConvertParams())
->withSeekStart(new SeekTime(10.242)) // 10 sec, 242 milli
->withSeekEnd(SeekTime::createFromHMS('12:52.015')); // 12 mins, 52 secs...
try {
/** @var \Soluble\MediaTools\Video\VideoConverterInterface $videoConverter */
$videoConverter->convert(
'/path/inputFile.mp4',
'/path/outputFile.mp4',
$params
);
} catch(Exception\ConverterExceptionInterface $e) {
// see chapter about exceptions
}
Multipass encoding¶
<?php
use Soluble\MediaTools\Video\{VideoConvertParams, VideoConvertParamsInterface};
use Soluble\MediaTools\Common\IO\PlatformNullFile;
// Where to store the result of first pass analysis
$logFile = tempnam(sys_get_temp_dir(), 'ffmpeg-passlog');
$pass1Params = (new VideoConvertParams())
->withVideoCodec('libvpx-vp9')
->withVideoBitrate('1M')
->withVideoMaxBitrate('1500k')
->withVideoMinBitrate('750k')
->withKeyframeSpacing(240)
->withTileColumns(1)
->withFrameParallel(1)
// Set the pass number
->withPass(1)
// Set the ffmpeg logfile
->withPassLogFile($logFile)
// Speed in first pass can be faster
->withSpeed(4)
// Audio does not need to be analyzed
->withNoAudio()
// Because we will pipe it to /dev/null
// we need to specify container
->withOutputFormat('webm');
// PASS 1 Conversion
$this->videoConvert->convert(
'/tmp/input.mov',
// In first pass we don't need to output the conversion result
// let's put in /dev/null.
new PlatformNullFile(),
$pass1Params
);
// Let's init pass 2 params from pass 1
$pass2Params = $pass1Params
// reinit audio
->withoutParam(VideoConvertParamsInterface::PARAM_NOAUDIO)
->withAudioCodec('libopus')
->withAudioBitrate('256k')
// Reset the pass number
->withPass(2)
// Speed in second pass must be slower
->withSpeed(1);
$this->videoConvert->convert(
'/tmp/input.mov',
'/tmp/output.webm',
$pass2Params
);