mirror of
https://github.com/morpheus65535/bazarr
synced 2024-12-23 08:13:14 +00:00
ec85f6e172
* Backup files should be listed with newest ones first Just like Sonarr and Radarr and everyone else. * Add check_parser_binary() validation method This is mainly to prevent the user from selecting mediainfo as the subtitles parser if it has not yet been installed on the user's system somewhere in the PATH. * import JSONDecodeError from requests.exceptions instead of json Because sometimes it will return the simplejson one instead and that one won't be caught by the except clause. * import JSONDecodeError from requests.exceptions instead of json Because sometimes it will return the simplejson one instead and that one won't be caught by the except clause. Also fixed User-Agent assignment.
190 lines
6 KiB
Python
190 lines
6 KiB
Python
# -*- coding: utf-8 -*-
|
|
import functools
|
|
from requests.exceptions import JSONDecodeError
|
|
import logging
|
|
import re
|
|
import time
|
|
|
|
from babelfish import language_converters
|
|
from guessit import guessit
|
|
from requests import Session
|
|
from subliminal_patch.core import Episode
|
|
from subliminal_patch.core import Movie
|
|
from subliminal_patch.providers import Provider
|
|
from subliminal_patch.providers.utils import get_archive_from_bytes
|
|
from subliminal_patch.providers.utils import get_subtitle_from_archive
|
|
from subliminal_patch.providers.utils import update_matches
|
|
from subliminal_patch.subtitle import Subtitle
|
|
from subzero.language import Language
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class HDBitsSubtitle(Subtitle):
|
|
provider_name = "hdbits"
|
|
hash_verifiable = False
|
|
hearing_impaired_verifiable = True
|
|
|
|
def __init__(self, language, id, name, filename, matches=None, episode=None):
|
|
super().__init__(language, hearing_impaired=language.hi)
|
|
self.item_id = id
|
|
self.release_info = name
|
|
self.filename = filename
|
|
self.episode = episode
|
|
self._matches = matches or set()
|
|
|
|
def get_matches(self, video):
|
|
update_matches(self._matches, video, self.release_info)
|
|
return self._matches
|
|
|
|
@property
|
|
def id(self):
|
|
return f"{self.provider_name}_{self.item_id}"
|
|
|
|
|
|
_SPECIAL_LANG_MAP = {"uk": ("eng",), "br": ("por", "BR"), "gr": ("ell",)}
|
|
_ALLOWED_EXTENSIONS = (".ass", ".srt", ".zip", ".rar")
|
|
_FILTER = re.compile("extra|commentary|lyrics|forced")
|
|
|
|
|
|
def _get_language(code):
|
|
special_args = _SPECIAL_LANG_MAP.get(code)
|
|
if special_args is None:
|
|
try:
|
|
return Language.fromietf(code)
|
|
except Exception as error:
|
|
logger.debug("Error [%s] loading language with '%s' code", error, code)
|
|
return None
|
|
|
|
return Language(*special_args)
|
|
|
|
|
|
class HDBitsProvider(Provider):
|
|
provider_name = "hdbits"
|
|
|
|
video_types = (Movie, Episode)
|
|
subtitle_class = HDBitsSubtitle
|
|
|
|
languages = {Language("por", "BR")} | {
|
|
Language.fromalpha2(l) for l in language_converters["alpha2"].codes
|
|
}
|
|
|
|
def __init__(self, username, passkey) -> None:
|
|
self._session = Session()
|
|
self._def_params = {"username": username, "passkey": passkey}
|
|
self._session.headers.update({"User-Agent": "Bazarr"})
|
|
|
|
def initialize(self):
|
|
pass
|
|
|
|
def terminate(self):
|
|
self._session.close()
|
|
|
|
def list_subtitles(self, video, languages):
|
|
episode = None
|
|
if isinstance(video, Movie):
|
|
lookup = {"imdb": {"id": (video.imdb_id or "").lstrip("tt")}}
|
|
matches = {"imdb_id", "title", "year"}
|
|
else:
|
|
lookup = {"tvdb": {"id": video.series_tvdb_id, "season": video.season}}
|
|
matches = {"tvdb_id", "imdb_id", "series", "title", "episode", "season"}
|
|
episode = video.episode
|
|
|
|
logger.debug("ID lookup: %s", lookup)
|
|
|
|
response = self._session.post(
|
|
"https://hdbits.org/api/torrents", json={**self._def_params, **lookup}
|
|
)
|
|
response.raise_for_status()
|
|
ids = [item["id"] for item in response.json()["data"]]
|
|
|
|
subtitles = []
|
|
for torrent_id in ids:
|
|
subtitles.extend(
|
|
self._parse_subtitles(torrent_id, languages, episode, matches)
|
|
)
|
|
time.sleep(0.5)
|
|
|
|
return subtitles
|
|
|
|
def _parse_subtitles(self, torrent_id, languages, episode=None, matches=None):
|
|
response = self._session.post(
|
|
"https://hdbits.org/api/subtitles",
|
|
json={**self._def_params, **{"torrent_id": torrent_id}},
|
|
)
|
|
try:
|
|
subtitles = response.json()["data"]
|
|
except JSONDecodeError:
|
|
logger.debug("Couldn't get reponse for %s", torrent_id)
|
|
return []
|
|
|
|
parsed_subs = []
|
|
for subtitle in subtitles:
|
|
if not subtitle["filename"].endswith(_ALLOWED_EXTENSIONS):
|
|
logger.debug("Extension not supported: %s", subtitle["filename"])
|
|
continue
|
|
|
|
language = _get_language(subtitle["language"])
|
|
if language is None:
|
|
continue
|
|
|
|
if not _is_allowed(subtitle):
|
|
continue
|
|
|
|
if language not in languages:
|
|
logger.debug("Ignoring language: %r !~ %r", language, languages)
|
|
continue
|
|
|
|
if episode is not None:
|
|
eps = _memoized_episode_guess(subtitle["title"]).get("episode")
|
|
if eps is not None and episode not in eps:
|
|
logger.debug("Not matched: %s != %s", subtitle["title"], episode)
|
|
continue
|
|
|
|
parsed = HDBitsSubtitle(
|
|
language,
|
|
subtitle["id"],
|
|
subtitle["title"],
|
|
subtitle["filename"],
|
|
matches,
|
|
episode,
|
|
)
|
|
parsed_subs.append(parsed)
|
|
|
|
return parsed_subs
|
|
|
|
def download_subtitle(self, subtitle):
|
|
response = self._session.get(
|
|
f"https://hdbits.org/getdox.php?id={subtitle.item_id}&passkey={self._def_params['passkey']}"
|
|
)
|
|
response.raise_for_status()
|
|
if subtitle.filename.endswith((".zip", ".rar")):
|
|
archive = get_archive_from_bytes(response.content)
|
|
subtitle.content = get_subtitle_from_archive(
|
|
archive, episode=subtitle.episode
|
|
)
|
|
else:
|
|
subtitle.content = response.content
|
|
|
|
|
|
def _is_allowed(subtitle):
|
|
for val in (subtitle["title"], subtitle["filename"]):
|
|
if _FILTER.search(val.lower()):
|
|
logger.debug("Not allowed subtitle: %s", subtitle)
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
@functools.lru_cache(2048)
|
|
def _memoized_episode_guess(content):
|
|
# Use include to save time from unnecessary checks
|
|
return guessit(
|
|
content,
|
|
{
|
|
"type": "episode",
|
|
# Add codec keys to avoid matching x264, 5.1, etc as episode info
|
|
"includes": ["season", "episode", "video_codec", "audio_codec"],
|
|
"enforce_list": True,
|
|
},
|
|
)
|