Completed
Push — master ( 477c6b...8b3cf8 )
by Sébastien
04:39 queued 01:53
created

VideoTranscode   A

Complexity

Total Complexity 10

Size/Duplication

Total Lines 154
Duplicated Lines 0 %

Test Coverage

Coverage 65.79%

Importance

Changes 0
Metric Value
wmc 10
dl 0
loc 154
ccs 25
cts 38
cp 0.6579
rs 10
c 0
b 0
f 0

4 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A ensureFileExists() 0 4 2
A transcode() 0 35 4
A getDeintFilter() 0 17 3
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 VideoTranscode
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, VideoTranscodeParams $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(VideoTranscodeParams::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 1
    public function transcode(string $videoFile, string $outputFile, VideoTranscodeParams $transcodeParams, ?VideoFilterInterface $videoFilter = null): Process
102
    {
103 1
        $this->ensureFileExists($videoFile);
104
105 1
        if ($videoFilter === null) {
106 1
            $videoFilter = new EmptyVideoFilter();
107
        }
108
109 1
        $process = $this->ffmpegConfig->getProcess();
110
111 1
        if (!$transcodeParams->hasOption(VideoTranscodeParams::OPTION_THREADS) && $this->ffmpegConfig->getThreads() !== null) {
112
            $transcodeParams = $transcodeParams->withThreads($this->ffmpegConfig->getThreads());
113
        }
114
115 1
        $ffmpegCmd = $process->buildCommand(
116 1
            array_merge(
117
                [
118 1
                    sprintf('-i %s', escapeshellarg($videoFile)), // input filename
119 1
                    $videoFilter->getFFMpegCLIArgument(), // add -vf yadif,nlmeans
120
                ],
121 1
                $transcodeParams->getFFMpegArguments(),
122
                [
123 1
                    '-y', // tell to overwrite
124 1
                    sprintf('%s', escapeshellarg($outputFile)),
125
                ]
126
            )
127
        );
128
129 1
        $process = new Process($ffmpegCmd);
130 1
        $process->setTimeout(null);
131
        // 60 seconds without output will stop the process
132 1
        $process->setIdleTimeout(60);
133 1
        $process->start();
134
135 1
        return $process;
136
    }
137
138
    /**
139
     * Try to guess if the original video is interlaced (bff, tff) and
140
     * return ffmpeg yadif filter argument and add denoise filter if any.
141
     *
142
     * @see https://ffmpeg.org/ffmpeg-filters.html (section yadif)
143
     * @see https://askubuntu.com/a/867203
144
     *
145
     * @return VideoFilterInterface|VideoFilterChain|EmptyVideoFilter|YadifVideoFilter
146
     */
147
    public function getDeintFilter(string $videoFile, ?VideoFilterTypeDenoiseInterface $denoiseFilter = null): VideoFilterInterface
148
    {
149
        $guess       = $this->videoProbe->guessInterlacing($videoFile);
150
        $deintFilter = $guess->getDeinterlaceVideoFilter();
151
        // skip all filters if video is not interlaces
152
        if ($deintFilter instanceof EmptyVideoFilter) {
153
            return $deintFilter;
154
        }
155
        if ($denoiseFilter !== null) {
156
            $videoFilterChain = new VideoFilterChain();
157
            $videoFilterChain->addFilter($deintFilter);
158
            $videoFilterChain->addFilter($denoiseFilter);
159
160
            return $videoFilterChain;
161
        }
162
163
        return $deintFilter;
164
    }
165
166 1
    protected function ensureFileExists(string $file): void
167
    {
168 1
        if (!file_exists($file)) {
169
            throw new FileNotFoundException(sprintf('File "%s" does not exists or is not readable', $file));
170
        }
171 1
    }
172
}
173