Completed
Push — master ( 252985...6cc43c )
by Sébastien
02:56 queued 14s
created

VideoInfoReader   A

Complexity

Total Complexity 11

Size/Duplication

Total Lines 89
Duplicated Lines 0 %

Test Coverage

Coverage 93.33%

Importance

Changes 0
Metric Value
wmc 11
eloc 48
dl 0
loc 89
ccs 42
cts 45
cp 0.9333
rs 10
c 0
b 0
f 0

3 Methods

Rating   Name   Duplication   Size   Complexity  
B getInfo() 0 39 9
A getSymfonyProcess() 0 17 1
A __construct() 0 4 1
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\FileNotFoundException;
19
use Soluble\MediaTools\Common\Exception\FileNotReadableException;
20
use Soluble\MediaTools\Common\Process\ProcessFactory;
21
use Soluble\MediaTools\Common\Process\ProcessParamsInterface;
22
use Soluble\MediaTools\Video\Config\FFProbeConfigInterface;
23
use Soluble\MediaTools\Video\Exception\InfoProcessReaderExceptionInterface;
24
use Soluble\MediaTools\Video\Exception\InfoReaderExceptionInterface;
25
use Soluble\MediaTools\Video\Exception\MissingFFProbeBinaryException;
26
use Soluble\MediaTools\Video\Exception\MissingInputFileException;
27
use Soluble\MediaTools\Video\Exception\ProcessFailedException;
28
use Soluble\MediaTools\Video\Exception\RuntimeReaderException;
29
use Symfony\Component\Process\Exception as SPException;
30
use Symfony\Component\Process\Process;
31
32
class VideoInfoReader implements VideoInfoReaderInterface
33
{
34
    use PathAssertionsTrait;
35
36
    /** @var FFProbeConfigInterface */
37
    private $ffprobeConfig;
38
39
    /** @var LoggerInterface */
40
    private $logger;
41
42 27
    public function __construct(FFProbeConfigInterface $ffProbeConfig, ?LoggerInterface $logger = null)
43
    {
44 27
        $this->ffprobeConfig = $ffProbeConfig;
45 27
        $this->logger        = $logger ?? new NullLogger();
46 27
    }
47
48
    /**
49
     * Return ready-to-run symfony process object that you can use
50
     * to `run()` or `start()` programmatically. Useful if you want to make
51
     * things your way...
52
     *
53
     * @see https://symfony.com/doc/current/components/process.html
54
     */
55 9
    public function getSymfonyProcess(string $inputFile, ?ProcessParamsInterface $processParams = null): Process
56
    {
57
        $ffprobeCmd = [
58 9
            $this->ffprobeConfig->getBinary(),
59 9
            '-v',
60 9
            'quiet',
61 9
            '-print_format',
62 9
            'json',
63 9
            '-show_format',
64 9
            '-show_streams',
65 9
            '-i',
66 9
            $inputFile,
67
        ];
68
69 9
        $pp = $processParams ?? $this->ffprobeConfig->getProcessParams();
70
71 9
        return (new ProcessFactory($ffprobeCmd, $pp))();
72
    }
73
74
    /**
75
     * @throws InfoReaderExceptionInterface
76
     * @throws InfoProcessReaderExceptionInterface
77
     * @throws ProcessFailedException
78
     * @throws MissingInputFileException
79
     * @throws MissingFFProbeBinaryException
80
     * @throws RuntimeReaderException
81
     */
82 11
    public function getInfo(string $file): VideoInfo
83
    {
84
        try {
85
            try {
86 11
                $this->ensureFileReadable($file);
87 9
                $process = $this->getSymfonyProcess($file);
88
89 9
                $process->mustRun();
90 7
                $output = $process->getOutput();
91 4
            } catch (FileNotFoundException | FileNotReadableException $e) {
92 2
                throw new MissingInputFileException($e->getMessage());
93 2
            } catch (SPException\ProcessFailedException $e) {
94 2
                $process = $e->getProcess();
95 2
                if ($process->getExitCode() === 127 ||
96 2
                    mb_strpos(mb_strtolower($process->getExitCodeText()), 'command not found') !== false) {
97 1
                    throw new MissingFFProbeBinaryException($process, $e);
98
                }
99 1
                throw new ProcessFailedException($process, $e);
100
            } catch (SPException\ProcessTimedOutException | SPException\ProcessSignaledException $e) {
101
                throw new ProcessFailedException($e->getProcess(), $e);
102
            } catch (SPException\RuntimeException $e) {
103 7
                throw new RuntimeReaderException($e->getMessage());
104
            }
105 4
        } catch (\Throwable $e) {
106 4
            $exceptionNs = explode('\\', get_class($e));
107 4
            $this->logger->log(
108 4
                ($e instanceof MissingInputFileException) ? LogLevel::WARNING : LogLevel::ERROR,
109 4
                sprintf(
110 4
                    'Video info retrieval failed \'%s\' with \'%s\'. "%s(%s)"',
111 4
                    $exceptionNs[count($exceptionNs) - 1],
112 4
                    __METHOD__,
113 4
                    $e->getMessage(),
114 4
                    $file
115
                )
116
            );
117 4
            throw $e;
118
        }
119
120 7
        return VideoInfo::createFromFFProbeJson($file, $output, $this->logger);
121
    }
122
}
123