Completed
Push — master ( 26f0a5...60c571 )
by Sébastien
03:08
created

VideoTranscodeParams   A

Complexity

Total Complexity 31

Size/Duplication

Total Lines 302
Duplicated Lines 0 %

Test Coverage

Coverage 96.47%

Importance

Changes 0
Metric Value
wmc 31
dl 0
loc 302
ccs 82
cts 85
cp 0.9647
rs 9.92
c 0
b 0
f 0

26 Methods

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