Failed Conditions
Push — master ( bd9fc8...d384bc )
by Sébastien
03:00
created

VideoConvert::convert()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 9
ccs 0
cts 7
cp 0
rs 9.9666
c 0
b 0
f 0
cc 3
nc 3
nop 4
crap 12
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Soluble\MediaTools;
6
7
use ProcessFailedException;
0 ignored issues
show
Bug introduced by
The type ProcessFailedException was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
8
use Soluble\MediaTools\Config\FFMpegConfig;
9
use Soluble\MediaTools\Exception\FileNotFoundException;
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\Process;
17
18
class VideoConvert
19
{
20
    use CommonAssertionsTrait;
21
22
    /** @var FFMpegConfig */
23
    protected $ffmpegConfig;
24
25
    /** @var VideoProbe */
26
    protected $videoProbe;
27
28 1
    public function __construct(FFMpegConfig $ffmpegConfig, VideoProbe $videoProbe)
29
    {
30 1
        $this->videoProbe   = $videoProbe;
31 1
        $this->ffmpegConfig = $ffmpegConfig;
32 1
        $this->ffmpegConfig->getProcess()->ensureBinaryExists();
33 1
    }
34
35
    /**
36
     * Return ready-to-run symfony process object that you can use
37
     * to `run()` or `start()` programmatically. Useful if you want to make
38
     * things async...
39
     *
40
     * @see https://symfony.com/doc/current/components/process.html
41
     *
42
     * @throws FileNotFoundException when inputFile does not exists
43
     */
44 1
    public function getConversionProcess(string $inputFile, string $outputFile, VideoConvertParams $convertParams): Process
45
    {
46 1
        $this->ensureFileExists($inputFile);
47
48 1
        $process = $this->ffmpegConfig->getProcess();
49
50 1
        if (!$convertParams->hasOption(VideoConvertParams::OPTION_THREADS) && $this->ffmpegConfig->getThreads() !== null) {
51
            $convertParams = $convertParams->withThreads($this->ffmpegConfig->getThreads());
52
        }
53
54 1
        $ffmpegCmd = $process->buildCommand(
55 1
            array_merge(
56
                [
57 1
                    sprintf('-i %s', escapeshellarg($inputFile)), // input filename
58
                ],
59 1
                $convertParams->getFFMpegArguments(),
60
                [
61 1
                    '-y', // tell to overwrite
62 1
                    sprintf('%s', escapeshellarg($outputFile)),
63
                ]
64
            )
65
        );
66
67 1
        $process = new Process($ffmpegCmd);
68 1
        $process->setTimeout($this->ffmpegConfig->getConversionTimeout());
69 1
        $process->setIdleTimeout($this->ffmpegConfig->getConversionIdleTimeout());
70
71 1
        return $process;
72
    }
73
74
    /**
75
     * Run a conversion, throw exception on error.
76
     *
77
     * @throws FileNotFoundException    when inputFile does not exists
78
     * @throws ProcessRuntimeException  The base class for all process exceptions
79
     * @throws ProcessFailedException   Whenever the process has failed to run
80
     * @throws ProcessTimedOutException Whenever a timeout occured before the process finished
81
     * @throws ProcessSignaledException Whenever the process was stopped
82
     */
83
    public function convert(string $inputFile, string $outputFile, VideoConvertParams $convertParams, ?VideoFilterInterface $videoFilter = null): void
84
    {
85
        $process = $this->getConversionProcess($inputFile, $outputFile, $convertParams);
86
        try {
87
            $process->mustRun();
88
        } catch (ProcessRuntimeException $e) {
0 ignored issues
show
Bug introduced by
The type Soluble\MediaTools\ProcessRuntimeException was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
89
            throw $e;
90
        } catch (FileNotFoundException $e) {
91
            throw $e;
92
        }
93
    }
94
95
    /**
96
     * Try to guess if the original video is interlaced (bff, tff) and
97
     * return ffmpeg yadif filter argument and add denoise filter if any.
98
     *
99
     * @see https://ffmpeg.org/ffmpeg-filters.html (section yadif)
100
     * @see https://askubuntu.com/a/867203
101
     *
102
     * @return VideoFilterInterface|VideoFilterChain|EmptyVideoFilter|YadifVideoFilter
103
     */
104
    public function getDeintFilter(string $videoFile, ?VideoFilterTypeDenoiseInterface $denoiseFilter = null): VideoFilterInterface
105
    {
106
        $guess       = $this->videoProbe->guessInterlacing($videoFile);
107
        $deintFilter = $guess->getDeinterlaceVideoFilter();
108
        // skip all filters if video is not interlaces
109
        if ($deintFilter instanceof EmptyVideoFilter) {
110
            return $deintFilter;
111
        }
112
        if ($denoiseFilter !== null) {
113
            $videoFilterChain = new VideoFilterChain();
114
            $videoFilterChain->addFilter($deintFilter);
115
            $videoFilterChain->addFilter($denoiseFilter);
116
117
            return $videoFilterChain;
118
        }
119
120
        return $deintFilter;
121
    }
122
123
    /*
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...
124
    public function transcodeMultiPass(string $videoFile, string $outputFile, VideoConvertParams $convertParams, VideoFilterInterface $videoFilter=null): void {
125
126
        $this->ensureFileExists($videoFile);
127
        if ($videoFilter === null) {
128
            $videoFilter = new EmptyVideoFilter();
129
        }
130
131
132
        $threads = $convertParams->getOption(VideoConvertParams::OPTION_THREADS, $this->ffmpegConfig->getThreads());
133
134
        $ffmpegBin = $this->ffmpegConfig->getBinary();
135
136
        $commonArgs = array_merge([
137
                $ffmpegBin,
138
                sprintf('-i %s', escapeshellarg($videoFile)), // input filename
139
                $videoFilter->getFFMpegCliArgument(), // add -vf yadif,nlmeans
140
                ($threads === null) ? '' : sprintf('-threads %s', $threads),
141
        ], $convertParams->getFFMpegArguments());
142
143
        $pass1Cmd = implode(' ', array_merge(
144
            $commonArgs,
145
            [
146
                '-pass 1',
147
                // tells VP9 to encode really fast, sacrificing quality. Useful to speed up the first pass.
148
                '-speed 4',
149
                '-y /dev/null',
150
            ]
151
        ));
152
153
        $pass2Cmd = implode( ' ', array_merge(
154
            $commonArgs,
155
            [
156
                '-pass 2',
157
                // speed 1 is a good speed vs. quality compromise.
158
                // Produces output quality typically very close to speed 0, but usually encodes much faster.
159
                '-speed 1',
160
                '-y',
161
                sprintf("%s", escapeshellarg($outputFile))
162
            ]
163
        ));
164
165
166
        $process = new Process($pass1Cmd);
167
        $process->setTimeout(null);
168
        $process->setIdleTimeout(60); // 60 seconds without output will stop the process
169
        $process->start();
170
        foreach ($process as $type => $data) {
171
            if ($process::OUT === $type) {
172
                echo "\nRead from stdout: ".$data;
173
            } else { // $process::ERR === $type
174
                echo "\nRead from stderr: ".$data;
175
            }
176
        }
177
178
        $process = new Process($pass2Cmd);
179
        $process->setTimeout(null);
180
        $process->setIdleTimeout(60); // 60 seconds without output will stop the process
181
        $process->start();
182
        foreach ($process as $type => $data) {
183
            if ($process::OUT === $type) {
184
                echo "\nRead from stdout: ".$data;
185
            } else { // $process::ERR === $type
186
                echo "\nRead from stderr: ".$data;
187
            }
188
        }
189
190
    }
191
    */
192
}
193