Passed
Push — master ( 4744e0...bd9fc8 )
by Sébastien
02:34
created

VideoConvert::convert()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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