Completed
Push — master ( 477c6b...8b3cf8 )
by Sébastien
04:39 queued 01:53
created

VideoProbe::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 5
ccs 4
cts 4
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Soluble\MediaTools;
6
7
use Soluble\MediaTools\Config\FFMpegConfig;
8
use Soluble\MediaTools\Config\FFProbeConfig;
9
use Soluble\MediaTools\Detection\InterlacingGuess;
10
use Soluble\MediaTools\Exception\FileNotFoundException;
11
use Symfony\Component\Process\Process;
12
13
class VideoProbe
14
{
15
    /** @var FFProbeConfig */
16
    protected $ffprobeConfig;
17
18
    /** @var FFMpegConfig */
19
    protected $ffmpegConfig;
20
21
    /** @var mixed[] */
22
    protected $cache = [];
23
24 1
    public function __construct(FFProbeConfig $ffProbeConfig, FFMpegConfig $ffmpegConfig)
25
    {
26 1
        $this->ffprobeConfig = $ffProbeConfig;
27 1
        $this->ffmpegConfig  = $ffmpegConfig;
28 1
        $this->ffprobeConfig->getProcess()->ensureBinaryExists();
29 1
    }
30
31
    public function getMediaInfo(string $file): VideoInfo
32
    {
33
        $this->ensureFileExists($file);
34
        $process = $this->ffprobeConfig->getProcess();
35
        $cmd     = $process->buildCommand([
36
            '-v quiet',
37
            '-print_format json',
38
            '-show_format',
39
            '-show_streams',
40
            sprintf('-i "%s"', $file),
41
        ]);
42
43
        try {
44
            $jsonOutput = $process->runCommand($cmd);
45
        } catch (\Throwable $e) {
46
            //$msg = $e->getMessage();
0 ignored issues
show
Unused Code Comprehensibility introduced by
60% 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...
47
            throw $e;
48
        }
49
50
        return VideoInfo::createFromFFProbeJson($file, $jsonOutput);
51
    }
52
53
    public function guessInterlacing(string $file, float $threshold = InterlacingGuess::INTERLACING_DETECTION_THRESHOLD, int $framesToAnalyze = 1000): InterlacingGuess
54
    {
55
        $cache_key = md5(sprintf('%s:%s:%s:%s', __METHOD__, $file, $threshold, $framesToAnalyze));
56
        if (isset($this->cache[$cache_key]) && $this->cache[$cache_key] instanceof InterlacingGuess) {
57
            return $this->cache[$cache_key];
58
        }
59
60
        $nbFrames       = $this->getMediaInfo($file)->getNbFrames();
61
        $analyzedFrames = ($nbFrames > 0)
62
                            ? min($framesToAnalyze, $nbFrames)
63
                            : $framesToAnalyze;
64
65
        // Step 2: Using frame detection
66
        $ffmpegProcess = $this->ffmpegConfig->getProcess();
67
68
        $ffmpegCmd = $ffmpegProcess->buildCommand(
69
            [
70
                sprintf('-i %s', escapeshellarg($file)),
71
                '-filter idet',
72
                sprintf('-frames:v %d', $analyzedFrames),
73
                '-an', // audio can be discarded
74
                '-f rawvideo', // output in raw
75
                '-y /dev/null', // discard the output
76
            ]
77
        );
78
79
        $process = new Process($ffmpegCmd);
80
        $process->mustRun();
81
        $stdErr = preg_split("/(\r\n|\n|\r)/", $process->getErrorOutput());
82
83
        // Counted frames
84
        $interlaced_tff = 0;
85
        $interlaced_bff = 0;
86
        $progressive    = 0;
87
        $undetermined   = 0;
88
        $total_frames   = 0;
89
90
        if ($stdErr !== false) {
91
            foreach ($stdErr as $line) {
92
                if (mb_substr($line, 0, 12) !== '[Parsed_idet') {
93
                    continue;
94
                }
95
96
                $unspaced = preg_replace('/( )+/', '', $line);
97
                $matches  = [];
98
                if (preg_match_all('/TFF:(\d+)BFF:(\d+)Progressive:(\d+)Undetermined:(\d+)/i', $unspaced, $matches) < 1) {
99
                    continue;
100
                }
101
102
                //$type = strpos(strtolower($unspaced), 'single') ? 'single' : 'multi';
0 ignored issues
show
Unused Code Comprehensibility introduced by
57% 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...
103
                $interlaced_tff += (int) $matches[1][0];
104
                $interlaced_bff += (int) $matches[2][0];
105
                $progressive += (int) $matches[3][0];
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
106
                $undetermined += (int) $matches[4][0];
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
107
                $total_frames += ((int) $matches[1][0] + (int) $matches[2][0] + (int) $matches[3][0] + (int) $matches[4][0]);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
108
            }
109
        }
110
111
        $guess                   = new InterlacingGuess($interlaced_tff, $interlaced_bff, $progressive, $undetermined);
112
        $this->cache[$cache_key] = $guess;
113
114
        return $guess;
115
    }
116
117
    /**
118
     * Use ffprobe to determine if a video is POTENTIALLY interlaced...
119
     * Warning:
120
     *  - There's no absolute truth in that. but if false it's not
121
     *  - Very slow.
122
     */
123
    /*
0 ignored issues
show
Unused Code Comprehensibility introduced by
51% 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...
124
    public function isVideoPotentiallyInterlaced(string $file): bool {
125
126
        $this->ensureFileExists($file);
127
128
        // Step 1: ffprobe to see if interlaced
129
        $cmd = $this->buildFFProbeCommand([
130
            sprintf('-i "%s"', $file),
131
            '-v quiet',             // do not display any messages
132
            '-select_streams v',    // select video stream
133
            '-show_entries "frame=pkt_pts_time,pkt_duration_time,interlaced_frame"',
134
            '-print_format json'
135
        ]);
136
137
        $output = $this->runCommand($cmd);
138
139
        $decoded = json_decode($output, true);
140
        if ($decoded === null || $output === '') {
141
            throw new JsonParseException(sprintf(
142
                'Cannot parse output from ffprobe (%s)', $cmd
143
            ));
144
        }
145
        $frame_stats = [
146
            'nb_frames' => count($decoded['frames']),
147
            'nb_interlaced' => 0,
148
        ];
149
        foreach ($decoded['frames'] as $frame) {
150
            if ((int) $frame['interlaced_frame'] === 1) {
151
                $frame_stats['nb_interlaced']++;
152
            }
153
        }
154
155
156
        if ($frame_stats['nb_interlaced'] === 0 && $frame_stats['nb_frames'] > 0) {
157
            // here we know for sure it is not
158
            return false;
159
        }
160
161
        return true;
162
    }
163
    */
164
165
    protected function ensureFileExists(string $file): void
166
    {
167
        if (!file_exists($file)) {
168
            throw new FileNotFoundException(sprintf('File "%s" does not exists or is not readable', $file));
169
        }
170
    }
171
}
172