|
|
|
@ -3,145 +3,14 @@
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import logging
|
|
|
|
|
import re
|
|
|
|
|
|
|
|
|
|
from app.config import get_settings
|
|
|
|
|
from app.database import TableCustomScoreProfileConditions as conditions_table, TableCustomScoreProfiles as \
|
|
|
|
|
profiles_table, database, select
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Condition:
|
|
|
|
|
"""Base class for score conditions. Every condition can take the amount
|
|
|
|
|
of attributes needed from a subtitle object in order to find a match."""
|
|
|
|
|
|
|
|
|
|
type = None
|
|
|
|
|
against = ()
|
|
|
|
|
|
|
|
|
|
# {type: provider, value: subdivx, required: False, negate: False}
|
|
|
|
|
def __init__(self, value: str, required=False, negate=False, **kwargs):
|
|
|
|
|
self._value = str(value)
|
|
|
|
|
self._negate = negate
|
|
|
|
|
self.required = required
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def from_dict(cls, item: dict) -> Condition:
|
|
|
|
|
"""A factory method to create a condition object from a database
|
|
|
|
|
dictionary."""
|
|
|
|
|
try:
|
|
|
|
|
new = _registered_conditions[item["type"]]
|
|
|
|
|
except IndexError:
|
|
|
|
|
raise NotImplementedError(f"{item} condition doesn't have a class.")
|
|
|
|
|
|
|
|
|
|
return new(**item)
|
|
|
|
|
|
|
|
|
|
def check(self, subtitle) -> bool:
|
|
|
|
|
"""Check if the condition is met against a Subtitle object. **May be implemented
|
|
|
|
|
in a subclass**."""
|
|
|
|
|
to_match = [str(getattr(subtitle, name, None)) for name in self.against]
|
|
|
|
|
met = any(item == self._value for item in to_match)
|
|
|
|
|
if met and not self._negate:
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
return not met and self._negate
|
|
|
|
|
|
|
|
|
|
def __repr__(self) -> str:
|
|
|
|
|
return f"<Condition {self.type}={self._value} (r:{self.required} n:{self._negate})>"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ProviderCondition(Condition):
|
|
|
|
|
type = "provider"
|
|
|
|
|
against = ("provider_name",)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class UploaderCondition(Condition):
|
|
|
|
|
type = "uploader"
|
|
|
|
|
against = ("uploader",)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class LanguageCondition(Condition):
|
|
|
|
|
type = "language"
|
|
|
|
|
against = ("language",)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class RegexCondition(Condition):
|
|
|
|
|
type = "regex"
|
|
|
|
|
against = ("release_info", "filename")
|
|
|
|
|
|
|
|
|
|
def check(self, subtitle):
|
|
|
|
|
to_match = [str(getattr(subtitle, name, None)) for name in self.against]
|
|
|
|
|
met = re.search(rf"{self._value}", "".join(to_match)) is not None
|
|
|
|
|
if met and not self._negate:
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
return not met and self._negate
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class CustomScoreProfile:
|
|
|
|
|
table = profiles_table
|
|
|
|
|
conditions_table = conditions_table
|
|
|
|
|
|
|
|
|
|
def __init__(self, id=None, name=None, score=0, media=None):
|
|
|
|
|
self.id = id
|
|
|
|
|
self.name = name or "N/A"
|
|
|
|
|
self.score = score
|
|
|
|
|
self.media = media
|
|
|
|
|
self._conditions = []
|
|
|
|
|
self._conditions_loaded = False
|
|
|
|
|
|
|
|
|
|
def load_conditions(self):
|
|
|
|
|
self._conditions = [
|
|
|
|
|
Condition.from_dict(item)
|
|
|
|
|
for item in database.execute(
|
|
|
|
|
select(self.conditions_table)
|
|
|
|
|
.where(self.conditions_table.profile_id == self.id))
|
|
|
|
|
.all()
|
|
|
|
|
]
|
|
|
|
|
if not self._conditions:
|
|
|
|
|
logger.debug("Conditions not found for %s", self)
|
|
|
|
|
self._conditions = []
|
|
|
|
|
|
|
|
|
|
self._conditions_loaded = True
|
|
|
|
|
|
|
|
|
|
def check(self, subtitle):
|
|
|
|
|
# Avoid calling the database on every score check
|
|
|
|
|
if not self._conditions_loaded:
|
|
|
|
|
self.load_conditions()
|
|
|
|
|
|
|
|
|
|
# Always return False if no conditions are set
|
|
|
|
|
if not self._conditions:
|
|
|
|
|
logger.debug("No conditions found in db for %s", self)
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
return self._check_conditions(subtitle)
|
|
|
|
|
|
|
|
|
|
def _check_conditions(self, subtitle):
|
|
|
|
|
logger.debug("Checking conditions for %s profile", self)
|
|
|
|
|
|
|
|
|
|
matches = []
|
|
|
|
|
for condition in self._conditions:
|
|
|
|
|
matched = condition.check(subtitle)
|
|
|
|
|
|
|
|
|
|
if matched is True:
|
|
|
|
|
logger.debug("%s Condition met", condition)
|
|
|
|
|
matches.append(True)
|
|
|
|
|
elif condition.required and not matched:
|
|
|
|
|
logger.debug("%s not met, discarding profile", condition)
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
met = True in matches
|
|
|
|
|
logger.debug("Profile conditions met? %s", met)
|
|
|
|
|
return met
|
|
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
|
return f"<ScoreProfile {self.name} (score: {self.score})>"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Score:
|
|
|
|
|
media = None
|
|
|
|
|
defaults = {}
|
|
|
|
|
profiles_table = profiles_table
|
|
|
|
|
|
|
|
|
|
def __init__(self, load_profiles=False, **kwargs):
|
|
|
|
|
self.data = self.defaults.copy()
|
|
|
|
@ -162,15 +31,8 @@ class Score:
|
|
|
|
|
matches.add(profile.name)
|
|
|
|
|
|
|
|
|
|
def load_profiles(self):
|
|
|
|
|
"""Load the profiles associated with the class. This method must be called
|
|
|
|
|
after every custom profile creation or update."""
|
|
|
|
|
self._profiles = [
|
|
|
|
|
CustomScoreProfile(**item)
|
|
|
|
|
for item in database.execute(
|
|
|
|
|
select(self.profiles_table)
|
|
|
|
|
.where(self.profiles_table.media == self.media))
|
|
|
|
|
.all()
|
|
|
|
|
]
|
|
|
|
|
self._profiles = []
|
|
|
|
|
|
|
|
|
|
if self._profiles:
|
|
|
|
|
logger.debug("Loaded profiles: %s", self._profiles)
|
|
|
|
|
else:
|
|
|
|
@ -273,12 +135,5 @@ class MovieScore(Score):
|
|
|
|
|
self.data.update(kwargs["movie_scores"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_registered_conditions = {
|
|
|
|
|
"provider": ProviderCondition,
|
|
|
|
|
"uploader": UploaderCondition,
|
|
|
|
|
"language": LanguageCondition,
|
|
|
|
|
"regex": RegexCondition,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
series_score = SeriesScore.from_config(**get_settings())
|
|
|
|
|
movie_score = MovieScore.from_config(**get_settings())
|
|
|
|
|