Completed
Push — master ( f72769...eb1d3e )
by Sébastien
08:28 queued 06:05
created

VideoConverter   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 105
Duplicated Lines 0 %

Test Coverage

Coverage 88.89%

Importance

Changes 0
Metric Value
wmc 15
eloc 48
dl 0
loc 105
ccs 40
cts 45
cp 0.8889
rs 10
c 0
b 0
f 0

3 Methods

Rating   Name   Duplication   Size   Complexity  
A getSymfonyProcess() 0 23 4
A __construct() 0 5 1
B convert() 0 34 10
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * @see       https://github.com/soluble-io/soluble-mediatools for the canonical repository
7
 *
8
 * @copyright Copyright (c) 2018-2019 Sébastien Vanvelthem. (https://github.com/belgattitude)
9
 * @license   https://github.com/soluble-io/soluble-mediatools/blob/master/LICENSE.md MIT
10
 */
11
12
namespace Soluble\MediaTools\Video;
13
14
use Psr\Log\LoggerInterface;
15
use Psr\Log\LogLevel;
16
use Psr\Log\NullLogger;
17
use Soluble\MediaTools\Common\Assert\PathAssertionsTrait;
18
use Soluble\MediaTools\Common\Exception as CommonException;
19
use Soluble\MediaTools\Common\IO\UnescapedFileInterface;
20
use Soluble\MediaTools\Common\Process\ProcessFactory;
21
use Soluble\MediaTools\Common\Process\ProcessParamsInterface;
22
use Soluble\MediaTools\Video\Config\FFMpegConfigInterface;
23
use Soluble\MediaTools\Video\Exception\ConverterExceptionInterface;
24
use Soluble\MediaTools\Video\Exception\ConverterProcessExceptionInterface;
25
use Soluble\MediaTools\Video\Exception\InvalidArgumentException;
26
use Soluble\MediaTools\Video\Exception\InvalidParamException;
27
use Soluble\MediaTools\Video\Exception\MissingInputFileException;
28
use Soluble\MediaTools\Video\Exception\ProcessFailedException;
29
use Soluble\MediaTools\Video\Exception\ProcessSignaledException;
30
use Soluble\MediaTools\Video\Exception\ProcessTimedOutException;
31
use Soluble\MediaTools\Video\Exception\RuntimeReaderException;
32
use Symfony\Component\Process\Exception as SPException;
33
use Symfony\Component\Process\Process;
34
35
class VideoConverter implements VideoConverterInterface
36
{
37
    use PathAssertionsTrait;
38
39
    /** @var FFMpegConfigInterface */
40
    protected $ffmpegConfig;
41
42
    /** @var LoggerInterface */
43
    protected $logger;
44
45 19
    public function __construct(FFMpegConfigInterface $ffmpegConfig, ?LoggerInterface $logger = null)
46
    {
47 19
        $this->ffmpegConfig = $ffmpegConfig;
48
49 19
        $this->logger = $logger ?? new NullLogger();
50 19
    }
51
52
    /**
53
     * Return ready-to-run symfony process object that you can use
54
     * to `run()` or `start()` programmatically. Useful if you want to make
55
     * things async...
56
     *
57
     * @param null|string|UnescapedFileInterface $outputFile
58
     *
59
     * @see https://symfony.com/doc/current/components/process.html
60
     *
61
     * @throws CommonException\UnsupportedParamException
62
     * @throws CommonException\UnsupportedParamValueException
63
     * @throws InvalidArgumentException
64
     */
65 16
    public function getSymfonyProcess(string $inputFile, $outputFile, VideoConvertParamsInterface $convertParams, ?ProcessParamsInterface $processParams = null): Process
66
    {
67 16
        $adapter = $this->ffmpegConfig->getAdapter();
68
69 16
        if (!$convertParams->hasParam(VideoConvertParamsInterface::PARAM_THREADS)
70 16
            && $adapter->getDefaultThreads() !== null) {
71 1
            $convertParams = $convertParams->withBuiltInParam(
72 1
                VideoConvertParamsInterface::PARAM_THREADS,
73 1
                $adapter->getDefaultThreads()
74
            );
75
        }
76
77 16
        $arguments = $adapter->getMappedConversionParams($convertParams);
78
79
        try {
80 16
            $ffmpegCmd = $adapter->getCliCommand($arguments, $inputFile, $outputFile);
81 1
        } catch (CommonException\InvalidArgumentException $e) {
82 1
            throw new InvalidArgumentException($e->getMessage(), (int) $e->getCode(), $e);
83
        }
84
85 15
        $pp = $processParams ?? $this->ffmpegConfig->getProcessParams();
86
87 15
        return (new ProcessFactory($ffmpegCmd, $pp))();
88
    }
89
90
    /**
91
     * Run a conversion, throw exception on error.
92
     *
93
     * @param null|string|UnescapedFileInterface $outputFile
94
     * @param callable|null                      $callback   A PHP callback to run whenever there is some
95
     *                                                       tmp available on STDOUT or STDERR
96
     *
97
     * @throws ConverterExceptionInterface        Base exception class for conversion exceptions
98
     * @throws ConverterProcessExceptionInterface Base exception class for process conversion exceptions
99
     * @throws MissingInputFileException
100
     * @throws ProcessTimedOutException
101
     * @throws ProcessFailedException
102
     * @throws ProcessSignaledException
103
     * @throws InvalidParamException
104
     * @throws RuntimeReaderException
105
     */
106 14
    public function convert(string $inputFile, $outputFile, VideoConvertParamsInterface $convertParams, ?callable $callback = null, ?ProcessParamsInterface $processParams = null): void
107
    {
108
        try {
109
            try {
110 14
                $this->ensureFileReadable($inputFile);
111 12
                $process = $this->getSymfonyProcess($inputFile, $outputFile, $convertParams, $processParams);
112 12
                $process->mustRun($callback);
113 14
            } catch (CommonException\FileNotFoundException | CommonException\FileNotReadableException $e) {
114 2
                throw new MissingInputFileException($e->getMessage());
115 12
            } catch (CommonException\UnsupportedParamValueException | CommonException\UnsupportedParamException $e) {
116
                throw new InvalidParamException($e->getMessage());
117 12
            } catch (SPException\ProcessTimedOutException $e) {
118
                throw new ProcessTimedOutException($e->getProcess(), $e);
119 12
            } catch (SPException\ProcessSignaledException $e) {
120
                throw new ProcessSignaledException($e->getProcess(), $e);
121 12
            } catch (SPException\ProcessFailedException $e) {
122 12
                throw new ProcessFailedException($e->getProcess(), $e);
123
            } catch (SPException\RuntimeException $e) {
124
                throw new RuntimeReaderException($e->getMessage());
125
            }
126 14
        } catch (\Throwable $e) {
127 14
            $exceptionNs = explode('\\', get_class($e));
128 14
            $this->logger->log(
129 14
                ($e instanceof MissingInputFileException) ? LogLevel::WARNING : LogLevel::ERROR,
130 14
                sprintf(
131 14
                    'Video conversion failed \'%s\' with \'%s\'. "%s(%s, %s,...)"',
132 14
                    $exceptionNs[count($exceptionNs) - 1],
133 14
                    __METHOD__,
134 14
                    $e->getMessage(),
135 14
                    $inputFile,
136 14
                    $outputFile instanceof UnescapedFileInterface ? $outputFile->getFile() : $outputFile
137
                )
138
            );
139 14
            throw $e;
140
        }
141
    }
142
}
143