You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
198 lines
6.0 KiB
198 lines
6.0 KiB
# coding=utf-8
|
|
|
|
from __future__ import absolute_import
|
|
import logging
|
|
|
|
from subliminal.video import Episode, Movie
|
|
from subliminal.score import get_scores
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
FPS_EQUALITY = ((23.976, 23.98, 24.0),)
|
|
|
|
|
|
def framerate_equal(source, check):
|
|
if source == check:
|
|
return True
|
|
|
|
source = float(source)
|
|
check = float(check)
|
|
if source == check:
|
|
return True
|
|
|
|
for equal_fps_tuple in FPS_EQUALITY:
|
|
if check in equal_fps_tuple and source in equal_fps_tuple:
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
DEFAULT_SCORES = {
|
|
"episode": {
|
|
"hash": 359,
|
|
"series": 180,
|
|
"year": 90,
|
|
"season": 30,
|
|
"episode": 30,
|
|
"release_group": 14,
|
|
"source": 7,
|
|
"audio_codec": 3,
|
|
"resolution": 2,
|
|
"video_codec": 2,
|
|
"streaming_service": 1,
|
|
"hearing_impaired": 1,
|
|
},
|
|
"movie": {
|
|
"hash": 119,
|
|
"title": 60,
|
|
"year": 30,
|
|
"release_group": 13,
|
|
"source": 7,
|
|
"audio_codec": 3,
|
|
"resolution": 2,
|
|
"video_codec": 2,
|
|
"streaming_service": 1,
|
|
"edition": 1,
|
|
"hearing_impaired": 1,
|
|
},
|
|
}
|
|
|
|
|
|
def _check_hash_sum(scores: dict):
|
|
hash_val = scores["hash"]
|
|
rest_sum = sum(val for key, val in scores.items() if key != "hash")
|
|
logger.debug("Hash value: %s -> Rest sum: %s", hash_val, rest_sum)
|
|
return rest_sum - 1 == hash_val
|
|
|
|
|
|
class ComputeScore:
|
|
def __init__(self, scores=None):
|
|
if scores:
|
|
valid = True
|
|
for val in scores.values():
|
|
if not _check_hash_sum(val):
|
|
logger.debug("Scores sum - 1 is not equal to hash. Using defaults")
|
|
self._scores = DEFAULT_SCORES
|
|
valid = False
|
|
break
|
|
|
|
if valid is True:
|
|
self._scores = scores
|
|
else:
|
|
self._scores = DEFAULT_SCORES
|
|
|
|
# Hash values should be the same. Update from defaults to ensure it
|
|
for key in self._scores.keys():
|
|
self._scores[key]["hash"] = DEFAULT_SCORES[key]["hash"]
|
|
|
|
def __call__(self, matches, subtitle, video, hearing_impaired=None):
|
|
scores = self._scores[video.__class__.__name__.lower()]
|
|
logger.debug("Scores to use for %s: %s", video, scores)
|
|
|
|
is_episode = isinstance(video, Episode)
|
|
is_movie = isinstance(video, Movie)
|
|
|
|
episode_hash_valid_if = {"series", "season", "episode", "source"}
|
|
movie_hash_valid_if = {"video_codec", "source"}
|
|
|
|
orig_matches = matches.copy()
|
|
|
|
# on hash match, discard everything else
|
|
if subtitle.hash_verifiable and "hash" in matches:
|
|
# hash is error-prone, try to fix that
|
|
hash_valid_if = episode_hash_valid_if if is_episode else movie_hash_valid_if
|
|
|
|
# don't validate hashes of specials, as season and episode tend to be wrong
|
|
if is_movie or not video.is_special:
|
|
if hash_valid_if <= set(matches):
|
|
# series, season and episode matched, hash is valid
|
|
logger.debug(
|
|
"%r: Using valid hash, as %s are correct (%r) and (%r)",
|
|
subtitle,
|
|
hash_valid_if,
|
|
matches,
|
|
video,
|
|
)
|
|
matches &= {"hash"}
|
|
else:
|
|
# no match, invalidate hash
|
|
logger.debug(
|
|
"%r: Ignoring hash as other matches are wrong (missing: %r) and (%r)",
|
|
subtitle,
|
|
hash_valid_if - matches,
|
|
video,
|
|
)
|
|
matches -= {"hash"}
|
|
elif "hash" in matches:
|
|
logger.debug(
|
|
"%r: Hash not verifiable for this provider. Keeping it", subtitle
|
|
)
|
|
matches &= {"hash"}
|
|
|
|
# handle equivalent matches
|
|
eq_matches = set()
|
|
if is_episode:
|
|
_episode_checks(video, eq_matches, matches)
|
|
elif is_movie and "imdb_id" in matches:
|
|
logger.debug("Adding imdb_id match equivalents")
|
|
eq_matches |= {"title", "year"}
|
|
|
|
matches |= eq_matches
|
|
|
|
# handle hearing impaired
|
|
if (
|
|
hearing_impaired is not None
|
|
and subtitle.hearing_impaired == hearing_impaired
|
|
):
|
|
logger.debug("Matched hearing_impaired")
|
|
matches.add("hearing_impaired")
|
|
orig_matches.add("hearing_impaired")
|
|
|
|
# compute the score
|
|
score = sum((scores.get(match, 0) for match in matches))
|
|
logger.info(
|
|
"%r: Computed score %r with final matches %r", subtitle, score, matches
|
|
)
|
|
|
|
score_without_hash = sum(
|
|
(
|
|
scores.get(match, 0)
|
|
for match in orig_matches | eq_matches
|
|
if match != "hash"
|
|
)
|
|
)
|
|
|
|
return score, score_without_hash
|
|
|
|
|
|
def _episode_checks(video, eq_matches, matches):
|
|
if "title" in matches:
|
|
logger.debug("Adding title match equivalent")
|
|
eq_matches.add("episode")
|
|
if "series_imdb_id" in matches:
|
|
logger.debug("Adding series_imdb_id match equivalent")
|
|
eq_matches |= {"series", "year"}
|
|
if "imdb_id" in matches:
|
|
logger.debug("Adding imdb_id match equivalents")
|
|
eq_matches |= {"series", "year", "season", "episode"}
|
|
if "tvdb_id" in matches:
|
|
logger.debug("Adding tvdb_id match equivalents")
|
|
eq_matches |= {"series", "year", "season", "episode", "title"}
|
|
if "series_tvdb_id" in matches:
|
|
logger.debug("Adding series_tvdb_id match equivalents")
|
|
eq_matches |= {"series", "year"}
|
|
|
|
# specials
|
|
if (
|
|
video.is_special
|
|
and "title" in matches
|
|
and "series" in matches
|
|
and "year" in matches
|
|
):
|
|
logger.debug("Adding special title match equivalent")
|
|
eq_matches |= {"season", "episode"}
|
|
|
|
|
|
compute_score = ComputeScore()
|