Passed
Push — master ( 1e8bfe...85c284 )
by Sébastien
03:02
created

VideoConversionParams   A

Complexity

Total Complexity 31

Size/Duplication

Total Lines 319
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 31
eloc 104
dl 0
loc 319
ccs 88
cts 88
cp 1
rs 9.92
c 0
b 0
f 0

26 Methods

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