Completed
Push — master ( e0e3f0...3077c0 )
by Sébastien
04:19
created

VideoConversionService   A

Complexity

Total Complexity 11

Size/Duplication

Total Lines 76
Duplicated Lines 0 %

Test Coverage

Coverage 83.33%

Importance

Changes 0
Metric Value
wmc 11
eloc 31
dl 0
loc 76
ccs 25
cts 30
cp 0.8333
rs 10
c 0
b 0
f 0

3 Methods

Rating   Name   Duplication   Size   Complexity  
A getSymfonyProcess() 0 15 3
A __construct() 0 4 1
B convert() 0 18 7
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Soluble\MediaTools;
6
7
use Soluble\MediaTools\Config\FFMpegConfigInterface;
8
use Soluble\MediaTools\Exception\FileNotFoundException;
9
use Soluble\MediaTools\Exception\UnsupportedParamException;
10
use Soluble\MediaTools\Exception\UnsupportedParamValueException;
11
use Soluble\MediaTools\Util\Assert\PathAssertionsTrait;
12
use Soluble\MediaTools\Video\ConversionParamsInterface;
13
use Soluble\MediaTools\Video\ConversionServiceInterface;
14
use Soluble\MediaTools\Video\Converter\FFMpegAdapter;
15
use Soluble\MediaTools\Video\Exception\ConversionExceptionInterface;
16
use Soluble\MediaTools\Video\Exception\ConversionProcessExceptionInterface;
17
use Soluble\MediaTools\Video\Exception\InvalidParamException;
18
use Soluble\MediaTools\Video\Exception\MissingInputFileException;
19
use Soluble\MediaTools\Video\Exception\ProcessFailedException;
20
use Soluble\MediaTools\Video\Exception\ProcessSignaledException;
21
use Soluble\MediaTools\Video\Exception\ProcessTimeOutException;
22
use Soluble\MediaTools\Video\Exception\RuntimeException;
23
use Symfony\Component\Process\Exception as SPException;
24
use Symfony\Component\Process\Process;
25
26
class VideoConversionService implements ConversionServiceInterface
27
{
28
    use PathAssertionsTrait;
29
30
    /** @var FFMpegConfigInterface */
31
    protected $ffmpegConfig;
32
33
    /** @var FFMpegAdapter */
34
    protected $converter;
35
36 6
    public function __construct(FFMpegConfigInterface $ffmpegConfig)
37
    {
38 6
        $this->ffmpegConfig = $ffmpegConfig;
39 6
        $this->converter    = new FFMpegAdapter($ffmpegConfig);
40 6
    }
41
42
    /**
43
     * Return ready-to-run symfony process object that you can use
44
     * to `run()` or `start()` programmatically. Useful if you want to make
45
     * things async...
46
     *
47
     * @see https://symfony.com/doc/current/components/process.html
48
     *
49
     * @throws UnsupportedParamException
50
     * @throws UnsupportedParamValueException
51
     */
52 5
    public function getSymfonyProcess(string $inputFile, string $outputFile, VideoConversionParams $convertParams): Process
53
    {
54 5
        if (!$convertParams->hasParam(ConversionParamsInterface::PARAM_THREADS) && $this->ffmpegConfig->getThreads() !== null) {
55 1
            $convertParams = $convertParams->withThreads($this->ffmpegConfig->getThreads());
56
        }
57
58 5
        $arguments = $this->converter->getMappedConversionParams($convertParams);
59 5
        $ffmpegCmd = $this->converter->getCliCommand($arguments, $inputFile, $outputFile);
60
61 5
        $process = new Process($ffmpegCmd);
62 5
        $process->setTimeout($this->ffmpegConfig->getTimeout());
63 5
        $process->setIdleTimeout($this->ffmpegConfig->getIdleTimeout());
64 5
        $process->setEnv($this->ffmpegConfig->getEnv());
65
66 5
        return $process;
67
    }
68
69
    /**
70
     * Run a conversion, throw exception on error.
71
     *
72
     * @param callable|null $callback A PHP callback to run whenever there is some
73
     *                                tmp available on STDOUT or STDERR
74
     *
75
     * @throws ConversionExceptionInterface        Base exception class for conversion exceptions
76
     * @throws ConversionProcessExceptionInterface Base exception class for process conversion exceptions
77
     * @throws MissingInputFileException
78
     * @throws ProcessTimeOutException
79
     * @throws ProcessFailedException
80
     * @throws ProcessSignaledException
81
     * @throws InvalidParamException
82
     * @throws RuntimeException
83
     */
84 4
    public function convert(string $inputFile, string $outputFile, VideoConversionParams $convertParams, ?callable $callback = null): void
85
    {
86
        try {
87 4
            $this->ensureFileExists($inputFile);
88 3
            $process = $this->getSymfonyProcess($inputFile, $outputFile, $convertParams);
89 3
            $process->mustRun($callback);
90 2
        } catch (FileNotFoundException $e) {
91 1
            throw new MissingInputFileException($e->getMessage());
92 1
        } catch (UnsupportedParamValueException | UnsupportedParamException $e) {
93
            throw new InvalidParamException($e->getMessage());
94 1
        } catch (SPException\ProcessTimedOutException $e) {
95
            throw new ProcessTimeOutException($e->getProcess(), $e);
96 1
        } catch (SPException\ProcessSignaledException $e) {
97
            throw new ProcessSignaledException($e->getProcess(), $e);
98 1
        } catch (SPException\ProcessFailedException $e) {
99 1
            throw new ProcessFailedException($e->getProcess(), $e);
100
        } catch (SPException\RuntimeException $e) {
101
            throw new RuntimeException($e->getMessage());
102
        }
103 2
    }
104
105
    /*
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...
106
     * FOR LATER REFERENCE !!!
107
    public function convertMultiPass(string $videoFile, string $outputFile, VideoConversionParams $convertParams, VideoFilterInterface $videoFilter=null): void {
108
109
        $this->ensureFileExists($videoFile);
110
        if ($videoFilter === null) {
111
            $videoFilter = new EmptyVideoFilter();
112
        }
113
114
115
        $threads = $convertParams->getOption(VideoConversionParams::OPTION_THREADS, $this->ffmpegConfig->getThreads());
116
117
        $ffmpegBin = $this->ffmpegConfig->getBinary();
118
119
        $commonArgs = array_merge([
120
                $ffmpegBin,
121
                sprintf('-i %s', escapeshellarg($videoFile)), // input filename
122
                $videoFilter->getFFMpegCliArgument(), // add -vf yadif,nlmeans
123
                ($threads === null) ? '' : sprintf('-threads %s', $threads),
124
        ], $convertParams->getFFMpegArguments());
125
126
        $pass1Cmd = implode(' ', array_merge(
127
            $commonArgs,
128
            [
129
                '-pass 1',
130
                // tells VP9 to encode really fast, sacrificing quality. Useful to speed up the first pass.
131
                '-speed 4',
132
                '-y /dev/null',
133
            ]
134
        ));
135
136
        $pass2Cmd = implode( ' ', array_merge(
137
            $commonArgs,
138
            [
139
                '-pass 2',
140
                // speed 1 is a good speed vs. quality compromise.
141
                // Produces tmp quality typically very close to speed 0, but usually encodes much faster.
142
                '-speed 1',
143
                '-y',
144
                sprintf("%s", escapeshellarg($outputFile))
145
            ]
146
        ));
147
148
149
        $process = new Process($pass1Cmd);
150
        $process->setTimeout(null);
151
        $process->setIdleTimeout(60); // 60 seconds without tmp will stop the process
152
        $process->start();
153
        foreach ($process as $type => $data) {
154
            if ($process::OUT === $type) {
155
                echo "\nRead from stdout: ".$data;
156
            } else { // $process::ERR === $type
157
                echo "\nRead from stderr: ".$data;
158
            }
159
        }
160
161
        $process = new Process($pass2Cmd);
162
        $process->setTimeout(null);
163
        $process->setIdleTimeout(60); // 60 seconds without tmp will stop the process
164
        $process->start();
165
        foreach ($process as $type => $data) {
166
            if ($process::OUT === $type) {
167
                echo "\nRead from stdout: ".$data;
168
            } else { // $process::ERR === $type
169
                echo "\nRead from stderr: ".$data;
170
            }
171
        }
172
173
    }
174
    */
175
}
176