Failed Conditions
Push — master ( 12954f...f40466 )
by Sébastien
02:10
created

VideoConverter::convert()   B

Complexity

Conditions 10
Paths 22

Size

Total Lines 34
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 25
CRAP Score 10.1228

Importance

Changes 0
Metric Value
eloc 29
dl 0
loc 34
ccs 25
cts 28
cp 0.8929
rs 7.6666
c 0
b 0
f 0
cc 10
nc 22
nop 5
crap 10.1228

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Soluble\MediaTools\Video;
6
7
use Psr\Log\LoggerInterface;
8
use Psr\Log\LogLevel;
9
use Psr\Log\NullLogger;
10
use Soluble\MediaTools\Common\Assert\PathAssertionsTrait;
11
use Soluble\MediaTools\Common\Exception as CommonException;
12
use Soluble\MediaTools\Common\IO\UnescapedFileInterface;
13
use Soluble\MediaTools\Common\Process\ProcessFactory;
14
use Soluble\MediaTools\Common\Process\ProcessParamsInterface;
15
use Soluble\MediaTools\Video\Config\FFMpegConfigInterface;
16
use Soluble\MediaTools\Video\Exception\ConverterExceptionInterface;
17
use Soluble\MediaTools\Video\Exception\ConverterProcessExceptionInterface;
18
use Soluble\MediaTools\Video\Exception\InvalidArgumentException;
19
use Soluble\MediaTools\Video\Exception\InvalidParamReaderException;
20
use Soluble\MediaTools\Video\Exception\MissingInputFileReaderException;
21
use Soluble\MediaTools\Video\Exception\ProcessFailedException;
22
use Soluble\MediaTools\Video\Exception\ProcessSignaledException;
23
use Soluble\MediaTools\Video\Exception\ProcessTimedOutException;
24
use Soluble\MediaTools\Video\Exception\RuntimeReaderException;
25
use Symfony\Component\Process\Exception as SPException;
26
use Symfony\Component\Process\Process;
27
28
class VideoConverter implements VideoConverterInterface
29
{
30
    use PathAssertionsTrait;
31
32
    /** @var FFMpegConfigInterface */
33
    protected $ffmpegConfig;
34
35
    /** @var LoggerInterface|NullLogger */
36
    protected $logger;
37
38 16
    public function __construct(FFMpegConfigInterface $ffmpegConfig, ?LoggerInterface $logger = null)
39
    {
40 16
        $this->ffmpegConfig  = $ffmpegConfig;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned correctly; expected 1 space but found 2 spaces

This check looks for improperly formatted assignments.

Every assignment must have exactly one space before and one space after the equals operator.

To illustrate:

$a = "a";
$ab = "ab";
$abc = "abc";

will have no issues, while

$a   = "a";
$ab  = "ab";
$abc = "abc";

will report issues in lines 1 and 2.

Loading history...
41
42 16
        $this->logger = $logger ?? new NullLogger();
43 16
    }
44
45
    /**
46
     * Return ready-to-run symfony process object that you can use
47
     * to `run()` or `start()` programmatically. Useful if you want to make
48
     * things async...
49
     *
50
     * @param null|string|UnescapedFileInterface $outputFile
51
     *
52
     * @see https://symfony.com/doc/current/components/process.html
53
     *
54
     * @throws CommonException\UnsupportedParamException
55
     * @throws CommonException\UnsupportedParamValueException
56
     * @throws InvalidArgumentException
57
     */
58 14
    public function getSymfonyProcess(string $inputFile, $outputFile, VideoConvertParamsInterface $convertParams, ?ProcessParamsInterface $processParams = null): Process
59
    {
60 14
        $adapter = $this->ffmpegConfig->getAdapter();
61
62 14
        if (!$convertParams->hasParam(VideoConvertParamsInterface::PARAM_THREADS)
63 14
            && $adapter->getDefaultThreads() !== null) {
64 1
            $convertParams = $convertParams->withBuiltInParam(
65 1
                VideoConvertParamsInterface::PARAM_THREADS,
66 1
                $adapter->getDefaultThreads()
67
            );
68
        }
69
70 14
        $arguments = $adapter->getMappedConversionParams($convertParams);
71
72
        try {
73 14
            $ffmpegCmd = $adapter->getCliCommand($arguments, $inputFile, $outputFile);
74 1
        } catch (CommonException\InvalidArgumentException $e) {
75 1
            throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
76
        }
77
78 13
        $pp = $processParams ?? $this->ffmpegConfig->getProcessParams();
79
80 13
        return (new ProcessFactory($ffmpegCmd, $pp))();
81
    }
82
83
    /**
84
     * Run a conversion, throw exception on error.
85
     *
86
     * @param null|string|UnescapedFileInterface $outputFile
87
     * @param callable|null                      $callback   A PHP callback to run whenever there is some
88
     *                                                       tmp available on STDOUT or STDERR
89
     *
90
     * @throws ConverterExceptionInterface        Base exception class for conversion exceptions
91
     * @throws ConverterProcessExceptionInterface Base exception class for process conversion exceptions
92
     * @throws MissingInputFileReaderException
93
     * @throws ProcessTimedOutException
94
     * @throws ProcessFailedException
95
     * @throws ProcessSignaledException
96
     * @throws InvalidParamReaderException
97
     * @throws RuntimeReaderException
98
     */
99 12
    public function convert(string $inputFile, $outputFile, VideoConvertParamsInterface $convertParams, ?callable $callback = null, ?ProcessParamsInterface $processParams = null): void
100
    {
101
        try {
102
            try {
103 12
                $this->ensureFileReadable($inputFile);
104 10
                $process = $this->getSymfonyProcess($inputFile, $outputFile, $convertParams, $processParams);
105 10
                $process->mustRun($callback);
106 9
            } catch (CommonException\FileNotFoundException | CommonException\FileNotReadableException $e) {
107 2
                throw new MissingInputFileReaderException($e->getMessage());
108 7
            } catch (CommonException\UnsupportedParamValueException | CommonException\UnsupportedParamException $e) {
109
                throw new InvalidParamReaderException($e->getMessage());
110 7
            } catch (SPException\ProcessTimedOutException $e) {
111 3
                throw new ProcessTimedOutException($e->getProcess(), $e);
112 4
            } catch (SPException\ProcessSignaledException $e) {
113
                throw new ProcessSignaledException($e->getProcess(), $e);
114 4
            } catch (SPException\ProcessFailedException $e) {
115 4
                throw new ProcessFailedException($e->getProcess(), $e);
116
            } catch (SPException\RuntimeException $e) {
117 3
                throw new RuntimeReaderException($e->getMessage());
118
            }
119 9
        } catch (\Throwable $e) {
120 9
            $exceptionNs = explode('\\', get_class($e));
121 9
            $this->logger->log(
122 9
                ($e instanceof MissingInputFileReaderException) ? LogLevel::WARNING : LogLevel::ERROR,
123 9
                sprintf(
124 9
                    'Video conversion failed \'%s\' with \'%s\'. "%s(%s, %s,...)"',
125 9
                    $exceptionNs[count($exceptionNs) - 1],
126 9
                    __METHOD__,
127 9
                    $e->getMessage(),
128 9
                    $inputFile,
129 9
                    $outputFile instanceof UnescapedFileInterface ? $outputFile->getFile() : $outputFile
130
                )
131
            );
132 9
            throw $e;
133
        }
134 3
    }
135
136
    /*
0 ignored issues
show
Unused Code Comprehensibility introduced by
47% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
137
     * FOR LATER REFERENCE !!!
138
    public function convertMultiPass(string $videoFile, string $outputFile, VideoConvertParams $convertParams, VideoFilterInterface $videoFilter=null): void {
139
140
        $this->ensureFileExists($videoFile);
141
        if ($videoFilter === null) {
142
            $videoFilter = new EmptyVideoFilter();
143
        }
144
145
146
        $threads = $convertParams->getOption(VideoConversionParams::OPTION_THREADS, $this->ffmpegConfig->getThreads());
147
148
        $ffmpegBin = $this->ffmpegConfig->getBinary();
149
150
        $commonArgs = array_merge([
151
                $ffmpegBin,
152
                sprintf('-i %s', escapeshellarg($videoFile)), // input filename
153
                $videoFilter->getFFMpegCliArgument(), // add -vf yadif,nlmeans
154
                ($threads === null) ? '' : sprintf('-threads %s', $threads),
155
        ], $convertParams->getFFMpegArguments());
156
157
        $pass1Cmd = implode(' ', array_merge(
158
            $commonArgs,
159
            [
160
                '-pass 1',
161
                // tells VP9 to encode really fast, sacrificing quality. Useful to speed up the first pass.
162
                '-speed 4',
163
                '-y /dev/null',
164
            ]
165
        ));
166
167
        $pass2Cmd = implode( ' ', array_merge(
168
            $commonArgs,
169
            [
170
                '-pass 2',
171
                // speed 1 is a good speed vs. quality compromise.
172
                // Produces tmp quality typically very close to speed 0, but usually encodes much faster.
173
                '-speed 1',
174
                '-y',
175
                sprintf("%s", escapeshellarg($outputFile))
176
            ]
177
        ));
178
179
180
        $process = new Process($pass1Cmd);
181
        $process->setTimeout(null);
182
        $process->setIdleTimeout(60); // 60 seconds without tmp will stop the process
183
        $process->start();
184
        foreach ($process as $type => $data) {
185
            if ($process::OUT === $type) {
186
                echo "\nRead from stdout: ".$data;
187
            } else { // $process::ERR === $type
188
                echo "\nRead from stderr: ".$data;
189
            }
190
        }
191
192
        $process = new Process($pass2Cmd);
193
        $process->setTimeout(null);
194
        $process->setIdleTimeout(60); // 60 seconds without tmp will stop the process
195
        $process->start();
196
        foreach ($process as $type => $data) {
197
            if ($process::OUT === $type) {
198
                echo "\nRead from stdout: ".$data;
199
            } else { // $process::ERR === $type
200
                echo "\nRead from stderr: ".$data;
201
            }
202
        }
203
204
    }
205
    */
206
}
207