Files
newznab-tmux/app/Services/Categorization/Categorizers/MovieCategorizer.php
T
2026-08-11 12:35:48 +02:00

229 lines
7.4 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Services\Categorization\Categorizers;
use App\Models\Category;
use App\Services\Categorization\CategorizationResult;
use App\Services\Categorization\ReleaseContext;
/**
* Categorizer for Movie content including HD, SD, UHD, 3D, Blu-ray, DVD, etc.
*/
class MovieCategorizer extends AbstractCategorizer
{
protected int $priority = 25;
public function getName(): string
{
return 'Movie';
}
public function shouldSkip(ReleaseContext $context): bool
{
// Skip if this looks like adult content
if ($context->hasAdultMarkers()) {
return true;
}
// A standalone Sxx token identifies a full TV season even when year or
// edition markers appear before the source and quality markers.
if ($context->hasStandaloneSeasonToken()) {
return true;
}
// Skip if it looks like a TV episode (S01E01) or season pack (S01.1080p)
if (preg_match('/[._ -]S\d{1,3}[._ -]?(E\d|D\d|Complete|Full|1080|720|480|2160|WEB|HDTV|BluRay|NF|AMZN)/i', $context->releaseName)) {
return true;
}
// Skip episode-only patterns (E01, E02) - common in anime
if (preg_match('/[._ -]E\d{1,4}[._ -]/i', $context->releaseName)) {
return true;
}
// Skip known anime release groups
if (preg_match('/(?:^|[.\-_ \[])(URANiME|ANiHLS|HaiKU|ANiURL|SkyAnime|Erai-raws|LostYears|Vodes|SubsPlease|Judas|Ember|YuiSubs|ASW|Tsundere-Raws|Anime-Raws)(?:[.\-_ \]]|$)/i', $context->releaseName)) {
return true;
}
return false;
}
public function categorize(ReleaseContext $context): CategorizationResult
{
$name = $context->releaseName;
// Check if it looks like movie content
if (! $this->looksLikeMovie($name)) {
return $this->noMatch();
}
// Try specific movie subcategories in order of specificity
if ($context->categorizeForeign && ($result = $this->checkForeign($name))) {
return $result;
}
if ($result = $this->checkX265($name)) {
return $result;
}
if ($result = $this->checkUHD($name)) {
return $result;
}
if ($result = $this->check3D($name)) {
return $result;
}
if ($result = $this->checkBluRay($name)) {
return $result;
}
if ($result = $this->checkDVD($name)) {
return $result;
}
if ($context->catWebDL && ($result = $this->checkWebDL($name))) {
return $result;
}
if ($result = $this->checkHD($name, $context->catWebDL)) {
return $result;
}
if ($result = $this->checkSD($name)) {
return $result;
}
if ($result = $this->checkOther($name)) {
return $result;
}
return $this->noMatch();
}
/**
* Check if release name looks like movie content.
*/
protected function looksLikeMovie(string $name): bool
{
return (bool) preg_match('/[._ -]AVC|[BH][DR]RIP|(Bluray|Blu-Ray)|BD[._ -]?(25|50)?|\bBR\b|Camrip|[._ -]\d{4}[._ -].+(720p|1080p|Cam|HDTS|2160p)|DIVX|[._ -]DVD[._ -]|DVD-?(5|9|R|Rip)|Untouched|VHSRip|XVID|[._ -](DTS|TVrip|webrip|WEBDL|WEB-DL)[._ -]|\b(2160)p\b.*\b(Netflix|Amazon|NF|AMZN|Disney)\b/i', $name);
}
protected function checkForeign(string $name): ?CategorizationResult
{
if (preg_match('/(danish|flemish|Deutsch|dutch|french|german|heb|hebrew|nl[._ -]?sub|dub(bed|s)?|\.NL|norwegian|swedish|swesub|spanish|Staffel)[._ -]|\(german\)|Multisub/i', $name)) {
return $this->matched(Category::MOVIE_FOREIGN, 0.8, 'foreign_language');
}
if (stripos($name, 'Castellano') !== false) {
return $this->matched(Category::MOVIE_FOREIGN, 0.8, 'foreign_castellano');
}
if (preg_match('/(720p|1080p|AC3|AVC|DIVX|DVD(5|9|RIP|R)|XVID)[._ -](Dutch|French|German|ITA)|\(?(Dutch|French|German|ITA)\)?[._ -](720P|1080p|AC3|AVC|DIVX|DVD(5|9|RIP|R)|WEB(-DL|-?RIP)|HD[._ -]|XVID)/i', $name)) {
return $this->matched(Category::MOVIE_FOREIGN, 0.85, 'foreign_pattern');
}
return null;
}
protected function checkX265(string $name): ?CategorizationResult
{
if (preg_match('/(\w+[\.-_\s]+).*(x265).*(Tigole|SESKAPiLE|CHD|IAMABLE|THREESOME|OohLaLa|DEFLATE|NCmt)/i', $name)) {
return $this->matched(Category::MOVIE_X265, 0.9, 'x265_group');
}
return null;
}
protected function checkUHD(string $name): ?CategorizationResult
{
// Skip TV shows
if (preg_match('/(S\d+).*(2160p).*(Netflix|Amazon|NF|AMZN).*(TrollUHD|NTb|VLAD|DEFLATE|CMRG)/i', $name)) {
return null;
}
// Check for UHD indicators
if (stripos($name, '2160p') !== false ||
preg_match('/\b(UHD|Ultra[._ -]HD|4K)\b/i', $name) ||
(preg_match('/\b(HDR|HDR10|HDR10\+|Dolby[._ -]?Vision)\b/i', $name) &&
preg_match('/\b(HEVC|H\.?265|x265)\b/i', $name)) ||
(stripos($name, 'UHD') !== false &&
preg_match('/\b(BR|BluRay|Blu[._ -]?Ray)\b/i', $name))) {
return $this->matched(Category::MOVIE_UHD, 0.9, 'uhd');
}
return null;
}
protected function check3D(string $name): ?CategorizationResult
{
if (preg_match('/[._ -]3D\s?[\.\-_\[ ](1080p|(19|20)\d\d|AVC|BD(25|50)|Blu[._ -]?ray|CEE|Complete|GER|MVC|MULTi|SBS|H(-)?SBS)[._ -]/i', $name)) {
return $this->matched(Category::MOVIE_3D, 0.9, '3d');
}
return null;
}
protected function checkBluRay(string $name): ?CategorizationResult
{
if (preg_match('/bluray-|[._ -]bd?[._ -]?(25|50)|blu-ray|Bluray\s-\sUntouched|[._ -]untouched[._ -]/i', $name) &&
! preg_match('/SecretUsenet\.com$/i', $name)) {
return $this->matched(Category::MOVIE_BLURAY, 0.9, 'bluray');
}
return null;
}
protected function checkDVD(string $name): ?CategorizationResult
{
if (preg_match('/(dvd\-?r|[._ -]dvd|dvd9|dvd5|[._ -]r5)[._ -]/i', $name)) {
return $this->matched(Category::MOVIE_DVD, 0.85, 'dvd');
}
return null;
}
protected function checkWebDL(string $name): ?CategorizationResult
{
if (preg_match('/web[._ -]dl|web-?rip/i', $name)) {
return $this->matched(Category::MOVIE_WEBDL, 0.85, 'webdl');
}
return null;
}
protected function checkHD(string $name, bool $catWebDL): ?CategorizationResult
{
if (preg_match('/720p|1080p|AVC|VC1|VC-1|web-dl|wmvhd|x264|XvidHD|bdrip/i', $name)) {
return $this->matched(Category::MOVIE_HD, 0.85, 'hd');
}
if (! $catWebDL && preg_match('/web[._ -]dl|web-?rip/i', $name)) {
return $this->matched(Category::MOVIE_HD, 0.8, 'hd_webdl_fallback');
}
return null;
}
protected function checkSD(string $name): ?CategorizationResult
{
if (preg_match('/(divx|dvdscr|extrascene|dvdrip|\.CAM|HDTS(-LINE)?|vhsrip|xvid(vd)?)[._ -]/i', $name)) {
return $this->matched(Category::MOVIE_SD, 0.8, 'sd');
}
return null;
}
protected function checkOther(string $name): ?CategorizationResult
{
if (preg_match('/[._ -]cam[._ -]/i', $name)) {
return $this->matched(Category::MOVIE_OTHER, 0.6, 'cam');
}
return null;
}
}