Failed Conditions
Push — master ( df4f19...50f2bb )
by Sébastien
02:46 queued 13s
created

ConvertParams::withPixFmt()   A

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
eloc 2
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Soluble\MediaTools\Video;
6
7
use Soluble\MediaTools\Exception\InvalidArgumentException;
8
use Soluble\MediaTools\Video\Converter\ParamsInterface;
9
use Soluble\MediaTools\Video\Filter\VideoFilterInterface;
10
11
class ConvertParams implements ParamsInterface
12
{
13
    public const SUPPORTED_OPTIONS = [
14
        self::PARAM_OUTPUT_FORMAT => [
15
            'ffmpeg_pattern' => '-f %s',
16
        ],
17
18
        self::PARAM_VIDEO_CODEC => [
19
            'ffmpeg_pattern' => '-vcodec %s',
20
        ],
21
        self::PARAM_VIDEO_BITRATE => [
22
            'ffmpeg_pattern' => '-b:v %s',
23
        ],
24
        self::PARAM_VIDEO_MIN_BITRATE => [
25
            'ffmpeg_pattern' => '-minrate %s',
26
        ],
27
        self::PARAM_VIDEO_MAX_BITRATE => [
28
            'ffmpeg_pattern' => '-maxrate %s',
29
        ],
30
31
        self::PARAM_AUDIO_CODEC => [
32
            'ffmpeg_pattern' => '-acodec %s',
33
        ],
34
        self::PARAM_AUDIO_BITRATE => [
35
            'ffmpeg_pattern' => '-b:a %s',
36
        ],
37
        self::PARAM_PIX_FMT => [
38
            'ffmpeg_pattern' => '-pix_fmt %s',
39
        ],
40
        self::PARAM_PRESET => [
41
            'ffmpeg_pattern' => '-preset %s',
42
        ],
43
        self::PARAM_SPEED => [
44
            'ffmpeg_pattern' => '-speed %s',
45
        ],
46
        self::PARAM_THREADS => [
47
            'ffmpeg_pattern' => '-threads %s',
48
        ],
49
50
        self::PARAM_KEYFRAME_SPACING => [
51
            'ffmpeg_pattern' => '-g %s',
52
        ],
53
        self::PARAM_QUALITY => [
54
            'ffmpeg_pattern' => '-quality %s',
55
        ],
56
        self::PARAM_CRF => [
57
            'ffmpeg_pattern' => '-crf %s',
58
        ],
59
        self::PARAM_STREAMABLE => [
60
            'ffmpeg_pattern' => '-movflags +faststart',
61
        ],
62
63
        self::PARAM_FRAME_PARALLEL => [
64
            'ffmpeg_pattern' => '-frame-parallel %s',
65
        ],
66
        self::PARAM_TILE_COLUMNS => [
67
            'ffmpeg_pattern' => '-tile-columns %s',
68
        ],
69
        self::PARAM_TUNE => [
70
            'ffmpeg_pattern' => '-tune %s',
71
        ],
72
        self::PARAM_VIDEO_FILTER => [
73
            'ffmpeg_pattern' => '-vf %s',
74
        ],
75
    ];
76
77
    /** @var array<string, bool|string|int|VideoFilterInterface> */
78
    protected $options = [];
79
80
    /**
81
     * @param array<string, bool|string|int|VideoFilterInterface> $options
82
     *
83
     * @throws InvalidArgumentException in case of unsupported option
84
     */
85 13
    public function __construct($options = [])
86
    {
87 13
        $this->ensureSupportedOptions($options);
88 12
        $this->options = $options;
89 12
    }
90
91 11
    public function isOptionValid(string $optionName): bool
92
    {
93 11
        return array_key_exists($optionName, self::SUPPORTED_OPTIONS);
94
    }
95
96 4
    public function getOptions(): array
97
    {
98 4
        return $this->options;
99
    }
100
101
    /**
102
     * @param string                                    $option
103
     * @param bool|string|int|VideoFilterInterface|null $default if options does not exists set this one
104
     *
105
     * @return bool|string|int|VideoFilterInterface|null
106
     */
107 2
    public function getOption(string $option, $default = null)
108
    {
109 2
        return $this->options[$option] ?? $default;
110
    }
111
112 5
    public function hasOption(string $option): bool
113
    {
114 5
        return array_key_exists($option, $this->options);
115
    }
116
117 5
    public function withVideoCodec(string $videoCodec): self
118
    {
119 5
        return new self(array_merge($this->options, [
120 5
            self::PARAM_VIDEO_CODEC => $videoCodec,
121
        ]));
122
    }
123
124 2
    public function withVideoFilter(VideoFilterInterface $videoFilter): self
125
    {
126 2
        return new self(array_merge($this->options, [
127 2
            self::PARAM_VIDEO_FILTER => $videoFilter,
128
        ]));
129
    }
130
131 2
    public function withAudioCodec(string $audioCodec): self
132
    {
133 2
        return new self(array_merge($this->options, [
134 2
            self::PARAM_AUDIO_CODEC => $audioCodec,
135
        ]));
136
    }
137
138
    /**
139
     * Tiling splits the video frame into multiple columns,
140
     * which slightly reduces quality but speeds up encoding performance.
141
     * Tiles must be at least 256 pixels wide, so there is a limit to how many tiles can be used.
142
     * Depending upon the number of tiles and the resolution of the output frame, more CPU threads may be useful.
143
     *
144
     * Generally speaking, there is limited value to multiple threads when the output frame size is very small.
145
     */
146 4
    public function withTileColumns(int $tileColumns): self
147
    {
148 4
        return new self(array_merge($this->options, [
149 4
            self::PARAM_TILE_COLUMNS => $tileColumns,
150
        ]));
151
    }
152
153
    /**
154
     * VP9 ?
155
     * It is recommended to allow up to 240 frames of video between keyframes (8 seconds for 30fps content).
156
     * Keyframes are video frames which are self-sufficient; they don't rely upon any other frames to render
157
     * but they tend to be larger than other frame types.
158
     * For web and mobile playback, generous spacing between keyframes allows the encoder to choose the best
159
     * placement of keyframes to maximize quality.
160
     */
161 2
    public function withKeyframeSpacing(int $keyframeSpacing): self
162
    {
163 2
        return new self(array_merge($this->options, [
164 2
            self::PARAM_KEYFRAME_SPACING => $keyframeSpacing,
165
        ]));
166
    }
167
168 2
    public function withFrameParallel(int $frameParallel): self
169
    {
170 2
        return new self(array_merge($this->options, [
171 2
            self::PARAM_FRAME_PARALLEL => $frameParallel,
172
        ]));
173
    }
174
175 2
    public function withCrf(int $crf): self
176
    {
177 2
        return new self(array_merge($this->options, [
178 2
            self::PARAM_CRF => $crf,
179
        ]));
180
    }
181
182 2
    public function withPixFmt(string $pixFmt): self
183
    {
184 2
        return new self(array_merge($this->options, [
185 2
            self::PARAM_PIX_FMT => $pixFmt,
186
        ]));
187
    }
188
189 2
    public function withPreset(string $preset): self
190
    {
191 2
        return new self(array_merge($this->options, [
192 2
            self::PARAM_PRESET => $preset,
193
        ]));
194
    }
195
196 2
    public function withSpeed(int $speed): self
197
    {
198 2
        return new self(array_merge($this->options, [
199 2
            self::PARAM_SPEED => $speed,
200
        ]));
201
    }
202
203 3
    public function withThreads(int $threads): self
204
    {
205 3
        return new self(array_merge($this->options, [
206 3
            self::PARAM_THREADS => $threads,
207
        ]));
208
    }
209
210 3
    public function withTune(string $tune): self
211
    {
212 3
        return new self(array_merge($this->options, [
213 3
            self::PARAM_TUNE => $tune,
214
        ]));
215
    }
216
217
    /**
218
     * If true, add streamable options for mp4 container (-movflags +faststart).
219
     */
220 1
    public function withStreamable(bool $streamable): self
221
    {
222 1
        return new self(array_merge($this->options, [
223 1
            self::PARAM_STREAMABLE => $streamable,
224
        ]));
225
    }
226
227
    /**
228
     * @param string $bitrate Bitrate with optional unit: 1000000, 1000k or 1M
229
     *
230
     * @throws InvalidArgumentException if bitrate value is invalid
231
     */
232 3
    public function withAudioBitrate(string $bitrate): self
233
    {
234 3
        $this->ensureValidBitRateUnit($bitrate);
235
236 2
        return new self(array_merge($this->options, [
237 2
            self::PARAM_AUDIO_BITRATE => $bitrate,
238
        ]));
239
    }
240
241
    /**
242
     * @param string $bitrate Bitrate or target bitrate with optional unit: 1000000, 1000k or 1M
243
     *
244
     * @throws InvalidArgumentException if bitrate value is invalid
245
     */
246 3
    public function withVideoBitrate(string $bitrate): self
247
    {
248 3
        $this->ensureValidBitRateUnit($bitrate);
249
250 2
        return new self(array_merge($this->options, [
251 2
            self::PARAM_VIDEO_BITRATE => $bitrate,
252
        ]));
253
    }
254
255
    /**
256
     * @param string $minBitrate Bitrate with optional unit: 1000000, 1000k or 1M
257
     *
258
     * @throws InvalidArgumentException if bitrate value is invalid
259
     */
260 3
    public function withVideoMinBitrate(string $minBitrate): self
261
    {
262 3
        $this->ensureValidBitRateUnit($minBitrate);
263
264 2
        return new self(array_merge($this->options, [
265 2
            self::PARAM_VIDEO_MIN_BITRATE => $minBitrate,
266
        ]));
267
    }
268
269
    /**
270
     * @param string $maxBitrate Bitrate with optional unit: 1000000, 1000k or 1M
271
     *
272
     * @throws InvalidArgumentException if bitrate value is invalid
273
     */
274 3
    public function withVideoMaxBitrate(string $maxBitrate): self
275
    {
276 3
        $this->ensureValidBitRateUnit($maxBitrate);
277
278 2
        return new self(array_merge($this->options, [
279 2
            self::PARAM_VIDEO_MAX_BITRATE => $maxBitrate,
280
        ]));
281
    }
282
283 1
    public function withQuality(string $quality): self
284
    {
285 1
        return new self(array_merge($this->options, [
286 1
            self::PARAM_QUALITY => $quality,
287
        ]));
288
    }
289
290 2
    public function withOutputFormat(string $outputFormat): self
291
    {
292 2
        return new self(array_merge($this->options, [
293 2
            self::PARAM_OUTPUT_FORMAT => $outputFormat,
294
        ]));
295
    }
296
297
    /**
298
     * @return array<string, string>
299
     */
300 6
    public function getFFMpegArguments(): array
301
    {
302 6
        $args = [];
303 6
        foreach ($this->options as $key => $value) {
304 6
            $ffmpeg_pattern = self::SUPPORTED_OPTIONS[$key]['ffmpeg_pattern'];
305 6
            if (is_bool($value)) {
306 1
                $args[$key] = $ffmpeg_pattern;
307 6
            } elseif ($value instanceof VideoFilterInterface) {
308 2
                $args[$key] = sprintf($ffmpeg_pattern, $value->getFFmpegCLIValue());
309
            } else {
310 6
                $args[$key] = sprintf($ffmpeg_pattern, $value);
311
            }
312
        }
313
314 6
        return $args;
315
    }
316
317
    /**
318
     * Ensure that a bitrate is valid (optional unit: k or M ).
319
     *
320
     * @throws InvalidArgumentException
321
     */
322 3
    protected function ensureValidBitRateUnit(string $bitrate): void
323
    {
324 3
        if (preg_match('/^\d+(k|M)?$/i', $bitrate) !== 1) {
325 1
            throw new InvalidArgumentException(sprintf('"%s"', $bitrate));
326
        }
327 2
    }
328
329
    /**
330
     * Ensure that all options are supported.
331
     *
332
     * @throws InvalidArgumentException in case of unsupported option
333
     */
334 13
    protected function ensureSupportedOptions(array $options): void
335
    {
336 13
        foreach (array_keys($options) as $optionName) {
337 11
            if (!$this->isOptionValid($optionName)) {
338 1
                throw new InvalidArgumentException(
339 11
                    sprintf('Unsupported option "%s" given.', $optionName)
340
                );
341
            }
342
        }
343 12
    }
344
}
345