InterlaceDetectGuess   A
last analyzed

Complexity

Total Complexity 20

Size/Duplication

Total Lines 152
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 20
eloc 46
dl 0
loc 152
ccs 45
cts 45
cp 1
rs 10
c 0
b 0
f 0

9 Methods

Rating   Name   Duplication   Size   Complexity  
A isInterlacedBff() 0 5 2
A getStats() 0 3 1
A isInterlacedTff() 0 5 2
A isInterlaced() 0 3 2
A __construct() 0 20 2
A getBestGuess() 0 10 3
A getDeinterlaceVideoFilter() 0 15 4
A isUndetermined() 0 5 2
A isProgressive() 0 5 2
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-2020 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\Detection;
13
14
use Soluble\MediaTools\Video\Filter\EmptyVideoFilter;
15
use Soluble\MediaTools\Video\Filter\Type\VideoFilterInterface;
16
use Soluble\MediaTools\Video\Filter\YadifVideoFilter;
17
18
final class InterlaceDetectGuess
19
{
20
    /**
21
     * Default interlacing detection threshold
22
     * 25% frames detected interlaced is a sufficient
23
     * threshold to detect interlacing.
24
     */
25
    public const DEFAULT_DETECTION_THRESHOLD = 0.25;
26
27
    public const MODE_INTERLACED_BFF = 'INTERLACED_BFF';
28
    public const MODE_INTERLACED_TFF = 'INTERLACED_TFF';
29
    public const MODE_PROGRESSIVE    = 'PROGRESSIVE';
30
    public const MODE_UNDETERMINED   = 'UNDETERMINED';
31
32
    /** @var float */
33
    private $detection_threshold;
34
35
    /** @var int */
36
    private $total_frames;
37
38
    /** @var array<string, int> */
39
    private $detected_frames;
40
41
    /** @var array<string, float> */
42
    private $percent_frames;
43
44
    /**
45
     * @param float $detection_threshold in percent: i.e 0.8, 0.6...
46
     */
47 4
    public function __construct(
48
        int $nb_frames_interlaced_tff,
49
        int $nb_frames_interlaced_bff,
50
        int $nb_frames_progressive,
51
        int $nb_frames_undetermined,
52
        float $detection_threshold = self::DEFAULT_DETECTION_THRESHOLD
53
    ) {
54 4
        $this->detection_threshold = $detection_threshold;
55
        $detected_frames           = [
56 4
            self::MODE_INTERLACED_TFF => $nb_frames_interlaced_tff,
57 4
            self::MODE_INTERLACED_BFF => $nb_frames_interlaced_bff,
58 4
            self::MODE_PROGRESSIVE    => $nb_frames_progressive,
59 4
            self::MODE_UNDETERMINED   => $nb_frames_undetermined,
60
        ];
61 4
        arsort($detected_frames, SORT_NUMERIC);
62 4
        $this->detected_frames = $detected_frames;
63 4
        $this->total_frames    = (int) array_sum(array_values($this->detected_frames));
64 4
        $this->percent_frames  = [];
65 4
        foreach ($this->detected_frames as $key => $value) {
66 4
            $this->percent_frames[$key] = $value / $this->total_frames;
67
        }
68 4
    }
69
70
    /**
71
     * @return float[]
72
     */
73 3
    public function getStats(): array
74
    {
75 3
        return $this->percent_frames;
76
    }
77
78 3
    public function getBestGuess(?float $threshold = null): string
79
    {
80 3
        $min_pct = $threshold !== null ? $threshold : $this->detection_threshold;
81 3
        reset($this->detected_frames);
82 3
        $bestGuessKey = (string) key($this->detected_frames);
83 3
        if ($this->percent_frames[$bestGuessKey] >= $min_pct) {
84 3
            return $bestGuessKey;
85
        }
86
87 2
        return self::MODE_UNDETERMINED;
88
    }
89
90
    /**
91
     * Whether the video seems to be interlaced in TFF (top field first)
92
     * within a certain probability threshold.
93
     *
94
     * @param float|null $threshold
95
     *
96
     * @return bool
97
     */
98 4
    public function isInterlacedTff(?float $threshold = null): bool
99
    {
100 4
        $min_pct = $threshold !== null ? $threshold : $this->detection_threshold;
101
102 4
        return $this->percent_frames[self::MODE_INTERLACED_TFF] >= $min_pct;
103
    }
104
105
    /**
106
     * Whether the video seems to be interlaced in BFF (bottom field first)
107
     * within a certain probability threshold.
108
     *
109
     * @param float|null $threshold
110
     *
111
     * @return bool
112
     */
113 4
    public function isInterlacedBff(?float $threshold = null): bool
114
    {
115 4
        $min_pct = $threshold !== null ? $threshold : $this->detection_threshold;
116
117 4
        return $this->percent_frames[self::MODE_INTERLACED_BFF] >= $min_pct;
118
    }
119
120
    /**
121
     * Whether the video seems to be interlaced either in BFF (bottom field first)
122
     * or TFF (top field first) within a certain probability threshold.
123
     *
124
     * @param float|null $threshold
125
     *
126
     * @return bool
127
     */
128 2
    public function isInterlaced(?float $threshold = null): bool
129
    {
130 2
        return $this->isInterlacedBff($threshold) || $this->isInterlacedTff($threshold);
131
    }
132
133 2
    public function isProgressive(?float $threshold = null): bool
134
    {
135 2
        $min_pct = $threshold !== null ? $threshold : $this->detection_threshold;
136
137 2
        return $this->percent_frames[self::MODE_PROGRESSIVE] >= $min_pct;
138
    }
139
140 2
    public function isUndetermined(?float $threshold = null): bool
141
    {
142 2
        $min_pct = $threshold !== null ? $threshold : $this->detection_threshold;
143
144 2
        return $this->percent_frames[self::MODE_UNDETERMINED] >= $min_pct;
145
    }
146
147
    /**
148
     * @see https://ffmpeg.org/ffmpeg-filters.html (section yadif)
149
     * @see https://askubuntu.com/a/867203
150
     *
151
     * @param float|null $threshold
152
     *
153
     * @return EmptyVideoFilter|YadifVideoFilter
154
     */
155 1
    public function getDeinterlaceVideoFilter(?float $threshold = null): VideoFilterInterface
156
    {
157 1
        if (!$this->isInterlaced($threshold)) {
158 1
            return new EmptyVideoFilter();
159
        }
160 1
        $parity = YadifVideoFilter::DEFAULT_PARITY;
161 1
        if ($this->isInterlacedBff($threshold)) {
162
            // parity=1,  bff - Assume the bottom field is first.
163 1
            $parity = 1;
164 1
        } elseif ($this->isInterlacedTff($threshold)) {
165
            // parity=0,  tff - Assume the top field is first.
166 1
            $parity = 0;
167
        }
168
169 1
        return new YadifVideoFilter(YadifVideoFilter::DEFAULT_MODE, $parity, YadifVideoFilter::DEFAULT_DEINT);
170
    }
171
}
172