mirror of
https://github.com/morpheus65535/bazarr.git
synced 2026-04-19 05:37:17 -04:00
205 lines
6.9 KiB
Python
205 lines
6.9 KiB
Python
# -*- coding: utf-8 -*-
|
|
import logging
|
|
from babelfish import language_converters
|
|
from requests import Session
|
|
from subzero.language import Language
|
|
from subliminal import Episode, Movie
|
|
from subliminal.exceptions import ConfigurationError, ProviderError
|
|
from subliminal.subtitle import fix_line_ending
|
|
from subliminal_patch.subtitle import Subtitle
|
|
from subliminal_patch.providers import Provider, utils
|
|
from .mixins import ProviderRetryMixin
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
language_converters.register('subsarr = subliminal_patch.converters.subsarr:SubsarrConverter')
|
|
|
|
|
|
class SubsarrSubtitle(Subtitle):
|
|
provider_name = 'subsarr'
|
|
hash_verifiable = False
|
|
hearing_impaired_verifiable = True
|
|
|
|
def __init__(self, language, hearing_impaired, record_id, download_url, title,
|
|
releases, filename, season=None, episode=None):
|
|
super().__init__(language)
|
|
language = Language.rebuild(language, hi=hearing_impaired)
|
|
|
|
self.language = language
|
|
self.hearing_impaired = hearing_impaired
|
|
self.record_id = record_id
|
|
self.download_url = download_url
|
|
self.title = title
|
|
self.releases = releases
|
|
self.release_info = ', '.join(releases) if releases else filename
|
|
self.filename = filename
|
|
self.season = season
|
|
self.episode = episode
|
|
self.matches = set()
|
|
|
|
@property
|
|
def id(self):
|
|
return self.record_id
|
|
|
|
def get_matches(self, video):
|
|
self.matches = set()
|
|
|
|
if isinstance(video, Episode):
|
|
if video.series and self.title and video.series.lower() == self.title.lower():
|
|
self.matches.add('series')
|
|
if video.season and self.season == video.season:
|
|
self.matches.add('season')
|
|
if video.episode and self.episode == video.episode:
|
|
self.matches.add('episode')
|
|
else:
|
|
if video.title and self.title and video.title.lower() == self.title.lower():
|
|
self.matches.add('title')
|
|
|
|
utils.update_matches(self.matches, video, self.release_info)
|
|
|
|
return self.matches
|
|
|
|
|
|
class SubsarrProvider(ProviderRetryMixin, Provider):
|
|
"""Subsarr Provider — self-hosted Subscene subtitle provider."""
|
|
provider_name = 'subsarr'
|
|
|
|
languages = {Language(*lang) for lang in list(language_converters['subsarr'].to_subsarr.keys())}
|
|
languages.update(set(Language.rebuild(lang, hi=True) for lang in languages))
|
|
|
|
video_types = (Episode, Movie)
|
|
subtitle_class = SubsarrSubtitle
|
|
|
|
def __init__(self, base_url=None):
|
|
if not base_url:
|
|
raise ConfigurationError('Base URL must be specified')
|
|
|
|
if not base_url.startswith(('http://', 'https://')):
|
|
raise ConfigurationError('Base URL must include scheme (http:// or https://)')
|
|
|
|
self.base_url = base_url.rstrip('/')
|
|
self.session = None
|
|
|
|
def initialize(self):
|
|
self.session = Session()
|
|
self.session.headers['User-Agent'] = 'Subliminal/2 Bazarr/1'
|
|
|
|
def terminate(self):
|
|
self.session.close()
|
|
|
|
def _url(self, path):
|
|
return f'{self.base_url}/api/v1{path}'
|
|
|
|
def ping(self):
|
|
try:
|
|
r = self.session.get(self._url('/info'), timeout=10)
|
|
return r.status_code == 200
|
|
except Exception:
|
|
return False
|
|
|
|
def _search(self, params):
|
|
r = self.retry(
|
|
lambda: self.session.get(self._url('/subtitles/search'), params=params, timeout=30),
|
|
amount=3,
|
|
retry_timeout=5,
|
|
)
|
|
if r.status_code != 200:
|
|
r.raise_for_status()
|
|
return r.json().get('items', [])
|
|
|
|
def query(self, languages, video):
|
|
imdb_id = None
|
|
season = None
|
|
episode = None
|
|
year = None
|
|
|
|
if isinstance(video, Episode):
|
|
title = video.series
|
|
imdb_id = video.series_imdb_id
|
|
season = video.season
|
|
episode = video.episode
|
|
else:
|
|
title = video.title
|
|
imdb_id = video.imdb_id
|
|
year = getattr(video, 'year', None)
|
|
|
|
subtitles = []
|
|
lang_names = set()
|
|
|
|
for lang in languages:
|
|
base_lang = Language.rebuild(lang, hi=False, forced=False)
|
|
try:
|
|
lang_name = language_converters['subsarr'].convert(
|
|
base_lang.alpha3, base_lang.country, base_lang.script
|
|
)
|
|
except ConfigurationError:
|
|
logger.debug('Language %s not supported by subsarr', lang)
|
|
continue
|
|
lang_names.add(lang_name)
|
|
|
|
for lang_name in lang_names:
|
|
params = {'language': lang_name, 'per_page': 100}
|
|
if season is not None:
|
|
params['season'] = season
|
|
if episode is not None:
|
|
params['episode'] = episode
|
|
|
|
items = []
|
|
if imdb_id:
|
|
imdb_params = {**params, 'imdb_id': imdb_id}
|
|
if year is not None:
|
|
imdb_params['year'] = year
|
|
items = self._search(imdb_params)
|
|
|
|
# Fall back to title query if IMDB search returned nothing
|
|
if not items and title:
|
|
items = self._search({**params, 'query': title})
|
|
|
|
for item in items:
|
|
try:
|
|
lang_obj = Language(*language_converters['subsarr'].reverse(item['language']))
|
|
except (ConfigurationError, KeyError):
|
|
logger.debug('Skipping unsupported language: %s', item.get('language'))
|
|
continue
|
|
|
|
hi = item.get('hi', False)
|
|
if hi:
|
|
lang_obj = Language.rebuild(lang_obj, hi=True)
|
|
|
|
if lang_obj not in languages:
|
|
continue
|
|
|
|
raw_releases = item.get('releases')
|
|
releases = raw_releases if isinstance(raw_releases, list) else []
|
|
|
|
subtitle = SubsarrSubtitle(
|
|
language=lang_obj,
|
|
hearing_impaired=hi,
|
|
record_id=item['id'],
|
|
download_url=item['download_url'],
|
|
title=item.get('title', ''),
|
|
releases=releases,
|
|
filename=item.get('filename', ''),
|
|
season=season,
|
|
episode=episode,
|
|
)
|
|
subtitle.get_matches(video)
|
|
subtitles.append(subtitle)
|
|
|
|
return subtitles
|
|
|
|
def list_subtitles(self, video, languages):
|
|
return self.query(languages, video)
|
|
|
|
def download_subtitle(self, subtitle):
|
|
logger.debug('Downloading subtitle %r from %s', subtitle, subtitle.download_url)
|
|
r = self.retry(
|
|
lambda: self.session.get(subtitle.download_url, timeout=30, allow_redirects=True),
|
|
amount=3,
|
|
retry_timeout=5,
|
|
)
|
|
if r.status_code != 200:
|
|
raise ProviderError(f'Download failed with status {r.status_code}')
|
|
|
|
subtitle.content = fix_line_ending(r.content)
|