SickGear/sickgear/subtitles.py

210 lines
8.4 KiB
Python
Raw Permalink Normal View History

# Author: Nyaran <nyayukko@gmail.com>, based on Antoine Bertin <diaoulael@gmail.com> work
#
# This file is part of SickGear.
#
# SickGear is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# SickGear is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with SickGear. If not, see <http://www.gnu.org/licenses/>.
import datetime
from . import db, helpers, logger
from .common import *
from .scheduler import Job
import sickgear
from lib import subliminal
SINGLE = 'und'
def sorted_service_list():
services_mapping = dict([(x.lower(), x) for x in subliminal.core.SERVICES])
new_list = []
# add all services in the priority list, in order
cur_index = 0
for cur_service in sickgear.SUBTITLES_SERVICES_LIST:
if cur_service in services_mapping:
cur_service_dict = dict(
id=cur_service,
image=cur_service + '.png',
name=services_mapping[cur_service],
enabled=1 == sickgear.SUBTITLES_SERVICES_ENABLED[cur_index],
api_based=__import__('lib.subliminal.services.' + cur_service, globals=globals(),
locals=locals(), fromlist=['Service']).Service.api_based,
url=__import__('lib.subliminal.services.' + cur_service, globals=globals(),
locals=locals(), fromlist=['Service']).Service.site_url)
new_list.append(cur_service_dict)
cur_index += 1
# add any services that are missing from that list
for cur_service in services_mapping:
if cur_service not in [x['id'] for x in new_list]:
cur_service_dict = dict(
id=cur_service,
image=cur_service + '.png',
name=services_mapping[cur_service],
enabled=False,
api_based=__import__('lib.subliminal.services.' + cur_service, globals=globals(),
locals=locals(), fromlist=['Service']).Service.api_based,
url=__import__('lib.subliminal.services.' + cur_service, globals=globals(),
locals=locals(), fromlist=['Service']).Service.site_url)
new_list.append(cur_service_dict)
return new_list
def get_enabled_service_list():
return [x['name'] for x in sorted_service_list() if x['enabled']]
def is_valid_language(language):
return subliminal.language.language_list(language)
def get_language_name(select_lang):
return subliminal.language.Language(select_lang).name
def wanted_languages(sql_like=False):
wanted_langs = sorted(sickgear.SUBTITLES_LANGUAGES)
if sql_like:
return '%' + ','.join(wanted_langs) + '%'
return wanted_langs
def subtitles_languages(video_path):
"""Return a list detected subtitles for the given video file"""
video = subliminal.videos.Video.from_path(video_path)
video.subtitle_path = sickgear.SUBTITLES_DIR
subtitles = video.scan()
languages = set()
for subtitle in subtitles:
if subtitle.language:
languages.add(subtitle.language.alpha2)
else:
languages.add(SINGLE)
return list(languages)
# Return a list with languages that have alpha2 code
def subtitle_language_filter():
return [language for language in subliminal.language.LANGUAGES if language[2] != ""]
class SubtitlesFinder(Job):
"""
The SubtitlesFinder will be executed every hour but will not necessarily search
and download subtitles. Only if the defined rule is true
"""
def __init__(self):
super(SubtitlesFinder, self).__init__(self.job_run, kwargs={}, thread_lock=True)
@staticmethod
def is_enabled():
return sickgear.USE_SUBTITLES
def job_run(self):
if self.is_enabled():
self._main()
def _main(self):
if 1 > len(sickgear.subtitles.get_enabled_service_list()):
logger.error('Not enough services selected. At least 1 service is required to'
' search subtitles in the background')
return
logger.log('Checking for subtitles')
# get episodes on which we want subtitles
# criteria is:
# - show subtitles = 1
# - episode subtitles != config wanted languages or SINGLE (depends on config multi)
# - search count < 2 and diff(airdate, now) > 1 week : now -> 1d
# - search count < 7 and diff(airdate, now) <= 1 week : now -> 4h -> 8h -> 16h -> 1d -> 1d -> 1d
today = datetime.date.today().toordinal()
# you have 5 minutes to understand that one. Good luck
my_db = db.DBConnection()
sql_result = my_db.select(
'SELECT s.show_name, e.indexer AS tv_id, e.showid AS prod_id,'
' e.season, e.episode, e.status, e.subtitles,'
' e.subtitles_searchcount AS searchcount, e.subtitles_lastsearch AS lastsearch,'
' e.location, (? - e.airdate) AS airdate_daydiff'
' FROM tv_episodes AS e'
' INNER JOIN tv_shows AS s'
' ON (e.indexer = s.indexer AND e.showid = s.indexer_id)'
' WHERE s.subtitles = 1 AND e.subtitles NOT LIKE (?)'
' AND ((e.subtitles_searchcount <= 2 AND (? - e.airdate) > 7)'
' OR (e.subtitles_searchcount <= 7 AND (? - e.airdate) <= 7))'
' AND (e.status IN (%s)' % ','.join([str(x) for x in Quality.DOWNLOADED])
+ ' OR (e.status IN (%s)' % ','.join([str(x) for x in Quality.SNATCHED + Quality.SNATCHED_PROPER])
+ ' AND e.location != \'\'))', [today, wanted_languages(True), today, today])
if 0 == len(sql_result):
logger.log('No subtitles to download', logger.MESSAGE)
return
rules = self._get_rules()
now = datetime.datetime.now()
for cur_result in sql_result:
if not os.path.isfile(cur_result['location']):
logger.debug(f'Episode file does not exist, cannot download subtitles for episode'
f' {cur_result["season"]:d}x{cur_result["episode"]:d} of show {cur_result["show_name"]}')
continue
# Old shows rule
_ = datetime.datetime.strptime('20110101', '%Y%m%d')
if ((cur_result['airdate_daydiff'] > 7 and cur_result['searchcount'] < 2
and now - datetime.datetime.strptime(cur_result['lastsearch'], '%Y-%m-%d %H:%M:%S')
> datetime.timedelta(hours=rules['old'][cur_result['searchcount']])) or
# Recent shows rule
(cur_result['airdate_daydiff'] <= 7 and cur_result['searchcount'] < 7
and now - datetime.datetime.strptime(cur_result['lastsearch'], '%Y-%m-%d %H:%M:%S')
> datetime.timedelta(hours=rules['new'][cur_result['searchcount']]))):
logger.debug(f'Downloading subtitles for episode {cur_result["season"]:d}x{cur_result["episode"]:d}'
f' of show {cur_result["show_name"]}')
show_obj = helpers.find_show_by_id({int(cur_result['tv_id']): int(cur_result['prod_id'])})
if not show_obj:
logger.debug('Show not found')
return
ep_obj = show_obj.get_episode(int(cur_result['season']), int(cur_result['episode']))
if isinstance(ep_obj, str):
logger.debug('Episode not found')
return
# noinspection PyUnusedLocal
previous_subtitles = ep_obj.subtitles
try:
# noinspection PyUnusedLocal
subtitles = ep_obj.download_subtitles()
except (BaseException, Exception):
logger.debug('Unable to find subtitles')
return
@staticmethod
def _get_rules():
"""
Define the hours to wait between 2 subtitles search depending on:
- the episode: new or old
- the number of searches done so far (searchcount), represented by the index of the list
"""
return {'old': [0, 24], 'new': [0, 4, 8, 4, 16, 24, 24]}