VideoConvertParams::withCrf()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
ccs 3
cts 3
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * @see       https://github.com/soluble-io/soluble-mediatools for the canonical repository
7
 *
8
 * @copyright Copyright (c) 2018-2020 Sébastien Vanvelthem. (https://github.com/belgattitude)
9
 * @license   https://github.com/soluble-io/soluble-mediatools/blob/master/LICENSE.md MIT
10
 */
11
12
namespace Soluble\MediaTools\Video;
13
14
use Soluble\MediaTools\Common\Assert\BitrateAssertionsTrait;
15
use Soluble\MediaTools\Video\Adapter\FFMpegCLIValueInterface;
16
use Soluble\MediaTools\Video\Exception\InvalidArgumentException;
17
use Soluble\MediaTools\Video\Exception\InvalidParamException;
18
use Soluble\MediaTools\Video\Exception\UnsetParamException;
19
use Soluble\MediaTools\Video\Filter\Type\VideoFilterInterface;
20
21
final class VideoConvertParams implements VideoConvertParamsInterface
22
{
23
    use BitrateAssertionsTrait;
24
25
    /** @var array<string, bool|string|int|VideoFilterInterface|FFMpegCLIValueInterface> */
26
    private $params = [];
27
28
    /**
29
     * @param array<string, bool|string|int|VideoFilterInterface|FFMpegCLIValueInterface> $params
30
     *
31
     * @throws InvalidParamException in case of unsupported option
32
     */
33 62
    public function __construct(array $params = [])
34
    {
35 62
        $this->ensureSupportedParams($params);
36 61
        $this->params = $params;
37 61
    }
38
39 21
    public function withVideoCodec(string $videoCodec): self
40
    {
41 21
        return new self(array_merge($this->params, [
42 21
            self::PARAM_VIDEO_CODEC => $videoCodec,
43
        ]));
44
    }
45
46 28
    public function withVideoFilter(VideoFilterInterface $videoFilter): self
47
    {
48 28
        return new self(array_merge($this->params, [
49 28
            self::PARAM_VIDEO_FILTER => $videoFilter,
50
        ]));
51
    }
52
53 6
    public function withAudioCodec(string $audioCodec): self
54
    {
55 6
        return new self(array_merge($this->params, [
56 6
            self::PARAM_AUDIO_CODEC => $audioCodec,
57
        ]));
58
    }
59
60
    /**
61
     * Set TileColumns (VP9 - to use in conjunction with FrameParallel).
62
     *
63
     * Tiling splits the video frame into multiple columns,
64
     * which slightly reduces quality but speeds up encoding performance.
65
     * Tiles must be at least 256 pixels wide, so there is a limit to how many tiles can be used.
66
     * Depending upon the number of tiles and the resolution of the tmp frame, more CPU threads may be useful.
67
     *
68
     * Generally speaking, there is limited value to multiple threads when the tmp frame size is very small.
69
     *
70
     * @see self::withFrameParallel()
71
     */
72 12
    public function withTileColumns(int $tileColumns): self
73
    {
74 12
        return new self(array_merge($this->params, [
75 12
            self::PARAM_TILE_COLUMNS => $tileColumns,
76
        ]));
77
    }
78
79
    /**
80
     * Set FrameParallel (VP9 - to use in conjunction with TileColumns).
81
     *
82
     * @see self::withTileColumns()
83
     */
84 6
    public function withFrameParallel(int $frameParallel): self
85
    {
86 6
        return new self(array_merge($this->params, [
87 6
            self::PARAM_FRAME_PARALLEL => $frameParallel,
88
        ]));
89
    }
90
91
    /**
92
     * Set KeyFrameSpacing (VP9).
93
     *
94
     * It is recommended to allow up to 240 frames of video between keyframes (8 seconds for 30fps content).
95
     * Keyframes are video frames which are self-sufficient; they don't rely upon any other frames to render
96
     * but they tend to be larger than other frame types.
97
     *
98
     * For web and mobile playback, generous spacing between keyframes allows the encoder to choose the best
99
     * placement of keyframes to maximize quality.
100
     */
101 6
    public function withKeyframeSpacing(int $keyframeSpacing): self
102
    {
103 6
        return new self(array_merge($this->params, [
104 6
            self::PARAM_KEYFRAME_SPACING => $keyframeSpacing,
105
        ]));
106
    }
107
108
    /**
109
     * The Constant Rate Factor (CRF) setting for the x264, x265 and vp9.
110
     *
111
     * encoders.
112
     *
113
     * - h264: You can set the values between 0 and 51, where lower values would result in better quality,
114
     *         at the expense of higher file sizes. Higher values mean more compression,
115
     *         but at some point you will notice the quality degradation.
116
     *         For x264, sane values are between 18 and 28. The default is 23, so you can use this as a starting point.
117
     *
118
     * - vp9:  The CRF value can be from 0–63. Lower values mean better quality. Recommended values range from 15–35,
119
     *         with 31 being recommended for 1080p HD video
120
     */
121 8
    public function withCrf(int $crf): self
122
    {
123 8
        return new self(array_merge($this->params, [
124 8
            self::PARAM_CRF => $crf,
125
        ]));
126
    }
127
128 6
    public function withPixFmt(string $pixFmt): self
129
    {
130 6
        return new self(array_merge($this->params, [
131 6
            self::PARAM_PIX_FMT => $pixFmt,
132
        ]));
133
    }
134
135 4
    public function withPreset(string $preset): self
136
    {
137 4
        return new self(array_merge($this->params, [
138 4
            self::PARAM_PRESET => $preset,
139
        ]));
140
    }
141
142 6
    public function withSpeed(int $speed): self
143
    {
144 6
        return new self(array_merge($this->params, [
145 6
            self::PARAM_SPEED => $speed,
146
        ]));
147
    }
148
149 8
    public function withThreads(int $threads): self
150
    {
151 8
        return new self(array_merge($this->params, [
152 8
            self::PARAM_THREADS => $threads,
153
        ]));
154
    }
155
156 4
    public function withTune(string $tune): self
157
    {
158 4
        return new self(array_merge($this->params, [
159 4
            self::PARAM_TUNE => $tune,
160
        ]));
161
    }
162
163
    /**
164
     * If true, add streamable options for mp4 container (-movflags +faststart).
165
     */
166 2
    public function withStreamable(bool $streamable): self
167
    {
168 2
        return new self(array_merge($this->params, [
169 2
            self::PARAM_STREAMABLE => $streamable,
170
        ]));
171
    }
172
173
    /**
174
     * @param string $bitrate Bitrate with optional unit: 1000000, 1000k or 1M
175
     *
176
     * @throws InvalidArgumentException if bitrate value is invalid
177
     */
178 7
    public function withAudioBitrate(string $bitrate): self
179
    {
180 7
        $this->ensureValidBitRateUnit($bitrate);
181
182 6
        return new self(array_merge($this->params, [
183 6
            self::PARAM_AUDIO_BITRATE => $bitrate,
184
        ]));
185
    }
186
187
    /**
188
     * @param string $bitrate Bitrate or target bitrate with optional unit: 1000000, 1000k or 1M
189
     *
190
     * @throws InvalidArgumentException if bitrate value is invalid
191
     */
192 8
    public function withVideoBitrate(string $bitrate): self
193
    {
194 8
        $this->ensureValidBitRateUnit($bitrate);
195
196 7
        return new self(array_merge($this->params, [
197 7
            self::PARAM_VIDEO_BITRATE => $bitrate,
198
        ]));
199
    }
200
201
    /**
202
     * @param string $minBitrate Bitrate with optional unit: 1000000, 1000k or 1M
203
     *
204
     * @throws InvalidArgumentException if bitrate value is invalid
205
     */
206 7
    public function withVideoMinBitrate(string $minBitrate): self
207
    {
208 7
        $this->ensureValidBitRateUnit($minBitrate);
209
210 6
        return new self(array_merge($this->params, [
211 6
            self::PARAM_VIDEO_MIN_BITRATE => $minBitrate,
212
        ]));
213
    }
214
215
    /**
216
     * @param string $maxBitrate Bitrate with optional unit: 1000000, 1000k or 1M
217
     *
218
     * @throws InvalidArgumentException if bitrate value is invalid
219
     */
220 7
    public function withVideoMaxBitrate(string $maxBitrate): self
221
    {
222 7
        $this->ensureValidBitRateUnit($maxBitrate);
223
224 6
        return new self(array_merge($this->params, [
225 6
            self::PARAM_VIDEO_MAX_BITRATE => $maxBitrate,
226
        ]));
227
    }
228
229
    /**
230
     * Add with overwrite option (default).
231
     *
232
     * @see self::withNoOverwrite()
233
     */
234 11
    public function withOverwrite(): self
235
    {
236 11
        return new self(array_merge($this->params, [
237 11
            self::PARAM_OVERWRITE => true,
238
        ]));
239
    }
240
241
    /**
242
     * Add protection against output file overwriting.
243
     *
244
     * @see self::witoOverwrite()
245
     */
246 1
    public function withNoOverwrite(): self
247
    {
248 1
        return new self(array_merge($this->params, [
249 1
            self::PARAM_OVERWRITE => false,
250
        ]));
251
    }
252
253 2
    public function withQuality(string $quality): self
254
    {
255 2
        return new self(array_merge($this->params, [
256 2
            self::PARAM_QUALITY => $quality,
257
        ]));
258
    }
259
260 11
    public function withOutputFormat(string $outputFormat): self
261
    {
262 11
        return new self(array_merge($this->params, [
263 11
            self::PARAM_OUTPUT_FORMAT => $outputFormat,
264
        ]));
265
    }
266
267 17
    public function withSeekStart(SeekTime $seekTimeStart): self
268
    {
269 17
        return new self(array_merge($this->params, [
270 17
            self::PARAM_SEEK_START => $seekTimeStart,
271
        ]));
272
    }
273
274 7
    public function withSeekEnd(SeekTime $seekTimeEnd): self
275
    {
276 7
        return new self(array_merge($this->params, [
277 7
            self::PARAM_SEEK_END => $seekTimeEnd,
278
        ]));
279
    }
280
281 8
    public function withNoAudio(): self
282
    {
283 8
        return new self(array_merge($this->params, [
284 8
            self::PARAM_NOAUDIO => true,
285
        ]));
286
    }
287
288 20
    public function withVideoFrames(int $numberOfFrames): self
289
    {
290 20
        return new self(array_merge($this->params, [
291 20
            self::PARAM_VIDEO_FRAMES => $numberOfFrames,
292
        ]));
293
    }
294
295
    /**
296
     * Set the video encoder quality scale. (-qscale:v <int>, alias to -q:v <int>).
297
     *
298
     * @param int $qualityScale a number interpreted by the encoder, generally 1-5
299
     *
300
     * @see self::withQuality()
301
     */
302 14
    public function withVideoQualityScale(int $qualityScale): self
303
    {
304 14
        return new self(array_merge($this->params, [
305 14
            self::PARAM_VIDEO_QUALITY_SCALE => $qualityScale,
306
        ]));
307
    }
308
309
    /**
310
     * Setting auto-alt-ref and lag-in-frames >= 12 will turn on VP9's alt-ref frames, a VP9 feature that enhances quality.
311
     */
312 4
    public function withAutoAltRef(int $autoAltRef): self
313
    {
314 4
        return new self(array_merge($this->params, [
315 4
            self::PARAM_AUTO_ALT_REF => $autoAltRef,
316
        ]));
317
    }
318
319
    /**
320
     * Setting auto-alt-ref and lag-in-frames >= 12 will turn on VP9's alt-ref frames, a VP9 feature that enhances quality.
321
     */
322 4
    public function withLagInFrames(int $lagInFrames): self
323
    {
324 4
        return new self(array_merge($this->params, [
325 4
            self::PARAM_LAG_IN_FRAMES => $lagInFrames,
326
        ]));
327
    }
328
329
    /**
330
     * Set the pass number.
331
     */
332 5
    public function withPass(int $passNumber): self
333
    {
334 5
        return new self(array_merge($this->params, [
335 5
            self::PARAM_PASS => $passNumber,
336
        ]));
337
    }
338
339
    /**
340
     * Set the passlogfile (only makes sense for multipass conversions).
341
     */
342 5
    public function withPassLogFile(string $passLogFile): self
343
    {
344 5
        return new self(array_merge($this->params, [
345 5
            self::PARAM_PASSLOGFILE => $passLogFile,
346
        ]));
347
    }
348
349
    /**
350
     * @param bool|string|int|VideoFilterInterface|FFMpegCLIValueInterface $paramValue
351
     *
352
     * @throws InvalidArgumentException in case of unsupported builtin param
353
     *
354
     * @return self (For static analysis the trick is to return 'self' instead of interface)
355
     */
356 36
    public function withBuiltInParam(string $paramName, $paramValue): VideoConvertParamsInterface
357
    {
358 36
        return new self(array_merge($this->params, [
359 36
            $paramName => $paramValue,
360
        ]));
361
    }
362
363
    /**
364
     * @return self (For static analysis the trick is to return 'self' instead of interface)
365
     */
366 3
    public function withoutParam(string $paramName): VideoConvertParamsInterface
367
    {
368 3
        $ao = (new \ArrayObject($this->params));
369 3
        if ($ao->offsetExists($paramName)) {
370 3
            $ao->offsetUnset($paramName);
371
        }
372
373 3
        return new self($ao->getArrayCopy());
374
    }
375
376
    /**
377
     * Return the internal array holding params.
378
     *
379
     * @return array<string,bool|string|int|VideoFilterInterface|FFMpegCLIValueInterface>
380
     */
381 48
    public function toArray(): array
382
    {
383 48
        return $this->params;
384
    }
385
386 57
    public function isParamValid(string $paramName): bool
387
    {
388 57
        return in_array($paramName, self::BUILTIN_PARAMS, true);
389
    }
390
391
    /**
392
     * Return a param, throw an exception if the param has not been defined yet or
393
     * use $default if it was set.
394
     *
395
     * @param mixed $default Will return default value instead of throwing exception
396
     *
397
     * @return bool|string|int|VideoFilterInterface|FFMpegCLIValueInterface|null
398
     *
399
     * @throws UnsetParamException
400
     */
401 13
    public function getParam(string $paramName, $default = null)
402
    {
403 13
        if (!$this->hasParam($paramName)) {
404 2
            if ($default !== null) {
405 1
                return $default;
406
            }
407
408 1
            throw new UnsetParamException(sprintf(
409 1
                'Cannot get param \'%s\', it has not been set',
410
                $paramName
411
            ));
412
        }
413
414 11
        return $this->params[$paramName];
415
    }
416
417 51
    public function hasParam(string $paramName): bool
418
    {
419 51
        return array_key_exists($paramName, $this->params);
420
    }
421
422
    /**
423
     * @return VideoConvertParams
424
     */
425 1
    public function withConvertParams(VideoConvertParamsInterface $extraParams): VideoConvertParamsInterface
426
    {
427 1
        return new self(
428 1
            array_merge($this->toArray(), $extraParams->toArray())
429
        );
430
    }
431
432
    /**
433
     * Ensure that all params are supported.
434
     *
435
     * @param array<string, bool|string|int|VideoFilterInterface|FFMpegCLIValueInterface> $params
436
     *
437
     * @throws InvalidParamException in case of unsupported option
438
     */
439 62
    private function ensureSupportedParams(array $params): void
440
    {
441 62
        foreach ($params as $paramName => $paramValue) {
442 57
            if (!$this->isParamValid($paramName)) {
443 1
                throw new InvalidParamException(
444 1
                    sprintf('Unsupported built-in param "%s" given.', $paramName)
445
                );
446
            }
447
        }
448 61
    }
449
}
450