Completed
Push — master ( d99315...945656 )
by Sébastien
03:35
created

VideoTranscodeParams   A

Complexity

Total Complexity 28

Size/Duplication

Total Lines 284
Duplicated Lines 0 %

Test Coverage

Coverage 88.46%

Importance

Changes 0
Metric Value
wmc 28
dl 0
loc 284
ccs 69
cts 78
cp 0.8846
rs 10
c 0
b 0
f 0

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