Skip to content

Commit

Permalink
Implemented words/regex ban list for subtitles
Browse files Browse the repository at this point in the history
  • Loading branch information
morpheus65535 committed Dec 11, 2021
1 parent a87a1fa commit 63b326a
Show file tree
Hide file tree
Showing 11 changed files with 141 additions and 19 deletions.
5 changes: 3 additions & 2 deletions bazarr/api/episodes/episodes_subtitles.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from flask_restful import Resource
from subliminal_patch.core import SUBTITLE_EXTENSIONS

from database import TableEpisodes, get_audio_profile_languages
from database import TableEpisodes, get_audio_profile_languages, get_profile_id
from ..utils import authenticate
from helper import path_mappings
from get_providers import get_providers, get_providers_auth
Expand Down Expand Up @@ -55,7 +55,8 @@ def patch(self):

try:
result = download_subtitle(episodePath, language, audio_language, hi, forced, providers_list,
providers_auth, sceneName, title, 'series')
providers_auth, sceneName, title, 'series',
profile_id=get_profile_id(episode_id=sonarrEpisodeId))
if result is not None:
message = result[0]
path = result[1]
Expand Down
5 changes: 3 additions & 2 deletions bazarr/api/movies/movies_subtitles.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from flask_restful import Resource
from subliminal_patch.core import SUBTITLE_EXTENSIONS

from database import TableMovies, get_audio_profile_languages
from database import TableMovies, get_audio_profile_languages, get_profile_id
from ..utils import authenticate
from helper import path_mappings
from get_providers import get_providers, get_providers_auth
Expand Down Expand Up @@ -57,7 +57,8 @@ def patch(self):

try:
result = download_subtitle(moviePath, language, audio_language, hi, forced, providers_list,
providers_auth, sceneName, title, 'movie')
providers_auth, sceneName, title, 'movie',
profile_id=get_profile_id(movie_id=radarrId))
if result is not None:
message = result[0]
path = result[1]
Expand Down
5 changes: 3 additions & 2 deletions bazarr/api/providers/providers_episodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from flask import request, jsonify
from flask_restful import Resource

from database import TableEpisodes, TableShows, get_audio_profile_languages
from database import TableEpisodes, TableShows, get_audio_profile_languages, get_profile_id
from helper import path_mappings
from get_providers import get_providers, get_providers_auth
from get_subtitle import manual_search, manual_download_subtitle
Expand Down Expand Up @@ -76,7 +76,8 @@ def post(self):

try:
result = manual_download_subtitle(episodePath, language, audio_language, hi, forced, subtitle,
selected_provider, providers_auth, sceneName, title, 'series')
selected_provider, providers_auth, sceneName, title, 'series',
profile_id=get_profile_id(episode_id=sonarrEpisodeId))
if result is not None:
message = result[0]
path = result[1]
Expand Down
5 changes: 3 additions & 2 deletions bazarr/api/providers/providers_movies.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from flask import request, jsonify
from flask_restful import Resource

from database import TableMovies, get_audio_profile_languages
from database import TableMovies, get_audio_profile_languages, get_profile_id
from helper import path_mappings
from get_providers import get_providers, get_providers_auth
from get_subtitle import manual_search, manual_download_subtitle
Expand Down Expand Up @@ -77,7 +77,8 @@ def post(self):

