Completed
Push — master ( ab42e2...d99315 )
by Sébastien
02:49
created

VideoTranscodeParams::withFrameParallel()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
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;
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
    ];
87
88
    /** @var array<string, bool|string|int|null> */
89
    protected $options = [];
90
91
    /**
92
     * @param array<string, bool|string|int|null> $options
93
     */
94 2
    public function __construct($options = [])
95
    {
96 2
        $this->checkOptions($options);
97 2
        $this->options = $options;
98 2
    }
99
100 2
    protected function checkOptions(array $options): void
101
    {
102 2
        foreach (array_keys($options) as $optionName) {
103 2
            if (!$this->isOptionValid($optionName)) {
104
                throw new InvalidArgumentException(
105 2
                    sprintf('Unsupported option "%s" given.', $optionName)
106
                );
107
            }
108
        }
109 2
    }
110
111 2
    public function isOptionValid(string $optionName): bool
112
    {
113 2
        return array_key_exists($optionName, self::SUPPORTED_OPTIONS);
114
    }
115
116 2
    public function getOptions(): array
117
    {
118 2
        return $this->options;
119
    }
120
121
    /**
122
     * @param string $option
123
     * @param mixed  $default if options does not exists set this one
124
     *
125
     * @return null|mixed
126
     */
127
    public function getOption(string $option, $default = null)
128
    {
129
        return $this->options[$option] ?? $default;
130
    }
131
132 1
    public function withVideoCodec(string $videoCodec): self
133
    {
134 1
        return new self(array_merge($this->options, [
135 1
            self::OPTION_VIDEO_CODEC => $videoCodec,
136
        ]));
137
    }
138
139 1
    public function withAudioCodec(string $audioCodec): self
140
    {
141 1
        return new self(array_merge($this->options, [
142 1
            self::OPTION_AUDIO_CODEC => $audioCodec,
143
        ]));
144
    }
145
146
    /**
147
     * Tiling splits the video frame into multiple columns,
148
     * which slightly reduces quality but speeds up encoding performance.
149
     * Tiles must be at least 256 pixels wide, so there is a limit to how many tiles can be used.
150
     * Depending upon the number of tiles and the resolution of the output frame, more CPU threads may be useful.
151
     *
152
     * Generally speaking, there is limited value to multiple threads when the output frame size is very small.
153
     */
154 1
    public function withTileColumns(int $tileColumns): self
155
    {
156 1
        return new self(array_merge($this->options, [
157 1
            self::OPTION_TILE_COLUMNS => $tileColumns,
158
        ]));
159
    }
160
161
    /**
162
     * VP9 ?
163
     * It is recommended to allow up to 240 frames of video between keyframes (8 seconds for 30fps content).
164
     * Keyframes are video frames which are self-sufficient; they don't rely upon any other frames to render
165
     * but they tend to be larger than other frame types.
166
     * For web and mobile playback, generous spacing between keyframes allows the encoder to choose the best
167
     * placement of keyframes to maximize quality.
168
     */
169 1
    public function withKeyframeSpacing(int $keyframeSpacing): self
170
    {
171 1
        return new self(array_merge($this->options, [
172 1
            self::OPTION_KEYFRAME_SPACING => $keyframeSpacing,
173
        ]));
174
    }
175
176 1
    public function withFrameParallel(int $frameParallel): self
177
    {
178 1
        return new self(array_merge($this->options, [
179 1
            self::OPTION_FRAME_PARALLEL => $frameParallel,
180
        ]));
181
    }
182
183 1
    public function withCrf(int $crf): self
184
    {
185 1
        return new self(array_merge($this->options, [
186 1
            self::OPTION_CRF => $crf,
187
        ]));
188
    }
189
190 1
    public function withPixFmt(string $pixFmt): self
191
    {
192 1
        return new self(array_merge($this->options, [
193 1
            self::OPTION_PIX_FMT => $pixFmt,
194
        ]));
195
    }
196
197 1
    public function withPreset(string $preset): self
198
    {
199 1
        return new self(array_merge($this->options, [
200 1
            self::OPTION_PRESET => $preset,
201
        ]));
202
    }
203
204 1
    public function withSpeed(int $speed): self
205
    {
206 1
        return new self(array_merge($this->options, [
207 1
            self::OPTION_SPEED => $speed,
208
        ]));
209
    }
210
211 2
    public function withThreads(int $threads): self
212
    {
213 2
        return new self(array_merge($this->options, [
214 2
            self::OPTION_THREADS => $threads,
215
        ]));
216
    }
217
218
    public function withTune(string $tune): self
219
    {
220
        return new self(array_merge($this->options, [
221
            self::OPTION_TUNE => $tune,
222
        ]));
223
    }
224
225 1
    public function withStreamable(bool $streamable): self
226
    {
227 1
        return new self(array_merge($this->options, [
228 1
            self::OPTION_STREAMABLE => $streamable,
229
        ]));
230
    }
231
232 1
    public function withAudioBitrate(string $bitrate): self
233
    {
234 1
        return new self(array_merge($this->options, [
235 1
            self::OPTION_AUDIO_BITRATE => $bitrate,
236
        ]));
237
    }
238
239 1
    public function withVideoBitrate(string $bitrate): self
240
    {
241 1
        return new self(array_merge($this->options, [
242 1
            self::OPTION_VIDEO_BITRATE => $bitrate,
243
        ]));
244
    }
245
246 1
    public function withVideoMinBitrate(string $minBitrate): self
247
    {
248 1
        return new self(array_merge($this->options, [
249 1
            self::OPTION_VIDEO_MIN_BITRATE => $minBitrate,
250
        ]));
251
    }
252
253 1
    public function withVideoMaxBitrate(string $maxBitrate): self
254
    {
255 1
        return new self(array_merge($this->options, [
256 1
            self::OPTION_VIDEO_MAX_BITRATE => $maxBitrate,
257
        ]));
258
    }
259
260 1
    public function withQuality(string $quality): self
261
    {
262 1
        return new self(array_merge($this->options, [
263 1
            self::OPTION_QUALITY => $quality,
264
        ]));
265
    }
266
267 1
    public function withOutputFormat(string $outputFormat): self
268
    {
269 1
        return new self(array_merge($this->options, [
270 1
            self::OPTION_OUTPUT_FORMAT => $outputFormat,
271
        ]));
272
    }
273
274
    /**
275
     * @return string[]
276
     */
277
    public function getFFMpegArguments(): array
278
    {
279
        $args = [];
280
        foreach ($this->options as $key => $value) {
281
            $ffmpeg_pattern = self::SUPPORTED_OPTIONS[$key]['ffmpeg_pattern'];
282
            if (is_bool($value)) {
283
                $args[] = $ffmpeg_pattern;
284
            } else {
285
                $args[] = sprintf($ffmpeg_pattern, $value);
286
            }
287
        }
288
289
        return $args;
290
    }
291
}
292