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

VideoInfoReader   A

Complexity

Total Complexity 8

Size/Duplication

Total Lines 81
Duplicated Lines 0 %

Test Coverage

Coverage 89.74%

Importance

Changes 0
Metric Value
wmc 8
eloc 42
dl 0
loc 81
ccs 35
cts 39
cp 0.8974
rs 10
c 0
b 0
f 0

3 Methods

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