From eee8659ce16841df634a3960604f9b50d4c5e8c2 Mon Sep 17 00:00:00 2001 From: morpheus65535 Date: Mon, 10 Jun 2024 19:29:31 -0400 Subject: [PATCH] Added normal only (non-hi) option to languages profile --- bazarr/app/database.py | 25 +++++++++ bazarr/main.py | 3 +- bazarr/subtitles/download.py | 11 ++-- bazarr/subtitles/indexer/movies.py | 44 ++++++++++----- bazarr/subtitles/indexer/series.py | 44 ++++++++++----- bazarr/subtitles/manual.py | 55 ++++++------------- .../src/components/forms/ProfileEditForm.tsx | 35 +++++++----- .../src/pages/Settings/Languages/table.tsx | 18 +++--- frontend/src/types/api.d.ts | 2 +- frontend/src/utilities/languages.ts | 2 +- 10 files changed, 139 insertions(+), 100 deletions(-) diff --git a/bazarr/app/database.py b/bazarr/app/database.py index c2a97987d..7c1663b67 100644 --- a/bazarr/app/database.py +++ b/bazarr/app/database.py @@ -497,3 +497,28 @@ def convert_list_to_clause(arr: list): return f"({','.join(str(x) for x in arr)})" else: return "" + + +def upgrade_languages_profile_hi_values(): + for languages_profile in (database.execute( + select( + TableLanguagesProfiles.profileId, + TableLanguagesProfiles.name, + TableLanguagesProfiles.cutoff, + TableLanguagesProfiles.items, + TableLanguagesProfiles.mustContain, + TableLanguagesProfiles.mustNotContain, + TableLanguagesProfiles.originalFormat) + ))\ + .all(): + items = json.loads(languages_profile.items) + for language in items: + if language['hi'] == "True": + language['hi'] = "only" + elif language['hi'] == "False": + language['hi'] = "also" + database.execute( + update(TableLanguagesProfiles) + .values({"items": json.dumps(items)}) + .where(TableLanguagesProfiles.profileId == languages_profile.profileId) + ) diff --git a/bazarr/main.py b/bazarr/main.py index ab25f6ae5..8fe9a43fd 100644 --- a/bazarr/main.py +++ b/bazarr/main.py @@ -35,7 +35,7 @@ else: # there's missing embedded packages after a commit check_if_new_update() -from app.database import System, database, update, migrate_db, create_db_revision # noqa E402 +from app.database import System, database, update, migrate_db, create_db_revision, upgrade_languages_profile_hi_values # noqa E402 from app.notifier import update_notifier # noqa E402 from languages.get_languages import load_language_in_db # noqa E402 from app.signalr_client import sonarr_signalr_client, radarr_signalr_client # noqa E402 @@ -49,6 +49,7 @@ if args.create_db_revision: stop_bazarr(EXIT_NORMAL) else: migrate_db(app) + upgrade_languages_profile_hi_values() configure_proxy_func() diff --git a/bazarr/subtitles/download.py b/bazarr/subtitles/download.py index 5f588fbf6..c22d6007f 100644 --- a/bazarr/subtitles/download.py +++ b/bazarr/subtitles/download.py @@ -41,10 +41,6 @@ def generate_subtitles(path, languages, audio_language, sceneName, title, media_ providers = pool.providers language_set = _get_language_obj(languages=languages) - hi_required = "force HI" if any([x.hi for x in language_set]) else False - also_forced = any([x.forced for x in language_set]) - forced_required = all([x.forced for x in language_set]) - _set_forced_providers(pool=pool, also_forced=also_forced, forced_required=forced_required) video = get_video(force_unicode(path), title, sceneName, providers=providers, media_type=media_type) @@ -60,6 +56,9 @@ def generate_subtitles(path, languages, audio_language, sceneName, title, media_ if forced_minimum_score: min_score = int(forced_minimum_score) + 1 for language in language_set: + # check if language appears more than one time which means that the languages profile requires normal + # and hi subtitles (also) + also_hi = len(set([x.hi for x in language_set if x.alpha3 == language.alpha3 and not x.forced])) > 1 # confirm if language is still missing or if cutoff has been reached if check_if_still_required and language not in check_missing_languages(path, media_type): # cutoff has been reached @@ -67,11 +66,13 @@ def generate_subtitles(path, languages, audio_language, sceneName, title, media_ f"has been reached during this search.") continue else: + hi_required = "force HI" if language.hi else "force non-HI" + _set_forced_providers(pool=pool, also_forced=language.forced, forced_required=language.forced) downloaded_subtitles = download_best_subtitles(videos={video}, languages={language}, pool_instance=pool, min_score=int(min_score), - hearing_impaired=hi_required, + hearing_impaired=False if also_hi else hi_required, compute_score=ComputeScore(get_scores())) if downloaded_subtitles: diff --git a/bazarr/subtitles/indexer/movies.py b/bazarr/subtitles/indexer/movies.py index 48c93001d..683530387 100644 --- a/bazarr/subtitles/indexer/movies.py +++ b/bazarr/subtitles/indexer/movies.py @@ -182,7 +182,9 @@ def list_missing_subtitles_movies(no=None, send_event=True): if any(x['code2'] == language['language'] for x in get_audio_profile_languages( movie_subtitles.audio_language)): continue - desired_subtitles_list.append([language['language'], language['forced'], language['hi']]) + desired_subtitles_list.append({'language': language['language'], + 'forced': language['forced'], + 'hi': language['hi']}) # get existing subtitles actual_subtitles_list = [] @@ -204,7 +206,9 @@ def list_missing_subtitles_movies(no=None, send_event=True): elif subtitles[1] == 'hi': forced = False hi = True - actual_subtitles_list.append([lang, str(forced), str(hi)]) + actual_subtitles_list.append({'language': lang, + 'forced': str(forced), + 'hi': str(hi)}) # check if cutoff is reached and skip any further check cutoff_met = False @@ -229,24 +233,34 @@ def list_missing_subtitles_movies(no=None, send_event=True): # get difference between desired and existing subtitles missing_subtitles_list = [] for item in desired_subtitles_list: - if item not in actual_subtitles_list: - missing_subtitles_list.append(item) - - # remove missing that have forced or hi subtitles for this language in existing - for item in actual_subtitles_list: - if item[2] == 'True': - try: - missing_subtitles_list.remove([item[0], 'False', 'False']) - except ValueError: - pass + if item['forced'] == 'True': + desired_item = {'language': item['language'], 'forced': item['forced'], 'hi': 'False'} + if desired_item not in actual_subtitles_list: + missing_subtitles_list.append(desired_item) + elif item['hi'] == 'never': + desired_item = {'language': item['language'], 'forced': item['forced'], 'hi': 'False'} + if desired_item not in actual_subtitles_list: + missing_subtitles_list.append(desired_item) + elif item['hi'] == 'only': + desired_item = {'language': item['language'], 'forced': item['forced'], 'hi': 'True'} + if desired_item not in actual_subtitles_list: + missing_subtitles_list.append(desired_item) + elif item['hi'] == 'also': + desired_items = [{'language': item['language'], 'forced': item['forced'], 'hi': 'True'}, + {'language': item['language'], 'forced': item['forced'], 'hi': 'False'}] + if [x for x in desired_items if x in actual_subtitles_list]: + continue + else: + for desired_item in desired_items: + missing_subtitles_list.append(desired_item) # make the missing languages list looks like expected missing_subtitles_output_list = [] for item in missing_subtitles_list: - lang = item[0] - if item[1] == 'True': + lang = item['language'] + if item['forced'] == 'True': lang += ':forced' - elif item[2] == 'True': + elif item['hi'] == 'True': lang += ':hi' missing_subtitles_output_list.append(lang) diff --git a/bazarr/subtitles/indexer/series.py b/bazarr/subtitles/indexer/series.py index 52622e9ea..33bf3f688 100644 --- a/bazarr/subtitles/indexer/series.py +++ b/bazarr/subtitles/indexer/series.py @@ -182,7 +182,9 @@ def list_missing_subtitles(no=None, epno=None, send_event=True): if any(x['code2'] == language['language'] for x in get_audio_profile_languages( episode_subtitles.audio_language)): continue - desired_subtitles_list.append([language['language'], language['forced'], language['hi']]) + desired_subtitles_list.append({'language': language['language'], + 'forced': language['forced'], + 'hi': language['hi']}) # get existing subtitles actual_subtitles_list = [] @@ -204,7 +206,9 @@ def list_missing_subtitles(no=None, epno=None, send_event=True): elif subtitles[1] == 'hi': forced = False hi = True - actual_subtitles_list.append([lang, str(forced), str(hi)]) + actual_subtitles_list.append({'language': lang, + 'forced': str(forced), + 'hi': str(hi)}) # check if cutoff is reached and skip any further check cutoff_met = False @@ -231,24 +235,34 @@ def list_missing_subtitles(no=None, epno=None, send_event=True): # get difference between desired and existing subtitles missing_subtitles_list = [] for item in desired_subtitles_list: - if item not in actual_subtitles_list: - missing_subtitles_list.append(item) - - # remove missing that have hi subtitles for this language in existing - for item in actual_subtitles_list: - if item[2] == 'True': - try: - missing_subtitles_list.remove([item[0], 'False', 'False']) - except ValueError: - pass + if item['forced'] == 'True': + desired_item = {'language': item['language'], 'forced': item['forced'], 'hi': 'False'} + if desired_item not in actual_subtitles_list: + missing_subtitles_list.append(desired_item) + elif item['hi'] == 'never': + desired_item = {'language': item['language'], 'forced': item['forced'], 'hi': 'False'} + if desired_item not in actual_subtitles_list: + missing_subtitles_list.append(desired_item) + elif item['hi'] == 'only': + desired_item = {'language': item['language'], 'forced': item['forced'], 'hi': 'True'} + if desired_item not in actual_subtitles_list: + missing_subtitles_list.append(desired_item) + elif item['hi'] == 'also': + desired_items = [{'language': item['language'], 'forced': item['forced'], 'hi': 'True'}, + {'language': item['language'], 'forced': item['forced'], 'hi': 'False'}] + if [x for x in desired_items if x in actual_subtitles_list]: + continue + else: + for desired_item in desired_items: + missing_subtitles_list.append(desired_item) # make the missing languages list looks like expected missing_subtitles_output_list = [] for item in missing_subtitles_list: - lang = item[0] - if item[1] == 'True': + lang = item['language'] + if item['forced'] == 'True': lang += ':forced' - elif item[2] == 'True': + elif item['hi'] == 'True': lang += ':hi' missing_subtitles_output_list.append(lang) diff --git a/bazarr/subtitles/manual.py b/bazarr/subtitles/manual.py index ba57eb193..ca8dd42b4 100644 --- a/bazarr/subtitles/manual.py +++ b/bazarr/subtitles/manual.py @@ -31,9 +31,9 @@ def manual_search(path, profile_id, providers, sceneName, title, media_type): pool = _get_pool(media_type, profile_id) - language_set, initial_language_set, original_format = _get_language_obj(profile_id=profile_id) - also_forced = any([x.forced for x in initial_language_set]) - forced_required = all([x.forced for x in initial_language_set]) + language_set, original_format = _get_language_obj(profile_id=profile_id) + also_forced = any([x.forced for x in language_set]) + forced_required = all([x.forced for x in language_set]) compute_score = ComputeScore(get_scores()) _set_forced_providers(pool=pool, also_forced=also_forced, forced_required=forced_required) @@ -57,6 +57,10 @@ def manual_search(path, profile_id, providers, sceneName, title, media_type): minimum_score_movie = settings.general.minimum_score_movie for s in subtitles[video]: + if s.language not in language_set: + # HI may not be matching what was requested + continue + try: matches = s.get_matches(video) except AttributeError: @@ -75,20 +79,8 @@ def manual_search(path, profile_id, providers, sceneName, title, media_type): logging.debug("BAZARR Ignoring invalid subtitles") continue - initial_hi = None - initial_hi_match = False - for language in initial_language_set: - if s.language.basename == language.basename and \ - s.language.forced == language.forced and \ - s.language.hi == language.hi: - initial_hi = language.hi - initial_hi_match = True - break - if not initial_hi_match: - initial_hi = None - _, max_score, scores = _get_scores(media_type, minimum_score_movie, minimum_score) - score, score_without_hash = compute_score(matches, s, video, hearing_impaired=initial_hi) + score, score_without_hash = compute_score(matches, s, video, hearing_impaired=s.language.hi) if 'hash' not in matches: not_matched = scores - matches s.score = score_without_hash @@ -96,11 +88,6 @@ def manual_search(path, profile_id, providers, sceneName, title, media_type): s.score = score not_matched = set() - if s.hearing_impaired == initial_hi: - matches.add('hearing_impaired') - else: - not_matched.add('hearing_impaired') - releases = [] if hasattr(s, 'release_info'): if s.release_info is not None: @@ -215,7 +202,6 @@ def manual_download_subtitle(path, audio_language, hi, forced, subtitle, provide def _get_language_obj(profile_id): - initial_language_set = set() language_set = set() profile = get_profiles_list(profile_id=int(profile_id)) @@ -232,22 +218,13 @@ def _get_language_obj(profile_id): lang_obj = _get_lang_obj(lang) if forced == "True": - lang_obj = Language.rebuild(lang_obj, forced=True) - - if hi == "True": - lang_obj = Language.rebuild(lang_obj, hi=True) - - initial_language_set.add(lang_obj) - - language_set = initial_language_set.copy() - for language in language_set.copy(): - lang_obj_for_hi = language - if not language.forced and not language.hi: - lang_obj_hi = Language.rebuild(lang_obj_for_hi, hi=True) - elif not language.forced and language.hi: - lang_obj_hi = Language.rebuild(lang_obj_for_hi, hi=False) + language_set.add(Language.rebuild(lang_obj, forced=True)) + elif hi == "only": + language_set.add(Language.rebuild(lang_obj, hi=True)) + elif hi == "also": + language_set.add(lang_obj) + language_set.add(Language.rebuild(lang_obj, hi=True)) else: - continue - language_set.add(lang_obj_hi) + language_set.add(lang_obj) - return language_set, initial_language_set, original_format + return language_set, original_format diff --git a/frontend/src/components/forms/ProfileEditForm.tsx b/frontend/src/components/forms/ProfileEditForm.tsx index c1104b223..81b50cfcd 100644 --- a/frontend/src/components/forms/ProfileEditForm.tsx +++ b/frontend/src/components/forms/ProfileEditForm.tsx @@ -12,6 +12,7 @@ import { } from "@mantine/core"; import { useForm } from "@mantine/form"; import { faTrash } from "@fortawesome/free-solid-svg-icons"; +import { cond } from "lodash"; import { Action, Selector, SelectorOption, SimpleTable } from "@/components"; import ChipInput from "@/components/inputs/ChipInput"; import { useModals, withModal } from "@/modules/modals"; @@ -30,7 +31,7 @@ const defaultCutoffOptions: SelectorOption[] = [ // eslint-disable-next-line camelcase audio_exclude: "False", forced: "False", - hi: "False", + hi: "also", language: "any", }, }, @@ -38,12 +39,16 @@ const defaultCutoffOptions: SelectorOption[] = [ const subtitlesTypeOptions: SelectorOption[] = [ { - label: "Normal or hearing-impaired", - value: "normal", + label: "Normal only", + value: "never", + }, + { + label: "Hearing-impaired only", + value: "only", }, { - label: "Hearing-impaired required", - value: "hi", + label: "Normal or hearing-impaired", + value: "also", }, { label: "Forced (foreign part only)", @@ -83,10 +88,14 @@ const ProfileEditForm: FunctionComponent = ({ const itemCutoffOptions = useSelectorOptions( form.values.items, (v) => { - const suffix = - v.hi === "True" ? ":hi" : v.forced === "True" ? ":forced" : ""; + const suffix = cond([ + [(v: { forced: string; hi: string }) => v.hi === "only", () => ":hi"], + [(v) => v.hi === "never", () => ":normal"], + [(v) => v.forced === "True", () => ":forced"], + [() => true, () => ""], + ]); - return v.language + suffix; + return v.language + suffix(v); }, (v) => String(v.id), ); @@ -136,7 +145,7 @@ const ProfileEditForm: FunctionComponent = ({ language, // eslint-disable-next-line camelcase audio_exclude: "False", - hi: "False", + hi: "also", forced: "False", }; @@ -184,10 +193,8 @@ const ProfileEditForm: FunctionComponent = ({ const selectValue = useMemo(() => { if (item.forced === "True") { return "forced"; - } else if (item.hi === "True") { - return "hi"; } else { - return "normal"; + return item.hi; } }, [item.forced, item.hi]); @@ -199,9 +206,9 @@ const ProfileEditForm: FunctionComponent = ({ if (value) { action.mutate(index, { ...item, - hi: value === "hi" ? "True" : "False", + hi: value, forced: value === "forced" ? "True" : "False", - }); + } as Language.ProfileItem); } }} > diff --git a/frontend/src/pages/Settings/Languages/table.tsx b/frontend/src/pages/Settings/Languages/table.tsx index 324acfb91..0ba64c637 100644 --- a/frontend/src/pages/Settings/Languages/table.tsx +++ b/frontend/src/pages/Settings/Languages/table.tsx @@ -2,7 +2,7 @@ import { FunctionComponent, useCallback, useMemo } from "react"; import { Column } from "react-table"; import { Badge, Button, Group } from "@mantine/core"; import { faTrash, faWrench } from "@fortawesome/free-solid-svg-icons"; -import { cloneDeep } from "lodash"; +import { cloneDeep, cond } from "lodash"; import { Action, SimpleTable } from "@/components"; import { anyCutoff, @@ -193,14 +193,14 @@ interface ItemProps { const ItemBadge: FunctionComponent = ({ cutoff, item }) => { const text = useMemo(() => { - let result = item.language; - if (item.hi === "True") { - result += ":HI"; - } else if (item.forced === "True") { - result += ":Forced"; - } - return result; - }, [item.hi, item.forced, item.language]); + const suffix = cond([ + [(v: { forced: string; hi: string }) => v.hi === "only", () => ":HI"], + [(v) => v.hi === "never", () => ":Normal"], + [(v) => v.forced === "True", () => ":Forced"], + [() => true, () => ""], + ]); + return item.language + suffix(item); + }, [item]); return ( (({ language: code, hi, forced }) => { const name = data?.find((v) => v.code2 === code)?.name ?? ""; return { - hi: hi === "True", + hi: hi === "only", forced: forced === "True", code2: code, name,