Source code for flexget.plugins.output.subtitles
import difflib
import os.path
import re
from xmlrpc.client import ServerProxy
from loguru import logger
from flexget import plugin
from flexget.event import event
logger = logger.bind(name='subtitles')
# movie hash, won't work here though
# http://trac.opensubtitles.org/projects/opensubtitles/wiki/HashSourceCodes#Python
# xmlrpc spec
# http://trac.opensubtitles.org/projects/opensubtitles/wiki/XMLRPC
[docs]
class Subtitles:
"""Fetch subtitles from opensubtitles.org."""
schema = {
'type': 'object',
'properties': {
'languages': {'type': 'array', 'items': {'type': 'string'}, 'default': ['eng']},
'min_sub_rating': {'type': 'number', 'default': 0.0},
'match_limit': {'type': 'number', 'default': 0.8},
'output': {'type': 'string', 'format': 'path'},
},
'additionalProperties': False,
}
[docs]
def prepare_config(self, config, task):
if not isinstance(config, dict):
config = {}
config.setdefault('output', task.manager.config_base)
config['output'] = os.path.expanduser(config['output'])
return config
[docs]
def on_task_download(self, task, config):
# filter all entries that have IMDB ID set
try:
entries = [e for e in task.accepted if e['imdb_id'] is not None]
except KeyError:
# No imdb urls on this task, skip it
# TODO: should do lookup via imdb_lookup plugin?
return
try:
s = ServerProxy('http://api.opensubtitles.org/xml-rpc')
res = s.LogIn('', '', 'en', 'FlexGet')
except Exception:
logger.warning('Error connecting to opensubtitles.org')
return
if res['status'] != '200 OK':
raise RuntimeError('Login to opensubtitles.org XML-RPC interface failed')
config = self.prepare_config(config, task)
token = res['token']
# configuration
languages = config['languages']
min_sub_rating = config['min_sub_rating']
match_limit = config[
'match_limit'
] # no need to change this, but it should be configurable
# loop through the entries
for entry in entries:
imdbid = entry.get('imdb_id')
if not imdbid:
logger.debug('no match for {}', entry['title'])
continue
query = [{'sublanguageid': language, 'imdbid': imdbid} for language in languages]
subtitles = s.SearchSubtitles(token, query)
subtitles = subtitles['data']
# nothing found -> continue
if not subtitles:
continue
# filter bad subs
subtitles = [x for x in subtitles if x['SubBad'] == '0']
# some quality required (0.0 == not reviewed)
subtitles = [
x
for x in subtitles
if float(x['SubRating']) >= min_sub_rating or float(x['SubRating']) == 0.0
]
filtered_subs = []
# find the best rated subs for each language
for language in languages:
langsubs = [x for x in subtitles if x['SubLanguageID'] == language]
# did we find any subs for this language?
if langsubs:
def seqmatch(subfile, entry=entry):
s = difflib.SequenceMatcher(lambda x: x in ' ._', entry['title'], subfile) # noqa: B023 This is a false positive, see https://github.com/astral-sh/ruff/issues/15716
# print "matching: ", entry['title'], subfile, s.ratio()
return s.ratio() > match_limit
# filter only those that have matching release names
langsubs = [x for x in subtitles if seqmatch(x['MovieReleaseName'])]
if langsubs:
# find the best one by SubRating
langsubs.sort(key=lambda x: float(x['SubRating']))
langsubs.reverse()
filtered_subs.append(langsubs[0])
# download
for sub in filtered_subs:
logger.debug(
'SUBS FOUND: {} {} {}',
sub['MovieReleaseName'],
sub['SubRating'],
sub['SubLanguageID'],
)
f = task.requests.get(sub['ZipDownloadLink'])
subfilename = re.match(
'^attachment; filename="(.*)"$', f.headers['content-disposition']
).group(1)
outfile = os.path.join(config['output'], subfilename)
with open(outfile, 'w') as fp:
fp.write(f.raw)
f.close()
s.LogOut(token)
[docs]
@event('plugin.register')
def register_plugin():
plugin.register(Subtitles, 'subtitles', api_ver=2)