try:
result = manual_download_subtitle(moviePath, language, audio_language, hi, forced, subtitle,
selected_provider, providers_auth, sceneName, title, 'movie')
selected_provider, providers_auth, sceneName, title, 'movie',
profile_id=get_profile_id(movie_id=radarrId))
if result is not None:
message = result[0]
path = result[1]
Expand Down
8 changes: 6 additions & 2 deletions bazarr/api/system/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ def post(self):
TableLanguagesProfiles.update({
TableLanguagesProfiles.name: item['name'],
TableLanguagesProfiles.cutoff: item['cutoff'] if item['cutoff'] != 'null' else None,
TableLanguagesProfiles.items: json.dumps(item['items'])
TableLanguagesProfiles.items: json.dumps(item['items']),
TableLanguagesProfiles.mustContain: item['mustContain'],
TableLanguagesProfiles.mustNotContain: item['mustNotContain'],
})\
.where(TableLanguagesProfiles.profileId == item['profileId'])\
.execute()
Expand All @@ -67,7 +69,9 @@ def post(self):
TableLanguagesProfiles.profileId: item['profileId'],
TableLanguagesProfiles.name: item['name'],
TableLanguagesProfiles.cutoff: item['cutoff'] if item['cutoff'] != 'null' else None,
TableLanguagesProfiles.items: json.dumps(item['items'])
TableLanguagesProfiles.items: json.dumps(item['items']),
TableLanguagesProfiles.mustContain: item['must_contain'],
TableLanguagesProfiles.mustNotContain: item['must_not_contain'],
}).execute()
for profileId in existing:
# Unassign this profileId from series and movies
Expand Down
36 changes: 31 additions & 5 deletions bazarr/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ class TableLanguagesProfiles(BaseModel):
items = TextField()
name = TextField()
profileId = AutoField()
mustContain = TextField(null=True)
mustNotContain = TextField(null=True)

class Meta:
table_name = 'table_languages_profiles'
Expand Down Expand Up @@ -329,7 +331,9 @@ def migrate_db():
migrator.add_column('table_history_movie', 'provider', TextField(null=True)),
migrator.add_column('table_history_movie', 'score', TextField(null=True)),
migrator.add_column('table_history_movie', 'subs_id', TextField(null=True)),
migrator.add_column('table_history_movie', 'subtitles_path', TextField(null=True))
migrator.add_column('table_history_movie', 'subtitles_path', TextField(null=True)),
migrator.add_column('table_languages_profiles', 'mustContain', TextField(null=True)),
migrator.add_column('table_languages_profiles', 'mustNotContain', TextField(null=True)),
)


Expand Down Expand Up @@ -394,10 +398,16 @@ def update_profile_id_list():
profile_id_list = TableLanguagesProfiles.select(TableLanguagesProfiles.profileId,
TableLanguagesProfiles.name,
TableLanguagesProfiles.cutoff,
TableLanguagesProfiles.items).dicts()
TableLanguagesProfiles.items,
TableLanguagesProfiles.mustContain,
TableLanguagesProfiles.mustNotContain).dicts()
profile_id_list = list(profile_id_list)
for profile in profile_id_list:
profile['items'] = json.loads(profile['items'])
profile['mustContain'] = ast.literal_eval(profile['mustContain']) if profile['mustContain'] else \
profile['mustContain']
profile['mustNotContain'] = ast.literal_eval(profile['mustNotContain']) if profile['mustNotContain'] else \
profile['mustNotContain']


def get_profiles_list(profile_id=None):
Expand All @@ -422,7 +432,7 @@ def get_desired_languages(profile_id):

if profile_id and profile_id != 'null':
for profile in profile_id_list:
profileId, name, cutoff, items = profile.values()
profileId, name, cutoff, items, mustContain, mustNotContain = profile.values()
if profileId == int(profile_id):
languages = [x['language'] for x in items]
break
Expand All @@ -438,7 +448,7 @@ def get_profile_id_name(profile_id):

if profile_id and profile_id != 'null':
for profile in profile_id_list:
profileId, name, cutoff, items = profile.values()
profileId, name, cutoff, items, mustContain, mustNotContain = profile.values()
if profileId == int(profile_id):
name_from_id = name
break
Expand All @@ -455,7 +465,7 @@ def get_profile_cutoff(profile_id):
if profile_id and profile_id != 'null':
cutoff_language = []
for profile in profile_id_list:
profileId, name, cutoff, items = profile.values()
profileId, name, cutoff, items, mustContain, mustNotContain = profile.values()
if cutoff:
if profileId == int(profile_id):
for item in items:
Expand Down Expand Up @@ -498,6 +508,22 @@ def get_audio_profile_languages(series_id=None, episode_id=None, movie_id=None):
return audio_languages


