Failed Conditions
Push — master ( 5e65a4...91e9bd )
by Sébastien
03:12
created

src/Video/ConversionService.php (1 issue)

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Soluble\MediaTools\Video;
6
7
use Psr\Log\LoggerInterface;
8
use Psr\Log\LogLevel;
9
use Psr\Log\NullLogger;
10
use Soluble\MediaTools\Common\Assert\PathAssertionsTrait;
11
use Soluble\MediaTools\Common\Exception\FileNotFoundException;
12
use Soluble\MediaTools\Common\Exception\UnsupportedParamException;
13
use Soluble\MediaTools\Common\Exception\UnsupportedParamValueException;
14
use Soluble\MediaTools\Common\Process\ProcessFactory;
15
use Soluble\MediaTools\Common\Process\ProcessParamsInterface;
16
use Soluble\MediaTools\Video\Config\FFMpegConfigInterface;
17
use Soluble\MediaTools\Video\Exception\ConversionExceptionInterface;
18
use Soluble\MediaTools\Video\Exception\ConversionProcessExceptionInterface;
19
use Soluble\MediaTools\Video\Exception\InvalidParamException;
20
use Soluble\MediaTools\Video\Exception\MissingInputFileException;
21
use Soluble\MediaTools\Video\Exception\ProcessFailedException;
22
use Soluble\MediaTools\Video\Exception\ProcessSignaledException;
23
use Soluble\MediaTools\Video\Exception\ProcessTimedOutException;
24
use Soluble\MediaTools\Video\Exception\RuntimeException;
25
use Symfony\Component\Process\Exception as SPException;
26
use Symfony\Component\Process\Process;
27
28
class ConversionService implements ConversionServiceInterface
29
{
30
    use PathAssertionsTrait;
31
32
    /** @var FFMpegConfigInterface */
33
    protected $ffmpegConfig;
34
35 10
    /** @var LoggerInterface|NullLogger */
36
    protected $logger;
37 10
38 10
    public function __construct(FFMpegConfigInterface $ffmpegConfig, ?LoggerInterface $logger = null)
39 10
    {
40
        $this->ffmpegConfig  = $ffmpegConfig;
41
42
        $this->logger = $logger ?? new NullLogger();
43
    }
44
45
    /**
46
     * Return ready-to-run symfony process object that you can use
47
     * to `run()` or `start()` programmatically. Useful if you want to make
48
     * things async...
49
     *
50
     * @see https://symfony.com/doc/current/components/process.html
51 9
     *
52
     * @throws UnsupportedParamException
53 9
     * @throws UnsupportedParamValueException
54 9
     */
55 1
    public function getSymfonyProcess(string $inputFile, string $outputFile, ConversionParamsInterface $convertParams, ?ProcessParamsInterface $processParams = null): Process
56 1
    {
57 1
        $adapter = $this->ffmpegConfig->getAdapter();
58
59
        if (!$convertParams->hasParam(ConversionParamsInterface::PARAM_THREADS)
60
            && $adapter->getDefaultThreads() !== null) {
61 9
            $convertParams = $convertParams->withBuiltInParam(
62 9
                ConversionParamsInterface::PARAM_THREADS,
63
                $adapter->getDefaultThreads()
64 9
            );
65
        }
66 9
67 9
        $arguments = $adapter->getMappedConversionParams($convertParams);
68 9
        $ffmpegCmd = $adapter->getCliCommand($arguments, $inputFile, $outputFile);
69 9
70
        $pp = $processParams ?? $this->ffmpegConfig->getProcessParams();
71 9
72
        return (new ProcessFactory($ffmpegCmd, $pp))();
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
     *                                tmp available on STDOUT or STDERR
80
     *
81
     * @throws ConversionExceptionInterface        Base exception class for conversion exceptions
82
     * @throws ConversionProcessExceptionInterface Base exception class for process conversion exceptions
83
     * @throws MissingInputFileException
84
     * @throws ProcessTimedOutException
85
     * @throws ProcessFailedException
86
     * @throws ProcessSignaledException
87
     * @throws InvalidParamException
88
     * @throws RuntimeException
89 8
     */
90
    public function convert(string $inputFile, string $outputFile, ConversionParamsInterface $convertParams, ?callable $callback = null, ?ProcessParamsInterface $processParams = null): void
91
    {
92 8
        try {
93 7
            try {
94 7
                $this->ensureFileExists($inputFile);
95 6
                $process = $this->getSymfonyProcess($inputFile, $outputFile, $convertParams, $processParams);
96 1
                $process->mustRun($callback);
97 5
            } catch (FileNotFoundException $e) {
98
                throw new MissingInputFileException($e->getMessage());
99 5
            } catch (UnsupportedParamValueException | UnsupportedParamException $e) {
100 3
                throw new InvalidParamException($e->getMessage());
101 2
            } catch (SPException\ProcessTimedOutException $e) {
102
                throw new ProcessTimedOutException($e->getProcess(), $e);
103 2
            } catch (SPException\ProcessSignaledException $e) {
104 2
                throw new ProcessSignaledException($e->getProcess(), $e);
105
            } catch (SPException\ProcessFailedException $e) {
106
                throw new ProcessFailedException($e->getProcess(), $e);
107
            } catch (SPException\RuntimeException $e) {
108 2
                throw new RuntimeException($e->getMessage());
109
            }
110
        } catch (\Throwable $e) {
111
            $exceptionNs = explode('\\', get_class($e));
112
            $this->logger->log(
113
                ($e instanceof MissingInputFileException) ? LogLevel::WARNING : LogLevel::ERROR,
114
                sprintf(
115
                    'Video conversion failed \'%s\' with \'%s\'. "%s(%s, %s,...)"',
116
                    $exceptionNs[count($exceptionNs) - 1],
117
                    __METHOD__,
118
                    $e->getMessage(),
119
                    $inputFile,
120
                    $outputFile
121
                )
122
            );
123
            throw $e;
124
        }
125
    }
126
127
    /*
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...
128
     * FOR LATER REFERENCE !!!
129
    public function convertMultiPass(string $videoFile, string $outputFile, ConversionParams $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(VideoConversionParams::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 tmp 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 tmp 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 tmp 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