diff --git a/README.md b/README.md index 336497efe..afaad0d29 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,7 @@ If you need something that is not already part of Bazarr, feel free to create a * Supersubtitles * Titlovi * Titrari.ro +* TuSubtitulo * TVSubtitles * Wizdom * XSubs diff --git a/libs/subliminal_patch/providers/tusubtitulo.py b/libs/subliminal_patch/providers/tusubtitulo.py new file mode 100644 index 000000000..e78884324 --- /dev/null +++ b/libs/subliminal_patch/providers/tusubtitulo.py @@ -0,0 +1,252 @@ +# -*- coding: utf-8 -*- +import logging +from urllib import parse +import re +from bs4 import BeautifulSoup as bso + +from requests import Session +from subzero.language import Language + +from subliminal import Episode +from subliminal.exceptions import ServiceUnavailable +from subliminal_patch.subtitle import Subtitle +from subliminal.subtitle import fix_line_ending +from subliminal_patch.providers import Provider + +logger = logging.getLogger(__name__) + + +BASE = "https://www.tusubtitulo.com/series.php?/" + + +class TuSubtituloSubtitle(Subtitle): + provider_name = "tusubtitulo" + + def __init__(self, language, filename, download_link, page_link, matches): + super(TuSubtituloSubtitle, self).__init__( + language, hearing_impaired=False, page_link=page_link + ) + self.download_link = download_link + self.page_link = page_link + self.language = language + self.release_info = filename + self.filename = filename + self.found_matches = matches + + @property + def id(self): + return self.download_link + + def get_matches(self, video): + if video.resolution and video.resolution.lower() in self.release_info.lower(): + self.found_matches.add("resolution") + + if video.source and video.source.lower() in self.release_info.lower(): + self.found_matches.add("source") + + if video.video_codec: + if video.video_codec == "H.264" and "x264" in self.release_info.lower(): + self.found_matches.add("video_codec") + elif video.video_codec == "H.265" and "x265" in self.release_info.lower(): + self.found_matches.add("video_codec") + elif video.video_codec.lower() in self.release_info.lower(): + self.found_matches.add("video_codec") + + if video.audio_codec: + if video.audio_codec.lower().replace(" ", ".") in self.release_info.lower(): + self.found_matches.add("audio_codec") + return self.found_matches + + +class TuSubtituloProvider(Provider): + """TuSubtitulo.com Provider""" + + BASE = "https://www.tusubtitulo.com/series.php?/" + languages = {Language.fromalpha2(l) for l in ["es"]} + language_list = list(languages) + logger.debug(languages) + video_types = (Episode,) + + def initialize(self): + self.session = Session() + self.session.headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36", + "referer": "https://www.tusubtitulo.com", + } + + def terminate(self): + self.session.close() + + def index_titles(self): + r = self.session.get(BASE) + r.raise_for_status() + soup = bso(r.content, "html.parser") + titles = [] + for a in soup.find_all("a"): + href_url = a.get("href") + if "show" in href_url: + titles.append({"title": a.text, "url": href_url}) + return titles + + def is_season_available(self, seasons, season): + for i in seasons: + if i == season: + return True + + def title_available(self, item): + try: + title_content = item[2].find_all("a")[0] + episode_number = re.search( + r".*\d+x(0+)?(\d+) - .*?", title_content.text + ).group(2) + episode_id = title_content.get("href").split("/")[4] + return { + "episode_number": episode_number, + "episode_id": episode_id, + "episode_url": title_content.get("href"), + } + except IndexError: + return + + def source_separator(self, item): + try: + text = item[3].text.replace("\n", "") + if "Vers" in text: + source = text.replace("VersiĆ³n ", "") + if not source: + source = "Unknown" + return source + except IndexError: + return + + def get_episodes(self, show_id, season): + logger.debug("https://www.tusubtitulo.com/show/{}/{}".format(show_id, season)) + r2 = self.session.get( + "https://www.tusubtitulo.com/show/{}/{}".format(show_id, season), + ) + r2.raise_for_status() + sopa = bso(r2.content, "lxml") + tables = sopa.find_all("tr") + seasons = [i.text for i in tables[1].find_all("a")] + if not self.is_season_available(seasons, season): + logger.debug("Season not found") + return + season_subs = [] + episodes = [] + + for tr in range(len(tables)): + data = tables[tr].find_all("td") + title = self.title_available(data) + if title: + episodes.append(title) + source_var = self.source_separator(data) + if source_var: + inc = 1 + while True: + try: + content = tables[tr + inc].find_all("td") + language = content[4].text + completed = content[5] + url = content[6].find_all("a")[0].get("href") + sub_id = parse.parse_qs(parse.urlparse(url).query)["id"][0] + lang_id = parse.parse_qs(parse.urlparse(url).query)["lang"][0] + version_ = parse.parse_qs(parse.urlparse(url).query)["version"][ + 0 + ] + download_url = ( + "https://www.tusubtitulo.com/updated/{}/{}/{}".format( + lang_id, sub_id, version_ + ) + ) + if "esp" in language.lower(): + season_subs.append( + { + "episode_id": sub_id, + "metadata": source_var, + "download_url": download_url, + } + ) + inc += 1 + except IndexError: + break + + final_list = [] + for i in episodes: + for t in season_subs: + if i["episode_id"] == t["episode_id"]: + final_list.append( + { + "episode_number": i["episode_number"], + "episode_url": i["episode_url"], + "metadata": t["metadata"], + "download_url": t["download_url"], + } + ) + return final_list + + def search(self, title, season, episode): + titles = self.index_titles() + found_tv_show = None + for i in titles: + if title.lower() == i["title"].lower(): + found_tv_show = i + break + if not found_tv_show: + logger.debug("Show not found") + return + tv_show_id = found_tv_show["url"].split("/")[2].replace(" ", "") + results = self.get_episodes(tv_show_id, season) + episode_list = [] + if results: + for i in results: + if i["episode_number"] == episode: + episode_list.append(i) + if episode_list: + return episode_list + logger.debug("Episode not found") + + def query(self, languages, video): + language = self.language_list[0] + query = "{} {} {}".format(video.series, video.season, video.episode) + logger.debug("Searching subtitles: {}".format(query)) + results = self.search(video.series, str(video.season), str(video.episode)) + + if results: + subtitles = [] + for i in results: + matches = set() + # self.search only returns results for the specific episode + matches.add("title") + matches.add("series") + matches.add("season") + matches.add("episode") + matches.add("year") + subtitles.append( + TuSubtituloSubtitle( + language, + i["metadata"], + i["download_url"], + i["episode_url"], + matches, + ) + ) + return subtitles + else: + logger.debug("No subtitles found") + return [] + + def list_subtitles(self, video, languages): + return self.query(languages, video) + + def _check_response(self, response): + if response.status_code != 200: + raise ServiceUnavailable("Bad status code: " + str(response.status_code)) + + def download_subtitle(self, subtitle): + logger.info("Downloading subtitle %r", subtitle) + response = self.session.get( + subtitle.download_link, headers={"Referer": subtitle.page_link}, timeout=10 + ) + response.raise_for_status() + self._check_response(response) + subtitle.content = fix_line_ending(response.content) diff --git a/views/settingsproviders.html b/views/settingsproviders.html index 727ca6f66..d7946c7f2 100644 --- a/views/settingsproviders.html +++ b/views/settingsproviders.html @@ -589,6 +589,18 @@ +