def get_profile_id(series_id=None, episode_id=None, movie_id=None):
if series_id:
profileId = TableShows.get(TableShows.sonarrSeriesId == series_id).profileId
elif episode_id:
profileId = TableShows.select(TableShows.profileId)\
.join(TableEpisodes, on=(TableShows.sonarrSeriesId == TableEpisodes.sonarrSeriesId))\
.where(TableEpisodes.sonarrEpisodeId == episode_id)\
.get().profileId
elif movie_id:
profileId = TableMovies.get(TableMovies.radarrId == movie_id).profileId
else:
return None

return profileId


def convert_list_to_clause(arr: list):
if isinstance(arr, list):
return f"({','.join(str(x) for x in arr)})"
Expand Down
18 changes: 16 additions & 2 deletions bazarr/get_subtitle.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ def get_video(path, title, sceneName, providers=None, media_type="movie"):


def download_subtitle(path, language, audio_language, hi, forced, providers, providers_auth, sceneName, title,
media_type, forced_minimum_score=None, is_upgrade=False):
media_type, forced_minimum_score=None, is_upgrade=False, profile_id=None):
# fixme: supply all missing languages, not only one, to hit providers only once who support multiple languages in
# one query

Expand Down Expand Up @@ -158,6 +158,7 @@ def download_subtitle(path, language, audio_language, hi, forced, providers, pro
compute_score=compute_score,
throttle_time=None, # fixme
blacklist=get_blacklist(media_type=media_type),
ban_list=get_ban_list(profile_id),
throttle_callback=provider_throttle,
score_obj=handler,
pre_download_hook=None, # fixme
Expand Down Expand Up @@ -361,6 +362,7 @@ def manual_search(path, profileId, providers, providers_auth, sceneName, title,
providers=providers,
provider_configs=providers_auth,
blacklist=get_blacklist(media_type=media_type),
ban_list=get_ban_list(profileId),
throttle_callback=provider_throttle,
language_hook=None) # fixme

Expand All @@ -375,6 +377,7 @@ def manual_search(path, profileId, providers, providers_auth, sceneName, title,
providers=['subscene'],
provider_configs=providers_auth,
blacklist=get_blacklist(media_type=media_type),
ban_list=get_ban_list(profileId),
throttle_callback=provider_throttle,
language_hook=None) # fixme
providers_auth['subscene']['only_foreign'] = False
Expand Down Expand Up @@ -466,7 +469,7 @@ def manual_search(path, profileId, providers, providers_auth, sceneName, title,


def manual_download_subtitle(path, language, audio_language, hi, forced, subtitle, provider, providers_auth, sceneName,
title, media_type):
title, media_type, profile_id):
logging.debug('BAZARR Manually downloading Subtitles for this file: ' + path)

if settings.general.getboolean('utf8_encode'):
Expand Down Expand Up @@ -498,6 +501,7 @@ def manual_download_subtitle(path, language, audio_language, hi, forced, subtitl
provider_configs=providers_auth,
pool_class=provider_pool(),
blacklist=get_blacklist(media_type=media_type),
ban_list=get_ban_list(profile_id),
throttle_callback=provider_throttle)
logging.debug('BAZARR Subtitles file downloaded for this file:' + path)
else:
Expand Down Expand Up @@ -1706,10 +1710,20 @@ def _get_lang_obj(alpha3):

return sub.subzero_language()


def _get_scores(media_type, min_movie=None, min_ep=None):
series = "series" == media_type
handler = series_score if series else movie_score
min_movie = min_movie or (60 * 100 / handler.max_score)
min_ep = min_ep or (240 * 100 / handler.max_score)
min_score_ = int(min_ep if series else min_movie)
return handler.get_scores(min_score_)


def get_ban_list(profile_id):
if profile_id:
profile = get_profiles_list(profile_id)
if profile:
return {'must_contain': profile['mustContain'] or [],
'must_not_contain': profile['mustNotContain'] or []}
return None
2 changes: 2 additions & 0 deletions frontend/src/@types/api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ declare namespace Language {
profileId: number;
cutoff: number | null;
items: ProfileItem[];
mustContain: string[];
mustNotContain: string[];
}
}

