Merge branch 'development' into subsync

# Conflicts:
#	bazarr/get_subtitle.py
pull/1038/head
Louis Vézina 4 years ago
commit d9a9c26d7e

@ -55,7 +55,7 @@ def get_video(path, title, sceneName, providers=None, media_type="movie"):
path = os.path.join(os.path.dirname(path), sceneName + os.path.splitext(path)[1]) path = os.path.join(os.path.dirname(path), sceneName + os.path.splitext(path)[1])
used_scene_name = True used_scene_name = True
hash_from = original_path hash_from = original_path
try: try:
video = parse_video(path, hints=hints, providers=providers, dry_run=used_scene_name, video = parse_video(path, hints=hints, providers=providers, dry_run=used_scene_name,
hash_from=hash_from) hash_from=hash_from)
@ -65,10 +65,10 @@ def get_video(path, title, sceneName, providers=None, media_type="movie"):
refine_from_db(original_path, video) refine_from_db(original_path, video)
refine_from_ffprobe(original_path, video) refine_from_ffprobe(original_path, video)
logging.debug('BAZARR is using these video object properties: %s', vars(video)) logging.debug('BAZARR is using these video object properties: %s', vars(video))
return video return video
except Exception as e: except Exception as e:
logging.exception("BAZARR Error trying to get video information for this file: " + path) logging.exception("BAZARR Error trying to get video information for this file: " + path)
@ -93,30 +93,30 @@ def get_scores(video, media_type, min_score_movie_perc=60 * 100 / 120.0, min_sco
scores = list(subliminal_scores.episode_scores.keys()) scores = list(subliminal_scores.episode_scores.keys())
if video.is_special: if video.is_special:
min_score = max_score * min_score_special_ep / 100.0 min_score = max_score * min_score_special_ep / 100.0
return min_score, max_score, set(scores) return min_score, max_score, set(scores)
def download_subtitle(path, language, audio_language, hi, forced, providers, providers_auth, sceneName, title, media_type, def download_subtitle(path, language, audio_language, hi, forced, providers, providers_auth, sceneName, title,
forced_minimum_score=None, is_upgrade=False): media_type, forced_minimum_score=None, is_upgrade=False):
# fixme: supply all missing languages, not only one, to hit providers only once who support multiple languages in # fixme: supply all missing languages, not only one, to hit providers only once who support multiple languages in
# one query # one query
if settings.general.getboolean('utf8_encode'): if settings.general.getboolean('utf8_encode'):
os.environ["SZ_KEEP_ENCODING"] = "" os.environ["SZ_KEEP_ENCODING"] = ""
else: else:
os.environ["SZ_KEEP_ENCODING"] = "True" os.environ["SZ_KEEP_ENCODING"] = "True"
logging.debug('BAZARR Searching subtitles for this file: ' + path) logging.debug('BAZARR Searching subtitles for this file: ' + path)
if hi == "True": if hi == "True":
hi = "force HI" hi = "force HI"
else: else:
hi = "force non-HI" hi = "force non-HI"
language_set = set() language_set = set()
if not isinstance(language, list): if not isinstance(language, list):
language = [language] language = [language]
if forced == "True": if forced == "True":
providers_auth['podnapisi']['only_foreign'] = True ## fixme: This is also in get_providers_auth() providers_auth['podnapisi']['only_foreign'] = True ## fixme: This is also in get_providers_auth()
providers_auth['subscene']['only_foreign'] = True ## fixme: This is also in get_providers_auth() providers_auth['subscene']['only_foreign'] = True ## fixme: This is also in get_providers_auth()
@ -125,7 +125,7 @@ def download_subtitle(path, language, audio_language, hi, forced, providers, pro
providers_auth['podnapisi']['only_foreign'] = False providers_auth['podnapisi']['only_foreign'] = False
providers_auth['subscene']['only_foreign'] = False providers_auth['subscene']['only_foreign'] = False
providers_auth['opensubtitles']['only_foreign'] = False providers_auth['opensubtitles']['only_foreign'] = False
for l in language: for l in language:
if l == 'pob': if l == 'pob':
lang_obj = Language('por', 'BR') lang_obj = Language('por', 'BR')
@ -136,13 +136,13 @@ def download_subtitle(path, language, audio_language, hi, forced, providers, pro
if forced == "True": if forced == "True":
lang_obj = Language.rebuild(lang_obj, forced=True) lang_obj = Language.rebuild(lang_obj, forced=True)
language_set.add(lang_obj) language_set.add(lang_obj)
minimum_score = settings.general.minimum_score minimum_score = settings.general.minimum_score
minimum_score_movie = settings.general.minimum_score_movie minimum_score_movie = settings.general.minimum_score_movie
use_postprocessing = settings.general.getboolean('use_postprocessing') use_postprocessing = settings.general.getboolean('use_postprocessing')
postprocessing_cmd = settings.general.postprocessing_cmd postprocessing_cmd = settings.general.postprocessing_cmd
single = settings.general.getboolean('single_language') single = settings.general.getboolean('single_language')
# todo: # todo:
""" """
AsyncProviderPool: AsyncProviderPool:
@ -157,7 +157,7 @@ def download_subtitle(path, language, audio_language, hi, forced, providers, pro
if video: if video:
min_score, max_score, scores = get_scores(video, media_type, min_score_movie_perc=int(minimum_score_movie), min_score, max_score, scores = get_scores(video, media_type, min_score_movie_perc=int(minimum_score_movie),
min_score_series_perc=int(minimum_score)) min_score_series_perc=int(minimum_score))
if providers: if providers:
if forced_minimum_score: if forced_minimum_score:
min_score = int(forced_minimum_score) + 1 min_score = int(forced_minimum_score) + 1
@ -176,13 +176,13 @@ def download_subtitle(path, language, audio_language, hi, forced, providers, pro
downloaded_subtitles = None downloaded_subtitles = None
logging.info("BAZARR All providers are throttled") logging.info("BAZARR All providers are throttled")
return None return None
saved_any = False saved_any = False
if downloaded_subtitles: if downloaded_subtitles:
for video, subtitles in downloaded_subtitles.items(): for video, subtitles in downloaded_subtitles.items():
if not subtitles: if not subtitles:
continue continue
try: try:
fld = get_target_folder(path) fld = get_target_folder(path)
chmod = int(settings.general.chmod, 8) if not sys.platform.startswith( chmod = int(settings.general.chmod, 8) if not sys.platform.startswith(
@ -195,7 +195,8 @@ def download_subtitle(path, language, audio_language, hi, forced, providers, pro
path_decoder=force_unicode path_decoder=force_unicode
) )
except Exception as e: except Exception as e:
logging.exception('BAZARR Error saving Subtitles file to disk for this file:' + path + ': ' + repr(e)) logging.exception(
'BAZARR Error saving Subtitles file to disk for this file:' + path + ': ' + repr(e))
pass pass
else: else:
saved_any = True saved_any = True
@ -252,12 +253,12 @@ def download_subtitle(path, language, audio_language, hi, forced, providers, pro
pp_threshold = int(settings.general.postprocessing_threshold_movie) pp_threshold = int(settings.general.postprocessing_threshold_movie)
if not use_pp_threshold or (use_pp_threshold and percent_score < pp_threshold): if not use_pp_threshold or (use_pp_threshold and percent_score < pp_threshold):
postprocessing(command, path)
logging.debug("BAZARR Using post-processing command: {}".format(command)) logging.debug("BAZARR Using post-processing command: {}".format(command))
postprocessing(command, path)
else: else:
logging.debug("BAZARR post-processing skipped because subtitles score isn't below this " logging.debug("BAZARR post-processing skipped because subtitles score isn't below this "
"threshold value: " + str(pp_threshold) + "%") "threshold value: " + str(pp_threshold) + "%")
# fixme: support multiple languages at once # fixme: support multiple languages at once
if media_type == 'series': if media_type == 'series':
reversed_path = path_mappings.path_replace_reverse(path) reversed_path = path_mappings.path_replace_reverse(path)
@ -267,19 +268,19 @@ def download_subtitle(path, language, audio_language, hi, forced, providers, pro
track_event(category=downloaded_provider, action=action, label=downloaded_language) track_event(category=downloaded_provider, action=action, label=downloaded_language)
return message, reversed_path, downloaded_language_code2, downloaded_provider, subtitle.score, subtitle.language.forced return message, reversed_path, downloaded_language_code2, downloaded_provider, subtitle.score, subtitle.language.forced
if not saved_any: if not saved_any:
logging.debug('BAZARR No Subtitles were found for this file: ' + path) logging.debug('BAZARR No Subtitles were found for this file: ' + path)
return None return None
subliminal.region.backend.sync() subliminal.region.backend.sync()
logging.debug('BAZARR Ended searching Subtitles for file: ' + path) logging.debug('BAZARR Ended searching Subtitles for file: ' + path)
def manual_search(path, language, hi, forced, providers, providers_auth, sceneName, title, media_type): def manual_search(path, language, hi, forced, providers, providers_auth, sceneName, title, media_type):
logging.debug('BAZARR Manually searching subtitles for this file: ' + path) logging.debug('BAZARR Manually searching subtitles for this file: ' + path)
final_subtitles = [] final_subtitles = []
initial_hi = True if hi == "True" else False initial_hi = True if hi == "True" else False
@ -288,7 +289,7 @@ def manual_search(path, language, hi, forced, providers, providers_auth, sceneNa
else: else:
hi = "force non-HI" hi = "force non-HI"
language_set = set() language_set = set()
if forced == "True": if forced == "True":
providers_auth['podnapisi']['only_foreign'] = True providers_auth['podnapisi']['only_foreign'] = True
providers_auth['subscene']['only_foreign'] = True providers_auth['subscene']['only_foreign'] = True
@ -297,7 +298,7 @@ def manual_search(path, language, hi, forced, providers, providers_auth, sceneNa
providers_auth['podnapisi']['only_foreign'] = False providers_auth['podnapisi']['only_foreign'] = False
providers_auth['subscene']['only_foreign'] = False providers_auth['subscene']['only_foreign'] = False
providers_auth['opensubtitles']['only_foreign'] = False providers_auth['opensubtitles']['only_foreign'] = False
for lang in ast.literal_eval(language): for lang in ast.literal_eval(language):
lang = alpha3_from_alpha2(lang) lang = alpha3_from_alpha2(lang)
if lang == 'pob': if lang == 'pob':
@ -309,7 +310,7 @@ def manual_search(path, language, hi, forced, providers, providers_auth, sceneNa
if forced == "True": if forced == "True":
lang_obj = Language.rebuild(lang_obj, forced=True) lang_obj = Language.rebuild(lang_obj, forced=True)
language_set.add(lang_obj) language_set.add(lang_obj)
minimum_score = settings.general.minimum_score minimum_score = settings.general.minimum_score
minimum_score_movie = settings.general.minimum_score_movie minimum_score_movie = settings.general.minimum_score_movie
use_postprocessing = settings.general.getboolean('use_postprocessing') use_postprocessing = settings.general.getboolean('use_postprocessing')
@ -323,7 +324,7 @@ def manual_search(path, language, hi, forced, providers, providers_auth, sceneNa
if video: if video:
min_score, max_score, scores = get_scores(video, media_type, min_score_movie_perc=int(minimum_score_movie), min_score, max_score, scores = get_scores(video, media_type, min_score_movie_perc=int(minimum_score_movie),
min_score_series_perc=int(minimum_score)) min_score_series_perc=int(minimum_score))
try: try:
if providers: if providers:
subtitles = list_all_subtitles([video], language_set, subtitles = list_all_subtitles([video], language_set,
@ -339,19 +340,19 @@ def manual_search(path, language, hi, forced, providers, providers_auth, sceneNa
logging.exception("BAZARR Error trying to get Subtitle list from provider for this file: " + path) logging.exception("BAZARR Error trying to get Subtitle list from provider for this file: " + path)
else: else:
subtitles_list = [] subtitles_list = []
for s in subtitles[video]: for s in subtitles[video]:
try: try:
matches = s.get_matches(video) matches = s.get_matches(video)
except AttributeError: except AttributeError:
continue continue
# skip wrong season/episodes # skip wrong season/episodes
if media_type == "series": if media_type == "series":
can_verify_series = True can_verify_series = True
if not s.hash_verifiable and "hash" in matches: if not s.hash_verifiable and "hash" in matches:
can_verify_series = False can_verify_series = False
if can_verify_series and not {"series", "season", "episode"}.issubset(matches): if can_verify_series and not {"series", "season", "episode"}.issubset(matches):
logging.debug(u"BAZARR Skipping %s, because it doesn't match our series/episode", s) logging.debug(u"BAZARR Skipping %s, because it doesn't match our series/episode", s)
continue continue
@ -369,7 +370,7 @@ def manual_search(path, language, hi, forced, providers, providers_auth, sceneNa
for s_item in s.release_info.split(','): for s_item in s.release_info.split(','):
if s_item.strip(): if s_item.strip():
releases.append(s_item) releases.append(s_item)
if len(releases) == 0: if len(releases) == 0:
releases = ['n/a'] releases = ['n/a']
@ -387,26 +388,26 @@ def manual_search(path, language, hi, forced, providers, providers_auth, sceneNa
subtitle=codecs.encode(pickle.dumps(s.make_picklable()), "base64").decode(), subtitle=codecs.encode(pickle.dumps(s.make_picklable()), "base64").decode(),
url=s.page_link, matches=list(matches), dont_matches=list(not_matched), url=s.page_link, matches=list(matches), dont_matches=list(not_matched),
release_info=releases, uploader=s_uploader)) release_info=releases, uploader=s_uploader))
final_subtitles = sorted(subtitles_list, key=lambda x: (x['orig_score'], x['score_without_hash']), final_subtitles = sorted(subtitles_list, key=lambda x: (x['orig_score'], x['score_without_hash']),
reverse=True) reverse=True)
logging.debug('BAZARR ' + str(len(final_subtitles)) + " Subtitles have been found for this file: " + path) logging.debug('BAZARR ' + str(len(final_subtitles)) + " Subtitles have been found for this file: " + path)
logging.debug('BAZARR Ended searching Subtitles for this file: ' + path) logging.debug('BAZARR Ended searching Subtitles for this file: ' + path)
subliminal.region.backend.sync() subliminal.region.backend.sync()
return final_subtitles return final_subtitles
def manual_download_subtitle(path, language, audio_language, hi, forced, subtitle, provider, providers_auth, sceneName, title, def manual_download_subtitle(path, language, audio_language, hi, forced, subtitle, provider, providers_auth, sceneName,
media_type): title, media_type):
logging.debug('BAZARR Manually downloading Subtitles for this file: ' + path) logging.debug('BAZARR Manually downloading Subtitles for this file: ' + path)
if settings.general.getboolean('utf8_encode'): if settings.general.getboolean('utf8_encode'):
os.environ["SZ_KEEP_ENCODING"] = "" os.environ["SZ_KEEP_ENCODING"] = ""
else: else:
os.environ["SZ_KEEP_ENCODING"] = "True" os.environ["SZ_KEEP_ENCODING"] = "True"
subtitle = pickle.loads(codecs.decode(subtitle.encode(), "base64")) subtitle = pickle.loads(codecs.decode(subtitle.encode(), "base64"))
use_postprocessing = settings.general.getboolean('use_postprocessing') use_postprocessing = settings.general.getboolean('use_postprocessing')
postprocessing_cmd = settings.general.postprocessing_cmd postprocessing_cmd = settings.general.postprocessing_cmd
@ -441,7 +442,7 @@ def manual_download_subtitle(path, language, audio_language, hi, forced, subtitl
chmod=chmod, chmod=chmod,
# formats=("srt", "vtt") # formats=("srt", "vtt")
path_decoder=force_unicode) path_decoder=force_unicode)
except Exception as e: except Exception as e:
logging.exception('BAZARR Error saving Subtitles file to disk for this file:' + path) logging.exception('BAZARR Error saving Subtitles file to disk for this file:' + path)
return return
@ -481,10 +482,12 @@ def manual_download_subtitle(path, language, audio_language, hi, forced, subtitl
srt_lang=downloaded_language_code3, media_type=media_type, srt_lang=downloaded_language_code3, media_type=media_type,
percent_score=score, radarr_id=movie_metadata['radarrId']) percent_score=score, radarr_id=movie_metadata['radarrId'])
if use_postprocessing is True: if use_postprocessing:
percent_score = round(subtitle.score * 100 / max_score, 2)
command = pp_replace(postprocessing_cmd, path, downloaded_path, downloaded_language, command = pp_replace(postprocessing_cmd, path, downloaded_path, downloaded_language,
downloaded_language_code2, downloaded_language_code3, audio_language, downloaded_language_code2, downloaded_language_code3, audio_language,
audio_language_code2, audio_language_code3, subtitle.language.forced) audio_language_code2, audio_language_code3, subtitle.language.forced,
percent_score)
if media_type == 'series': if media_type == 'series':
use_pp_threshold = settings.general.getboolean('use_postprocessing_threshold') use_pp_threshold = settings.general.getboolean('use_postprocessing_threshold')
@ -494,27 +497,29 @@ def manual_download_subtitle(path, language, audio_language, hi, forced, subtitl
pp_threshold = settings.general.postprocessing_threshold_movie pp_threshold = settings.general.postprocessing_threshold_movie
if not use_pp_threshold or (use_pp_threshold and score < float(pp_threshold)): if not use_pp_threshold or (use_pp_threshold and score < float(pp_threshold)):
logging.debug("BAZARR Using post-processing command: {}".format(command))
postprocessing(command, path) postprocessing(command, path)
else: else:
logging.debug("BAZARR post-processing skipped because subtitles score isn't below this " logging.debug("BAZARR post-processing skipped because subtitles score isn't below this "
"threshold value: " + pp_threshold + "%") "threshold value: " + pp_threshold + "%")
if media_type == 'series': if media_type == 'series':
reversed_path = path_mappings.path_replace_reverse(path) reversed_path = path_mappings.path_replace_reverse(path)
else: else:
reversed_path = path_mappings.path_replace_reverse_movie(path) reversed_path = path_mappings.path_replace_reverse_movie(path)
track_event(category=downloaded_provider, action="manually_downloaded", label=downloaded_language) track_event(category=downloaded_provider, action="manually_downloaded",
label=downloaded_language)
return message, reversed_path, downloaded_language_code2, downloaded_provider, subtitle.score, subtitle.language.forced return message, reversed_path, downloaded_language_code2, downloaded_provider, subtitle.score, subtitle.language.forced
else: else:
logging.error( logging.error(
"BAZARR Tried to manually download a Subtitles for file: " + path + " but we weren't able to do (probably throttled by " + str( "BAZARR Tried to manually download a Subtitles for file: " + path + " but we weren't able to do (probably throttled by " + str(
subtitle.provider_name) + ". Please retry later or select a Subtitles from another provider.") subtitle.provider_name) + ". Please retry later or select a Subtitles from another provider.")
return None return None
subliminal.region.backend.sync() subliminal.region.backend.sync()
logging.debug('BAZARR Ended manually downloading Subtitles for file: ' + path) logging.debug('BAZARR Ended manually downloading Subtitles for file: ' + path)
@ -588,8 +593,8 @@ def manual_upload_subtitle(path, language, forced, title, scene_name, media_type
os.chmod(subtitle_path, chmod) os.chmod(subtitle_path, chmod)
message = language_from_alpha3(language) + (" forced" if forced else "") + " Subtitles manually uploaded." message = language_from_alpha3(language) + (" forced" if forced else "") + " Subtitles manually uploaded."
uploaded_language_code3 = language uploaded_language_code3 = language
uploaded_language = language_from_alpha3(uploaded_language_code3) uploaded_language = language_from_alpha3(uploaded_language_code3)
uploaded_language_code2 = alpha2_from_alpha3(uploaded_language_code3) uploaded_language_code2 = alpha2_from_alpha3(uploaded_language_code3)
audio_language_code2 = alpha2_from_language(audio_language) audio_language_code2 = alpha2_from_language(audio_language)
@ -609,13 +614,12 @@ def manual_upload_subtitle(path, language, forced, title, scene_name, media_type
sync_subtitles(video_path=path, srt_path=subtitle_path, srt_lang=uploaded_language_code3, media_type=media_type, sync_subtitles(video_path=path, srt_path=subtitle_path, srt_lang=uploaded_language_code3, media_type=media_type,
percent_score=100, radarr_id=movie_metadata['radarrId']) percent_score=100, radarr_id=movie_metadata['radarrId'])
if use_postprocessing is True: if use_postprocessing :
command = pp_replace(postprocessing_cmd, path, subtitle_path, uploaded_language, command = pp_replace(postprocessing_cmd, path, subtitle_path, uploaded_language,
uploaded_language_code2, uploaded_language_code3, audio_language, uploaded_language_code2, uploaded_language_code3, audio_language,
audio_language_code2, audio_language_code3, forced) audio_language_code2, audio_language_code3, forced, 100)
postprocessing(command, path) postprocessing(command, path)
if media_type == 'series': if media_type == 'series':
reversed_path = path_mappings.path_replace_reverse(path) reversed_path = path_mappings.path_replace_reverse(path)
else: else:
@ -629,7 +633,7 @@ def series_download_subtitles(no):
episodes_details_clause = " AND monitored='True'" episodes_details_clause = " AND monitored='True'"
else: else:
episodes_details_clause = '' episodes_details_clause = ''
episodes_details = database.execute("SELECT path, missing_subtitles, sonarrEpisodeId, scene_name " episodes_details = database.execute("SELECT path, missing_subtitles, sonarrEpisodeId, scene_name "
"FROM table_episodes WHERE sonarrSeriesId=? and missing_subtitles!='[]'" + "FROM table_episodes WHERE sonarrSeriesId=? and missing_subtitles!='[]'" +
episodes_details_clause, (no,)) episodes_details_clause, (no,))
@ -637,17 +641,18 @@ def series_download_subtitles(no):
logging.debug("BAZARR no episode for that sonarrSeriesId can be found in database:", str(no)) logging.debug("BAZARR no episode for that sonarrSeriesId can be found in database:", str(no))
return return
series_details = database.execute("SELECT hearing_impaired, audio_language, title, forced FROM table_shows WHERE sonarrSeriesId=?", series_details = database.execute(
(no,), only_one=True) "SELECT hearing_impaired, audio_language, title, forced FROM table_shows WHERE sonarrSeriesId=?",
(no,), only_one=True)
if not series_details: if not series_details:
logging.debug("BAZARR no series with that sonarrSeriesId can be found in database:", str(no)) logging.debug("BAZARR no series with that sonarrSeriesId can be found in database:", str(no))
return return
providers_list = get_providers() providers_list = get_providers()
providers_auth = get_providers_auth() providers_auth = get_providers_auth()
count_episodes_details = len(episodes_details) count_episodes_details = len(episodes_details)
for i, episode in enumerate(episodes_details, 1): for i, episode in enumerate(episodes_details, 1):
if providers_list: if providers_list:
for language in ast.literal_eval(episode['missing_subtitles']): for language in ast.literal_eval(episode['missing_subtitles']):
@ -682,7 +687,7 @@ def episode_download_subtitles(no):
episodes_details_clause = " AND monitored='True'" episodes_details_clause = " AND monitored='True'"
else: else:
episodes_details_clause = '' episodes_details_clause = ''
episodes_details = database.execute("SELECT table_episodes.path, table_episodes.missing_subtitles, " episodes_details = database.execute("SELECT table_episodes.path, table_episodes.missing_subtitles, "
"table_episodes.sonarrEpisodeId, table_episodes.scene_name, " "table_episodes.sonarrEpisodeId, table_episodes.scene_name, "
"table_shows.hearing_impaired, table_shows.title, table_shows.sonarrSeriesId, " "table_shows.hearing_impaired, table_shows.title, table_shows.sonarrSeriesId, "
@ -693,10 +698,10 @@ def episode_download_subtitles(no):
if not episodes_details: if not episodes_details:
logging.debug("BAZARR no episode with that sonarrEpisodeId can be found in database:", str(no)) logging.debug("BAZARR no episode with that sonarrEpisodeId can be found in database:", str(no))
return return
providers_list = get_providers() providers_list = get_providers()
providers_auth = get_providers_auth() providers_auth = get_providers_auth()
for episode in episodes_details: for episode in episodes_details:
if providers_list: if providers_list:
for language in ast.literal_eval(episode['missing_subtitles']): for language in ast.literal_eval(episode['missing_subtitles']):
@ -719,7 +724,8 @@ def episode_download_subtitles(no):
provider = result[3] provider = result[3]
score = result[4] score = result[4]
store_subtitles(episode['path'], path_mappings.path_replace(episode['path'])) store_subtitles(episode['path'], path_mappings.path_replace(episode['path']))
history_log(1, episode['sonarrSeriesId'], episode['sonarrEpisodeId'], message, path, language_code, provider, score) history_log(1, episode['sonarrSeriesId'], episode['sonarrEpisodeId'], message, path,
language_code, provider, score)
send_notifications(episode['sonarrSeriesId'], episode['sonarrEpisodeId'], message) send_notifications(episode['sonarrSeriesId'], episode['sonarrEpisodeId'], message)
else: else:
logging.info("BAZARR All providers are throttled") logging.info("BAZARR All providers are throttled")
@ -732,13 +738,14 @@ def movies_download_subtitles(no):
else: else:
movie_details_clause = '' movie_details_clause = ''
movie = database.execute("SELECT path, missing_subtitles, audio_language, radarrId, sceneName, hearing_impaired, title, forced " movie = database.execute(
"FROM table_movies WHERE radarrId=?" + movie_details_clause, (no,), only_one=True) "SELECT path, missing_subtitles, audio_language, radarrId, sceneName, hearing_impaired, title, forced "
"FROM table_movies WHERE radarrId=?" + movie_details_clause, (no,), only_one=True)
if not movie: if not movie:
logging.debug("BAZARR no movie with that radarrId can be found in database:", str(no)) logging.debug("BAZARR no movie with that radarrId can be found in database:", str(no))
return return
providers_list = get_providers() providers_list = get_providers()
providers_auth = get_providers_auth() providers_auth = get_providers_auth()
@ -746,7 +753,7 @@ def movies_download_subtitles(no):
count_movie = len(ast.literal_eval(movie['missing_subtitles'])) count_movie = len(ast.literal_eval(movie['missing_subtitles']))
else: else:
count_movie = 0 count_movie = 0
for i, language in enumerate(ast.literal_eval(movie['missing_subtitles']), 1): for i, language in enumerate(ast.literal_eval(movie['missing_subtitles']), 1):
if providers_list: if providers_list:
if language is not None: if language is not None:
@ -784,10 +791,10 @@ def wanted_download_subtitles(path, l, count_episodes):
"table_episodes.sonarrSeriesId = table_shows.sonarrSeriesId " "table_episodes.sonarrSeriesId = table_shows.sonarrSeriesId "
"WHERE table_episodes.path=? and table_episodes.missing_subtitles!='[]'", "WHERE table_episodes.path=? and table_episodes.missing_subtitles!='[]'",
(path_mappings.path_replace_reverse(path),)) (path_mappings.path_replace_reverse(path),))
providers_list = get_providers() providers_list = get_providers()
providers_auth = get_providers_auth() providers_auth = get_providers_auth()
for episode in episodes_details: for episode in episodes_details:
attempt = episode['failedAttempts'] attempt = episode['failedAttempts']
if type(attempt) == str: if type(attempt) == str:
@ -803,7 +810,7 @@ def wanted_download_subtitles(path, l, count_episodes):
database.execute("UPDATE table_episodes SET failedAttempts=? WHERE sonarrEpisodeId=?", database.execute("UPDATE table_episodes SET failedAttempts=? WHERE sonarrEpisodeId=?",
(str(attempt), episode['sonarrEpisodeId'])) (str(attempt), episode['sonarrEpisodeId']))
for i in range(len(attempt)): for i in range(len(attempt)):
if attempt[i][0] == language: if attempt[i][0] == language:
if search_active(attempt[i][1]): if search_active(attempt[i][1]):
@ -825,21 +832,24 @@ def wanted_download_subtitles(path, l, count_episodes):
provider = result[3] provider = result[3]
score = result[4] score = result[4]
store_subtitles(episode['path'], path_mappings.path_replace(episode['path'])) store_subtitles(episode['path'], path_mappings.path_replace(episode['path']))
history_log(1, episode['sonarrSeriesId'], episode['sonarrEpisodeId'], message, path, language_code, provider, score) history_log(1, episode['sonarrSeriesId'], episode['sonarrEpisodeId'], message, path,
language_code, provider, score)
send_notifications(episode['sonarrSeriesId'], episode['sonarrEpisodeId'], message) send_notifications(episode['sonarrSeriesId'], episode['sonarrEpisodeId'], message)
else: else:
logging.debug( logging.debug(
'BAZARR Search is not active for episode ' + episode['path'] + ' Language: ' + attempt[i][0]) 'BAZARR Search is not active for episode ' + episode['path'] + ' Language: ' + attempt[i][
0])
def wanted_download_subtitles_movie(path, l, count_movies): def wanted_download_subtitles_movie(path, l, count_movies):
movies_details = database.execute("SELECT path, missing_subtitles, radarrId, hearing_impaired, audio_language, sceneName, " movies_details = database.execute(
"failedAttempts, title, forced FROM table_movies WHERE path = ? " "SELECT path, missing_subtitles, radarrId, hearing_impaired, audio_language, sceneName, "
"AND missing_subtitles != '[]'", (path_mappings.path_replace_reverse_movie(path),)) "failedAttempts, title, forced FROM table_movies WHERE path = ? "
"AND missing_subtitles != '[]'", (path_mappings.path_replace_reverse_movie(path),))
providers_list = get_providers() providers_list = get_providers()
providers_auth = get_providers_auth() providers_auth = get_providers_auth()
for movie in movies_details: for movie in movies_details:
attempt = movie['failedAttempts'] attempt = movie['failedAttempts']
if type(attempt) == str: if type(attempt) == str:
@ -852,10 +862,10 @@ def wanted_download_subtitles_movie(path, l, count_movies):
att = list(zip(*attempt))[0] att = list(zip(*attempt))[0]
if language not in att: if language not in att:
attempt.append([language, time.time()]) attempt.append([language, time.time()])
database.execute("UPDATE table_movies SET failedAttempts=? WHERE radarrId=?", database.execute("UPDATE table_movies SET failedAttempts=? WHERE radarrId=?",
(str(attempt), movie['radarrId'])) (str(attempt), movie['radarrId']))
for i in range(len(attempt)): for i in range(len(attempt)):
if attempt[i][0] == language: if attempt[i][0] == language:
if search_active(attempt[i][1]) is True: if search_active(attempt[i][1]) is True:
@ -881,7 +891,8 @@ def wanted_download_subtitles_movie(path, l, count_movies):
send_notifications_movie(movie['radarrId'], message) send_notifications_movie(movie['radarrId'], message)
else: else:
logging.info( logging.info(
'BAZARR Search is not active for this Movie ' + movie['path'] + ' Language: ' + attempt[i][0]) 'BAZARR Search is not active for this Movie ' + movie['path'] + ' Language: ' + attempt[i][
0])
def wanted_search_missing_subtitles_series(): def wanted_search_missing_subtitles_series():
@ -903,7 +914,7 @@ def wanted_search_missing_subtitles_series():
else: else:
logging.info("BAZARR All providers are throttled") logging.info("BAZARR All providers are throttled")
return return
logging.info('BAZARR Finished searching for missing Series Subtitles. Check History for more information.') logging.info('BAZARR Finished searching for missing Series Subtitles. Check History for more information.')
@ -951,13 +962,14 @@ def search_active(timestamp):
def refine_from_db(path, video): def refine_from_db(path, video):
if isinstance(video, Episode): if isinstance(video, Episode):
data = database.execute("SELECT table_shows.title as seriesTitle, table_episodes.season, table_episodes.episode, " data = database.execute(
"table_episodes.title as episodeTitle, table_shows.year, table_shows.tvdbId, " "SELECT table_shows.title as seriesTitle, table_episodes.season, table_episodes.episode, "
"table_shows.alternateTitles, table_episodes.format, table_episodes.resolution, " "table_episodes.title as episodeTitle, table_shows.year, table_shows.tvdbId, "
"table_episodes.video_codec, table_episodes.audio_codec, table_episodes.path " "table_shows.alternateTitles, table_episodes.format, table_episodes.resolution, "
"FROM table_episodes INNER JOIN table_shows on " "table_episodes.video_codec, table_episodes.audio_codec, table_episodes.path "
"table_shows.sonarrSeriesId = table_episodes.sonarrSeriesId " "FROM table_episodes INNER JOIN table_shows on "
"WHERE table_episodes.path = ?", (path_mappings.path_replace_reverse(path),), only_one=True) "table_shows.sonarrSeriesId = table_episodes.sonarrSeriesId "
"WHERE table_episodes.path = ?", (path_mappings.path_replace_reverse(path),), only_one=True)
if data: if data:
video.series = data['seriesTitle'] video.series = data['seriesTitle']
@ -995,7 +1007,7 @@ def refine_from_db(path, video):
if data['video_codec']: video.video_codec = data['video_codec'] if data['video_codec']: video.video_codec = data['video_codec']
if not video.audio_codec: if not video.audio_codec:
if data['audio_codec']: video.audio_codec = data['audio_codec'] if data['audio_codec']: video.audio_codec = data['audio_codec']
return video return video
@ -1006,7 +1018,7 @@ def refine_from_ffprobe(path, video):
return return
else: else:
logging.debug('BAZARR FFprobe used is %s', exe) logging.debug('BAZARR FFprobe used is %s', exe)
api.initialize({'provider': 'ffmpeg', 'ffmpeg': exe}) api.initialize({'provider': 'ffmpeg', 'ffmpeg': exe})
data = api.know(path) data = api.know(path)
@ -1073,7 +1085,7 @@ def upgrade_subtitles():
"table_episodes on table_episodes.sonarrEpisodeId = " "table_episodes on table_episodes.sonarrEpisodeId = "
"table_history.sonarrEpisodeId WHERE action IN " "table_history.sonarrEpisodeId WHERE action IN "
"(" + ','.join(map(str, query_actions)) + ") AND timestamp > ? AND " "(" + ','.join(map(str, query_actions)) + ") AND timestamp > ? AND "
"score is not null" + series_monitored_only_query_string + "score is not null" + series_monitored_only_query_string +
" GROUP BY table_history.video_path, table_history.language", " GROUP BY table_history.video_path, table_history.language",
(minimum_timestamp,)) (minimum_timestamp,))
@ -1125,7 +1137,7 @@ def upgrade_subtitles():
movies_to_upgrade.append(movie) movies_to_upgrade.append(movie)
count_movie_to_upgrade = len(movies_to_upgrade) count_movie_to_upgrade = len(movies_to_upgrade)
providers_list = get_providers() providers_list = get_providers()
providers_auth = get_providers_auth() providers_auth = get_providers_auth()
@ -1145,7 +1157,7 @@ def upgrade_subtitles():
forced_languages = [l + ":forced" for l in desired_languages] + desired_languages forced_languages = [l + ":forced" for l in desired_languages] + desired_languages
else: else:
forced_languages = desired_languages forced_languages = desired_languages
if episode['language'] in forced_languages: if episode['language'] in forced_languages:
if episode['language'].endswith('forced'): if episode['language'].endswith('forced'):
language = episode['language'].split(':')[0] language = episode['language'].split(':')[0]
@ -1153,7 +1165,7 @@ def upgrade_subtitles():
else: else:
language = episode['language'] language = episode['language']
is_forced = "False" is_forced = "False"
result = download_subtitle(path_mappings.path_replace(episode['video_path']), result = download_subtitle(path_mappings.path_replace(episode['video_path']),
str(alpha3_from_alpha2(language)), str(alpha3_from_alpha2(language)),
episode['audio_language'], episode['audio_language'],
@ -1174,9 +1186,10 @@ def upgrade_subtitles():
provider = result[3] provider = result[3]
score = result[4] score = result[4]
store_subtitles(episode['video_path'], path_mappings.path_replace(episode['video_path'])) store_subtitles(episode['video_path'], path_mappings.path_replace(episode['video_path']))
history_log(3, episode['sonarrSeriesId'], episode['sonarrEpisodeId'], message, path, language_code, provider, score) history_log(3, episode['sonarrSeriesId'], episode['sonarrEpisodeId'], message, path,
language_code, provider, score)
send_notifications(episode['sonarrSeriesId'], episode['sonarrEpisodeId'], message) send_notifications(episode['sonarrSeriesId'], episode['sonarrEpisodeId'], message)
if settings.general.getboolean('use_radarr'): if settings.general.getboolean('use_radarr'):
for i, movie in enumerate(movies_to_upgrade, 1): for i, movie in enumerate(movies_to_upgrade, 1):
if movie['languages'] in [None, 'None', '[]']: if movie['languages'] in [None, 'None', '[]']:
@ -1193,7 +1206,7 @@ def upgrade_subtitles():
forced_languages = [l + ":forced" for l in desired_languages] + desired_languages forced_languages = [l + ":forced" for l in desired_languages] + desired_languages
else: else:
forced_languages = desired_languages forced_languages = desired_languages
if movie['language'] in forced_languages: if movie['language'] in forced_languages:
if movie['language'].endswith('forced'): if movie['language'].endswith('forced'):
language = movie['language'].split(':')[0] language = movie['language'].split(':')[0]
@ -1201,7 +1214,7 @@ def upgrade_subtitles():
else: else:
language = movie['language'] language = movie['language']
is_forced = "False" is_forced = "False"
result = download_subtitle(path_mappings.path_replace_movie(movie['video_path']), result = download_subtitle(path_mappings.path_replace_movie(movie['video_path']),
str(alpha3_from_alpha2(language)), str(alpha3_from_alpha2(language)),
movie['audio_language'], movie['audio_language'],
@ -1221,7 +1234,8 @@ def upgrade_subtitles():
language_code = result[2] + ":forced" if forced else result[2] language_code = result[2] + ":forced" if forced else result[2]
provider = result[3] provider = result[3]
score = result[4] score = result[4]
store_subtitles_movie(movie['video_path'], path_mappings.path_replace_movie(movie['video_path'])) store_subtitles_movie(movie['video_path'],
path_mappings.path_replace_movie(movie['video_path']))
history_log_movie(3, movie['radarrId'], message, path, language_code, provider, score) history_log_movie(3, movie['radarrId'], message, path, language_code, provider, score)
send_notifications_movie(movie['radarrId'], message) send_notifications_movie(movie['radarrId'], message)

@ -95,7 +95,7 @@ class PathMappings:
path_mappings = PathMappings() path_mappings = PathMappings()
def pp_replace(pp_command, episode, subtitles, language, language_code2, language_code3, episode_language, episode_language_code2, episode_language_code3, forced): def pp_replace(pp_command, episode, subtitles, language, language_code2, language_code3, episode_language, episode_language_code2, episode_language_code3, forced, score):
is_forced = ":forced" if forced else "" is_forced = ":forced" if forced else ""
is_forced_string = " forced" if forced else "" is_forced_string = " forced" if forced else ""
pp_command = pp_command.replace('{{directory}}', os.path.dirname(episode)) pp_command = pp_command.replace('{{directory}}', os.path.dirname(episode))
@ -108,6 +108,7 @@ def pp_replace(pp_command, episode, subtitles, language, language_code2, languag
pp_command = pp_command.replace('{{episode_language}}', episode_language) pp_command = pp_command.replace('{{episode_language}}', episode_language)
pp_command = pp_command.replace('{{episode_language_code2}}', episode_language_code2) pp_command = pp_command.replace('{{episode_language_code2}}', episode_language_code2)
pp_command = pp_command.replace('{{episode_language_code3}}', episode_language_code3) pp_command = pp_command.replace('{{episode_language_code3}}', episode_language_code3)
pp_command = pp_command.replace('{{score}}', str(score))
return pp_command return pp_command

@ -364,8 +364,8 @@ def guess_external_subtitles(dest_folder, subtitles):
logging.debug("BAZARR falling back to file content analysis to detect language.") logging.debug("BAZARR falling back to file content analysis to detect language.")
detected_language = None detected_language = None
# to improve performance, skip detection of files larger that 5M # to improve performance, skip detection of files larger that 1M
if os.path.getsize(subtitle_path) > 5*1024*1024: if os.path.getsize(subtitle_path) > 1*1024*1024:
logging.debug("BAZARR subtitles file is too large to be text based. Skipping this file: " + logging.debug("BAZARR subtitles file is too large to be text based. Skipping this file: " +
subtitle_path) subtitle_path)
continue continue
@ -374,16 +374,11 @@ def guess_external_subtitles(dest_folder, subtitles):
text = f.read() text = f.read()
try: try:
# to improve performance, use only the first 32K to detect encoding guess = chardet.detect(text)
guess = chardet.detect(text[:32768])
logging.debug('BAZARR detected encoding %r', guess) logging.debug('BAZARR detected encoding %r', guess)
if guess["confidence"] < 0.6:
raise UnicodeError
if guess["encoding"] == "ascii":
guess["encoding"] = "utf-8"
text = text.decode(guess["encoding"]) text = text.decode(guess["encoding"])
detected_language = guess_language(text) detected_language = guess_language(text)
except UnicodeError: except (UnicodeDecodeError, TypeError):
logging.exception("BAZARR subtitles file doesn't seems to be text based. Skipping this file: " + logging.exception("BAZARR subtitles file doesn't seems to be text based. Skipping this file: " +
subtitle_path) subtitle_path)
except: except:

@ -605,6 +605,11 @@ def _search_external_subtitles(path, languages=None, only_one=False, scandir_gen
if not INCLUDE_EXOTIC_SUBS and p_ext not in (".srt", ".ass", ".ssa", ".vtt"): if not INCLUDE_EXOTIC_SUBS and p_ext not in (".srt", ".ass", ".ssa", ".vtt"):
continue continue
if p_root.lower() == fn_no_ext_lower:
# skip check for language code if the subtitle file name is the same as the video name
subtitles[p] = None
continue
# extract potential forced/normal/default tag # extract potential forced/normal/default tag
# fixme: duplicate from subtitlehelpers # fixme: duplicate from subtitlehelpers
split_tag = p_root.rsplit('.', 1) split_tag = p_root.rsplit('.', 1)

@ -139,7 +139,11 @@ class BetaSeriesProvider(Provider):
def download_subtitle(self, subtitle): def download_subtitle(self, subtitle):
logger.info('Downloading subtitle %r', subtitle) logger.info('Downloading subtitle %r', subtitle)
r = self.session.get(subtitle.download_link, timeout=10) r = self.session.get(subtitle.download_link, timeout=10)
r.raise_for_status() if r.status_code == 404:
logger.error('Error 404 downloading %r', subtitle)
return
else:
r.raise_for_status()
archive = _get_archive(r.content) archive = _get_archive(r.content)
if archive: if archive:
@ -153,7 +157,7 @@ class BetaSeriesProvider(Provider):
if subtitle_content: if subtitle_content:
subtitle.content = fix_line_ending(subtitle_content) subtitle.content = fix_line_ending(subtitle_content)
else: else:
logger.debug('Could not extract subtitle from %r', archive) logger.error('Could not extract subtitle from %r', archive)
def _get_archive(content): def _get_archive(content):

@ -66,7 +66,6 @@ class TitulkySubtitle(Subtitle):
self.version = version self.version = version
self.year = year self.year = year
self.download_link = download_link self.download_link = download_link
self.encoding = 'UTF-8'
for t in title: for t in title:
self.title = t self.title = t
if year: if year:

@ -105,7 +105,7 @@ class Subtitle(Subtitle_):
return self return self
def get_encoding(self): def get_encoding(self):
return self.encoding if self.encoding else self.guess_encoding() return self.guess_encoding()
def set_encoding(self, encoding): def set_encoding(self, encoding):
ge = self.get_encoding() ge = self.get_encoding()
@ -116,7 +116,6 @@ class Subtitle(Subtitle_):
logger.debug("Changing encoding: to %s, from %s", encoding, ge) logger.debug("Changing encoding: to %s, from %s", encoding, ge)
self.content = unicontent.encode(encoding) self.content = unicontent.encode(encoding)
self._guessed_encoding = encoding self._guessed_encoding = encoding
self.encoding = encoding
def normalize(self): def normalize(self):
""" """
@ -141,6 +140,16 @@ class Subtitle(Subtitle_):
if self._guessed_encoding: if self._guessed_encoding:
return self._guessed_encoding return self._guessed_encoding
if self.encoding:
# check provider encoding and use it only if it is valid
try:
self.content.decode(self.encoding)
self._guessed_encoding = self.encoding
return self._guessed_encoding
except:
# provider specified encoding is invalid, fallback to guessing
pass
logger.info('Guessing encoding for language %s', self.language) logger.info('Guessing encoding for language %s', self.language)
encodings = ['utf-8'] encodings = ['utf-8']
@ -206,7 +215,7 @@ class Subtitle(Subtitle_):
else: else:
# Western European (windows-1252) / Northern European # Western European (windows-1252) / Northern European
encodings.extend(['latin-1', 'iso-8859-15', 'iso-8859-9', 'iso-8859-4', 'iso-8859-1']) encodings.extend(['windows-1252', 'iso-8859-15', 'iso-8859-9', 'iso-8859-4', 'iso-8859-1'])
# try to decode # try to decode
logger.debug('Trying encodings %r', encodings) logger.debug('Trying encodings %r', encodings)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -38,6 +38,7 @@
<link rel="stylesheet" type="text/css" <link rel="stylesheet" type="text/css"
href="{{ url_for('static',filename='plugins/datatables.net-bs4/css/dataTables.bootstrap4.min.css') }}"> href="{{ url_for('static',filename='plugins/datatables.net-bs4/css/dataTables.bootstrap4.min.css') }}">
<link rel="stylesheet" href="{{ url_for('static',filename='css/bootstrap-select.css') }}"/> <link rel="stylesheet" href="{{ url_for('static',filename='css/bootstrap-select.css') }}"/>
<link rel="stylesheet" href="{{ url_for('static',filename='css/bootstrap-slider.min.css') }}"/>
<link rel="stylesheet" href="{{ url_for('static',filename='css/jquery.typeahead.min.css') }}"/> <link rel="stylesheet" href="{{ url_for('static',filename='css/jquery.typeahead.min.css') }}"/>
{% endblock head_css %} {% endblock head_css %}
@ -130,7 +131,7 @@
<!-- ============================================================== --> <!-- ============================================================== -->
<!-- Search --> <!-- Search -->
<!-- ============================================================== --> <!-- ============================================================== -->
<li class="nav-item hidden-sm-down search-box"> <li class="nav-item search-box">
<form class="form-material"> <form class="form-material">
<div class="typeahead__container"> <div class="typeahead__container">
<div class="typeahead__field"> <div class="typeahead__field">
@ -321,6 +322,7 @@
<script src="{{ url_for('static',filename='js/custom.js') }}"></script> <script src="{{ url_for('static',filename='js/custom.js') }}"></script>
<script src="{{ url_for('static',filename='js/socket.io.js') }}"></script> <script src="{{ url_for('static',filename='js/socket.io.js') }}"></script>
<script src="{{ url_for('static',filename='js/bootstrap-select.min.js') }}"></script> <script src="{{ url_for('static',filename='js/bootstrap-select.min.js') }}"></script>
<script src="{{ url_for('static',filename='js/bootstrap-slider.min.js') }}"></script>
<script src="{{ url_for('static',filename='moment/moment.js') }}"></script> <script src="{{ url_for('static',filename='moment/moment.js') }}"></script>
<script src="{{ url_for('static',filename='js/jquery.typeahead.min.js') }}"></script> <script src="{{ url_for('static',filename='js/jquery.typeahead.min.js') }}"></script>
@ -341,6 +343,11 @@
$('[data-toggle="tooltip"]').tooltip({html: true}); $('[data-toggle="tooltip"]').tooltip({html: true});
}); });
$(".slider").slider({
tooltip: 'always',
tooltip_position: 'bottom'
});
events = io.connect({ events = io.connect({
path: '{{ settings.general.base_url.rstrip('/') }}/socket.io', path: '{{ settings.general.base_url.rstrip('/') }}/socket.io',
transports: ['polling'], transports: ['polling'],

@ -112,8 +112,8 @@
<div class="col-sm-3 text-right"> <div class="col-sm-3 text-right">
<b>Minimum Score</b> <b>Minimum Score</b>
</div> </div>
<div class="col-sm-1"> <div class="col-sm-8">
<input type="number" min="0" max="100" step="1" onkeydown="return false" class="form-control" id="settings-general-minimum_score_movie" name="settings-general-minimum_score_movie" value="{{settings.general.minimum_score_movie}}"> <input class="slider" id="settings-general-minimum_score_movie" name="settings-general-minimum_score_movie" data-slider-id='settings-general-minimum_score_movie' type="text" data-slider-min="1" data-slider-max="100" data-slider-step="1" data-slider-value="{{settings.general.minimum_score_movie}}"/>
</div> </div>
</div> </div>
<div class="row"> <div class="row">

@ -112,8 +112,8 @@
<div class="col-sm-3 text-right"> <div class="col-sm-3 text-right">
<b>Minimum Score</b> <b>Minimum Score</b>
</div> </div>
<div class="col-sm-1"> <div class="col-sm-8">
<input type="number" min="0" max="100" step="1" onkeydown="return false" class="form-control" id="settings-general-minimum_score" name="settings-general-minimum_score" value="{{settings.general.minimum_score}}"> <input class="slider" id="settings-general-minimum_score" name="settings-general-minimum_score" data-slider-id='settings-general-minimum_score' type="text" data-slider-min="1" data-slider-max="100" data-slider-step="1" data-slider-value="{{settings.general.minimum_score}}"/>
</div> </div>
</div> </div>
<div class="row"> <div class="row">

@ -11,7 +11,8 @@
<button class="btn btn-outline" id="save_button"> <button class="btn btn-outline" id="save_button">
<div> <div>
<span class="fa-stack"> <span class="fa-stack">
<i class="fas fa-save fa-stack-2x align-top text-themecolor text-center font-20" aria-hidden="true"></i> <i class="fas fa-save fa-stack-2x align-top text-themecolor text-center font-20"
aria-hidden="true"></i>
<i id="save_button_checkmark" class="fas fa-check fa-stack-2x" style="color:green;"></i> <i id="save_button_checkmark" class="fas fa-check fa-stack-2x" style="color:green;"></i>
</span> </span>
</div> </div>
@ -34,21 +35,24 @@
<b>Subtitle Folder</b> <b>Subtitle Folder</b>
</div> </div>
<div class="form-group col-sm-4"> <div class="form-group col-sm-4">
<select class="form-control selectpicker" id="settings-general-subfolder" name="settings-general-subfolder"> <select class="form-control selectpicker" id="settings-general-subfolder"
name="settings-general-subfolder">
<option value="current">Alongside Media File</option> <option value="current">Alongside Media File</option>
<option value="relative">Relative Path To Media File</option> <option value="relative">Relative Path To Media File</option>
<option value="absolute">Absolute Path</option> <option value="absolute">Absolute Path</option>
</select> </select>
<label for="settings-general-subfolder">Choose the folder you wish to store/read the Subtitles</label> <label for="settings-general-subfolder">Choose the folder you wish to store/read the
Subtitles</label>
</div> </div>
</div> </div>
<div id="subfolder_div"> <div id="subfolder_div">
<div class="row"> <div class="row">
<div class="col-sm-3 text-right"> <div class="col-sm-3 text-right">
<b>Custom Subtitle Folder</b> <b>Custom Subtitle Folder</b>
</div> </div>
<div class="col-sm-4"> <div class="col-sm-4">
<input type="text" class="form-control" id="settings-general-subfolder_custom" name="settings-general-subfolder_custom" value="{{settings.general.subfolder_custom}}"> <input type="text" class="form-control" id="settings-general-subfolder_custom"
name="settings-general-subfolder_custom" value="{{ settings.general.subfolder_custom }}">
<label for="settings-general-subfolder_custom">Choose your own folder for Subtitles</label> <label for="settings-general-subfolder_custom">Choose your own folder for Subtitles</label>
</div> </div>
</div> </div>
@ -60,7 +64,8 @@
</div> </div>
<div class="form-group col-sm-8"> <div class="form-group col-sm-8">
<label class="custom-control custom-checkbox"> <label class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="settings-general-upgrade_subs" name="settings-general-upgrade_subs"> <input type="checkbox" class="custom-control-input" id="settings-general-upgrade_subs"
name="settings-general-upgrade_subs">
<span class="custom-control-label" for="settings-general-upgrade_subs"></span> <span class="custom-control-label" for="settings-general-upgrade_subs"></span>
</label> </label>
<label>Schedule a task to upgrade Subtitles previously downloaded by Bazarr.</label> <label>Schedule a task to upgrade Subtitles previously downloaded by Bazarr.</label>
@ -73,7 +78,7 @@
<b>Number of days to go back in history to upgrade subtitles (up to 30)</b> <b>Number of days to go back in history to upgrade subtitles (up to 30)</b>
</div> </div>
<div class="col-sm-4"> <div class="col-sm-4">
<input type="number" class="form-control" id="settings-general-days_to_upgrade_subs" name="settings-general-days_to_upgrade_subs" min="1" max="30" value="{{settings.general.days_to_upgrade_subs}}" onkeydown="return false"> <input class="slider" id="settings-general-days_to_upgrade_subs" name="settings-general-days_to_upgrade_subs" data-slider-id='settings-general-days_to_upgrade_subs' type="text" data-slider-min="0" data-slider-max="30" data-slider-step="1" data-slider-value="{{settings.general.days_to_upgrade_subs}}"/>
</div> </div>
</div> </div>
<br> <br>
@ -83,7 +88,8 @@
</div> </div>
<div class="form-group col-sm-8"> <div class="form-group col-sm-8">
<label class="custom-control custom-checkbox"> <label class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="settings-general-upgrade_manual" name="settings-general-upgrade_manual"> <input type="checkbox" class="custom-control-input" id="settings-general-upgrade_manual"
name="settings-general-upgrade_manual">
<span class="custom-control-label" for="settings-general-upgrade_manual"></span> <span class="custom-control-label" for="settings-general-upgrade_manual"></span>
</label> </label>
<label>Enable or disable upgrade of manually searched and downloaded Subtitles.</label> <label>Enable or disable upgrade of manually searched and downloaded Subtitles.</label>
@ -99,15 +105,17 @@
<b>Provider</b> <b>Provider</b>
</div> </div>
<div class="form-group col-sm-4"> <div class="form-group col-sm-4">
<select class="form-control selectpicker" id="settings-general-anti_captcha_provider" name="settings-general-anti_captcha_provider"> <select class="form-control selectpicker" id="settings-general-anti_captcha_provider"
name="settings-general-anti_captcha_provider">
<option value="None">None</option> <option value="None">None</option>
<option value="anti-captcha">Anti-Captcha</option> <option value="anti-captcha">Anti-Captcha</option>
<option value="death-by-captcha">Death by Captcha</option> <option value="death-by-captcha">Death by Captcha</option>
</select> </select>
<label for="settings-general-anti_captcha_provider">Choose the Anti-Captcha provider you want to use.</label> <label for="settings-general-anti_captcha_provider">Choose the Anti-Captcha provider you want to
use.</label>
</div> </div>
</div> </div>
<div id="anticaptcha_div"> <div id="anticaptcha_div">
<div class="row"> <div class="row">
<div class="col-sm-4 text-right"> <div class="col-sm-4 text-right">
<b>Provider Website</b> <b>Provider Website</b>
@ -122,12 +130,14 @@
<b>Account Key</b> <b>Account Key</b>
</div> </div>
<div class="col-sm-4"> <div class="col-sm-4">
<input type="text" class="form-control" id="settings-anticaptcha-anti_captcha_key" name="settings-anticaptcha-anti_captcha_key" value="{{settings.anticaptcha.anti_captcha_key}}"> <input type="text" class="form-control" id="settings-anticaptcha-anti_captcha_key"
name="settings-anticaptcha-anti_captcha_key"
value="{{ settings.anticaptcha.anti_captcha_key }}">
</div> </div>
</div> </div>
<br> <br>
</div> </div>
<div id="deathbycaptcha_div"> <div id="deathbycaptcha_div">
<div class="row"> <div class="row">
<div class="col-sm-4 text-right"> <div class="col-sm-4 text-right">
<b>Provider Website</b> <b>Provider Website</b>
@ -142,7 +152,8 @@
<b>Username</b> <b>Username</b>
</div> </div>
<div class="col-sm-4"> <div class="col-sm-4">
<input type="text" class="form-control" id="settings-deathbycaptcha-username" name="settings-deathbycaptcha-username" value="{{settings.deathbycaptcha.username}}"> <input type="text" class="form-control" id="settings-deathbycaptcha-username"
name="settings-deathbycaptcha-username" value="{{ settings.deathbycaptcha.username }}">
</div> </div>
</div> </div>
<br> <br>
@ -151,7 +162,8 @@
<b>Password</b> <b>Password</b>
</div> </div>
<div class="col-sm-4"> <div class="col-sm-4">
<input type="password" class="form-control" id="settings-deathbycaptcha-password" name="settings-deathbycaptcha-password" value="{{settings.deathbycaptcha.password}}"> <input type="password" class="form-control" id="settings-deathbycaptcha-password"
name="settings-deathbycaptcha-password" value="{{ settings.deathbycaptcha.password }}">
</div> </div>
</div> </div>
<br> <br>
@ -165,10 +177,12 @@
</div> </div>
<div class="form-group col-sm-8"> <div class="form-group col-sm-8">
<label class="custom-control custom-checkbox"> <label class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="settings-general-adaptive_searching" name="settings-general-adaptive_searching"> <input type="checkbox" class="custom-control-input" id="settings-general-adaptive_searching"
name="settings-general-adaptive_searching">
<span class="custom-control-label" for="settings-general-adaptive_searching"></span> <span class="custom-control-label" for="settings-general-adaptive_searching"></span>
</label> </label>
<label>When searching for Subtitles, Bazarr will search less frequently to limit call to providers.</label> <label>When searching for Subtitles, Bazarr will search less frequently to limit call to
providers.</label>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
@ -177,7 +191,8 @@
</div> </div>
<div class="form-group col-sm-8"> <div class="form-group col-sm-8">
<label class="custom-control custom-checkbox"> <label class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="settings-general-multithreading" name="settings-general-multithreading"> <input type="checkbox" class="custom-control-input" id="settings-general-multithreading"
name="settings-general-multithreading">
<span class="custom-control-label" for="settings-general-multithreading"></span> <span class="custom-control-label" for="settings-general-multithreading"></span>
</label> </label>
<label>Search multiple providers at once (Don't choose this on low powered devices)</label> <label>Search multiple providers at once (Don't choose this on low powered devices)</label>
@ -189,7 +204,8 @@
</div> </div>
<div class="form-group col-sm-8"> <div class="form-group col-sm-8">
<label class="custom-control custom-checkbox"> <label class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="settings-general-use_embedded_subs" name="settings-general-use_embedded_subs"> <input type="checkbox" class="custom-control-input" id="settings-general-use_embedded_subs"
name="settings-general-use_embedded_subs">
<span class="custom-control-label" for="settings-general-use_embedded_subs"></span> <span class="custom-control-label" for="settings-general-use_embedded_subs"></span>
</label> </label>
<label>Use embedded Subtitles in media files when determining missing ones.</label> <label>Use embedded Subtitles in media files when determining missing ones.</label>
@ -202,10 +218,12 @@
</div> </div>
<div class="form-group col-sm-8"> <div class="form-group col-sm-8">
<label class="custom-control custom-checkbox"> <label class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="settings-general-ignore_pgs_subs" name="settings-general-ignore_pgs_subs"> <input type="checkbox" class="custom-control-input" id="settings-general-ignore_pgs_subs"
name="settings-general-ignore_pgs_subs">
<span class="custom-control-label" for="settings-general-ignore_pgs_subs"></span> <span class="custom-control-label" for="settings-general-ignore_pgs_subs"></span>
</label> </label>
<label>Ignores PGS Subtitles in Embedded Subtitles detection. Only relevant if 'Use embedded Subtitles' is enabled.</label> <label>Ignores PGS Subtitles in Embedded Subtitles detection. Only relevant if 'Use embedded
Subtitles' is enabled.</label>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
@ -214,7 +232,9 @@
</div> </div>
<div class="form-group col-sm-8"> <div class="form-group col-sm-8">
<label class="custom-control custom-checkbox"> <label class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="settings-general-embedded_subs_show_desired" name="settings-general-embedded_subs_show_desired"> <input type="checkbox" class="custom-control-input"
id="settings-general-embedded_subs_show_desired"
name="settings-general-embedded_subs_show_desired">
<span class="custom-control-label" for="settings-general-embedded_subs_show_desired"></span> <span class="custom-control-label" for="settings-general-embedded_subs_show_desired"></span>
</label> </label>
<label>Hide embedded subtitles for languages that are not currently desired.</label> <label>Hide embedded subtitles for languages that are not currently desired.</label>
@ -230,36 +250,39 @@
</div> </div>
<div class="form-group col-sm-8"> <div class="form-group col-sm-8">
<label class="custom-control custom-checkbox"> <label class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="settings-general-utf8_encode" name="settings-general-utf8_encode"> <input type="checkbox" class="custom-control-input" id="settings-general-utf8_encode"
name="settings-general-utf8_encode">
<span class="custom-control-label" for="settings-general-utf8_encode"></span> <span class="custom-control-label" for="settings-general-utf8_encode"></span>
</label> </label>
<label>Re-encode downloaded Subtitles to UTF8. Should be left enabled in most case.</label> <label>Re-encode downloaded Subtitles to UTF8. Should be left enabled in most case.</label>
</div> </div>
</div> </div>
{% if not os.startswith('win') %} {% if not os.startswith('win') %}
<div class="row">
<div class="col-sm-3 text-right">
<b>Enable CHMOD</b>
</div>
<div class="form-group col-sm-1">
<label class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="settings-general-chmod_enabled" name="settings-general-chmod_enabled">
<span class="custom-control-label" for="settings-general-chmod_enabled"></span>
</label>
</div>
</div>
<div id="chmod_div">
<div class="row"> <div class="row">
<div class="col-sm-4 text-right"> <div class="col-sm-3 text-right">
<b>Set Subtitle file permissions to</b> <b>Enable CHMOD</b>
</div> </div>
<div class="col-sm-4"> <div class="form-group col-sm-1">
<input type="text" class="form-control" id="settings-general-chmod" name="settings-general-chmod" value="{{settings.general.chmod}}"> <label class="custom-control custom-checkbox">
<label for="settings-general-chmod">Must be 4 digit octal, e.g.: 0775</label> <input type="checkbox" class="custom-control-input" id="settings-general-chmod_enabled"
name="settings-general-chmod_enabled">
<span class="custom-control-label" for="settings-general-chmod_enabled"></span>
</label>
</div> </div>
</div> </div>
<br> <div id="chmod_div">
</div> <div class="row">
<div class="col-sm-4 text-right">
<b>Set Subtitle file permissions to</b>
</div>
<div class="col-sm-4">
<input type="text" class="form-control" id="settings-general-chmod"
name="settings-general-chmod" value="{{ settings.general.chmod }}">
<label for="settings-general-chmod">Must be 4 digit octal, e.g.: 0775</label>
</div>
</div>
<br>
</div>
{% endif %} {% endif %}
<div class="row"> <div class="row">
<div class="col-sm-3 text-right"> <div class="col-sm-3 text-right">
@ -323,7 +346,8 @@
</div> </div>
<div class="form-group col-sm-8"> <div class="form-group col-sm-8">
<label class="custom-control custom-checkbox"> <label class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="settings-general-use_postprocessing" name="settings-general-use_postprocessing"> <input type="checkbox" class="custom-control-input" id="settings-general-use_postprocessing"
name="settings-general-use_postprocessing">
<span class="custom-control-label" for="settings-general-use_postprocessing"></span> <span class="custom-control-label" for="settings-general-use_postprocessing"></span>
</label> </label>
<label>Enable the post-processing execution after downloading a subtitles.</label> <label>Enable the post-processing execution after downloading a subtitles.</label>
@ -336,8 +360,11 @@
</div> </div>
<div class="col-sm-1"> <div class="col-sm-1">
<label class="custom-control custom-checkbox"> <label class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="settings-general-use_postprocessing_threshold" name="settings-general-use_postprocessing_threshold"> <input type="checkbox" class="custom-control-input"
<span class="custom-control-label" for="settings-general-use_postprocessing_threshold"></span> id="settings-general-use_postprocessing_threshold"
name="settings-general-use_postprocessing_threshold">
<span class="custom-control-label"
for="settings-general-use_postprocessing_threshold"></span>
</label> </label>
</div> </div>
</div> </div>
@ -346,8 +373,8 @@
<div class="col-sm-5 text-right"> <div class="col-sm-5 text-right">
<b>Only for score below</b> <b>Only for score below</b>
</div> </div>
<div class="col-sm-1"> <div class="col-sm-4">
<input type="number" class="form-control" id="settings-general-postprocessing_threshold" name="settings-general-postprocessing_threshold" min="0" max="100" step="1" onkeydown="return false" value="{{settings.general.postprocessing_threshold}}"> <input class="slider" id="settings-general-postprocessing_threshold" name="settings-general-postprocessing_threshold" data-slider-id='settings-general-postprocessing_threshold' type="text" data-slider-min="1" data-slider-max="100" data-slider-step="1" data-slider-value="{{settings.general.postprocessing_threshold}}"/>
</div> </div>
</div> </div>
<br> <br>
@ -357,8 +384,11 @@
</div> </div>
<div class="col-sm-1"> <div class="col-sm-1">
<label class="custom-control custom-checkbox"> <label class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="settings-general-use_postprocessing_threshold_movie" name="settings-general-use_postprocessing_threshold_movie"> <input type="checkbox" class="custom-control-input"
<span class="custom-control-label" for="settings-general-use_postprocessing_threshold_movie"></span> id="settings-general-use_postprocessing_threshold_movie"
name="settings-general-use_postprocessing_threshold_movie">
<span class="custom-control-label"
for="settings-general-use_postprocessing_threshold_movie"></span>
</label> </label>
</div> </div>
</div> </div>
@ -367,8 +397,8 @@
<div class="col-sm-5 text-right"> <div class="col-sm-5 text-right">
<b>Only for score below</b> <b>Only for score below</b>
</div> </div>
<div class="col-sm-1"> <div class="col-sm-4">
<input type="number" class="form-control" id="settings-general-postprocessing_threshold_movie" name="settings-general-postprocessing_threshold_movie" min="0" max="100" step="1" onkeydown="return false" value="{{settings.general.postprocessing_threshold_movie}}"> <input class="slider" id="settings-general-postprocessing_threshold_movie" name="settings-general-postprocessing_threshold_movie" data-slider-id='settings-general-postprocessing_threshold_movie' type="text" data-slider-min="1" data-slider-max="100" data-slider-step="1" data-slider-value="{{settings.general.postprocessing_threshold_movie}}"/>
</div> </div>
</div> </div>
<br> <br>
@ -377,7 +407,9 @@
<b>Post-processing command</b> <b>Post-processing command</b>
</div> </div>
<div class="col-sm-8"> <div class="col-sm-8">
<input type="text" class="form-control" id="settings-general-postprocessing_cmd" name="settings-general-postprocessing_cmd" value="{{settings.general.postprocessing_cmd}}"> <input type="text" class="form-control" id="settings-general-postprocessing_cmd"
name="settings-general-postprocessing_cmd"
value="{{ settings.general.postprocessing_cmd }}">
</div> </div>
</div> </div>
<br> <br>
@ -406,6 +438,8 @@
<p>The 2-letter ISO-639 language code of the episode audio language.</p> <p>The 2-letter ISO-639 language code of the episode audio language.</p>
<b>&lbrace;&lbrace;episode_language_code3&rbrace;&rbrace;</b> <b>&lbrace;&lbrace;episode_language_code3&rbrace;&rbrace;</b>
<p>The 3-letter ISO-639 language code of the episode audio language.</p> <p>The 3-letter ISO-639 language code of the episode audio language.</p>
<b>&lbrace;&lbrace;score&rbrace;&rbrace;</b>
<p>The score of the subtitles file.</p>
</div> </div>
</div> </div>
</div> </div>
@ -418,7 +452,7 @@
$(document).ready(function () { $(document).ready(function () {
// Show warning if there's unsaved changes in the settings_form // Show warning if there's unsaved changes in the settings_form
var form_changed = false; var form_changed = false;
$(window).on('beforeunload', function() { $(window).on('beforeunload', function () {
if (form_changed) { if (form_changed) {
return ""; return "";
} }
@ -429,7 +463,7 @@
$('#save_button').prop('disabled', true).css('cursor', 'not-allowed'); $('#save_button').prop('disabled', true).css('cursor', 'not-allowed');
// Hide *_div on Select input changed to None // Hide *_div on Select input changed to None
$('#settings-general-subfolder').on('change', function() { $('#settings-general-subfolder').on('change', function () {
if ($(this).val() === 'current') { if ($(this).val() === 'current') {
$('#subfolder_div').hide(); $('#subfolder_div').hide();
} else { } else {
@ -437,7 +471,7 @@
} }
}); });
$('#settings-general-upgrade_subs').on('change', function() { $('#settings-general-upgrade_subs').on('change', function () {
if ($(this).prop('checked')) { if ($(this).prop('checked')) {
$('#upgradesubs_div').show(); $('#upgradesubs_div').show();
} else { } else {
@ -445,7 +479,7 @@
} }
}); });
$('#settings-general-anti_captcha_provider').on('change', function() { $('#settings-general-anti_captcha_provider').on('change', function () {
if ($(this).val() === 'anti-captcha') { if ($(this).val() === 'anti-captcha') {
$('#anticaptcha_div').show(); $('#anticaptcha_div').show();
$('#deathbycaptcha_div').hide(); $('#deathbycaptcha_div').hide();
@ -458,7 +492,7 @@
} }
}); });
$('#settings-general-use_embedded_subs').on('change', function() { $('#settings-general-use_embedded_subs').on('change', function () {
if ($(this).prop('checked')) { if ($(this).prop('checked')) {
$('#embedded_div').show(); $('#embedded_div').show();
} else { } else {
@ -466,7 +500,7 @@
} }
}); });
$('#settings-general-chmod_enabled').on('change', function() { $('#settings-general-chmod_enabled').on('change', function () {
if ($(this).prop('checked')) { if ($(this).prop('checked')) {
$('#chmod_div').show(); $('#chmod_div').show();
} else { } else {
@ -474,7 +508,7 @@
} }
}); });
$('#settings-general-use_postprocessing').on('change', function() { $('#settings-general-use_postprocessing').on('change', function () {
if ($(this).prop('checked')) { if ($(this).prop('checked')) {
$('#custompp_div').show(); $('#custompp_div').show();
} else { } else {
@ -512,7 +546,7 @@
$('#settings-general-use_postprocessing_threshold').prop('checked', {{'true' if settings.general.getboolean('use_postprocessing_threshold') else 'false'}}).trigger('change'); $('#settings-general-use_postprocessing_threshold').prop('checked', {{'true' if settings.general.getboolean('use_postprocessing_threshold') else 'false'}}).trigger('change');
$('#settings-general-use_postprocessing_threshold_movie').prop('checked', {{'true' if settings.general.getboolean('use_postprocessing_threshold_movie') else 'false'}}).trigger('change'); $('#settings-general-use_postprocessing_threshold_movie').prop('checked', {{'true' if settings.general.getboolean('use_postprocessing_threshold_movie') else 'false'}}).trigger('change');
$('#save_button').on('click', function() { $('#save_button').on('click', function () {
var formdata = new FormData(document.getElementById("settings_form")); var formdata = new FormData(document.getElementById("settings_form"));
// Make sure all checkbox input are sent with true/false value // Make sure all checkbox input are sent with true/false value
@ -531,8 +565,7 @@
form_changed = false; form_changed = false;
$('#save_button').prop('disabled', true).css('cursor', 'not-allowed'); $('#save_button').prop('disabled', true).css('cursor', 'not-allowed');
setTimeout( setTimeout(
function() function () {
{
$('#save_button_checkmark').hide(); $('#save_button_checkmark').hide();
}, 2000); }, 2000);
} }
@ -540,7 +573,7 @@
}); });
// monitor changes to the settings_form // monitor changes to the settings_form
$('#settings_form').on('change', function() { $('#settings_form').on('change', function () {
form_changed = true; form_changed = true;
$('#save_button').prop('disabled', false).css('cursor', 'auto'); $('#save_button').prop('disabled', false).css('cursor', 'auto');
}) })

Loading…
Cancel
Save