Failed Conditions
Push — master ( 814bec...e68911 )
by Sébastien
03:09
created

VideoConverter::convert()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 4.3731

Importance

Changes 0
Metric Value
dl 0
loc 11
ccs 5
cts 7
cp 0.7143
rs 9.9
c 0
b 0
f 0
cc 4
nc 3
nop 5
crap 4.3731
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Soluble\MediaTools;
6
7
use Soluble\MediaTools\Config\FFMpegConfig;
8
use Soluble\MediaTools\Exception\FileNotFoundException;
9
use Soluble\MediaTools\Exception\ProcessConversionException;
10
use Soluble\MediaTools\Util\Assert\PathAssertionsTrait;
11
use Soluble\MediaTools\Video\Converter\ParamsInterface;
12
use Soluble\MediaTools\Video\Filter\EmptyVideoFilter;
13
use Soluble\MediaTools\Video\Filter\VideoFilterChain;
14
use Soluble\MediaTools\Video\Filter\VideoFilterInterface;
15
use Soluble\MediaTools\Video\Filter\VideoFilterTypeDenoiseInterface;
16
use Soluble\MediaTools\Video\Filter\YadifVideoFilter;
17
use Soluble\MediaTools\Video\VideoConverterServiceInterface;
18
use Symfony\Component\Process\Exception as ProcessException;
19
use Symfony\Component\Process\Process;
20
21
class VideoConverter implements VideoConverterServiceInterface
22
{
23
    use PathAssertionsTrait;
24
25
    /** @var FFMpegConfig */
26
    protected $ffmpegConfig;
27
28
    /** @var VideoProbe */
29
    protected $videoProbe;
30
31 5
    public function __construct(FFMpegConfig $ffmpegConfig, VideoProbe $videoProbe)
32
    {
33 5
        $this->videoProbe   = $videoProbe;
34 5
        $this->ffmpegConfig = $ffmpegConfig;
35 5
        $this->ffmpegConfig->getProcess()->ensureBinaryExists();
36 5
    }
37
38
    /**
39
     * Return ready-to-run symfony process object that you can use
40
     * to `run()` or `start()` programmatically. Useful if you want to make
41
     * things async...
42
     *
43
     * @see https://symfony.com/doc/current/components/process.html
44
     *
45
     * @throws FileNotFoundException when inputFile does not exists
46
     */
47 5
    public function getConversionProcess(string $inputFile, string $outputFile, VideoConvertParams $convertParams): Process
48
    {
49 5
        $this->ensureFileExists($inputFile);
50
51 4
        $process = $this->ffmpegConfig->getProcess();
52
53 4
        if (!$convertParams->hasOption(ParamsInterface::PARAM_THREADS) && $this->ffmpegConfig->getThreads() !== null) {
54
            $convertParams = $convertParams->withThreads($this->ffmpegConfig->getThreads());
55
        }
56
57 4
        $ffmpegCmd = $process->buildCommand(
58 4
            array_merge(
59
                [
60 4
                    sprintf('-i %s', escapeshellarg($inputFile)), // input filename
61
                ],
62 4
                $convertParams->getFFMpegArguments(),
63
                [
64 4
                    '-y', // tell to overwrite
65 4
                    sprintf('%s', escapeshellarg($outputFile)),
66
                ]
67
            )
68
        );
69
70 4
        $process = new Process($ffmpegCmd);
71 4
        $process->setTimeout($this->ffmpegConfig->getConversionTimeout());
72 4
        $process->setIdleTimeout($this->ffmpegConfig->getConversionIdleTimeout());
73
74 4
        return $process;
75
    }
76
77
    /**
78
     * Run a conversion, throw exception on error.
79
     *
80
     * @param callable|null                 $callback A PHP callback to run whenever there is some
81
     *                                                output available on STDOUT or STDERR
82
     * @param array<string,string|int>|null $env      An array of env vars to set
83
     *                                                when running the process
84
     *
85
     * @throws FileNotFoundException      When inputFile does not exists
86
     * @throws ProcessConversionException When the ffmpeg process conversion failed
87
     */
88 5
    public function convert(string $inputFile, string $outputFile, VideoConvertParams $convertParams, ?callable $callback = null, ?array $env = null): void
89
    {
90 5
        $process = $this->getConversionProcess($inputFile, $outputFile, $convertParams);
91
92
        try {
93 4
            $process->mustRun($callback, (is_array($env) ? $env : $this->ffmpegConfig->getConversionEnv()));
94 2
        } catch (ProcessException\RuntimeException $symfonyProcessException) {
95
            // will include: ProcessFailedException|ProcessTimedOutException|ProcessSignaledException
0 ignored issues
show
Unused Code Comprehensibility introduced by
37% 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...
96 2
            throw new ProcessConversionException($process, $symfonyProcessException);
97
        } catch (FileNotFoundException $e) {
98
            throw $e;
99
        }
100 2
    }
101
102
    /**
103
     * Try to guess if the original video is interlaced (bff, tff) and
104
     * return ffmpeg yadif filter argument and add denoise filter if any.
105
     *
106
     * @see https://ffmpeg.org/ffmpeg-filters.html (section yadif)
107
     * @see https://askubuntu.com/a/867203
108
     *
109
     * @return VideoFilterInterface|VideoFilterChain|EmptyVideoFilter|YadifVideoFilter
110
     */
111
    public function getDeintFilter(string $videoFile, ?VideoFilterTypeDenoiseInterface $denoiseFilter = null): VideoFilterInterface
112
    {
113
        $guess       = $this->videoProbe->guessInterlacing($videoFile);
114
        $deintFilter = $guess->getDeinterlaceVideoFilter();
115
        // skip all filters if video is not interlaces
116
        if ($deintFilter instanceof EmptyVideoFilter) {
117
            return $deintFilter;
118
        }
119
        if ($denoiseFilter !== null) {
120
            $videoFilterChain = new VideoFilterChain();
121
            $videoFilterChain->addFilter($deintFilter);
122
            $videoFilterChain->addFilter($denoiseFilter);
123
124
            return $videoFilterChain;
125
        }
126
127
        return $deintFilter;
128
    }
129
130
    /*
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...
131
    public function transcodeMultiPass(string $videoFile, string $outputFile, VideoConvertParams $convertParams, VideoFilterInterface $videoFilter=null): void {
132
133
        $this->ensureFileExists($videoFile);
134
        if ($videoFilter === null) {
135
            $videoFilter = new EmptyVideoFilter();
136
        }
137
138
139
        $threads = $convertParams->getOption(VideoConvertParams::OPTION_THREADS, $this->ffmpegConfig->getThreads());
140
141
        $ffmpegBin = $this->ffmpegConfig->getBinary();
142
143
        $commonArgs = array_merge([
144
                $ffmpegBin,
145
                sprintf('-i %s', escapeshellarg($videoFile)), // input filename
146
                $videoFilter->getFFMpegCliArgument(), // add -vf yadif,nlmeans
147
                ($threads === null) ? '' : sprintf('-threads %s', $threads),
148
        ], $convertParams->getFFMpegArguments());
149
150
        $pass1Cmd = implode(' ', array_merge(
151
            $commonArgs,
152
            [
153
                '-pass 1',
154
                // tells VP9 to encode really fast, sacrificing quality. Useful to speed up the first pass.
155
                '-speed 4',
156
                '-y /dev/null',
157
            ]
158
        ));
159
160
        $pass2Cmd = implode( ' ', array_merge(
161
            $commonArgs,
162
            [
163
                '-pass 2',
164
                // speed 1 is a good speed vs. quality compromise.
165
                // Produces output quality typically very close to speed 0, but usually encodes much faster.
166
                '-speed 1',
167
                '-y',
168
                sprintf("%s", escapeshellarg($outputFile))
169
            ]
170
        ));
171
172
173
        $process = new Process($pass1Cmd);
174
        $process->setTimeout(null);
175
        $process->setIdleTimeout(60); // 60 seconds without output will stop the process
176
        $process->start();
177
        foreach ($process as $type => $data) {
178
            if ($process::OUT === $type) {
179
                echo "\nRead from stdout: ".$data;
180
            } else { // $process::ERR === $type
181
                echo "\nRead from stderr: ".$data;
182
            }
183
        }
184
185
        $process = new Process($pass2Cmd);
186
        $process->setTimeout(null);
187
        $process->setIdleTimeout(60); // 60 seconds without output will stop the process
188
        $process->start();
189
        foreach ($process as $type => $data) {
190
            if ($process::OUT === $type) {
191
                echo "\nRead from stdout: ".$data;
192
            } else { // $process::ERR === $type
193
                echo "\nRead from stderr: ".$data;
194
            }
195
        }
196
197
    }
198
    */
199
}
200