Failed Conditions
Push — master ( f1c7bf...dcde3e )
by Sébastien
02:05
created

FFMpegAdapter::getArgsWithExplodedValues()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
eloc 8
dl 0
loc 13
ccs 0
cts 0
cp 0
rs 10
c 0
b 0
f 0
cc 3
nc 3
nop 1
crap 12
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Soluble\MediaTools\Video\Adapter;
6
7
use Soluble\MediaTools\Common\Exception\InvalidArgumentException;
8
use Soluble\MediaTools\Common\Exception\UnsupportedParamException;
9
use Soluble\MediaTools\Common\Exception\UnsupportedParamValueException;
10
use Soluble\MediaTools\Common\IO\UnescapedFileInterface;
11
use Soluble\MediaTools\Video\Adapter\Validator\FFMpegParamValidator;
12
use Soluble\MediaTools\Video\Config\FFMpegConfigInterface;
13
use Soluble\MediaTools\Video\Exception\ParamValidationException;
14
use Soluble\MediaTools\Video\Exception\UnexpectedValueException;
15
use Soluble\MediaTools\Video\VideoConvertParamsInterface;
16
17
class FFMpegAdapter implements ConverterAdapterInterface
18
{
19
    /** @var FFMpegConfigInterface */
20
    protected $ffmpegConfig;
21
22 39
    public function __construct(FFMpegConfigInterface $ffmpegConfig)
23
    {
24 39
        $this->ffmpegConfig = $ffmpegConfig;
25 39
    }
26
27
    /**
28
     * @return array<string, array<string, string>>
29
     */
30 38
    public function getParamsOptions(): array
31
    {
32
        return [
33 38
            VideoConvertParamsInterface::PARAM_OUTPUT_FORMAT => [
34
                'pattern' => '-f %s',
35
            ],
36 38
            VideoConvertParamsInterface::PARAM_VIDEO_CODEC => [
37
                'pattern' => '-c:v %s',
38
            ],
39 38
            VideoConvertParamsInterface::PARAM_VIDEO_BITRATE => [
40
                'pattern' => '-b:v %s',
41
            ],
42 38
            VideoConvertParamsInterface::PARAM_VIDEO_MIN_BITRATE => [
43
                'pattern' => '-minrate %s',
44
            ],
45 38
            VideoConvertParamsInterface::PARAM_VIDEO_MAX_BITRATE => [
46
                'pattern' => '-maxrate %s',
47
            ],
48 38
            VideoConvertParamsInterface::PARAM_AUDIO_CODEC => [
49
                'pattern' => '-c:a %s',
50
            ],
51 38
            VideoConvertParamsInterface::PARAM_AUDIO_BITRATE => [
52
                'pattern' => '-b:a %s',
53
            ],
54 38
            VideoConvertParamsInterface::PARAM_PIX_FMT => [
55
                'pattern' => '-pix_fmt %s',
56
            ],
57 38
            VideoConvertParamsInterface::PARAM_PRESET => [
58
                'pattern' => '-preset %s',
59
            ],
60 38
            VideoConvertParamsInterface::PARAM_SPEED => [
61
                'pattern' => '-speed %d',
62
            ],
63 38
            VideoConvertParamsInterface::PARAM_THREADS => [
64
                'pattern' => '-threads %d',
65
            ],
66 38
            VideoConvertParamsInterface::PARAM_KEYFRAME_SPACING => [
67
                'pattern' => '-g %d',
68
            ],
69 38
            VideoConvertParamsInterface::PARAM_QUALITY => [
70
                'pattern' => '-quality %s',
71
            ],
72 38
            VideoConvertParamsInterface::PARAM_VIDEO_QUALITY_SCALE => [
73
                'pattern' => '-qscale:v %d',
74
            ],
75 38
            VideoConvertParamsInterface::PARAM_CRF => [
76
                'pattern' => '-crf %d',
77
            ],
78 38
            VideoConvertParamsInterface::PARAM_STREAMABLE => [
79
                'pattern' => '-movflags +faststart',
80
            ],
81 38
            VideoConvertParamsInterface::PARAM_FRAME_PARALLEL => [
82
                'pattern' => '-frame-parallel %s',
83
            ],
84 38
            VideoConvertParamsInterface::PARAM_TILE_COLUMNS => [
85
                'pattern' => '-tile-columns %s',
86
            ],
87 38
            VideoConvertParamsInterface::PARAM_TUNE => [
88
                'pattern' => '-tune %s',
89
            ],
90 38
            VideoConvertParamsInterface::PARAM_VIDEO_FILTER => [
91
                'pattern' => '-filter:v %s',
92
            ],
93 38
            VideoConvertParamsInterface::PARAM_OVERWRITE => [
94
                'pattern' => '-y',
95
            ],
96 38
            VideoConvertParamsInterface::PARAM_VIDEO_FRAMES => [
97
                'pattern' => '-frames:v %d',
98
            ],
99 38
            VideoConvertParamsInterface::PARAM_NOAUDIO => [
100
                'pattern' => '-an',
101
            ],
102 38
            VideoConvertParamsInterface::PARAM_SEEK_START => [
103
                'pattern' => '-ss %s',
104
            ],
105 38
            VideoConvertParamsInterface::PARAM_SEEK_END => [
106
                'pattern' => '-to %s',
107
            ],
108 38
            VideoConvertParamsInterface::PARAM_PASSLOGFILE => [
109
                'pattern' => '-passlogfile %s',
110
            ],
111 38
            VideoConvertParamsInterface::PARAM_PASS => [
112
                'pattern' => '-pass %s',
113
            ],
114 38
            VideoConvertParamsInterface::PARAM_AUTO_ALT_REF => [
115
                'pattern' => '-auto-alt-ref %s',
116
            ],
117 38
            VideoConvertParamsInterface::PARAM_LAG_IN_FRAMES => [
118
                'pattern' => '-lag-in-frames %s',
119
            ],
120
        ];
121
    }
122
123
    /**
124
     * Return an array version of params suitable for ffmpeg cli.
125
     *
126
     * @param bool $validateParams whether to run ffmpeg validation process validation
127
     *
128
     * @return array<string, string>
129
     *
130
     * @throws UnsupportedParamException
131
     * @throws UnsupportedParamValueException
132
     * @throws ParamValidationException
133
     */
134 38
    public function getMappedConversionParams(VideoConvertParamsInterface $conversionParams, bool $validateParams = true): array
135
    {
136 38
        $args             = [];
137 38
        $supportedOptions = $this->getParamsOptions();
138
139
        // Add default overwrite option if not set
140 38
        $overwriteParam = VideoConvertParamsInterface::PARAM_OVERWRITE;
141 38
        if (!$conversionParams->hasParam($overwriteParam)) {
142 34
            $conversionParams = $conversionParams->withBuiltInParam(
143 34
                $overwriteParam,
144 34
                true
145
            );
146
        }
147
148 38
        foreach ($conversionParams->toArray() as $paramName => $value) {
149 38
            if (!array_key_exists($paramName, $supportedOptions)) {
150
                throw new UnsupportedParamException(
151
                    sprintf(
152
                        'FFMpegAdapter does not support param \'%s\'',
153
                        $paramName
154
                    )
155
                );
156
            }
157 38
            $pattern = $supportedOptions[$paramName]['pattern'];
158 38
            if (is_bool($value)) {
159 37
                $args[$paramName] = $value ? $pattern : '';
160 35
            } elseif ($value instanceof FFMpegCLIValueInterface) {
161
                // Will test also FFMpegVideoFilterInterface
162 23
                $cliValue = $value->getFFmpegCLIValue();
163 23
                if ($cliValue !== null) {
164 23
                    $args[$paramName] = sprintf($pattern, $cliValue);
165
                }
166 32
            } elseif (is_string($value) || is_int($value)) {
167 31
                $args[$paramName] = sprintf($pattern, $value);
168
            } else {
169 1
                throw new UnsupportedParamValueException(
170 1
                    sprintf(
171 1
                        'Param \'%s\' has an unsupported type: \'%s\'',
172 1
                        $paramName,
173 38
                        is_object($value) ? get_class($value) : gettype($value)
174
                    )
175
                );
176
            }
177
        }
178
179
        // Validation
180 37
        if ($validateParams) {
181 37
            (new FFMpegParamValidator($conversionParams))->validate();
182
        }
183
184 36
        return $args;
185
    }
186
187
    /**
188
     * @param array<string,string>               $arguments        args that will be added
189
     * @param null|string                        $inputFile
190
     * @param null|string|UnescapedFileInterface $outputFile
191
     * @param array<string,string>               $prependArguments args that must be added at the beginning of the command
192
     *
193
     * @throws InvalidArgumentException
194
     */
195 31
    public function getCliCommand(array $arguments, ?string $inputFile, $outputFile = null, array $prependArguments = []): array
196
    {
197 31
        $inputArg = ($inputFile !== null && $inputFile !== '')
0 ignored issues
show
Unused Code introduced by
The assignment to $inputArg is dead and can be removed.
Loading history...
198 31
                        ? sprintf('-i %s', escapeshellarg($inputFile))
199 31
                        : '';
200
201 31
        $outputArg = '';
202 31
        if ($outputFile instanceof UnescapedFileInterface) {
203 5
            $outputArg = $outputFile->getFile();
204 28
        } elseif (is_string($outputFile)) {
205 26
            //$outputArg = sprintf('%s', escapeshellarg($outputFile));
206 2
            $outputArg = $outputFile;
207 2
        } elseif ($outputFile !== null) {
0 ignored issues
show
introduced by
The condition $outputFile !== null is always false.
Loading history...
208 2
            throw new InvalidArgumentException(sprintf(
209 2
                'Output file must be either a non empty string, null or PlatformNullFile (type %s)',
210
                gettype($outputFile)
211
            ));
212
        }
213 29
214 29
        $ffmpegCmd = array_merge(
215 29
            [
216 29
                $this->ffmpegConfig->getBinary(),
217 29
            ],
218 29
            $this->getArgsWithExplodedValues($prependArguments),
219 29
            ['-i', $inputFile],
220 29
            $this->getArgsWithExplodedValues($arguments),
221 29
            [$outputArg]
222 29
        );
223
224
        if (count($ffmpegCmd) < 2) {
225
            throw new UnexpectedValueException(
226 29
                'Cannot generate ffmpeg cli command'
227
            );
228
        }
229
230
        return $ffmpegCmd;
231
    }
232 29
233
    /**
234
     * As we rely on symfony process unescaping, we
235 24
     * need to explode options name and values... i.e
236
     * ['-tune animation'] will become ['-tune', 'animation'].
237 24
     *
238
     * @param array<string, string> $args
239
     *
240
     * @return string[]
241
     */
242
    private function getArgsWithExplodedValues(array $args): array
243
    {
244
        $exploded = [];
245
        foreach ($args as $key => $value) {
246
            $elems      = explode(' ', $value);
247
            $exploded[] = (string) array_shift($elems);
248
            if (count($elems) <= 0) {
249
                continue;
250
            }
251
            $exploded[] = implode(' ', $elems);
252
        }
253
254
        return $exploded;
255
    }
256
257
    public function getDefaultThreads(): ?int
258
    {
259
        return $this->ffmpegConfig->getThreads();
260
    }
261
}
262