Passed
Push — master ( 47837a...ef23e7 )
by Darko
12:06
created

TvCategorizer::checkAnime()   C

Complexity

Conditions 13
Paths 7

Size

Total Lines 34
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 18
c 2
b 0
f 0
dl 0
loc 34
rs 6.6166
cc 13
nc 7
nop 1

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace App\Services\Categorization\Categorizers;
4
5
use App\Models\Category;
6
use App\Services\Categorization\CategorizationResult;
7
use App\Services\Categorization\ReleaseContext;
8
9
/**
10
 * Categorizer for TV content including HD, SD, UHD, Anime, Sports, Documentaries, etc.
11
 */
12
class TvCategorizer extends AbstractCategorizer
13
{
14
    protected int $priority = 20;
15
16
    public function getName(): string
17
    {
18
        return 'TV';
19
    }
20
21
    public function shouldSkip(ReleaseContext $context): bool
22
    {
23
        return $context->hasAdultMarkers();
24
    }
25
26
    public function categorize(ReleaseContext $context): CategorizationResult
27
    {
28
        $name = $context->releaseName;
29
30
        if (!$this->looksLikeTV($name)) {
31
            return $this->noMatch();
32
        }
33
34
        if ($result = $this->checkAnime($name)) {
35
            return $result;
36
        }
37
        if ($result = $this->checkSport($name)) {
38
            return $result;
39
        }
40
        if ($result = $this->checkDocumentary($name)) {
41
            return $result;
42
        }
43
        if ($result = $this->checkForeign($context)) {
44
            return $result;
45
        }
46
        if ($result = $this->checkX265($name)) {
47
            return $result;
48
        }
49
        if ($context->catWebDL && ($result = $this->checkWebDL($name))) {
50
            return $result;
51
        }
52
        if ($result = $this->checkUHD($name)) {
53
            return $result;
54
        }
55
        if ($result = $this->checkHD($name, $context->catWebDL)) {
56
            return $result;
57
        }
58
        if ($result = $this->checkSD($name)) {
59
            return $result;
60
        }
61
        if ($result = $this->checkOther($name)) {
62
            return $result;
63
        }
64
65
        return $this->noMatch();
66
    }
67
68
    protected function looksLikeTV(string $name): bool
69
    {
70
        // Season + Episode pattern: S01E01, S01.E01, S1D1, etc.
71
        if (preg_match('/[._ -]s\d{1,3}[._ -]?(e|d(isc)?)\d{1,3}([._ -]|$)/i', $name)) {
72
            return true;
73
        }
74
        // Episode-only pattern: .E01., .E02., E01.1080p (common in anime)
75
        if (preg_match('/[._ -]E\d{1,4}[._ -]/i', $name)) {
76
            return true;
77
        }
78
        // Season pack with Complete/Full: S01.Complete, S01.Full
79
        if (preg_match('/[._ -]S\d{1,3}[._ -]?(Complete|COMPLETE|Full|FULL)/i', $name)) {
80
            return true;
81
        }
82
        // Season pack with resolution/quality: S01.1080p, S01.720p, S01.2160p, S02.WEB-DL
83
        if (preg_match('/[._ -]S\d{1,3}[._ -](480p|720p|1080[pi]|2160p|4K|UHD|WEB|HDTV|BluRay|NF|AMZN|DSNP|ATVP|HMAX)/i', $name)) {
84
            return true;
85
        }
86
        // Episode pattern: Episode 01, Ep.01, Ep 1
87
        if (preg_match('/\b(Episode|Ep)[._ -]?\d{1,4}\b/i', $name)) {
88
            return true;
89
        }
90
        // TV source markers
91
        if (preg_match('/\b(HDTV|PDTV|DSR|TVRip|SATRip|DTHRip)\b/i', $name)) {
92
            return true;
93
        }
94
        // Daily show pattern: Show.Name.2024.01.15 or Show.Name.2024-01-15
95
        if (preg_match('/[._ -](19|20)\d{2}[._ -]\d{2}[._ -]\d{2}[._ -]/i', $name)) {
96
            return true;
97
        }
98
        // Known anime release groups (should be treated as TV)
99
        if (preg_match('/[.\-_ ](URANiME|ANiHLS|HaiKU|ANiURL|SkyAnime|Erai-raws|LostYears|Vodes|SubsPlease|Judas|Ember|EMBER|YuiSubs|ASW|Tsundere-Raws|Anime-Raws)[.\-_ ]?/i', $name)) {
100
            return true;
101
        }
102
        return false;
103
    }
104
105
    protected function checkAnime(string $name): ?CategorizationResult
106
    {
107
        if (preg_match('/[._ -]Anime[._ -]/i', $name)) {
108
            return $this->matched(Category::TV_ANIME, 0.95, 'anime_pattern');
109
        }
110
        // Known anime release groups - now matches anywhere in the name, not just at the end
111
        if (preg_match('/[.\-_ ](URANiME|ANiHLS|HaiKU|ANiURL|SkyAnime|Erai-raws|LostYears|Vodes|SubsPlease|Judas|Ember|EMBER|YuiSubs|ASW|Tsundere-Raws|Anime-Raws)[.\-_ ]?/i', $name)) {
112
            return $this->matched(Category::TV_ANIME, 0.95, 'anime_group');
113
        }
114
        // Anime hash pattern: [GroupName] Title - 01 [ABCD1234]
115
        if (preg_match('/^\[.+\].*\d{2,3}.*\[[a-fA-F0-9]{8}\]/i', $name)) {
116
            return $this->matched(Category::TV_ANIME, 0.9, 'anime_hash');
117
        }
118
        // Japanese title pattern with "no" particle (e.g., Shuumatsu.no.Valkyrie, Shingeki.no.Kyojin)
119
        // Combined with episode-only pattern (E05 without season prefix) or roman numeral season
120
        if (preg_match('/[._ ]no[._ ]/i', $name) &&
121
            (preg_match('/[._ ](I{1,3}|IV|V|VI{0,3}|IX|X)[._ ]?E\d{1,4}[._ ]/i', $name) ||
122
             (preg_match('/[._ ]E\d{1,4}[._ ]/i', $name) && !preg_match('/[._ ]S\d{1,3}[._ ]?E\d/i', $name)))) {
123
            return $this->matched(Category::TV_ANIME, 0.9, 'anime_japanese_title');
124
        }
125
        // Episode pattern with known anime indicators
126
        if (preg_match('/[._ -]E\d{1,4}[._ -]/i', $name) &&
127
            preg_match('/\b(BluRay|BD|BDRip)\b/i', $name) &&
128
            !preg_match('/\bS\d{1,3}\b/i', $name)) {
129
            // Episode-only pattern with BluRay but no season - likely anime
130
            return $this->matched(Category::TV_ANIME, 0.8, 'anime_episode_bluray');
131
        }
132
        // Roman numeral season with episode-only pattern (common in anime)
133
        // e.g., Title.III.E05, Title.II.E12 - typically anime naming convention
134
        if (preg_match('/[._ ](I{1,3}|IV|V|VI{0,3}|IX|X)[._ ]E\d{1,4}[._ ]/i', $name) &&
135
            !preg_match('/[._ ]S\d{1,3}[._ ]?E\d/i', $name)) {
136
            return $this->matched(Category::TV_ANIME, 0.85, 'anime_roman_numeral_season');
137
        }
138
        return null;
139
    }
140
141
    protected function checkSport(string $name): ?CategorizationResult
142
    {
143
        if (preg_match('/\b(NFL|NBA|NHL|MLB|MLS|UFC|WWE|Boxing|F1|Formula[._ -]?1|NASCAR|PGA|Tennis|Golf|Soccer|Football|Cricket|Rugby)\b/i', $name) &&
144
            preg_match('/\d{4}|\b(Season|Week|Round|Match|Game)\b/i', $name)) {
145
            return $this->matched(Category::TV_SPORT, 0.85, 'sport');
146
        }
147
        return null;
148
    }
149
150
    protected function checkDocumentary(string $name): ?CategorizationResult
151
    {
152
        if (preg_match('/\b(Documentary|Docu[._ -]?Series|DOCU)\b/i', $name)) {
153
            return $this->matched(Category::TV_DOCU, 0.85, 'documentary');
154
        }
155
        return null;
156
    }
157
158
    protected function checkForeign(ReleaseContext $context): ?CategorizationResult
159
    {
160
        if (!$context->categorizeForeign) {
161
            return null;
162
        }
163
        if (preg_match('/(danish|flemish|Deutsch|dutch|french|german|hebrew|nl[._ -]?sub|dub(bed|s)?|\.NL|norwegian|swedish|swesub|spanish|Staffel)[._ -]|\(german\)|Multisub/i', $context->releaseName)) {
164
            return $this->matched(Category::TV_FOREIGN, 0.8, 'foreign_language');
165
        }
166
        return null;
167
    }
168
169
    protected function checkX265(string $name): ?CategorizationResult
170
    {
171
        if (preg_match('/(S\d+).*(x265).*(rmteam|MeGusta|HETeam|PSA|ONLY|H4S5S|TrollHD|ImE)/i', $name)) {
172
            return $this->matched(Category::TV_X265, 0.9, 'x265_group');
173
        }
174
        return null;
175
    }
176
177
    protected function checkWebDL(string $name): ?CategorizationResult
178
    {
179
        if (preg_match('/web[._ -]dl|web-?rip/i', $name)) {
180
            return $this->matched(Category::TV_WEBDL, 0.85, 'webdl');
181
        }
182
        return null;
183
    }
184
185
    protected function checkUHD(string $name): ?CategorizationResult
186
    {
187
        // Single episode UHD
188
        if (preg_match('/S\d+[._ -]?E\d+/i', $name) && preg_match('/2160p/i', $name)) {
189
            return $this->matched(Category::TV_UHD, 0.9, 'uhd_episode');
190
        }
191
        // Season pack UHD
192
        if (preg_match('/[._ -]S\d+[._ -].*2160p/i', $name)) {
193
            return $this->matched(Category::TV_UHD, 0.9, 'uhd_season');
194
        }
195
        // UHD with streaming service markers
196
        if (preg_match('/(S\d+).*(2160p).*(Netflix|Amazon|NF|AMZN).*(TrollUHD|NTb|VLAD|DEFLATE|POFUDUK|CMRG)/i', $name)) {
197
            return $this->matched(Category::TV_UHD, 0.9, 'uhd_streaming');
198
        }
199
        return null;
200
    }
201
202
    protected function checkHD(string $name, bool $catWebDL): ?CategorizationResult
203
    {
204
        if (preg_match('/1080([ip])|720p|bluray/i', $name)) {
205
            return $this->matched(Category::TV_HD, 0.85, 'hd_resolution');
206
        }
207
        if (!$catWebDL && preg_match('/web[._ -]dl|web-?rip/i', $name)) {
208
            return $this->matched(Category::TV_HD, 0.8, 'hd_webdl_fallback');
209
        }
210
        return null;
211
    }
212
213
    protected function checkSD(string $name): ?CategorizationResult
214
    {
215
        if (preg_match('/(360|480|576)p|Complete[._ -]Season|dvdr(ip)?|dvd5|dvd9|\.pdtv|SD[._ -]TV|TVRip|NTSC|BDRip|hdtv|xvid/i', $name)) {
216
            return $this->matched(Category::TV_SD, 0.8, 'sd_format');
217
        }
218
        if (preg_match('/(([HP])D[._ -]?TV|DSR|WebRip)[._ -]x264/i', $name)) {
219
            return $this->matched(Category::TV_SD, 0.8, 'sd_codec');
220
        }
221
        return null;
222
    }
223
224
    protected function checkOther(string $name): ?CategorizationResult
225
    {
226
        // Season + episode pattern
227
        if (preg_match('/[._ -]s\d{1,3}[._ -]?(e|d(isc)?)\d{1,3}([._ -]|$)/i', $name)) {
228
            return $this->matched(Category::TV_OTHER, 0.6, 'tv_other');
229
        }
230
        // Season pack pattern (S01, S02, etc.) with any quality marker
231
        if (preg_match('/[._ -]S\d{1,3}[._ -]/i', $name)) {
232
            return $this->matched(Category::TV_OTHER, 0.6, 'tv_season_pack');
233
        }
234
        return null;
235
    }
236
}
237