Expand Down
25 changes: 25 additions & 0 deletions frontend/src/Settings/Languages/modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
ActionButton,
BaseModal,
BaseModalProps,
Chips,
LanguageSelector,
Selector,
SimpleTable,
Expand All @@ -31,6 +32,8 @@ function createDefaultProfile(): Language.Profile {
name: "",
items: [],
cutoff: null,
mustContain: [],
mustNotContain: [],
};
}

Expand Down Expand Up @@ -260,6 +263,28 @@ const LanguagesProfileModal: FunctionComponent<Props & BaseModalProps> = (
></Selector>
<Message>Ignore others if existing</Message>
</Input>
<Input name="Release info must contain">
<Chips
value={current.mustContain}
onChange={(mc) => updateProfile("mustContain", mc)}
></Chips>
<Message>
Subtitles release info must include one of those words or they will be
excluded from search results (regex supported).
</Message>
</Input>
<Input name="Release info must not contain">
<Chips
value={current.mustNotContain}
onChange={(mnc: string[]) => {
updateProfile("mustNotContain", mnc);
}}
></Chips>
<Message>
Subtitles release info including one of those words (case insensitive)
will be excluded from search results (regex supported).
</Message>
</Input>
</BaseModal>
);
};
Expand Down
34 changes: 34 additions & 0 deletions frontend/src/Settings/Languages/table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,40 @@ const Table: FunctionComponent = () => {
});
},
},
{
Header: "Must contain",
accessor: "mustContain",
Cell: (row) => {
const items = row.value;
if (!items) {
return false;
}
return items.map((v) => {
return (
<Badge className={"mx-1"} variant={"secondary"}>
{v}
</Badge>
);
});
},
},
{
Header: "Must not contain",
accessor: "mustNotContain",
Cell: (row) => {
const items = row.value;
if (!items) {
return false;
}
return items.map((v) => {
return (
<Badge className={"mx-1"} variant={"secondary"}>
{v}
</Badge>
);
});
},
},
{
accessor: "profileId",
Cell: ({ row, update }) => {
Expand Down
17 changes: 15 additions & 2 deletions libs/subliminal_patch/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def repl(m):


class SZProviderPool(ProviderPool):
def __init__(self, providers=None, provider_configs=None, blacklist=None, throttle_callback=None,
def __init__(self, providers=None, provider_configs=None, blacklist=None, ban_list=None, throttle_callback=None,
pre_download_hook=None, post_download_hook=None, language_hook=None):
#: Name of providers to use
self.providers = providers
Expand All @@ -82,6 +82,9 @@ def __init__(self, providers=None, provider_configs=None, blacklist=None, thrott

self.blacklist = blacklist or []

#: Should be a dict of 2 lists of strings
self.ban_list = ban_list or {'must_contain': [], 'must_not_contain': []}

self.throttle_callback = throttle_callback

self.pre_download_hook = pre_download_hook
Expand Down Expand Up @@ -184,6 +187,16 @@ def list_subtitles_provider(self, provider, video, languages):
if (str(provider), str(s.id)) in self.blacklist:
logger.info("Skipping blacklisted subtitle: %s", s)
continue
if hasattr(s, 'release_info'):
if s.release_info is not None:
if any([x for x in self.ban_list["must_not_contain"]
if re.search(x, s.release_info, flags=re.IGNORECASE) is not None]):
logger.info("Skipping subtitle because release name contains prohibited string: %s", s)
continue
if any([x for x in self.ban_list["must_contain"]
if re.search(x, s.release_info, flags=re.IGNORECASE) is None]):
logger.info("Skipping subtitle because release name does not contains required string: %s", s)
continue
if s.id in seen:
continue
s.plex_media_fps = float(video.fps) if video.fps else None
Expand Down Expand Up @@ -506,7 +519,7 @@ def list_subtitles_provider(self, provider, video, languages):

return provider, provider_subtitles

def list_subtitles(self, video, languages, blacklist=None):
def list_subtitles(self, video, languages, blacklist=None, ban_list=None):
if is_windows_special_path:
return super(SZAsyncProviderPool, self).list_subtitles(video, languages)

Expand Down

0 comments on commit 63b326a

Please sign in to comment.