Failed Conditions
Push — master ( c8847f...26090e )
by Sébastien
03:53
created

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