Removed everything related to Sonarr, Radarr and path mapping.

pull/1559/head
morpheus65535 3 years ago
parent 3a944251da
commit 9b8f562078

@ -12,10 +12,6 @@ from pyga.entities import CustomVariable
from get_args import args
from config import settings
from utils import get_sonarr_info, get_radarr_info
sonarr_version = get_sonarr_info.version()
radarr_version = get_radarr_info.version()
def track_event(category=None, action=None, label=None):
@ -49,15 +45,7 @@ def track_event(category=None, action=None, label=None):
tracker.add_custom_variable(CustomVariable(index=1, name='BazarrVersion',
value=os.environ["BAZARR_VERSION"].lstrip('v'), scope=1))
tracker.add_custom_variable(CustomVariable(index=2, name='PythonVersion', value=platform.python_version(), scope=1))
if settings.general.getboolean('use_sonarr'):
tracker.add_custom_variable(CustomVariable(index=3, name='SonarrVersion', value=sonarr_version, scope=1))
else:
tracker.add_custom_variable(CustomVariable(index=3, name='SonarrVersion', value='unused', scope=1))
if settings.general.getboolean('use_radarr'):
tracker.add_custom_variable(CustomVariable(index=4, name='RadarrVersion', value=radarr_version, scope=1))
else:
tracker.add_custom_variable(CustomVariable(index=4, name='RadarrVersion', value='unused', scope=1))
tracker.add_custom_variable(CustomVariable(index=5, name='OSVersion', value=platform.platform(), scope=1))
tracker.add_custom_variable(CustomVariable(index=3, name='OSVersion', value=platform.platform(), scope=1))
try:
tracker.track_event(event, session, visitor)

File diff suppressed because it is too large Load Diff

@ -16,7 +16,7 @@ from config import settings
def check_releases():
releases = []
url_releases = 'https://api.github.com/repos/morpheus65535/Bazarr/releases?per_page=100'
url_releases = 'https://api.github.com/repos/Bazarr/Bazarr2/releases?per_page=100'
try:
logging.debug('BAZARR getting releases from Github: {}'.format(url_releases))
r = requests.get(url_releases, allow_redirects=True)

@ -27,22 +27,19 @@ defaults = {
'ip': '0.0.0.0',
'port': '6767',
'base_url': '',
'path_mappings': '[]',
'debug': 'False',
'branch': 'master',
'auto_update': 'True',
'single_language': 'False',
'minimum_score': '90',
'use_scenename': 'True',
'use_postprocessing': 'False',
'postprocessing_cmd': '',
'postprocessing_threshold': '90',
'use_postprocessing_threshold': 'False',
'postprocessing_threshold_movie': '70',
'use_postprocessing_threshold_movie': 'False',
'use_sonarr': 'False',
'use_radarr': 'False',
'path_mappings_movie': '[]',
'use_series': 'False',
'use_movies': 'False',
'serie_default_enabled': 'False',
'serie_default_profile': '',
'movie_default_enabled': 'False',
@ -78,33 +75,20 @@ defaults = {
'username': '',
'password': ''
},
'sonarr': {
'ip': '127.0.0.1',
'port': '8989',
'base_url': '/',
'ssl': 'False',
'apikey': '',
'series': {
'full_update': 'Daily',
'full_update_day': '6',
'full_update_hour': '4',
'only_monitored': 'False',
'series_sync': '60',
'episodes_sync': '60',
'excluded_tags': '[]',
'excluded_series_types': '[]',
'use_ffprobe_cache': 'True'
},
'radarr': {
'ip': '127.0.0.1',
'port': '7878',
'base_url': '/',
'ssl': 'False',
'apikey': '',
'movies': {
'full_update': 'Daily',
'full_update_day': '6',
'full_update_hour': '5',
'only_monitored': 'False',
'movies_sync': '60',
'excluded_tags': '[]',
'use_ffprobe_cache': 'True'
},
@ -116,6 +100,52 @@ defaults = {
'password': '',
'exclude': '["localhost","127.0.0.1"]'
},
'anticaptcha': {
'anti_captcha_key': ''
},
'deathbycaptcha': {
'username': '',
'password': ''
},
'analytics': {
'enabled': 'True'
},
'subsync': {
'use_subsync': 'False',
'use_subsync_threshold': 'False',
'subsync_threshold': '90',
'use_subsync_movie_threshold': 'False',
'subsync_movie_threshold': '70',
'debug': 'False'
},
'series_scores': {
"hash": 359,
"series": 180,
"year": 90,
"season": 30,
"episode": 30,
"release_group": 15,
"source": 7,
"audio_codec": 3,
"resolution": 2,
"video_codec": 2,
"hearing_impaired": 1,
"streaming_service": 0,
"edition": 0,
},
'movie_scores': {
"hash": 119,
"title": 60,
"year": 30,
"release_group": 15,
"source": 7,
"audio_codec": 3,
"resolution": 2,
"video_codec": 2,
"hearing_impaired": 1,
"streaming_service": 0,
"edition": 0,
},
'opensubtitles': {
'username': '',
'password': '',
@ -155,13 +185,6 @@ defaults = {
'assrt': {
'token': ''
},
'anticaptcha': {
'anti_captcha_key': ''
},
'deathbycaptcha': {
'username': '',
'password': ''
},
'napisy24': {
'username': '',
'password': ''
@ -173,48 +196,9 @@ defaults = {
'betaseries': {
'token': ''
},
'analytics': {
'enabled': 'True'
},
'titlovi': {
'username': '',
'password': ''
},
'subsync': {
'use_subsync': 'False',
'use_subsync_threshold': 'False',
'subsync_threshold': '90',
'use_subsync_movie_threshold': 'False',
'subsync_movie_threshold': '70',
'debug': 'False'
},
'series_scores': {
"hash": 359,
"series": 180,
"year": 90,
"season": 30,
"episode": 30,
"release_group": 15,
"source": 7,
"audio_codec": 3,
"resolution": 2,
"video_codec": 2,
"hearing_impaired": 1,
"streaming_service": 0,
"edition": 0,
},
'movie_scores': {
"hash": 119,
"title": 60,
"year": 30,
"release_group": 15,
"source": 7,
"audio_codec": 3,
"resolution": 2,
"video_codec": 2,
"hearing_impaired": 1,
"streaming_service": 0,
"edition": 0,
}
}
@ -235,22 +219,12 @@ array_keys = ['excluded_tags',
'exclude',
'subzero_mods',
'excluded_series_types',
'enabled_providers',
'path_mappings',
'path_mappings_movie']
'enabled_providers']
str_keys = ['chmod']
empty_values = ['', 'None', 'null', 'undefined', None, []]
# Increase Sonarr and Radarr sync interval since we now use SignalR feed to update in real time
if int(settings.sonarr.series_sync) < 15:
settings.sonarr.series_sync = "60"
if int(settings.sonarr.episodes_sync) < 15:
settings.sonarr.episodes_sync = "60"
if int(settings.radarr.movies_sync) < 15:
settings.radarr.movies_sync = "60"
if os.path.exists(os.path.join(args.config_dir, 'config', 'config.ini')):
with open(os.path.join(args.config_dir, 'config', 'config.ini'), 'w+') as handle:
settings.write(handle)
@ -304,13 +278,11 @@ def save_settings(settings_items):
configure_debug = False
configure_captcha = False
update_schedule = False
sonarr_changed = False
radarr_changed = False
update_path_map = False
configure_proxy = False
exclusion_updated = False
sonarr_exclusion_updated = False
radarr_exclusion_updated = False
series_exclusion_updated = False
movies_exclusion_updated = False
# Subzero Mods
update_subzero = False
@ -333,10 +305,6 @@ def save_settings(settings_items):
if settings_keys[-1] in array_keys and value[0] in empty_values :
value = []
# Handle path mappings settings since they are array in array
if settings_keys[-1] in ['path_mappings', 'path_mappings_movie']:
value = [v.split(',') for v in value]
if value == 'true':
value = 'True'
elif value == 'false':
@ -353,41 +321,26 @@ def save_settings(settings_items):
'settings-deathbycaptcha-username', 'settings-deathbycaptcha-password']:
configure_captcha = True
if key in ['update_schedule', 'settings-general-use_sonarr', 'settings-general-use_radarr',
'settings-general-auto_update', 'settings-general-upgrade_subs',
'settings-sonarr-series_sync', 'settings-sonarr-episodes_sync', 'settings-radarr-movies_sync',
'settings-sonarr-full_update', 'settings-sonarr-full_update_day', 'settings-sonarr-full_update_hour',
'settings-radarr-full_update', 'settings-radarr-full_update_day', 'settings-radarr-full_update_hour',
if key in ['update_schedule', 'settings-general-auto_update', 'settings-general-upgrade_subs',
'settings-general-wanted_search_frequency', 'settings-general-wanted_search_frequency_movie',
'settings-general-upgrade_frequency']:
update_schedule = True
if key in ['settings-general-use_sonarr', 'settings-sonarr-ip', 'settings-sonarr-port',
'settings-sonarr-base_url', 'settings-sonarr-ssl', 'settings-sonarr-apikey']:
sonarr_changed = True
if key in ['settings-general-use_radarr', 'settings-radarr-ip', 'settings-radarr-port',
'settings-radarr-base_url', 'settings-radarr-ssl', 'settings-radarr-apikey']:
radarr_changed = True
if key in ['settings-general-path_mappings', 'settings-general-path_mappings_movie']:
update_path_map = True
if key in ['settings-proxy-type', 'settings-proxy-url', 'settings-proxy-port', 'settings-proxy-username',
'settings-proxy-password']:
configure_proxy = True
if key in ['settings-sonarr-excluded_tags', 'settings-sonarr-only_monitored',
'settings-sonarr-excluded_series_types', 'settings.radarr.excluded_tags',
'settings-radarr-only_monitored']:
if key in ['settings-series-excluded_tags', 'settings-series-only_monitored',
'settings-series-excluded_series_types', 'settings.movies.excluded_tags',
'settings-movies-only_monitored']:
exclusion_updated = True
if key in ['settings-sonarr-excluded_tags', 'settings-sonarr-only_monitored',
'settings-sonarr-excluded_series_types']:
sonarr_exclusion_updated = True
if key in ['settings-series-excluded_tags', 'settings-series-only_monitored',
'settings-series-excluded_series_types']:
series_exclusion_updated = True
if key in ['settings.radarr.excluded_tags', 'settings-radarr-only_monitored']:
radarr_exclusion_updated = True
if key in ['settings-movies-excluded_tags', 'settings-movies-only_monitored']:
movies_exclusion_updated = True
if key == 'settings-addic7ed-username':
if key != settings.addic7ed.username:
@ -456,106 +409,18 @@ def save_settings(settings_items):
from api import scheduler
scheduler.update_configurable_tasks()
if sonarr_changed:
from signalr_client import sonarr_signalr_client
try:
sonarr_signalr_client.restart()
except:
pass
if radarr_changed:
from signalr_client import radarr_signalr_client
try:
radarr_signalr_client.restart()
except:
pass
if update_path_map:
from helper import path_mappings
path_mappings.update()
if configure_proxy:
configure_proxy_func()
if exclusion_updated:
from event_handler import event_stream
event_stream(type='badges')
if sonarr_exclusion_updated:
if series_exclusion_updated:
event_stream(type='reset-episode-wanted')
if radarr_exclusion_updated:
if movies_exclusion_updated:
event_stream(type='reset-movie-wanted')
def url_sonarr():
if settings.sonarr.getboolean('ssl'):
protocol_sonarr = "https"
else:
protocol_sonarr = "http"
if settings.sonarr.base_url == '':
settings.sonarr.base_url = "/"
if not settings.sonarr.base_url.startswith("/"):
settings.sonarr.base_url = "/" + settings.sonarr.base_url
if settings.sonarr.base_url.endswith("/"):
settings.sonarr.base_url = settings.sonarr.base_url[:-1]
if settings.sonarr.port in empty_values:
port = ""
else:
port = f":{settings.sonarr.port}"
return f"{protocol_sonarr}://{settings.sonarr.ip}{port}{settings.sonarr.base_url}"
def url_sonarr_short():
if settings.sonarr.getboolean('ssl'):
protocol_sonarr = "https"
else:
protocol_sonarr = "http"
if settings.sonarr.port in empty_values:
port = ""
else:
port = f":{settings.sonarr.port}"
return f"{protocol_sonarr}://{settings.sonarr.ip}{port}"
def url_radarr():
if settings.radarr.getboolean('ssl'):
protocol_radarr = "https"
else:
protocol_radarr = "http"
if settings.radarr.base_url == '':
settings.radarr.base_url = "/"
if not settings.radarr.base_url.startswith("/"):
settings.radarr.base_url = "/" + settings.radarr.base_url
if settings.radarr.base_url.endswith("/"):
settings.radarr.base_url = settings.radarr.base_url[:-1]
if settings.radarr.port in empty_values:
port = ""
else:
port = f":{settings.radarr.port}"
return f"{protocol_radarr}://{settings.radarr.ip}{port}{settings.radarr.base_url}"
def url_radarr_short():
if settings.radarr.getboolean('ssl'):
protocol_radarr = "https"
else:
protocol_radarr = "http"
if settings.radarr.port in empty_values:
port = ""
else:
port = f":{settings.radarr.port}"
return f"{protocol_radarr}://{settings.radarr.ip}{port}"
def get_array_from(property):
if property:
if '[' in property:

@ -8,9 +8,7 @@ from peewee import *
from playhouse.sqliteq import SqliteQueueDatabase
from playhouse.shortcuts import model_to_dict
from playhouse.migrate import *
from playhouse.sqlite_ext import RowIDField
from helper import path_mappings
from config import settings, get_array_from
from get_args import args
@ -47,8 +45,8 @@ class System(BaseModel):
class TableBlacklist(BaseModel):
language = TextField(null=True)
provider = TextField(null=True)
sonarr_episode_id = IntegerField(null=True)
sonarr_series_id = IntegerField(null=True)
episode_id = IntegerField(null=True)
series_id = IntegerField(null=True)
subs_id = TextField(null=True)
timestamp = IntegerField(null=True)
@ -60,7 +58,7 @@ class TableBlacklist(BaseModel):
class TableBlacklistMovie(BaseModel):
language = TextField(null=True)
provider = TextField(null=True)
radarr_id = IntegerField(null=True)
movie_id = IntegerField(null=True)
subs_id = TextField(null=True)
timestamp = IntegerField(null=True)
@ -70,7 +68,6 @@ class TableBlacklistMovie(BaseModel):
class TableEpisodes(BaseModel):
rowid = RowIDField()
audio_codec = TextField(null=True)
audio_language = TextField(null=True)
episode = IntegerField()
@ -83,17 +80,15 @@ class TableEpisodes(BaseModel):
monitored = TextField(null=True)
path = TextField()
resolution = TextField(null=True)
scene_name = TextField(null=True)
season = IntegerField()
sonarrEpisodeId = IntegerField(unique=True)
sonarrSeriesId = IntegerField()
episodeId = AutoField()
seriesId = IntegerField()
subtitles = TextField(null=True)
title = TextField()
video_codec = TextField(null=True)
class Meta:
table_name = 'table_episodes'
primary_key = False
class TableHistory(BaseModel):
@ -103,8 +98,8 @@ class TableHistory(BaseModel):
language = TextField(null=True)
provider = TextField(null=True)
score = TextField(null=True)
sonarrEpisodeId = IntegerField()
sonarrSeriesId = IntegerField()
episodeId = IntegerField()
seriesId = IntegerField()
subs_id = TextField(null=True)
subtitles_path = TextField(null=True)
timestamp = IntegerField()
@ -120,7 +115,7 @@ class TableHistoryMovie(BaseModel):
id = AutoField()
language = TextField(null=True)
provider = TextField(null=True)
radarrId = IntegerField()
movieId = IntegerField()
score = TextField(null=True)
subs_id = TextField(null=True)
subtitles_path = TextField(null=True)
@ -142,7 +137,6 @@ class TableLanguagesProfiles(BaseModel):
class TableMovies(BaseModel):
rowid = RowIDField()
alternativeTitles = TextField(null=True)
audio_codec = TextField(null=True)
audio_language = TextField(null=True)
@ -159,9 +153,8 @@ class TableMovies(BaseModel):
path = TextField(unique=True)
poster = TextField(null=True)
profileId = IntegerField(null=True)
radarrId = IntegerField(unique=True)
movieId = AutoField()
resolution = TextField(null=True)
sceneName = TextField(null=True)
sortTitle = TextField(null=True)
subtitles = TextField(null=True)
tags = TextField(null=True)
@ -215,11 +208,11 @@ class TableShows(BaseModel):
poster = TextField(null=True)
profileId = IntegerField(null=True)
seriesType = TextField(null=True)
sonarrSeriesId = IntegerField(unique=True)
seriesId = AutoField()
sortTitle = TextField(null=True)
tags = TextField(null=True)
title = TextField()
tvdbId = AutoField()
tvdbId = TextField(unique=True)
year = TextField(null=True)
class Meta:
@ -290,99 +283,34 @@ def init_db():
def migrate_db():
migrate(
migrator.add_column('table_shows', 'year', TextField(null=True)),
migrator.add_column('table_shows', 'alternateTitles', TextField(null=True)),
migrator.add_column('table_shows', 'tags', TextField(default='[]', null=True)),
migrator.add_column('table_shows', 'seriesType', TextField(default='""', null=True)),
migrator.add_column('table_shows', 'imdbId', TextField(default='""', null=True)),
migrator.add_column('table_shows', 'profileId', IntegerField(null=True)),
migrator.add_column('table_episodes', 'format', TextField(null=True)),
migrator.add_column('table_episodes', 'resolution', TextField(null=True)),
migrator.add_column('table_episodes', 'video_codec', TextField(null=True)),
migrator.add_column('table_episodes', 'audio_codec', TextField(null=True)),
migrator.add_column('table_episodes', 'episode_file_id', IntegerField(null=True)),
migrator.add_column('table_episodes', 'audio_language', TextField(null=True)),
migrator.add_column('table_episodes', 'file_size', IntegerField(default=0, null=True)),
migrator.add_column('table_episodes', 'ffprobe_cache', BlobField(null=True)),
migrator.add_column('table_movies', 'sortTitle', TextField(null=True)),
migrator.add_column('table_movies', 'year', TextField(null=True)),
migrator.add_column('table_movies', 'alternativeTitles', TextField(null=True)),
migrator.add_column('table_movies', 'format', TextField(null=True)),
migrator.add_column('table_movies', 'resolution', TextField(null=True)),
migrator.add_column('table_movies', 'video_codec', TextField(null=True)),
migrator.add_column('table_movies', 'audio_codec', TextField(null=True)),
migrator.add_column('table_movies', 'imdbId', TextField(null=True)),
migrator.add_column('table_movies', 'movie_file_id', IntegerField(null=True)),
migrator.add_column('table_movies', 'tags', TextField(default='[]', null=True)),
migrator.add_column('table_movies', 'profileId', IntegerField(null=True)),
migrator.add_column('table_movies', 'file_size', IntegerField(default=0, null=True)),
migrator.add_column('table_movies', 'ffprobe_cache', BlobField(null=True)),
migrator.add_column('table_history', 'video_path', TextField(null=True)),
migrator.add_column('table_history', 'language', TextField(null=True)),
migrator.add_column('table_history', 'provider', TextField(null=True)),
migrator.add_column('table_history', 'score', TextField(null=True)),
migrator.add_column('table_history', 'subs_id', TextField(null=True)),
migrator.add_column('table_history', 'subtitles_path', TextField(null=True)),
migrator.add_column('table_history_movie', 'video_path', TextField(null=True)),
migrator.add_column('table_history_movie', 'language', TextField(null=True)),
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))
)
class SqliteDictPathMapper:
def __init__(self):
pass
@staticmethod
def path_replace(values_dict):
if type(values_dict) is list:
for item in values_dict:
item['path'] = path_mappings.path_replace(item['path'])
elif type(values_dict) is dict:
values_dict['path'] = path_mappings.path_replace(values_dict['path'])
else:
return path_mappings.path_replace(values_dict)
@staticmethod
def path_replace_movie(values_dict):
if type(values_dict) is list:
for item in values_dict:
item['path'] = path_mappings.path_replace_movie(item['path'])
elif type(values_dict) is dict:
values_dict['path'] = path_mappings.path_replace_movie(values_dict['path'])
else:
return path_mappings.path_replace_movie(values_dict)
dict_mapper = SqliteDictPathMapper()
pass
# migrate(
# migrator.add_column('table_shows', 'year', TextField(null=True))
# )
def get_exclusion_clause(exclusion_type):
where_clause = []
if exclusion_type == 'series':
tagsList = ast.literal_eval(settings.sonarr.excluded_tags)
tagsList = ast.literal_eval(settings.series.excluded_tags)
for tag in tagsList:
where_clause.append(~(TableShows.tags.contains("\'"+tag+"\'")))
else:
tagsList = ast.literal_eval(settings.radarr.excluded_tags)
tagsList = ast.literal_eval(settings.movies.excluded_tags)
for tag in tagsList:
where_clause.append(~(TableMovies.tags.contains("\'"+tag+"\'")))
if exclusion_type == 'series':
monitoredOnly = settings.sonarr.getboolean('only_monitored')
monitoredOnly = settings.series.getboolean('only_monitored')
if monitoredOnly:
where_clause.append((TableEpisodes.monitored == 'True'))
else:
monitoredOnly = settings.radarr.getboolean('only_monitored')
monitoredOnly = settings.movies.getboolean('only_monitored')
if monitoredOnly:
where_clause.append((TableMovies.monitored == 'True'))
if exclusion_type == 'series':
typesList = get_array_from(settings.sonarr.excluded_series_types)
typesList = get_array_from(settings.series.excluded_series_types)
for item in typesList:
where_clause.append((TableShows.seriesType != item))
@ -475,11 +403,11 @@ def get_audio_profile_languages(series_id=None, episode_id=None, movie_id=None):
audio_languages = []
if series_id:
audio_languages_list_str = TableShows.get(TableShows.sonarrSeriesId == series_id).audio_language
audio_languages_list_str = TableShows.get(TableShows.seriesId == series_id).audio_language
elif episode_id:
audio_languages_list_str = TableEpisodes.get(TableEpisodes.sonarrEpisodeId == episode_id).audio_language
audio_languages_list_str = TableEpisodes.get(TableEpisodes.episodeId == episode_id).audio_language
elif movie_id:
audio_languages_list_str = TableMovies.get(TableMovies.radarrId == movie_id).audio_language
audio_languages_list_str = TableMovies.get(TableMovies.movieId == movie_id).audio_language
else:
return audio_languages

@ -9,7 +9,6 @@ from enzyme.exceptions import MalformedMKVError
from enzyme.exceptions import MalformedMKVError
from custom_lang import CustomLanguage
from database import TableEpisodes, TableMovies
from helper import path_mappings
logger = logging.getLogger(__name__)
@ -78,12 +77,12 @@ def parse_video_metadata(file, file_size, episode_file_id=None, movie_file_id=No
# Get the actual cache value form database
if episode_file_id:
cache_key = TableEpisodes.select(TableEpisodes.ffprobe_cache)\
.where(TableEpisodes.path == path_mappings.path_replace_reverse(file))\
.where(TableEpisodes.path == file)\
.dicts()\
.get()
elif movie_file_id:
cache_key = TableMovies.select(TableMovies.ffprobe_cache)\
.where(TableMovies.path == path_mappings.path_replace_reverse_movie(file))\
.where(TableMovies.path == file)\
.dicts()\
.get()
else:
@ -126,10 +125,10 @@ def parse_video_metadata(file, file_size, episode_file_id=None, movie_file_id=No
# we write to db the result and return the newly cached ffprobe dict
if episode_file_id:
TableEpisodes.update({TableEpisodes.ffprobe_cache: pickle.dumps(data, pickle.HIGHEST_PROTOCOL)})\
.where(TableEpisodes.path == path_mappings.path_replace_reverse(file))\
.where(TableEpisodes.path == file)\
.execute()
elif movie_file_id:
TableMovies.update({TableEpisodes.ffprobe_cache: pickle.dumps(data, pickle.HIGHEST_PROTOCOL)})\
.where(TableMovies.path == path_mappings.path_replace_reverse_movie(file))\
.where(TableMovies.path == file)\
.execute()
return data

@ -5,8 +5,7 @@ import requests
import logging
import string
from config import settings, url_sonarr, url_radarr
from utils import get_sonarr_info, get_radarr_info
from config import settings
headers = {"User-Agent": os.environ["SZ_USER_AGENT"]}
@ -43,64 +42,3 @@ def browse_bazarr_filesystem(path='#'):
result.update({'parent': parent})
return result
def browse_sonarr_filesystem(path='#'):
if path == '#':
path = ''
if get_sonarr_info.is_legacy():
url_sonarr_api_filesystem = url_sonarr() + "/api/filesystem?path=" + path + \
"&allowFoldersWithoutTrailingSlashes=true&includeFiles=false&apikey=" + \
settings.sonarr.apikey
else:
url_sonarr_api_filesystem = url_sonarr() + "/api/v3/filesystem?path=" + path + \
"&allowFoldersWithoutTrailingSlashes=true&includeFiles=false&apikey=" + \
settings.sonarr.apikey
try:
r = requests.get(url_sonarr_api_filesystem, timeout=60, verify=False, headers=headers)
r.raise_for_status()
except requests.exceptions.HTTPError:
logging.exception("BAZARR Error trying to get series from Sonarr. Http error.")
return
except requests.exceptions.ConnectionError:
logging.exception("BAZARR Error trying to get series from Sonarr. Connection Error.")
return
except requests.exceptions.Timeout:
logging.exception("BAZARR Error trying to get series from Sonarr. Timeout Error.")
return
except requests.exceptions.RequestException:
logging.exception("BAZARR Error trying to get series from Sonarr.")
return
return r.json()
def browse_radarr_filesystem(path='#'):
if path == '#':
path = ''
if get_radarr_info.is_legacy():
url_radarr_api_filesystem = url_radarr() + "/api/filesystem?path=" + path + \
"&allowFoldersWithoutTrailingSlashes=true&includeFiles=false&apikey=" + \
settings.radarr.apikey
else:
url_radarr_api_filesystem = url_radarr() + "/api/v3/filesystem?path=" + path + \
"&allowFoldersWithoutTrailingSlashes=true&includeFiles=false&apikey=" + \
settings.radarr.apikey
try:
r = requests.get(url_radarr_api_filesystem, timeout=60, verify=False, headers=headers)
r.raise_for_status()
except requests.exceptions.HTTPError:
logging.exception("BAZARR Error trying to get series from Radarr. Http error.")
return
except requests.exceptions.ConnectionError:
logging.exception("BAZARR Error trying to get series from Radarr. Connection Error.")
return
except requests.exceptions.Timeout:
logging.exception("BAZARR Error trying to get series from Radarr. Timeout Error.")
return
except requests.exceptions.RequestException:
logging.exception("BAZARR Error trying to get series from Radarr.")
return
return r.json()

@ -1,417 +1,10 @@
# coding=utf-8
import os
import requests
import logging
from gevent import sleep
from peewee import DoesNotExist
from database import get_exclusion_clause, TableEpisodes, TableShows
from config import settings, url_sonarr
from helper import path_mappings
from list_subtitles import store_subtitles, series_full_scan_subtitles
from get_subtitle import episode_download_subtitles
from event_handler import event_stream, show_progress, hide_progress
from utils import get_sonarr_info
headers = {"User-Agent": os.environ["SZ_USER_AGENT"]}
from list_subtitles import series_full_scan_subtitles
def update_all_episodes():
series_full_scan_subtitles()
logging.info('BAZARR All existing episode subtitles indexed from disk.')
def sync_episodes(series_id=None, send_event=True):
logging.debug('BAZARR Starting episodes sync from Sonarr.')
apikey_sonarr = settings.sonarr.apikey
# Get current episodes id in DB
current_episodes_db = TableEpisodes.select(TableEpisodes.sonarrEpisodeId,
TableEpisodes.path,
TableEpisodes.sonarrSeriesId)\
.where((TableEpisodes.sonarrSeriesId == series_id) if series_id else None)\
.dicts()
current_episodes_db_list = [x['sonarrEpisodeId'] for x in current_episodes_db]
current_episodes_sonarr = []
episodes_to_update = []
episodes_to_add = []
altered_episodes = []
# Get sonarrId for each series from database
seriesIdList = get_series_from_sonarr_api(series_id=series_id, url=url_sonarr(), apikey_sonarr=apikey_sonarr,)
series_count = len(seriesIdList)
for i, seriesId in enumerate(seriesIdList):
sleep()
if send_event:
show_progress(id='episodes_progress',
header='Syncing episodes...',
name=seriesId['title'],
value=i,
count=series_count)
# Get episodes data for a series from Sonarr
episodes = get_episodes_from_sonarr_api(url=url_sonarr(), apikey_sonarr=apikey_sonarr,
series_id=seriesId['sonarrSeriesId'])
if not episodes:
continue
else:
# For Sonarr v3, we need to update episodes to integrate the episodeFile API endpoint results
if not get_sonarr_info.is_legacy():
episodeFiles = get_episodesFiles_from_sonarr_api(url=url_sonarr(), apikey_sonarr=apikey_sonarr,
series_id=seriesId['sonarrSeriesId'])
for episode in episodes:
if episode['hasFile']:
item = [x for x in episodeFiles if x['id'] == episode['episodeFileId']]
if item:
episode['episodeFile'] = item[0]
for episode in episodes:
sleep()
if 'hasFile' in episode:
if episode['hasFile'] is True:
if 'episodeFile' in episode:
if episode['episodeFile']['size'] > 20480:
# Add episodes in sonarr to current episode list
current_episodes_sonarr.append(episode['id'])
# Parse episode data
if episode['id'] in current_episodes_db_list:
episodes_to_update.append(episodeParser(episode))
else:
episodes_to_add.append(episodeParser(episode))
if send_event:
hide_progress(id='episodes_progress')
# Remove old episodes from DB
removed_episodes = list(set(current_episodes_db_list) - set(current_episodes_sonarr))
for removed_episode in removed_episodes:
sleep()
episode_to_delete = TableEpisodes.select(TableEpisodes.sonarrSeriesId, TableEpisodes.sonarrEpisodeId)\
.where(TableEpisodes.sonarrEpisodeId == removed_episode)\
.dicts()\
.get()
TableEpisodes.delete().where(TableEpisodes.sonarrEpisodeId == removed_episode).execute()
if send_event:
event_stream(type='episode', action='delete', payload=episode_to_delete['sonarrEpisodeId'])
# Update existing episodes in DB
episode_in_db_list = []
episodes_in_db = TableEpisodes.select(TableEpisodes.sonarrSeriesId,
TableEpisodes.sonarrEpisodeId,
TableEpisodes.title,
TableEpisodes.path,
TableEpisodes.season,
TableEpisodes.episode,
TableEpisodes.scene_name,
TableEpisodes.monitored,
TableEpisodes.format,
TableEpisodes.resolution,
TableEpisodes.video_codec,
TableEpisodes.audio_codec,
TableEpisodes.episode_file_id,
TableEpisodes.audio_language,
TableEpisodes.file_size).dicts()
for item in episodes_in_db:
episode_in_db_list.append(item)
episodes_to_update_list = [i for i in episodes_to_update if i not in episode_in_db_list]
for updated_episode in episodes_to_update_list:
sleep()
TableEpisodes.update(updated_episode).where(TableEpisodes.sonarrEpisodeId ==
updated_episode['sonarrEpisodeId']).execute()
altered_episodes.append([updated_episode['sonarrEpisodeId'],
updated_episode['path'],
updated_episode['sonarrSeriesId']])
# Insert new episodes in DB
for added_episode in episodes_to_add:
sleep()
result = TableEpisodes.insert(added_episode).on_conflict(action='IGNORE').execute()
if result > 0:
altered_episodes.append([added_episode['sonarrEpisodeId'],
added_episode['path'],
added_episode['monitored']])
if send_event:
event_stream(type='episode', payload=added_episode['sonarrEpisodeId'])
else:
logging.debug('BAZARR unable to insert this episode into the database:{}'.format(
path_mappings.path_replace(added_episode['path'])))
# Store subtitles for added or modified episodes
for i, altered_episode in enumerate(altered_episodes, 1):
sleep()
store_subtitles(altered_episode[1], path_mappings.path_replace(altered_episode[1]))
logging.debug('BAZARR All episodes synced from Sonarr into database.')
def sync_one_episode(episode_id):
logging.debug('BAZARR syncing this specific episode from Sonarr: {}'.format(episode_id))
url = url_sonarr()
apikey_sonarr = settings.sonarr.apikey
# Check if there's a row in database for this episode ID
try:
existing_episode = TableEpisodes.select(TableEpisodes.path, TableEpisodes.episode_file_id)\
.where(TableEpisodes.sonarrEpisodeId == episode_id)\
.dicts()\
.get()
except DoesNotExist:
existing_episode = None
try:
# Get episode data from sonarr api
episode = None
episode_data = get_episodes_from_sonarr_api(url=url, apikey_sonarr=apikey_sonarr,
episode_id=episode_id)
if not episode_data:
return
else:
# For Sonarr v3, we need to update episodes to integrate the episodeFile API endpoint results
if not get_sonarr_info.is_legacy() and existing_episode and episode_data['hasFile']:
episode_data['episodeFile'] = \
get_episodesFiles_from_sonarr_api(url=url, apikey_sonarr=apikey_sonarr,
episode_file_id=existing_episode['episode_file_id'])
episode = episodeParser(episode_data)
except Exception:
logging.debug('BAZARR cannot get episode returned by SignalR feed from Sonarr API.')
return
# Drop useless events
if not episode and not existing_episode:
return
# Remove episode from DB
if not episode and existing_episode:
TableEpisodes.delete().where(TableEpisodes.sonarrEpisodeId == episode_id).execute()
event_stream(type='episode', action='delete', payload=int(episode_id))
logging.debug('BAZARR deleted this episode from the database:{}'.format(path_mappings.path_replace(
existing_episode['path'])))
return
# Update existing episodes in DB
elif episode and existing_episode:
TableEpisodes.update(episode).where(TableEpisodes.sonarrEpisodeId == episode_id).execute()
event_stream(type='episode', action='update', payload=int(episode_id))
logging.debug('BAZARR updated this episode into the database:{}'.format(path_mappings.path_replace(
episode['path'])))
# Insert new episodes in DB
elif episode and not existing_episode:
TableEpisodes.insert(episode).on_conflict(action='IGNORE').execute()
event_stream(type='episode', action='update', payload=int(episode_id))
logging.debug('BAZARR inserted this episode into the database:{}'.format(path_mappings.path_replace(
episode['path'])))
# Storing existing subtitles
logging.debug('BAZARR storing subtitles for this episode: {}'.format(path_mappings.path_replace(
episode['path'])))
store_subtitles(episode['path'], path_mappings.path_replace(episode['path']))
# Downloading missing subtitles
logging.debug('BAZARR downloading missing subtitles for this episode: {}'.format(path_mappings.path_replace(
episode['path'])))
episode_download_subtitles(episode_id)
def SonarrFormatAudioCodec(audio_codec):
if audio_codec == 'AC-3':
return 'AC3'
if audio_codec == 'E-AC-3':
return 'EAC3'
if audio_codec == 'MPEG Audio':
return 'MP3'
return audio_codec
def SonarrFormatVideoCodec(video_codec):
if video_codec == 'x264' or video_codec == 'AVC':
return 'h264'
elif video_codec == 'x265' or video_codec == 'HEVC':
return 'h265'
elif video_codec.startswith('XviD'):
return 'XviD'
elif video_codec.startswith('DivX'):
return 'DivX'
elif video_codec == 'MPEG-1 Video':
return 'Mpeg'
elif video_codec == 'MPEG-2 Video':
return 'Mpeg2'
elif video_codec == 'MPEG-4 Video':
return 'Mpeg4'
elif video_codec == 'VC-1':
return 'VC1'
elif video_codec.endswith('VP6'):
return 'VP6'
elif video_codec.endswith('VP7'):
return 'VP7'
elif video_codec.endswith('VP8'):
return 'VP8'
elif video_codec.endswith('VP9'):
return 'VP9'
else:
return video_codec
def episodeParser(episode):
if 'hasFile' in episode:
if episode['hasFile'] is True:
if 'episodeFile' in episode:
if episode['episodeFile']['size'] > 20480:
if 'sceneName' in episode['episodeFile']:
sceneName = episode['episodeFile']['sceneName']
else:
sceneName = None
audio_language = []
if 'language' in episode['episodeFile'] and len(episode['episodeFile']['language']):
item = episode['episodeFile']['language']
if isinstance(item, dict):
if 'name' in item:
audio_language.append(item['name'])
else:
audio_language = TableShows.get(TableShows.sonarrSeriesId == episode['seriesId']).audio_language
if 'mediaInfo' in episode['episodeFile']:
if 'videoCodec' in episode['episodeFile']['mediaInfo']:
videoCodec = episode['episodeFile']['mediaInfo']['videoCodec']
videoCodec = SonarrFormatVideoCodec(videoCodec)
else:
videoCodec = None
if 'audioCodec' in episode['episodeFile']['mediaInfo']:
audioCodec = episode['episodeFile']['mediaInfo']['audioCodec']
audioCodec = SonarrFormatAudioCodec(audioCodec)
else:
audioCodec = None
else:
videoCodec = None
audioCodec = None
try:
video_format, video_resolution = episode['episodeFile']['quality']['quality']['name'].split('-')
except:
video_format = episode['episodeFile']['quality']['quality']['name']
try:
video_resolution = str(episode['episodeFile']['quality']['quality']['resolution']) + 'p'
except:
video_resolution = None
return {'sonarrSeriesId': episode['seriesId'],
'sonarrEpisodeId': episode['id'],
'title': episode['title'],
'path': episode['episodeFile']['path'],
'season': episode['seasonNumber'],
'episode': episode['episodeNumber'],
'scene_name': sceneName,
'monitored': str(bool(episode['monitored'])),
'format': video_format,
'resolution': video_resolution,
'video_codec': videoCodec,
'audio_codec': audioCodec,
'episode_file_id': episode['episodeFile']['id'],
'audio_language': str(audio_language),
'file_size': episode['episodeFile']['size']}
def get_series_from_sonarr_api(series_id, url, apikey_sonarr):
if series_id:
url_sonarr_api_series = url + "/api/{0}series/{1}?apikey={2}".format(
'' if get_sonarr_info.is_legacy() else 'v3/', series_id, apikey_sonarr)
else:
url_sonarr_api_series = url + "/api/{0}series?apikey={1}".format(
'' if get_sonarr_info.is_legacy() else 'v3/', apikey_sonarr)
try:
r = requests.get(url_sonarr_api_series, timeout=60, verify=False, headers=headers)
r.raise_for_status()
except requests.exceptions.HTTPError as e:
if e.response.status_code:
raise requests.exceptions.HTTPError
logging.exception("BAZARR Error trying to get series from Sonarr. Http error.")
return
except requests.exceptions.ConnectionError:
logging.exception("BAZARR Error trying to get series from Sonarr. Connection Error.")
return
except requests.exceptions.Timeout:
logging.exception("BAZARR Error trying to get series from Sonarr. Timeout Error.")
return
except requests.exceptions.RequestException:
logging.exception("BAZARR Error trying to get series from Sonarr.")
return
else:
series_json = []
if series_id:
series_json.append(r.json())
else:
series_json = r.json()
series_list = []
for series in series_json:
series_list.append({'sonarrSeriesId': series['id'], 'title': series['title']})
return series_list
def get_episodes_from_sonarr_api(url, apikey_sonarr, series_id=None, episode_id=None):
if series_id:
url_sonarr_api_episode = url + "/api/{0}episode?seriesId={1}&apikey={2}".format(
'' if get_sonarr_info.is_legacy() else 'v3/', series_id, apikey_sonarr)
elif episode_id:
url_sonarr_api_episode = url + "/api/{0}episode/{1}?apikey={2}".format(
'' if get_sonarr_info.is_legacy() else 'v3/', episode_id, apikey_sonarr)
else:
return
try:
r = requests.get(url_sonarr_api_episode, timeout=60, verify=False, headers=headers)
r.raise_for_status()
except requests.exceptions.HTTPError:
logging.exception("BAZARR Error trying to get episodes from Sonarr. Http error.")
return
except requests.exceptions.ConnectionError:
logging.exception("BAZARR Error trying to get episodes from Sonarr. Connection Error.")
return
except requests.exceptions.Timeout:
logging.exception("BAZARR Error trying to get episodes from Sonarr. Timeout Error.")
return
except requests.exceptions.RequestException:
logging.exception("BAZARR Error trying to get episodes from Sonarr.")
return
else:
return r.json()
def get_episodesFiles_from_sonarr_api(url, apikey_sonarr, series_id=None, episode_file_id=None):
if series_id:
url_sonarr_api_episodeFiles = url + "/api/v3/episodeFile?seriesId={0}&apikey={1}".format(series_id,
apikey_sonarr)
elif episode_file_id:
url_sonarr_api_episodeFiles = url + "/api/v3/episodeFile/{0}?apikey={1}".format(episode_file_id, apikey_sonarr)
else:
return
try:
r = requests.get(url_sonarr_api_episodeFiles, timeout=60, verify=False, headers=headers)
r.raise_for_status()
except requests.exceptions.HTTPError:
logging.exception("BAZARR Error trying to get episodeFiles from Sonarr. Http error.")
return
except requests.exceptions.ConnectionError:
logging.exception("BAZARR Error trying to get episodeFiles from Sonarr. Connection Error.")
return
except requests.exceptions.Timeout:
logging.exception("BAZARR Error trying to get episodeFiles from Sonarr. Timeout Error.")
return
except requests.exceptions.RequestException:
logging.exception("BAZARR Error trying to get episodeFiles from Sonarr.")
return
else:
return r.json()

@ -1,537 +1,10 @@
# coding=utf-8
import os
import requests
import logging
import operator
from functools import reduce
from gevent import sleep
from peewee import DoesNotExist
from config import settings, url_radarr
from helper import path_mappings
from utils import get_radarr_info
from list_subtitles import store_subtitles_movie, movies_full_scan_subtitles
from get_rootfolder import check_radarr_rootfolder
from get_subtitle import movies_download_subtitles
from database import get_exclusion_clause, TableMovies
from event_handler import event_stream, show_progress, hide_progress
headers = {"User-Agent": os.environ["SZ_USER_AGENT"]}
from list_subtitles import movies_full_scan_subtitles
def update_all_movies():
movies_full_scan_subtitles()
logging.info('BAZARR All existing movie subtitles indexed from disk.')
def update_movies(send_event=True):
check_radarr_rootfolder()
logging.debug('BAZARR Starting movie sync from Radarr.')
apikey_radarr = settings.radarr.apikey
movie_default_enabled = settings.general.getboolean('movie_default_enabled')
if movie_default_enabled is True:
movie_default_profile = settings.general.movie_default_profile
if movie_default_profile == '':
movie_default_profile = None
else:
movie_default_profile = None
if apikey_radarr is None:
pass
else:
audio_profiles = get_profile_list()
tagsDict = get_tags()
# Get movies data from radarr
movies = get_movies_from_radarr_api(url=url_radarr(), apikey_radarr=apikey_radarr)
if not movies:
return
else:
# Get current movies in DB
current_movies_db = TableMovies.select(TableMovies.tmdbId, TableMovies.path, TableMovies.radarrId).dicts()
current_movies_db_list = [x['tmdbId'] for x in current_movies_db]
current_movies_radarr = []
movies_to_update = []
movies_to_add = []
altered_movies = []
# Build new and updated movies
movies_count = len(movies)
for i, movie in enumerate(movies):
sleep()
if send_event:
show_progress(id='movies_progress',
header='Syncing movies...',
name=movie['title'],
value=i,
count=movies_count)
if movie['hasFile'] is True:
if 'movieFile' in movie:
if movie['movieFile']['size'] > 20480:
# Add movies in radarr to current movies list
current_movies_radarr.append(str(movie['tmdbId']))
if str(movie['tmdbId']) in current_movies_db_list:
movies_to_update.append(movieParser(movie, action='update',
tags_dict=tagsDict,
movie_default_profile=movie_default_profile,
audio_profiles=audio_profiles))
else:
movies_to_add.append(movieParser(movie, action='insert',
tags_dict=tagsDict,
movie_default_profile=movie_default_profile,
audio_profiles=audio_profiles))
if send_event:
hide_progress(id='movies_progress')
# Remove old movies from DB
removed_movies = list(set(current_movies_db_list) - set(current_movies_radarr))
for removed_movie in removed_movies:
sleep()
TableMovies.delete().where(TableMovies.tmdbId == removed_movie).execute()
# Update movies in DB
movies_in_db_list = []
movies_in_db = TableMovies.select(TableMovies.radarrId,
TableMovies.title,
TableMovies.path,
TableMovies.tmdbId,
TableMovies.overview,
TableMovies.poster,
TableMovies.fanart,
TableMovies.audio_language,
TableMovies.sceneName,
TableMovies.monitored,
TableMovies.sortTitle,
TableMovies.year,
TableMovies.alternativeTitles,
TableMovies.format,
TableMovies.resolution,
TableMovies.video_codec,
TableMovies.audio_codec,
TableMovies.imdbId,
TableMovies.movie_file_id,
TableMovies.tags,
TableMovies.file_size).dicts()
for item in movies_in_db:
movies_in_db_list.append(item)
movies_to_update_list = [i for i in movies_to_update if i not in movies_in_db_list]
for updated_movie in movies_to_update_list:
sleep()
TableMovies.update(updated_movie).where(TableMovies.tmdbId == updated_movie['tmdbId']).execute()
altered_movies.append([updated_movie['tmdbId'],
updated_movie['path'],
updated_movie['radarrId'],
updated_movie['monitored']])
# Insert new movies in DB
for added_movie in movies_to_add:
sleep()
result = TableMovies.insert(added_movie).on_conflict(action='IGNORE').execute()
if result > 0:
altered_movies.append([added_movie['tmdbId'],
added_movie['path'],
added_movie['radarrId'],
added_movie['monitored']])
if send_event:
event_stream(type='movie', action='update', payload=int(added_movie['radarrId']))
else:
logging.debug('BAZARR unable to insert this movie into the database:',
path_mappings.path_replace_movie(added_movie['path']))
# Store subtitles for added or modified movies
for i, altered_movie in enumerate(altered_movies, 1):
sleep()
store_subtitles_movie(altered_movie[1], path_mappings.path_replace_movie(altered_movie[1]))
logging.debug('BAZARR All movies synced from Radarr into database.')
def update_one_movie(movie_id, action):
logging.debug('BAZARR syncing this specific movie from Radarr: {}'.format(movie_id))
# Check if there's a row in database for this movie ID
try:
existing_movie = TableMovies.select(TableMovies.path)\
.where(TableMovies.radarrId == movie_id)\
.dicts()\
.get()
except DoesNotExist:
existing_movie = None
# Remove movie from DB
if action == 'deleted':
if existing_movie:
TableMovies.delete().where(TableMovies.radarrId == movie_id).execute()
event_stream(type='movie', action='delete', payload=int(movie_id))
logging.debug('BAZARR deleted this movie from the database:{}'.format(path_mappings.path_replace_movie(
existing_movie['path'])))
return
movie_default_enabled = settings.general.getboolean('movie_default_enabled')
if movie_default_enabled is True:
movie_default_profile = settings.general.movie_default_profile
if movie_default_profile == '':
movie_default_profile = None
else:
movie_default_profile = None
audio_profiles = get_profile_list()
tagsDict = get_tags()
try:
# Get movie data from radarr api
movie = None
movie_data = get_movies_from_radarr_api(url=url_radarr(), apikey_radarr=settings.radarr.apikey,
radarr_id=movie_id)
if not movie_data:
return
else:
if action == 'updated' and existing_movie:
movie = movieParser(movie_data, action='update', tags_dict=tagsDict,
movie_default_profile=movie_default_profile, audio_profiles=audio_profiles)
elif action == 'updated' and not existing_movie:
movie = movieParser(movie_data, action='insert', tags_dict=tagsDict,
movie_default_profile=movie_default_profile, audio_profiles=audio_profiles)
except Exception:
logging.debug('BAZARR cannot get movie returned by SignalR feed from Radarr API.')
return
# Drop useless events
if not movie and not existing_movie:
return
# Remove movie from DB
if not movie and existing_movie:
TableMovies.delete().where(TableMovies.radarrId == movie_id).execute()
event_stream(type='movie', action='delete', payload=int(movie_id))
logging.debug('BAZARR deleted this movie from the database:{}'.format(path_mappings.path_replace_movie(
existing_movie['path'])))
return
# Update existing movie in DB
elif movie and existing_movie:
TableMovies.update(movie).where(TableMovies.radarrId == movie['radarrId']).execute()
event_stream(type='movie', action='update', payload=int(movie_id))
logging.debug('BAZARR updated this movie into the database:{}'.format(path_mappings.path_replace_movie(
movie['path'])))
# Insert new movie in DB
elif movie and not existing_movie:
TableMovies.insert(movie).on_conflict(action='IGNORE').execute()
event_stream(type='movie', action='update', payload=int(movie_id))
logging.debug('BAZARR inserted this movie into the database:{}'.format(path_mappings.path_replace_movie(
movie['path'])))
# Storing existing subtitles
logging.debug('BAZARR storing subtitles for this movie: {}'.format(path_mappings.path_replace_movie(
movie['path'])))
store_subtitles_movie(movie['path'], path_mappings.path_replace_movie(movie['path']))
# Downloading missing subtitles
logging.debug('BAZARR downloading missing subtitles for this movie: {}'.format(path_mappings.path_replace_movie(
movie['path'])))
movies_download_subtitles(movie_id)
def get_profile_list():
apikey_radarr = settings.radarr.apikey
profiles_list = []
# Get profiles data from radarr
if get_radarr_info.is_legacy():
url_radarr_api_movies = url_radarr() + "/api/profile?apikey=" + apikey_radarr
else:
url_radarr_api_movies = url_radarr() + "/api/v3/qualityprofile?apikey=" + apikey_radarr
try:
profiles_json = requests.get(url_radarr_api_movies, timeout=60, verify=False, headers=headers)
except requests.exceptions.ConnectionError as errc:
logging.exception("BAZARR Error trying to get profiles from Radarr. Connection Error.")
except requests.exceptions.Timeout as errt:
logging.exception("BAZARR Error trying to get profiles from Radarr. Timeout Error.")
except requests.exceptions.RequestException as err:
logging.exception("BAZARR Error trying to get profiles from Radarr.")
else:
# Parsing data returned from radarr
if get_radarr_info.is_legacy():
for profile in profiles_json.json():
profiles_list.append([profile['id'], profile['language'].capitalize()])
else:
for profile in profiles_json.json():
profiles_list.append([profile['id'], profile['language']['name'].capitalize()])
return profiles_list
return None
def profile_id_to_language(id, profiles):
for profile in profiles:
profiles_to_return = []
if id == profile[0]:
profiles_to_return.append(profile[1])
return profiles_to_return
def RadarrFormatAudioCodec(audioFormat, audioCodecID, audioProfile, audioAdditionalFeatures):
if audioFormat == "AC-3": return "AC3"
if audioFormat == "E-AC-3": return "EAC3"
if audioFormat == "AAC":
if audioCodecID == "A_AAC/MPEG4/LC/SBR":
return "HE-AAC"
else:
return "AAC"
if audioFormat.strip() == "mp3": return "MP3"
if audioFormat == "MPEG Audio":
if audioCodecID == "55" or audioCodecID == "A_MPEG/L3" or audioProfile == "Layer 3": return "MP3"
if audioCodecID == "A_MPEG/L2" or audioProfile == "Layer 2": return "MP2"
if audioFormat == "MLP FBA":
if audioAdditionalFeatures == "16-ch":
return "TrueHD Atmos"
else:
return "TrueHD"
return audioFormat
def RadarrFormatVideoCodec(videoFormat, videoCodecID, videoCodecLibrary):
if videoFormat == "x264": return "h264"
if videoFormat == "AVC" or videoFormat == "V.MPEG4/ISO/AVC": return "h264"
if videoCodecLibrary and (videoFormat == "HEVC" or videoFormat == "V_MPEGH/ISO/HEVC"):
if videoCodecLibrary.startswith("x265"): return "h265"
if videoCodecID and videoFormat == "MPEG Video":
if videoCodecID == "2" or videoCodecID == "V_MPEG2":
return "Mpeg2"
else:
return "Mpeg"
if videoFormat == "MPEG-1 Video": return "Mpeg"
if videoFormat == "MPEG-2 Video": return "Mpeg2"
if videoCodecLibrary and videoCodecID and videoFormat == "MPEG-4 Visual":
if videoCodecID.endswith("XVID") or videoCodecLibrary.startswith("XviD"): return "XviD"
if videoCodecID.endswith("DIV3") or videoCodecID.endswith("DIVX") or videoCodecID.endswith(
"DX50") or videoCodecLibrary.startswith("DivX"): return "DivX"
if videoFormat == "VC-1": return "VC1"
if videoFormat == "WMV2":
return "WMV"
if videoFormat == "DivX" or videoFormat == "div3":
return "DivX"
return videoFormat
def get_tags():
apikey_radarr = settings.radarr.apikey
tagsDict = []
# Get tags data from Radarr
if get_radarr_info.is_legacy():
url_radarr_api_series = url_radarr() + "/api/tag?apikey=" + apikey_radarr
else:
url_radarr_api_series = url_radarr() + "/api/v3/tag?apikey=" + apikey_radarr
try:
tagsDict = requests.get(url_radarr_api_series, timeout=60, verify=False, headers=headers)
except requests.exceptions.ConnectionError:
logging.exception("BAZARR Error trying to get tags from Radarr. Connection Error.")
return []
except requests.exceptions.Timeout:
logging.exception("BAZARR Error trying to get tags from Radarr. Timeout Error.")
return []
except requests.exceptions.RequestException:
logging.exception("BAZARR Error trying to get tags from Radarr.")
return []
else:
return tagsDict.json()
def movieParser(movie, action, tags_dict, movie_default_profile, audio_profiles):
if 'movieFile' in movie:
# Detect file separator
if movie['path'][0] == "/":
separator = "/"
else:
separator = "\\"
try:
overview = str(movie['overview'])
except:
overview = ""
try:
poster_big = movie['images'][0]['url']
poster = os.path.splitext(poster_big)[0] + '-500' + os.path.splitext(poster_big)[1]
except:
poster = ""
try:
fanart = movie['images'][1]['url']
except:
fanart = ""
if 'sceneName' in movie['movieFile']:
sceneName = movie['movieFile']['sceneName']
else:
sceneName = None
alternativeTitles = None
if get_radarr_info.is_legacy():
if 'alternativeTitles' in movie:
alternativeTitles = str([item['title'] for item in movie['alternativeTitles']])
else:
if 'alternateTitles' in movie:
alternativeTitles = str([item['title'] for item in movie['alternateTitles']])
if 'imdbId' in movie:
imdbId = movie['imdbId']
else:
imdbId = None
try:
format, resolution = movie['movieFile']['quality']['quality']['name'].split('-')
except:
format = movie['movieFile']['quality']['quality']['name']
try:
resolution = str(movie['movieFile']['quality']['quality']['resolution']) + 'p'
except:
resolution = None
if 'mediaInfo' in movie['movieFile']:
videoFormat = videoCodecID = videoProfile = videoCodecLibrary = None
if get_radarr_info.is_legacy():
if 'videoFormat' in movie['movieFile']['mediaInfo']: videoFormat = \
movie['movieFile']['mediaInfo']['videoFormat']
else:
if 'videoCodec' in movie['movieFile']['mediaInfo']: videoFormat = \
movie['movieFile']['mediaInfo']['videoCodec']
if 'videoCodecID' in movie['movieFile']['mediaInfo']: videoCodecID = \
movie['movieFile']['mediaInfo']['videoCodecID']
if 'videoProfile' in movie['movieFile']['mediaInfo']: videoProfile = \
movie['movieFile']['mediaInfo']['videoProfile']
if 'videoCodecLibrary' in movie['movieFile']['mediaInfo']: videoCodecLibrary = \
movie['movieFile']['mediaInfo']['videoCodecLibrary']
videoCodec = RadarrFormatVideoCodec(videoFormat, videoCodecID, videoCodecLibrary)
audioFormat = audioCodecID = audioProfile = audioAdditionalFeatures = None
if get_radarr_info.is_legacy():
if 'audioFormat' in movie['movieFile']['mediaInfo']: audioFormat = \
movie['movieFile']['mediaInfo']['audioFormat']
else:
if 'audioCodec' in movie['movieFile']['mediaInfo']: audioFormat = \
movie['movieFile']['mediaInfo']['audioCodec']
if 'audioCodecID' in movie['movieFile']['mediaInfo']: audioCodecID = \
movie['movieFile']['mediaInfo']['audioCodecID']
if 'audioProfile' in movie['movieFile']['mediaInfo']: audioProfile = \
movie['movieFile']['mediaInfo']['audioProfile']
if 'audioAdditionalFeatures' in movie['movieFile']['mediaInfo']: audioAdditionalFeatures = \
movie['movieFile']['mediaInfo']['audioAdditionalFeatures']
audioCodec = RadarrFormatAudioCodec(audioFormat, audioCodecID, audioProfile,
audioAdditionalFeatures)
else:
videoCodec = None
audioCodec = None
audio_language = []
if get_radarr_info.is_legacy():
if 'mediaInfo' in movie['movieFile']:
if 'audioLanguages' in movie['movieFile']['mediaInfo']:
audio_languages_list = movie['movieFile']['mediaInfo']['audioLanguages'].split('/')
if len(audio_languages_list):
for audio_language_list in audio_languages_list:
audio_language.append(audio_language_list.strip())
if not audio_language:
audio_language = profile_id_to_language(movie['qualityProfileId'], audio_profiles)
else:
if 'languages' in movie['movieFile'] and len(movie['movieFile']['languages']):
for item in movie['movieFile']['languages']:
if isinstance(item, dict):
if 'name' in item:
audio_language.append(item['name'])
tags = [d['label'] for d in tags_dict if d['id'] in movie['tags']]
if action == 'update':
return {'radarrId': int(movie["id"]),
'title': movie["title"],
'path': movie["path"] + separator + movie['movieFile']['relativePath'],
'tmdbId': str(movie["tmdbId"]),
'poster': poster,
'fanart': fanart,
'audio_language': str(audio_language),
'sceneName': sceneName,
'monitored': str(bool(movie['monitored'])),
'year': str(movie['year']),
'sortTitle': movie['sortTitle'],
'alternativeTitles': alternativeTitles,
'format': format,
'resolution': resolution,
'video_codec': videoCodec,
'audio_codec': audioCodec,
'overview': overview,
'imdbId': imdbId,
'movie_file_id': int(movie['movieFile']['id']),
'tags': str(tags),
'file_size': movie['movieFile']['size']}
else:
return {'radarrId': int(movie["id"]),
'title': movie["title"],
'path': movie["path"] + separator + movie['movieFile']['relativePath'],
'tmdbId': str(movie["tmdbId"]),
'subtitles': '[]',
'overview': overview,
'poster': poster,
'fanart': fanart,
'audio_language': str(audio_language),
'sceneName': sceneName,
'monitored': str(bool(movie['monitored'])),
'sortTitle': movie['sortTitle'],
'year': str(movie['year']),
'alternativeTitles': alternativeTitles,
'format': format,
'resolution': resolution,
'video_codec': videoCodec,
'audio_codec': audioCodec,
'imdbId': imdbId,
'movie_file_id': int(movie['movieFile']['id']),
'tags': str(tags),
'profileId': movie_default_profile,
'file_size': movie['movieFile']['size']}
def get_movies_from_radarr_api(url, apikey_radarr, radarr_id=None):
if get_radarr_info.is_legacy():
url_radarr_api_movies = url + "/api/movie" + ("/{}".format(radarr_id) if radarr_id else "") + "?apikey=" + \
apikey_radarr
else:
url_radarr_api_movies = url + "/api/v3/movie" + ("/{}".format(radarr_id) if radarr_id else "") + "?apikey=" + \
apikey_radarr
try:
r = requests.get(url_radarr_api_movies, timeout=60, verify=False, headers=headers)
if r.status_code == 404:
return
r.raise_for_status()
except requests.exceptions.HTTPError as errh:
logging.exception("BAZARR Error trying to get movies from Radarr. Http error.")
return
except requests.exceptions.ConnectionError as errc:
logging.exception("BAZARR Error trying to get movies from Radarr. Connection Error.")
return
except requests.exceptions.Timeout as errt:
logging.exception("BAZARR Error trying to get movies from Radarr. Timeout Error.")
return
except requests.exceptions.RequestException as err:
logging.exception("BAZARR Error trying to get movies from Radarr.")
return
else:
return r.json()

@ -1,159 +1,8 @@
# coding=utf-8
import os
import requests
import logging
def get_series_rootfolder():
pass
from config import settings, url_sonarr, url_radarr
from helper import path_mappings
from database import TableShowsRootfolder, TableMoviesRootfolder, TableShows, TableMovies
from utils import get_sonarr_info, get_radarr_info
headers = {"User-Agent": os.environ["SZ_USER_AGENT"]}
def get_sonarr_rootfolder():
apikey_sonarr = settings.sonarr.apikey
sonarr_rootfolder = []
# Get root folder data from Sonarr
if get_sonarr_info.is_legacy():
url_sonarr_api_rootfolder = url_sonarr() + "/api/rootfolder?apikey=" + apikey_sonarr
else:
url_sonarr_api_rootfolder = url_sonarr() + "/api/v3/rootfolder?apikey=" + apikey_sonarr
try:
rootfolder = requests.get(url_sonarr_api_rootfolder, timeout=60, verify=False, headers=headers)
except requests.exceptions.ConnectionError:
logging.exception("BAZARR Error trying to get rootfolder from Sonarr. Connection Error.")
return []
except requests.exceptions.Timeout:
logging.exception("BAZARR Error trying to get rootfolder from Sonarr. Timeout Error.")
return []
except requests.exceptions.RequestException:
logging.exception("BAZARR Error trying to get rootfolder from Sonarr.")
return []
else:
sonarr_movies_paths = list(TableShows.select(TableShows.path).dicts())
for folder in rootfolder.json():
if any(item['path'].startswith(folder['path']) for item in sonarr_movies_paths):
sonarr_rootfolder.append({'id': folder['id'], 'path': folder['path']})
db_rootfolder = TableShowsRootfolder.select(TableShowsRootfolder.id, TableShowsRootfolder.path).dicts()
rootfolder_to_remove = [x for x in db_rootfolder if not
next((item for item in sonarr_rootfolder if item['id'] == x['id']), False)]
rootfolder_to_update = [x for x in sonarr_rootfolder if
next((item for item in db_rootfolder if item['id'] == x['id']), False)]
rootfolder_to_insert = [x for x in sonarr_rootfolder if not
next((item for item in db_rootfolder if item['id'] == x['id']), False)]
for item in rootfolder_to_remove:
TableShowsRootfolder.delete().where(TableShowsRootfolder.id == item['id']).execute()
for item in rootfolder_to_update:
TableShowsRootfolder.update({TableShowsRootfolder.path: item['path']})\
.where(TableShowsRootfolder.id == item['id'])\
.execute()
for item in rootfolder_to_insert:
TableShowsRootfolder.insert({TableShowsRootfolder.id: item['id'], TableShowsRootfolder.path: item['path']})\
.execute()
def check_sonarr_rootfolder():
get_sonarr_rootfolder()
rootfolder = TableShowsRootfolder.select(TableShowsRootfolder.id, TableShowsRootfolder.path).dicts()
for item in rootfolder:
root_path = item['path']
if not root_path.endswith(('/', '\\')):
if root_path.startswith('/'):
root_path += '/'
else:
root_path += '\\'
if not os.path.isdir(path_mappings.path_replace(root_path)):
TableShowsRootfolder.update({TableShowsRootfolder.accessible: 0,
TableShowsRootfolder.error: 'This Sonarr root directory does not seems to '
'be accessible by Bazarr. Please check path '
'mapping.'})\
.where(TableShowsRootfolder.id == item['id'])\
.execute()
elif not os.access(path_mappings.path_replace(root_path), os.W_OK):
TableShowsRootfolder.update({TableShowsRootfolder.accessible: 0,
TableShowsRootfolder.error: 'Bazarr cannot write to this directory.'}) \
.where(TableShowsRootfolder.id == item['id']) \
.execute()
else:
TableShowsRootfolder.update({TableShowsRootfolder.accessible: 1,
TableShowsRootfolder.error: ''}) \
.where(TableShowsRootfolder.id == item['id']) \
.execute()
def get_radarr_rootfolder():
apikey_radarr = settings.radarr.apikey
radarr_rootfolder = []
# Get root folder data from Radarr
if get_radarr_info.is_legacy():
url_radarr_api_rootfolder = url_radarr() + "/api/rootfolder?apikey=" + apikey_radarr
else:
url_radarr_api_rootfolder = url_radarr() + "/api/v3/rootfolder?apikey=" + apikey_radarr
try:
rootfolder = requests.get(url_radarr_api_rootfolder, timeout=60, verify=False, headers=headers)
except requests.exceptions.ConnectionError:
logging.exception("BAZARR Error trying to get rootfolder from Radarr. Connection Error.")
return []
except requests.exceptions.Timeout:
logging.exception("BAZARR Error trying to get rootfolder from Radarr. Timeout Error.")
return []
except requests.exceptions.RequestException:
logging.exception("BAZARR Error trying to get rootfolder from Radarr.")
return []
else:
radarr_movies_paths = list(TableMovies.select(TableMovies.path).dicts())
for folder in rootfolder.json():
if any(item['path'].startswith(folder['path']) for item in radarr_movies_paths):
radarr_rootfolder.append({'id': folder['id'], 'path': folder['path']})
db_rootfolder = TableMoviesRootfolder.select(TableMoviesRootfolder.id, TableMoviesRootfolder.path).dicts()
rootfolder_to_remove = [x for x in db_rootfolder if not
next((item for item in radarr_rootfolder if item['id'] == x['id']), False)]
rootfolder_to_update = [x for x in radarr_rootfolder if
next((item for item in db_rootfolder if item['id'] == x['id']), False)]
rootfolder_to_insert = [x for x in radarr_rootfolder if not
next((item for item in db_rootfolder if item['id'] == x['id']), False)]
for item in rootfolder_to_remove:
TableMoviesRootfolder.delete().where(TableMoviesRootfolder.id == item['id']).execute()
for item in rootfolder_to_update:
TableMoviesRootfolder.update({TableMoviesRootfolder.path: item['path']})\
.where(TableMoviesRootfolder.id == item['id']).execute()
for item in rootfolder_to_insert:
TableMoviesRootfolder.insert({TableMoviesRootfolder.id: item['id'],
TableMoviesRootfolder.path: item['path']}).execute()
def check_radarr_rootfolder():
get_radarr_rootfolder()
rootfolder = TableMoviesRootfolder.select(TableMoviesRootfolder.id, TableMoviesRootfolder.path).dicts()
for item in rootfolder:
root_path = item['path']
if not root_path.endswith(('/', '\\')):
if root_path.startswith('/'):
root_path += '/'
else:
root_path += '\\'
if not os.path.isdir(path_mappings.path_replace_movie(root_path)):
TableMoviesRootfolder.update({TableMoviesRootfolder.accessible: 0,
TableMoviesRootfolder.error: 'This Radarr root directory does not seems to '
'be accessible by Bazarr. Please check path '
'mapping.'}) \
.where(TableMoviesRootfolder.id == item['id']) \
.execute()
elif not os.access(path_mappings.path_replace_movie(root_path), os.W_OK):
TableMoviesRootfolder.update({TableMoviesRootfolder.accessible: 0,
TableMoviesRootfolder.error: 'Bazarr cannot write to this directory'}) \
.where(TableMoviesRootfolder.id == item['id']) \
.execute()
else:
TableMoviesRootfolder.update({TableMoviesRootfolder.accessible: 1,
TableMoviesRootfolder.error: ''}) \
.where(TableMoviesRootfolder.id == item['id']) \
.execute()
def get_movies_rootfolder():
pass

@ -1,346 +1 @@
# coding=utf-8
import os
import requests
import logging
from gevent import sleep
from peewee import DoesNotExist
from config import settings, url_sonarr
from list_subtitles import list_missing_subtitles
from get_rootfolder import check_sonarr_rootfolder
from database import TableShows, TableEpisodes
from get_episodes import sync_episodes
from utils import get_sonarr_info
from helper import path_mappings
from event_handler import event_stream, show_progress, hide_progress
headers = {"User-Agent": os.environ["SZ_USER_AGENT"]}
def update_series(send_event=True):
check_sonarr_rootfolder()
apikey_sonarr = settings.sonarr.apikey
if apikey_sonarr is None:
return
serie_default_enabled = settings.general.getboolean('serie_default_enabled')
if serie_default_enabled is True:
serie_default_profile = settings.general.serie_default_profile
if serie_default_profile == '':
serie_default_profile = None
else:
serie_default_profile = None
audio_profiles = get_profile_list()
tagsDict = get_tags()
# Get shows data from Sonarr
series = get_series_from_sonarr_api(url=url_sonarr(), apikey_sonarr=apikey_sonarr)
if not series:
return
else:
# Get current shows in DB
current_shows_db = TableShows.select(TableShows.sonarrSeriesId).dicts()
current_shows_db_list = [x['sonarrSeriesId'] for x in current_shows_db]
current_shows_sonarr = []
series_to_update = []
series_to_add = []
series_count = len(series)
for i, show in enumerate(series):
sleep()
if send_event:
show_progress(id='series_progress',
header='Syncing series...',
name=show['title'],
value=i,
count=series_count)
# Add shows in Sonarr to current shows list
current_shows_sonarr.append(show['id'])
if show['id'] in current_shows_db_list:
series_to_update.append(seriesParser(show, action='update', tags_dict=tagsDict,
serie_default_profile=serie_default_profile,
audio_profiles=audio_profiles))
else:
series_to_add.append(seriesParser(show, action='insert', tags_dict=tagsDict,
serie_default_profile=serie_default_profile,
audio_profiles=audio_profiles))
if send_event:
hide_progress(id='series_progress')
# Remove old series from DB
removed_series = list(set(current_shows_db_list) - set(current_shows_sonarr))
for series in removed_series:
sleep()
TableShows.delete().where(TableShows.sonarrSeriesId == series).execute()
if send_event:
event_stream(type='series', action='delete', payload=series)
# Update existing series in DB
series_in_db_list = []
series_in_db = TableShows.select(TableShows.title,
TableShows.path,
TableShows.tvdbId,
TableShows.sonarrSeriesId,
TableShows.overview,
TableShows.poster,
TableShows.fanart,
TableShows.audio_language,
TableShows.sortTitle,
TableShows.year,
TableShows.alternateTitles,
TableShows.tags,
TableShows.seriesType,
TableShows.imdbId).dicts()
for item in series_in_db:
series_in_db_list.append(item)
series_to_update_list = [i for i in series_to_update if i not in series_in_db_list]
for updated_series in series_to_update_list:
sleep()
TableShows.update(updated_series).where(TableShows.sonarrSeriesId ==
updated_series['sonarrSeriesId']).execute()
if send_event:
event_stream(type='series', payload=updated_series['sonarrSeriesId'])
# Insert new series in DB
for added_series in series_to_add:
sleep()
result = TableShows.insert(added_series).on_conflict(action='IGNORE').execute()
if result:
list_missing_subtitles(no=added_series['sonarrSeriesId'])
else:
logging.debug('BAZARR unable to insert this series into the database:',
path_mappings.path_replace(added_series['path']))
if send_event:
event_stream(type='series', action='update', payload=added_series['sonarrSeriesId'])
logging.debug('BAZARR All series synced from Sonarr into database.')
def update_one_series(series_id, action):
logging.debug('BAZARR syncing this specific series from Sonarr: {}'.format(series_id))
# Check if there's a row in database for this series ID
try:
existing_series = TableShows.select(TableShows.path)\
.where(TableShows.sonarrSeriesId == series_id)\
.dicts()\
.get()
except DoesNotExist:
existing_series = None
# Delete series from DB
if action == 'deleted' and existing_series:
TableShows.delete().where(TableShows.sonarrSeriesId == int(series_id)).execute()
TableEpisodes.delete().where(TableEpisodes.sonarrSeriesId == int(series_id)).execute()
event_stream(type='series', action='delete', payload=int(series_id))
return
serie_default_enabled = settings.general.getboolean('serie_default_enabled')
if serie_default_enabled is True:
serie_default_profile = settings.general.serie_default_profile
if serie_default_profile == '':
serie_default_profile = None
else:
serie_default_profile = None
audio_profiles = get_profile_list()
tagsDict = get_tags()
try:
# Get series data from sonarr api
series = None
series_data = get_series_from_sonarr_api(url=url_sonarr(), apikey_sonarr=settings.sonarr.apikey,
sonarr_series_id=int(series_id))
if not series_data:
return
else:
if action == 'updated' and existing_series:
series = seriesParser(series_data, action='update', tags_dict=tagsDict,
serie_default_profile=serie_default_profile,
audio_profiles=audio_profiles)
elif action == 'updated' and not existing_series:
series = seriesParser(series_data, action='insert', tags_dict=tagsDict,
serie_default_profile=serie_default_profile,
audio_profiles=audio_profiles)
except Exception:
logging.debug('BAZARR cannot parse series returned by SignalR feed.')
return
# Update existing series in DB
if action == 'updated' and existing_series:
TableShows.update(series).where(TableShows.sonarrSeriesId == series['sonarrSeriesId']).execute()
sync_episodes(series_id=int(series_id), send_event=True)
event_stream(type='series', action='update', payload=int(series_id))
logging.debug('BAZARR updated this series into the database:{}'.format(path_mappings.path_replace(
series['path'])))
# Insert new series in DB
elif action == 'updated' and not existing_series:
TableShows.insert(series).on_conflict(action='IGNORE').execute()
event_stream(type='series', action='update', payload=int(series_id))
logging.debug('BAZARR inserted this series into the database:{}'.format(path_mappings.path_replace(
series['path'])))
def get_profile_list():
apikey_sonarr = settings.sonarr.apikey
profiles_list = []
# Get profiles data from Sonarr
if get_sonarr_info.is_legacy():
url_sonarr_api_series = url_sonarr() + "/api/profile?apikey=" + apikey_sonarr
else:
url_sonarr_api_series = url_sonarr() + "/api/v3/languageprofile?apikey=" + apikey_sonarr
try:
profiles_json = requests.get(url_sonarr_api_series, timeout=60, verify=False, headers=headers)
except requests.exceptions.ConnectionError:
logging.exception("BAZARR Error trying to get profiles from Sonarr. Connection Error.")
return None
except requests.exceptions.Timeout:
logging.exception("BAZARR Error trying to get profiles from Sonarr. Timeout Error.")
return None
except requests.exceptions.RequestException:
logging.exception("BAZARR Error trying to get profiles from Sonarr.")
return None
# Parsing data returned from Sonarr
if get_sonarr_info.is_legacy():
for profile in profiles_json.json():
profiles_list.append([profile['id'], profile['language'].capitalize()])
else:
for profile in profiles_json.json():
profiles_list.append([profile['id'], profile['name'].capitalize()])
return profiles_list
def profile_id_to_language(id_, profiles):
profiles_to_return = []
for profile in profiles:
if id_ == profile[0]:
profiles_to_return.append(profile[1])
return profiles_to_return
def get_tags():
apikey_sonarr = settings.sonarr.apikey
tagsDict = []
# Get tags data from Sonarr
if get_sonarr_info.is_legacy():
url_sonarr_api_series = url_sonarr() + "/api/tag?apikey=" + apikey_sonarr
else:
url_sonarr_api_series = url_sonarr() + "/api/v3/tag?apikey=" + apikey_sonarr
try:
tagsDict = requests.get(url_sonarr_api_series, timeout=60, verify=False, headers=headers)
except requests.exceptions.ConnectionError:
logging.exception("BAZARR Error trying to get tags from Sonarr. Connection Error.")
return []
except requests.exceptions.Timeout:
logging.exception("BAZARR Error trying to get tags from Sonarr. Timeout Error.")
return []
except requests.exceptions.RequestException:
logging.exception("BAZARR Error trying to get tags from Sonarr.")
return []
else:
return tagsDict.json()
def seriesParser(show, action, tags_dict, serie_default_profile, audio_profiles):
overview = show['overview'] if 'overview' in show else ''
poster = ''
fanart = ''
for image in show['images']:
if image['coverType'] == 'poster':
poster_big = image['url'].split('?')[0]
poster = os.path.splitext(poster_big)[0] + '-250' + os.path.splitext(poster_big)[1]
if image['coverType'] == 'fanart':
fanart = image['url'].split('?')[0]
alternate_titles = None
if show['alternateTitles'] is not None:
alternate_titles = str([item['title'] for item in show['alternateTitles']])
audio_language = []
if get_sonarr_info.is_legacy():
audio_language = profile_id_to_language(show['qualityProfileId'], audio_profiles)
else:
audio_language = profile_id_to_language(show['languageProfileId'], audio_profiles)
tags = [d['label'] for d in tags_dict if d['id'] in show['tags']]
imdbId = show['imdbId'] if 'imdbId' in show else None
if action == 'update':
return {'title': show["title"],
'path': show["path"],
'tvdbId': int(show["tvdbId"]),
'sonarrSeriesId': int(show["id"]),
'overview': overview,
'poster': poster,
'fanart': fanart,
'audio_language': str(audio_language),
'sortTitle': show['sortTitle'],
'year': str(show['year']),
'alternateTitles': alternate_titles,
'tags': str(tags),
'seriesType': show['seriesType'],
'imdbId': imdbId}
else:
return {'title': show["title"],
'path': show["path"],
'tvdbId': show["tvdbId"],
'sonarrSeriesId': show["id"],
'overview': overview,
'poster': poster,
'fanart': fanart,
'audio_language': str(audio_language),
'sortTitle': show['sortTitle'],
'year': str(show['year']),
'alternateTitles': alternate_titles,
'tags': str(tags),
'seriesType': show['seriesType'],
'imdbId': imdbId,
'profileId': serie_default_profile}
def get_series_from_sonarr_api(url, apikey_sonarr, sonarr_series_id=None):
url_sonarr_api_series = url + "/api/{0}series/{1}?apikey={2}".format(
'' if get_sonarr_info.is_legacy() else 'v3/', sonarr_series_id if sonarr_series_id else "", apikey_sonarr)
try:
r = requests.get(url_sonarr_api_series, timeout=60, verify=False, headers=headers)
r.raise_for_status()
except requests.exceptions.HTTPError as e:
if e.response.status_code:
raise requests.exceptions.HTTPError
logging.exception("BAZARR Error trying to get series from Sonarr. Http error.")
return
except requests.exceptions.ConnectionError:
logging.exception("BAZARR Error trying to get series from Sonarr. Connection Error.")
return
except requests.exceptions.Timeout:
logging.exception("BAZARR Error trying to get series from Sonarr. Timeout Error.")
return
except requests.exceptions.RequestException:
logging.exception("BAZARR Error trying to get series from Sonarr.")
return
else:
return r.json()

@ -26,16 +26,16 @@ from subliminal_patch.subtitle import Subtitle
from get_languages import language_from_alpha3, alpha2_from_alpha3, alpha3_from_alpha2, language_from_alpha2, \
alpha2_from_language, alpha3_from_language
from config import settings, get_array_from
from helper import path_mappings, pp_replace, get_target_folder, force_unicode
from helper import pp_replace, get_target_folder, force_unicode
from list_subtitles import store_subtitles, list_missing_subtitles, store_subtitles_movie, list_missing_subtitles_movies
from utils import history_log, history_log_movie, get_binary, get_blacklist, notify_sonarr, notify_radarr
from utils import history_log, history_log_movie, get_binary, get_blacklist
from notifier import send_notifications, send_notifications_movie
from get_providers import get_providers, get_providers_auth, provider_throttle, provider_pool
from knowit import api
from subsyncer import subsync
from guessit import guessit
from custom_lang import CustomLanguage
from database import dict_mapper, get_exclusion_clause, get_profiles_list, get_audio_profile_languages, \
from database import get_exclusion_clause, get_profiles_list, get_audio_profile_languages, \
get_desired_languages, TableShows, TableEpisodes, TableMovies, TableHistory, TableHistoryMovie
from event_handler import event_stream, show_progress, hide_progress
from embedded_subs_reader import parse_video_metadata
@ -221,29 +221,29 @@ def download_subtitle(path, language, audio_language, hi, forced, providers, pro
downloaded_provider + " with a score of " + str(percent_score) + "%."
if media_type == 'series':
episode_metadata = TableEpisodes.select(TableEpisodes.sonarrSeriesId,
TableEpisodes.sonarrEpisodeId)\
.where(TableEpisodes.path == path_mappings.path_replace_reverse(path))\
episode_metadata = TableEpisodes.select(TableEpisodes.seriesId,
TableEpisodes.episodeId)\
.where(TableEpisodes.path == path)\
.dicts()\
.get()
series_id = episode_metadata['sonarrSeriesId']
episode_id = episode_metadata['sonarrEpisodeId']
series_id = episode_metadata['seriesId']
episode_id = episode_metadata['episodeId']
sync_subtitles(video_path=path, srt_path=downloaded_path,
srt_lang=downloaded_language_code2, media_type=media_type,
percent_score=percent_score,
sonarr_series_id=episode_metadata['sonarrSeriesId'],
sonarr_episode_id=episode_metadata['sonarrEpisodeId'])
series_id=episode_metadata['seriesId'],
episode_id=episode_metadata['episodeId'])
else:
movie_metadata = TableMovies.select(TableMovies.radarrId)\
.where(TableMovies.path == path_mappings.path_replace_reverse_movie(path))\
movie_metadata = TableMovies.select(TableMovies.movieId)\
.where(TableMovies.path == path)\
.dicts()\
.get()
series_id = ""
episode_id = movie_metadata['radarrId']
episode_id = movie_metadata['movieId']
sync_subtitles(video_path=path, srt_path=downloaded_path,
srt_lang=downloaded_language_code2, media_type=media_type,
percent_score=percent_score,
radarr_id=movie_metadata['radarrId'])
movie_id=movie_metadata['movieId'])
if use_postprocessing is True:
command = pp_replace(postprocessing_cmd, path, downloaded_path, downloaded_language,
@ -268,21 +268,15 @@ def download_subtitle(path, language, audio_language, hi, forced, providers, pro
# fixme: support multiple languages at once
if media_type == 'series':
reversed_path = path_mappings.path_replace_reverse(path)
reversed_subtitles_path = path_mappings.path_replace_reverse(downloaded_path)
notify_sonarr(episode_metadata['sonarrSeriesId'])
event_stream(type='episode-wanted', action='delete', payload=episode_metadata['sonarrEpisodeId'])
event_stream(type='episode-wanted', action='delete', payload=episode_metadata['episodeId'])
else:
reversed_path = path_mappings.path_replace_reverse_movie(path)
reversed_subtitles_path = path_mappings.path_replace_reverse_movie(downloaded_path)
notify_radarr(movie_metadata['radarrId'])
event_stream(type='movie-wanted', action='delete', payload=movie_metadata['radarrId'])
event_stream(type='movie-wanted', action='delete', payload=movie_metadata['movieId'])
track_event(category=downloaded_provider, action=action, label=downloaded_language)
return message, reversed_path, downloaded_language_code2, downloaded_provider, subtitle.score, \
subtitle.language.forced, subtitle.id, reversed_subtitles_path, subtitle.language.hi
return message, path, downloaded_language_code2, downloaded_provider, subtitle.score, \
subtitle.language.forced, subtitle.id, downloaded_path, subtitle.language.hi
if not saved_any:
logging.debug('BAZARR No Subtitles were found for this file: ' + path)
@ -542,28 +536,28 @@ def manual_download_subtitle(path, language, audio_language, hi, forced, subtitl
downloaded_provider + " with a score of " + str(score) + "% using manual search."
if media_type == 'series':
episode_metadata = TableEpisodes.select(TableEpisodes.sonarrSeriesId,
TableEpisodes.sonarrEpisodeId)\
.where(TableEpisodes.path == path_mappings.path_replace_reverse(path))\
episode_metadata = TableEpisodes.select(TableEpisodes.seriesId,
TableEpisodes.episodeId)\
.where(TableEpisodes.path == path)\
.dicts()\
.get()
series_id = episode_metadata['sonarrSeriesId']
episode_id = episode_metadata['sonarrEpisodeId']
series_id = episode_metadata['seriesId']
episode_id = episode_metadata['episodeId']
sync_subtitles(video_path=path, srt_path=downloaded_path,
srt_lang=downloaded_language_code2, media_type=media_type,
percent_score=score,
sonarr_series_id=episode_metadata['sonarrSeriesId'],
sonarr_episode_id=episode_metadata['sonarrEpisodeId'])
series_id=episode_metadata['seriesId'],
episode_id=episode_metadata['episodeId'])
else:
movie_metadata = TableMovies.select(TableMovies.radarrId)\
.where(TableMovies.path == path_mappings.path_replace_reverse_movie(path))\
movie_metadata = TableMovies.select(TableMovies.movieId)\
.where(TableMovies.path == path)\
.dicts()\
.get()
series_id = ""
episode_id = movie_metadata['radarrId']
episode_id = movie_metadata['movieId']
sync_subtitles(video_path=path, srt_path=downloaded_path,
srt_lang=downloaded_language_code2, media_type=media_type,
percent_score=score, radarr_id=movie_metadata['radarrId'])
percent_score=score, movie_id=movie_metadata['movieId'])
if use_postprocessing:
percent_score = round(subtitle.score * 100 / max_score, 2)
@ -587,20 +581,11 @@ def manual_download_subtitle(path, language, audio_language, hi, forced, subtitl
logging.debug("BAZARR post-processing skipped because subtitles score isn't below this "
"threshold value: " + pp_threshold + "%")
if media_type == 'series':
reversed_path = path_mappings.path_replace_reverse(path)
reversed_subtitles_path = path_mappings.path_replace_reverse(downloaded_path)
notify_sonarr(episode_metadata['sonarrSeriesId'])
else:
reversed_path = path_mappings.path_replace_reverse_movie(path)
reversed_subtitles_path = path_mappings.path_replace_reverse_movie(downloaded_path)
notify_radarr(movie_metadata['radarrId'])
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, subtitle.id, reversed_subtitles_path, subtitle.language.hi
return message, path, downloaded_language_code2, downloaded_provider, subtitle.score, \
subtitle.language.forced, subtitle.id, downloaded_path, subtitle.language.hi
else:
logging.error(
"BAZARR Tried to manually download a Subtitles for file: " + path + " but we weren't able to do (probably throttled by " + str(
@ -687,24 +672,24 @@ def manual_upload_subtitle(path, language, forced, hi, title, scene_name, media_
audio_language_code3 = alpha3_from_language(audio_language)
if media_type == 'series':
episode_metadata = TableEpisodes.select(TableEpisodes.sonarrSeriesId, TableEpisodes.sonarrEpisodeId)\
.where(TableEpisodes.path == path_mappings.path_replace_reverse(path))\
episode_metadata = TableEpisodes.select(TableEpisodes.seriesId, TableEpisodes.episodeId)\
.where(TableEpisodes.path == path)\
.dicts()\
.get()
series_id = episode_metadata['sonarrSeriesId']
episode_id = episode_metadata['sonarrEpisodeId']
series_id = episode_metadata['seriesId']
episode_id = episode_metadata['episodeId']
sync_subtitles(video_path=path, srt_path=subtitle_path, srt_lang=uploaded_language_code2, media_type=media_type,
percent_score=100, sonarr_series_id=episode_metadata['sonarrSeriesId'],
sonarr_episode_id=episode_metadata['sonarrEpisodeId'])
percent_score=100, series_id=episode_metadata['seriesId'],
episode_id=episode_metadata['episodeId'])
else:
movie_metadata = TableMovies.select(TableMovies.radarrId)\
.where(TableMovies.path == path_mappings.path_replace_reverse_movie(path))\
movie_metadata = TableMovies.select(TableMovies.movieId)\
.where(TableMovies.path == path)\
.dicts()\
.get()
series_id = ""
episode_id = movie_metadata['radarrId']
episode_id = movie_metadata['movieId']
sync_subtitles(video_path=path, srt_path=subtitle_path, srt_lang=uploaded_language_code2, media_type=media_type,
percent_score=100, radarr_id=movie_metadata['radarrId'])
percent_score=100, movie_id=movie_metadata['movieId'])
if use_postprocessing :
command = pp_replace(postprocessing_cmd, path, subtitle_path, uploaded_language,
@ -713,26 +698,17 @@ def manual_upload_subtitle(path, language, forced, hi, title, scene_name, media_
episode_id, hi=hi)
postprocessing(command, path)
if media_type == 'series':
reversed_path = path_mappings.path_replace_reverse(path)
reversed_subtitles_path = path_mappings.path_replace_reverse(subtitle_path)
notify_sonarr(episode_metadata['sonarrSeriesId'])
else:
reversed_path = path_mappings.path_replace_reverse_movie(path)
reversed_subtitles_path = path_mappings.path_replace_reverse_movie(subtitle_path)
notify_radarr(movie_metadata['radarrId'])
return message, reversed_path, reversed_subtitles_path
return message, path, subtitles_path
def series_download_subtitles(no):
conditions = [(TableEpisodes.sonarrSeriesId == no),
conditions = [(TableEpisodes.seriesId == no),
(TableEpisodes.missing_subtitles != '[]')]
conditions += get_exclusion_clause('series')
episodes_details = TableEpisodes.select(TableEpisodes.path,
TableEpisodes.missing_subtitles,
TableEpisodes.monitored,
TableEpisodes.sonarrEpisodeId,
TableEpisodes.episodeId,
TableEpisodes.scene_name,
TableShows.tags,
TableShows.seriesType,
@ -741,11 +717,11 @@ def series_download_subtitles(no):
TableEpisodes.season,
TableEpisodes.episode,
TableEpisodes.title.alias('episodeTitle'))\
.join(TableShows, on=(TableEpisodes.sonarrSeriesId == TableShows.sonarrSeriesId))\
.join(TableShows, on=(TableEpisodes.seriesId == TableShows.seriesId))\
.where(reduce(operator.and_, conditions))\
.dicts()
if not episodes_details:
logging.debug("BAZARR no episode for that sonarrSeriesId have been found in database or they have all been "
logging.debug("BAZARR no episode for that seriesId have been found in database or they have all been "
"ignored because of monitored status, series type or series tags: {}".format(no))
return
@ -768,20 +744,20 @@ def series_download_subtitles(no):
for language in ast.literal_eval(episode['missing_subtitles']):
# confirm if language is still missing or if cutoff have been reached
confirmed_missing_subs = TableEpisodes.select(TableEpisodes.missing_subtitles) \
.where(TableEpisodes.sonarrEpisodeId == episode['sonarrEpisodeId']) \
.where(TableEpisodes.episodeId == episode['episodeId']) \
.dicts() \
.get()
if language not in ast.literal_eval(confirmed_missing_subs['missing_subtitles']):
continue
if language is not None:
audio_language_list = get_audio_profile_languages(episode_id=episode['sonarrEpisodeId'])
audio_language_list = get_audio_profile_languages(episode_id=episode['episodeId'])
if len(audio_language_list) > 0:
audio_language = audio_language_list[0]['name']
else:
audio_language = 'None'
result = download_subtitle(path_mappings.path_replace(episode['path']),
result = download_subtitle(episode['path'],
language.split(':')[0],
audio_language,
"True" if language.endswith(':hi') else "False",
@ -805,10 +781,10 @@ def series_download_subtitles(no):
score = result[4]
subs_id = result[6]
subs_path = result[7]
store_subtitles(episode['path'], path_mappings.path_replace(episode['path']))
history_log(1, no, episode['sonarrEpisodeId'], message, path, language_code, provider, score,
store_subtitles(episode['path'])
history_log(1, no, episode['episodeId'], message, path, language_code, provider, score,
subs_id, subs_path)
send_notifications(no, episode['sonarrEpisodeId'], message)
send_notifications(no, episode['episodeId'], message)
else:
logging.info("BAZARR All providers are throttled")
break
@ -817,26 +793,26 @@ def series_download_subtitles(no):
def episode_download_subtitles(no, send_progress=False):
conditions = [(TableEpisodes.sonarrEpisodeId == no)]
conditions = [(TableEpisodes.episodeId == no)]
conditions += get_exclusion_clause('series')
episodes_details = TableEpisodes.select(TableEpisodes.path,
TableEpisodes.missing_subtitles,
TableEpisodes.monitored,
TableEpisodes.sonarrEpisodeId,
TableEpisodes.episodeId,
TableEpisodes.scene_name,
TableShows.tags,
TableShows.title,
TableShows.sonarrSeriesId,
TableShows.seriesId,
TableEpisodes.audio_language,
TableShows.seriesType,
TableEpisodes.title.alias('episodeTitle'),
TableEpisodes.season,
TableEpisodes.episode)\
.join(TableShows, on=(TableEpisodes.sonarrSeriesId == TableShows.sonarrSeriesId))\
.join(TableShows, on=(TableEpisodes.seriesId == TableShows.seriesId))\
.where(reduce(operator.and_, conditions))\
.dicts()
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 episodeId can be found in database:", str(no))
return
providers_list = get_providers()
@ -856,20 +832,20 @@ def episode_download_subtitles(no, send_progress=False):
for language in ast.literal_eval(episode['missing_subtitles']):
# confirm if language is still missing or if cutoff have been reached
confirmed_missing_subs = TableEpisodes.select(TableEpisodes.missing_subtitles) \
.where(TableEpisodes.sonarrEpisodeId == episode['sonarrEpisodeId']) \
.where(TableEpisodes.episodeId == episode['episodeId']) \
.dicts() \
.get()
if language not in ast.literal_eval(confirmed_missing_subs['missing_subtitles']):
continue
if language is not None:
audio_language_list = get_audio_profile_languages(episode_id=episode['sonarrEpisodeId'])
audio_language_list = get_audio_profile_languages(episode_id=episode['episodeId'])
if len(audio_language_list) > 0:
audio_language = audio_language_list[0]['name']
else:
audio_language = 'None'
result = download_subtitle(path_mappings.path_replace(episode['path']),
result = download_subtitle(episode['path'],
language.split(':')[0],
audio_language,
"True" if language.endswith(':hi') else "False",
@ -893,10 +869,10 @@ def episode_download_subtitles(no, send_progress=False):
score = result[4]
subs_id = result[6]
subs_path = result[7]
store_subtitles(episode['path'], path_mappings.path_replace(episode['path']))
history_log(1, episode['sonarrSeriesId'], episode['sonarrEpisodeId'], message, path,
store_subtitles(episode['path'])
history_log(1, episode['seriesId'], episode['episodeId'], message, path,
language_code, provider, score, subs_id, subs_path)
send_notifications(episode['sonarrSeriesId'], episode['sonarrEpisodeId'], message)
send_notifications(episode['seriesId'], episode['episodeId'], message)
if send_progress:
hide_progress(id='episode_search_progress_{}'.format(no))
else:
@ -905,12 +881,12 @@ def episode_download_subtitles(no, send_progress=False):
def movies_download_subtitles(no):
conditions = [(TableMovies.radarrId == no)]
conditions = [(TableMovies.movieId == no)]
conditions += get_exclusion_clause('movie')
movies = TableMovies.select(TableMovies.path,
TableMovies.missing_subtitles,
TableMovies.audio_language,
TableMovies.radarrId,
TableMovies.movieId,
TableMovies.sceneName,
TableMovies.title,
TableMovies.tags,
@ -918,7 +894,7 @@ def movies_download_subtitles(no):
.where(reduce(operator.and_, conditions))\
.dicts()
if not len(movies):
logging.debug("BAZARR no movie with that radarrId can be found in database:", str(no))
logging.debug("BAZARR no movie with that movieId can be found in database:", str(no))
return
else:
movie = movies[0]
@ -934,7 +910,7 @@ def movies_download_subtitles(no):
for i, language in enumerate(ast.literal_eval(movie['missing_subtitles'])):
# confirm if language is still missing or if cutoff have been reached
confirmed_missing_subs = TableMovies.select(TableMovies.missing_subtitles)\
.where(TableMovies.radarrId == movie['radarrId'])\
.where(TableMovies.movieId == movie['movieId'])\
.dicts()\
.get()
if language not in ast.literal_eval(confirmed_missing_subs['missing_subtitles']):
@ -948,13 +924,13 @@ def movies_download_subtitles(no):
count=count_movie)
if language is not None:
audio_language_list = get_audio_profile_languages(movie_id=movie['radarrId'])
audio_language_list = get_audio_profile_languages(movie_id=movie['movieId'])
if len(audio_language_list) > 0:
audio_language = audio_language_list[0]['name']
else:
audio_language = 'None'
result = download_subtitle(path_mappings.path_replace_movie(movie['path']),
result = download_subtitle(movie['path'],
language.split(':')[0],
audio_language,
"True" if language.endswith(':hi') else "False",
@ -978,7 +954,7 @@ def movies_download_subtitles(no):
score = result[4]
subs_id = result[6]
subs_path = result[7]
store_subtitles_movie(movie['path'], path_mappings.path_replace_movie(movie['path']))
store_subtitles_movie(movie['path'])
history_log_movie(1, no, message, path, language_code, provider, score, subs_id, subs_path)
send_notifications_movie(no, message)
else:
@ -988,17 +964,17 @@ def movies_download_subtitles(no):
hide_progress(id='movie_search_progress_{}'.format(no))
def wanted_download_subtitles(sonarr_episode_id):
def wanted_download_subtitles(episode_id):
episodes_details = TableEpisodes.select(TableEpisodes.path,
TableEpisodes.missing_subtitles,
TableEpisodes.sonarrEpisodeId,
TableEpisodes.sonarrSeriesId,
TableEpisodes.episodeId,
TableEpisodes.seriesId,
TableEpisodes.audio_language,
TableEpisodes.scene_name,
TableEpisodes.failedAttempts,
TableShows.title)\
.join(TableShows, on=(TableEpisodes.sonarrSeriesId == TableShows.sonarrSeriesId))\
.where((TableEpisodes.sonarrEpisodeId == sonarr_episode_id))\
.join(TableShows, on=(TableEpisodes.seriesId == TableShows.seriesId))\
.where((TableEpisodes.episodeId == episode_id))\
.dicts()
episodes_details = list(episodes_details)
@ -1012,7 +988,7 @@ def wanted_download_subtitles(sonarr_episode_id):
for language in ast.literal_eval(episode['missing_subtitles']):
# confirm if language is still missing or if cutoff have been reached
confirmed_missing_subs = TableEpisodes.select(TableEpisodes.missing_subtitles) \
.where(TableEpisodes.sonarrEpisodeId == episode['sonarrEpisodeId']) \
.where(TableEpisodes.episodeId == episode['episodeId']) \
.dicts() \
.get()
if language not in ast.literal_eval(confirmed_missing_subs['missing_subtitles']):
@ -1027,19 +1003,19 @@ def wanted_download_subtitles(sonarr_episode_id):
attempt.append([language, time.time()])
TableEpisodes.update({TableEpisodes.failedAttempts: str(attempt)})\
.where(TableEpisodes.sonarrEpisodeId == episode['sonarrEpisodeId'])\
.where(TableEpisodes.episodeId == episode['episodeId'])\
.execute()
for i in range(len(attempt)):
if attempt[i][0] == language:
if search_active(attempt[i][1]):
audio_language_list = get_audio_profile_languages(episode_id=episode['sonarrEpisodeId'])
audio_language_list = get_audio_profile_languages(episode_id=episode['episodeId'])
if len(audio_language_list) > 0:
audio_language = audio_language_list[0]['name']
else:
audio_language = 'None'
result = download_subtitle(path_mappings.path_replace(episode['path']),
result = download_subtitle(episode['path'],
language.split(':')[0],
audio_language,
"True" if language.endswith(':hi') else "False",
@ -1063,26 +1039,26 @@ def wanted_download_subtitles(sonarr_episode_id):
score = result[4]
subs_id = result[6]
subs_path = result[7]
store_subtitles(episode['path'], path_mappings.path_replace(episode['path']))
history_log(1, episode['sonarrSeriesId'], episode['sonarrEpisodeId'], message, path,
store_subtitles(episode['path'])
history_log(1, episode['seriesId'], episode['episodeId'], message, path,
language_code, provider, score, subs_id, subs_path)
event_stream(type='episode-wanted', action='delete', payload=episode['sonarrEpisodeId'])
send_notifications(episode['sonarrSeriesId'], episode['sonarrEpisodeId'], message)
event_stream(type='episode-wanted', action='delete', payload=episode['episodeId'])
send_notifications(episode['seriesId'], episode['episodeId'], message)
else:
logging.debug(
'BAZARR Search is not active for episode ' + episode['path'] + ' Language: ' + attempt[i][
0])
def wanted_download_subtitles_movie(radarr_id):
def wanted_download_subtitles_movie(movie_id):
movies_details = TableMovies.select(TableMovies.path,
TableMovies.missing_subtitles,
TableMovies.radarrId,
TableMovies.movieId,
TableMovies.audio_language,
TableMovies.sceneName,
TableMovies.failedAttempts,
TableMovies.title)\
.where((TableMovies.radarrId == radarr_id))\
.where((TableMovies.movieId == movie_id))\
.dicts()
movies_details = list(movies_details)
@ -1096,7 +1072,7 @@ def wanted_download_subtitles_movie(radarr_id):
for language in ast.literal_eval(movie['missing_subtitles']):
# confirm if language is still missing or if cutoff have been reached
confirmed_missing_subs = TableMovies.select(TableMovies.missing_subtitles) \
.where(TableMovies.radarrId == movie['radarrId']) \
.where(TableMovies.movieId == movie['movieId']) \
.dicts() \
.get()
if language not in ast.literal_eval(confirmed_missing_subs['missing_subtitles']):
@ -1111,19 +1087,19 @@ def wanted_download_subtitles_movie(radarr_id):
attempt.append([language, time.time()])
TableMovies.update({TableMovies.failedAttempts: str(attempt)})\
.where(TableMovies.radarrId == movie['radarrId'])\
.where(TableMovies.movieId == movie['movieId'])\
.execute()
for i in range(len(attempt)):
if attempt[i][0] == language:
if search_active(attempt[i][1]) is True:
audio_language_list = get_audio_profile_languages(movie_id=movie['radarrId'])
audio_language_list = get_audio_profile_languages(movie_id=movie['movieId'])
if len(audio_language_list) > 0:
audio_language = audio_language_list[0]['name']
else:
audio_language = 'None'
result = download_subtitle(path_mappings.path_replace_movie(movie['path']),
result = download_subtitle(movie['path'],
language.split(':')[0],
audio_language,
"True" if language.endswith(':hi') else "False",
@ -1147,11 +1123,11 @@ def wanted_download_subtitles_movie(radarr_id):
score = result[4]
subs_id = result[6]
subs_path = result[7]
store_subtitles_movie(movie['path'], path_mappings.path_replace_movie(movie['path']))
history_log_movie(1, movie['radarrId'], message, path, language_code, provider, score,
store_subtitles_movie(movie['path'])
history_log_movie(1, movie['movieId'], message, path, language_code, provider, score,
subs_id, subs_path)
event_stream(type='movie-wanted', action='delete', payload=movie['radarrId'])
send_notifications_movie(movie['radarrId'], message)
event_stream(type='movie-wanted', action='delete', payload=movie['movieId'])
send_notifications_movie(movie['movieId'], message)
else:
logging.info(
'BAZARR Search is not active for this Movie ' + movie['path'] + ' Language: ' + attempt[i][
@ -1161,8 +1137,8 @@ def wanted_download_subtitles_movie(radarr_id):
def wanted_search_missing_subtitles_series():
conditions = [(TableEpisodes.missing_subtitles != '[]')]
conditions += get_exclusion_clause('series')
episodes = TableEpisodes.select(TableEpisodes.sonarrSeriesId,
TableEpisodes.sonarrEpisodeId,
episodes = TableEpisodes.select(TableEpisodes.seriesId,
TableEpisodes.episodeId,
TableShows.tags,
TableEpisodes.monitored,
TableShows.title,
@ -1170,7 +1146,7 @@ def wanted_search_missing_subtitles_series():
TableEpisodes.episode,
TableEpisodes.title.alias('episodeTitle'),
TableShows.seriesType)\
.join(TableShows, on=(TableEpisodes.sonarrSeriesId == TableShows.sonarrSeriesId))\
.join(TableShows, on=(TableEpisodes.seriesId == TableShows.seriesId))\
.where(reduce(operator.and_, conditions))\
.dicts()
episodes = list(episodes)
@ -1188,7 +1164,7 @@ def wanted_search_missing_subtitles_series():
providers = get_providers()
if providers:
wanted_download_subtitles(episode['sonarrEpisodeId'])
wanted_download_subtitles(episode['episodeId'])
else:
logging.info("BAZARR All providers are throttled")
return
@ -1201,7 +1177,7 @@ def wanted_search_missing_subtitles_series():
def wanted_search_missing_subtitles_movies():
conditions = [(TableMovies.missing_subtitles != '[]')]
conditions += get_exclusion_clause('movie')
movies = TableMovies.select(TableMovies.radarrId,
movies = TableMovies.select(TableMovies.movieId,
TableMovies.tags,
TableMovies.monitored,
TableMovies.title)\
@ -1219,7 +1195,7 @@ def wanted_search_missing_subtitles_movies():
providers = get_providers()
if providers:
wanted_download_subtitles_movie(movie['radarrId'])
wanted_download_subtitles_movie(movie['movieId'])
else:
logging.info("BAZARR All providers are throttled")
return
@ -1270,8 +1246,8 @@ def refine_from_db(path, video):
TableEpisodes.audio_codec,
TableEpisodes.path,
TableShows.imdbId)\
.join(TableShows, on=(TableEpisodes.sonarrSeriesId == TableShows.sonarrSeriesId))\
.where((TableEpisodes.path == path_mappings.path_replace_reverse(path)))\
.join(TableShows, on=(TableEpisodes.seriesId == TableShows.seriesId))\
.where((TableEpisodes.path == path))\
.dicts()
if len(data):
@ -1280,9 +1256,8 @@ def refine_from_db(path, video):
video.season = int(data['season'])
video.episode = int(data['episode'])
video.title = data['episodeTitle']
# Commented out because Sonarr provided so much bad year
# if data['year']:
# if int(data['year']) > 0: video.year = int(data['year'])
if data['year']:
if int(data['year']) > 0: video.year = int(data['year'])
video.series_tvdb_id = int(data['tvdbId'])
video.alternative_series = ast.literal_eval(data['alternateTitles'])
if data['imdbId'] and not video.series_imdb_id:
@ -1304,15 +1279,14 @@ def refine_from_db(path, video):
TableMovies.video_codec,
TableMovies.audio_codec,
TableMovies.imdbId)\
.where(TableMovies.path == path_mappings.path_replace_reverse_movie(path))\
.where(TableMovies.path == path)\
.dicts()
if len(data):
data = data[0]
video.title = re.sub(r'\s(\(\d\d\d\d\))', '', data['title'])
# Commented out because Radarr provided so much bad year
# if data['year']:
# if int(data['year']) > 0: video.year = int(data['year'])
if data['year']:
if int(data['year']) > 0: video.year = int(data['year'])
if data['imdbId'] and not video.imdb_id:
video.imdb_id = data['imdbId']
video.alternative_titles = ast.literal_eval(data['alternativeTitles'])
@ -1331,12 +1305,12 @@ def refine_from_db(path, video):
def refine_from_ffprobe(path, video):
if isinstance(video, Movie):
file_id = TableMovies.select(TableMovies.movie_file_id, TableMovies.file_size)\
.where(TableMovies.path == path_mappings.path_replace_reverse_movie(path))\
.where(TableMovies.path == path)\
.dicts()\
.get()
else:
file_id = TableEpisodes.select(TableEpisodes.episode_file_id, TableEpisodes.file_size)\
.where(TableEpisodes.path == path_mappings.path_replace_reverse(path))\
.where(TableEpisodes.path == path)\
.dicts()\
.get()
@ -1395,7 +1369,7 @@ def upgrade_subtitles():
else:
query_actions = [1, 3]
if settings.general.getboolean('use_sonarr'):
if settings.general.getboolean('use_series'):
upgradable_episodes_conditions = [(TableHistory.action << query_actions),
(TableHistory.timestamp > minimum_timestamp),
(TableHistory.score is not None)]
@ -1408,18 +1382,18 @@ def upgrade_subtitles():
TableEpisodes.audio_language,
TableEpisodes.scene_name,
TableEpisodes.title,
TableEpisodes.sonarrSeriesId,
TableEpisodes.seriesId,
TableHistory.action,
TableHistory.subtitles_path,
TableEpisodes.sonarrEpisodeId,
TableEpisodes.episodeId,
fn.MAX(TableHistory.timestamp).alias('timestamp'),
TableEpisodes.monitored,
TableEpisodes.season,
TableEpisodes.episode,
TableShows.title.alias('seriesTitle'),
TableShows.seriesType)\
.join(TableShows, on=(TableHistory.sonarrSeriesId == TableShows.sonarrSeriesId))\
.join(TableEpisodes, on=(TableHistory.sonarrEpisodeId == TableEpisodes.sonarrEpisodeId))\
.join(TableShows, on=(TableHistory.seriesId == TableShows.seriesId))\
.join(TableEpisodes, on=(TableHistory.episodeId == TableEpisodes.episodeId))\
.where(reduce(operator.and_, upgradable_episodes_conditions))\
.group_by(TableHistory.video_path, TableHistory.language)\
.dicts()
@ -1437,12 +1411,12 @@ def upgrade_subtitles():
episodes_to_upgrade = []
for episode in upgradable_episodes_not_perfect:
if os.path.exists(path_mappings.path_replace(episode['subtitles_path'])) and int(episode['score']) < 357:
if os.path.exists(episode['subtitles_path']) and int(episode['score']) < 357:
episodes_to_upgrade.append(episode)
count_episode_to_upgrade = len(episodes_to_upgrade)
if settings.general.getboolean('use_radarr'):
if settings.general.getboolean('use_movies'):
upgradable_movies_conditions = [(TableHistoryMovie.action << query_actions),
(TableHistoryMovie.timestamp > minimum_timestamp),
(TableHistoryMovie.score is not None)]
@ -1458,9 +1432,9 @@ def upgrade_subtitles():
fn.MAX(TableHistoryMovie.timestamp).alias('timestamp'),
TableMovies.monitored,
TableMovies.tags,
TableMovies.radarrId,
TableMovies.movieId,
TableMovies.title)\
.join(TableMovies, on=(TableHistoryMovie.radarrId == TableMovies.radarrId))\
.join(TableMovies, on=(TableHistoryMovie.movieId == TableMovies.movieId))\
.where(reduce(operator.and_, upgradable_movies_conditions))\
.group_by(TableHistoryMovie.video_path, TableHistoryMovie.language)\
.dicts()
@ -1478,7 +1452,7 @@ def upgrade_subtitles():
movies_to_upgrade = []
for movie in upgradable_movies_not_perfect:
if os.path.exists(path_mappings.path_replace_movie(movie['subtitles_path'])) and int(movie['score']) < 117:
if os.path.exists(movie['subtitles_path']) and int(movie['score']) < 117:
movies_to_upgrade.append(movie)
count_movie_to_upgrade = len(movies_to_upgrade)
@ -1486,7 +1460,7 @@ def upgrade_subtitles():
providers_list = get_providers()
providers_auth = get_providers_auth()
if settings.general.getboolean('use_sonarr'):
if settings.general.getboolean('use_series'):
for i, episode in enumerate(episodes_to_upgrade):
show_progress(id='upgrade_episodes_progress',
header='Upgrading episodes subtitles...',
@ -1514,13 +1488,13 @@ def upgrade_subtitles():
is_forced = "False"
is_hi = "False"
audio_language_list = get_audio_profile_languages(episode_id=episode['sonarrEpisodeId'])
audio_language_list = get_audio_profile_languages(episode_id=episode['episodeId'])
if len(audio_language_list) > 0:
audio_language = audio_language_list[0]['name']
else:
audio_language = 'None'
result = download_subtitle(path_mappings.path_replace(episode['video_path']),
result = download_subtitle(episode['video_path'],
language,
audio_language,
is_hi,
@ -1546,14 +1520,14 @@ def upgrade_subtitles():
score = result[4]
subs_id = result[6]
subs_path = result[7]
store_subtitles(episode['video_path'], path_mappings.path_replace(episode['video_path']))
history_log(3, episode['sonarrSeriesId'], episode['sonarrEpisodeId'], message, path,
store_subtitles(episode['video_path'])
history_log(3, episode['seriesId'], episode['episodeId'], message, path,
language_code, provider, score, subs_id, subs_path)
send_notifications(episode['sonarrSeriesId'], episode['sonarrEpisodeId'], message)
send_notifications(episode['seriesId'], episode['episodeId'], message)
hide_progress(id='upgrade_episodes_progress')
if settings.general.getboolean('use_radarr'):
if settings.general.getboolean('use_movies'):
for i, movie in enumerate(movies_to_upgrade):
show_progress(id='upgrade_movies_progress',
header='Upgrading movies subtitles...',
@ -1581,13 +1555,13 @@ def upgrade_subtitles():
is_forced = "False"
is_hi = "False"
audio_language_list = get_audio_profile_languages(movie_id=movie['radarrId'])
audio_language_list = get_audio_profile_languages(movie_id=movie['movieId'])
if len(audio_language_list) > 0:
audio_language = audio_language_list[0]['name']
else:
audio_language = 'None'
result = download_subtitle(path_mappings.path_replace_movie(movie['video_path']),
result = download_subtitle(movie['video_path'],
language,
audio_language,
is_hi,
@ -1613,10 +1587,9 @@ def upgrade_subtitles():
score = result[4]
subs_id = result[6]
subs_path = result[7]
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, subs_id, subs_path)
send_notifications_movie(movie['radarrId'], message)
store_subtitles_movie(movie['video_path'])
history_log_movie(3, movie['movieId'], message, path, language_code, provider, score, subs_id, subs_path)
send_notifications_movie(movie['movieId'], message)
hide_progress(id='upgrade_movies_progress')
@ -1653,8 +1626,8 @@ def postprocessing(command, path):
logging.info('BAZARR Post-processing result for file ' + path + ' : ' + out)
def sync_subtitles(video_path, srt_path, srt_lang, media_type, percent_score, sonarr_series_id=None,
sonarr_episode_id=None, radarr_id=None):
def sync_subtitles(video_path, srt_path, srt_lang, media_type, percent_score, series_id=None,
episode_id=None, movie_id=None):
if settings.subsync.getboolean('use_subsync'):
if media_type == 'series':
use_subsync_threshold = settings.subsync.getboolean('use_subsync_threshold')
@ -1665,7 +1638,7 @@ def sync_subtitles(video_path, srt_path, srt_lang, media_type, percent_score, so
if not use_subsync_threshold or (use_subsync_threshold and percent_score < float(subsync_threshold)):
subsync.sync(video_path=video_path, srt_path=srt_path, srt_lang=srt_lang, media_type=media_type,
sonarr_series_id=sonarr_series_id, sonarr_episode_id=sonarr_episode_id, radarr_id=radarr_id)
series_id=series_id, episode_id=episode_id, movie_id=movie_id)
return True
else:
logging.debug("BAZARR subsync skipped because subtitles score isn't below this "

@ -11,91 +11,6 @@ from bs4 import UnicodeDammit
from config import settings, get_array_from
class PathMappings:
def __init__(self):
self.path_mapping_series = []
self.path_mapping_movies = []
def update(self):
self.path_mapping_series = [x for x in get_array_from(settings.general.path_mappings) if x[0] != x[1]]
self.path_mapping_movies = [x for x in get_array_from(settings.general.path_mappings_movie) if x[0] != x[1]]
def path_replace(self, path):
if path is None:
return None
for path_mapping in self.path_mapping_series:
if path_mapping[0] == path_mapping[1]:
continue
if '' in path_mapping:
continue
if path_mapping[0] in path:
path = path.replace(path_mapping[0], path_mapping[1])
if path.startswith('\\\\') or re.match(r'^[a-zA-Z]:\\', path):
path = path.replace('/', '\\')
elif path.startswith('/'):
path = path.replace('\\', '/')
break
return path
def path_replace_reverse(self, path):
if path is None:
return None
for path_mapping in self.path_mapping_series:
if path_mapping[0] == path_mapping[1]:
continue
if '' in path_mapping:
continue
if path_mapping[1] in path:
path = path.replace(path_mapping[1], path_mapping[0])
if path.startswith('\\\\') or re.match(r'^[a-zA-Z]:\\', path):
path = path.replace('/', '\\')
elif path.startswith('/'):
path = path.replace('\\', '/')
break
return path
def path_replace_movie(self, path):
if path is None:
return None
for path_mapping in self.path_mapping_movies:
if path_mapping[0] == path_mapping[1]:
continue
if '' in path_mapping:
continue
if path_mapping[0] in path:
path = path.replace(path_mapping[0], path_mapping[1])
if path.startswith('\\\\') or re.match(r'^[a-zA-Z]:\\', path):
path = path.replace('/', '\\')
elif path.startswith('/'):
path = path.replace('\\', '/')
break
return path
def path_replace_reverse_movie(self, path):
if path is None:
return None
for path_mapping in self.path_mapping_movies:
if path_mapping[0] == path_mapping[1]:
continue
if '' in path_mapping:
continue
if path_mapping[1] in path:
path = path.replace(path_mapping[1], path_mapping[0])
if path.startswith('\\\\') or re.match(r'^[a-zA-Z]:\\', path):
path = path.replace('/', '\\')
elif path.startswith('/'):
path = path.replace('\\', '/')
break
return path
path_mappings = PathMappings()
def pp_replace(pp_command, episode, subtitles, language, language_code2, language_code3, episode_language, episode_language_code2, episode_language_code3, forced, score, subtitle_id, provider, series_id, episode_id, hi):
if hi:
modifier_string = " HI"

@ -9,7 +9,6 @@ import subprocess
from config import settings, configure_captcha_func
from get_args import args
from logger import configure_logging
from helper import path_mappings
from dogpile.cache.region import register_backend as register_cache_backend
import subliminal
@ -187,4 +186,3 @@ from database import init_db, migrate_db
init_db()
migrate_db()
init_binaries()
path_mappings.update()

@ -14,7 +14,7 @@ from custom_lang import CustomLanguage
from database import get_profiles_list, get_profile_cutoff, TableEpisodes, TableShows, TableMovies
from get_languages import alpha2_from_alpha3, language_from_alpha2, get_language_set
from config import settings
from helper import path_mappings, get_subtitle_destination_folder
from helper import get_subtitle_destination_folder
from embedded_subs_reader import embedded_subs_reader
from event_handler import event_stream, show_progress, hide_progress
@ -26,18 +26,18 @@ global hi_regex
hi_regex = re.compile(r'[*¶♫♪].{3,}[*¶♫♪]|[\[\(\{].{3,}[\]\)\}](?<!{\\an\d})')
def store_subtitles(original_path, reversed_path, use_cache=True):
logging.debug('BAZARR started subtitles indexing for this file: ' + reversed_path)
def store_subtitles(path, use_cache=True):
logging.debug('BAZARR started subtitles indexing for this file: ' + path)
actual_subtitles = []
if os.path.exists(reversed_path):
if os.path.exists(path):
if settings.general.getboolean('use_embedded_subs'):
logging.debug("BAZARR is trying to index embedded subtitles.")
try:
item = TableEpisodes.select(TableEpisodes.episode_file_id, TableEpisodes.file_size)\
.where(TableEpisodes.path == original_path)\
.where(TableEpisodes.path == path)\
.dicts()\
.get()
subtitle_languages = embedded_subs_reader(reversed_path,
subtitle_languages = embedded_subs_reader(path,
file_size=item['file_size'],
episode_file_id=item['episode_file_id'],
use_cache=use_cache)
@ -64,19 +64,19 @@ def store_subtitles(original_path, reversed_path, use_cache=True):
pass
except Exception as e:
logging.exception(
"BAZARR error when trying to analyze this %s file: %s" % (os.path.splitext(reversed_path)[1], reversed_path))
"BAZARR error when trying to analyze this %s file: %s" % (os.path.splitext(path)[1], path))
pass
try:
dest_folder = get_subtitle_destination_folder()
core.CUSTOM_PATHS = [dest_folder] if dest_folder else []
subtitles = search_external_subtitles(reversed_path, languages=get_language_set(),
subtitles = search_external_subtitles(path, languages=get_language_set(),
only_one=settings.general.getboolean('single_language'))
full_dest_folder_path = os.path.dirname(reversed_path)
full_dest_folder_path = os.path.dirname(path)
if dest_folder:
if settings.general.subfolder == "absolute":
full_dest_folder_path = dest_folder
elif settings.general.subfolder == "relative":
full_dest_folder_path = os.path.join(os.path.dirname(reversed_path), dest_folder)
full_dest_folder_path = os.path.join(os.path.dirname(path), dest_folder)
subtitles = guess_external_subtitles(full_dest_folder_path, subtitles)
except Exception:
logging.exception("BAZARR unable to index external subtitles.")
@ -85,11 +85,11 @@ def store_subtitles(original_path, reversed_path, use_cache=True):
if not language:
continue
subtitle_path = get_external_subtitles_path(reversed_path, subtitle)
subtitle_path = get_external_subtitles_path(path, subtitle)
custom = CustomLanguage.found_external(subtitle, subtitle_path)
if custom is not None:
actual_subtitles.append([custom, path_mappings.path_replace_reverse(subtitle_path)])
actual_subtitles.append([custom, subtitle_path])
elif str(language) != 'und':
if language.forced:
@ -99,41 +99,41 @@ def store_subtitles(original_path, reversed_path, use_cache=True):
else:
language_str = str(language)
logging.debug("BAZARR external subtitles detected: " + language_str)
actual_subtitles.append([language_str, path_mappings.path_replace_reverse(subtitle_path)])
actual_subtitles.append([language_str, subtitle_path])
TableEpisodes.update({TableEpisodes.subtitles: str(actual_subtitles)})\
.where(TableEpisodes.path == original_path)\
.where(TableEpisodes.path == path)\
.execute()
matching_episodes = TableEpisodes.select(TableEpisodes.sonarrEpisodeId, TableEpisodes.sonarrSeriesId)\
.where(TableEpisodes.path == original_path)\
matching_episodes = TableEpisodes.select(TableEpisodes.episodeId, TableEpisodes.seriesId)\
.where(TableEpisodes.path == path)\
.dicts()
for episode in matching_episodes:
if episode:
logging.debug("BAZARR storing those languages to DB: " + str(actual_subtitles))
list_missing_subtitles(epno=episode['sonarrEpisodeId'])
list_missing_subtitles(epno=episode['episodeId'])
else:
logging.debug("BAZARR haven't been able to update existing subtitles to DB : " + str(actual_subtitles))
else:
logging.debug("BAZARR this file doesn't seems to exist or isn't accessible.")
logging.debug('BAZARR ended subtitles indexing for this file: ' + reversed_path)
logging.debug('BAZARR ended subtitles indexing for this file: ' + path)
return actual_subtitles
def store_subtitles_movie(original_path, reversed_path, use_cache=True):
logging.debug('BAZARR started subtitles indexing for this file: ' + reversed_path)
def store_subtitles_movie(path, use_cache=True):
logging.debug('BAZARR started subtitles indexing for this file: ' + path)
actual_subtitles = []
if os.path.exists(reversed_path):
if os.path.exists(path):
if settings.general.getboolean('use_embedded_subs'):
logging.debug("BAZARR is trying to index embedded subtitles.")
try:
item = TableMovies.select(TableMovies.movie_file_id, TableMovies.file_size)\
.where(TableMovies.path == original_path)\
.where(TableMovies.path == path)\
.dicts()\
.get()
subtitle_languages = embedded_subs_reader(reversed_path,
subtitle_languages = embedded_subs_reader(path,
file_size=item['file_size'],
movie_file_id=item['movie_file_id'],
use_cache=use_cache)
@ -160,19 +160,19 @@ def store_subtitles_movie(original_path, reversed_path, use_cache=True):
pass
except Exception:
logging.exception(
"BAZARR error when trying to analyze this %s file: %s" % (os.path.splitext(reversed_path)[1], reversed_path))
"BAZARR error when trying to analyze this %s file: %s" % (os.path.splitext(path)[1], path))
pass
try:
dest_folder = get_subtitle_destination_folder() or ''
core.CUSTOM_PATHS = [dest_folder] if dest_folder else []
subtitles = search_external_subtitles(reversed_path, languages=get_language_set())
full_dest_folder_path = os.path.dirname(reversed_path)
subtitles = search_external_subtitles(path, languages=get_language_set())
full_dest_folder_path = os.path.dirname(path)
if dest_folder:
if settings.general.subfolder == "absolute":
full_dest_folder_path = dest_folder
elif settings.general.subfolder == "relative":
full_dest_folder_path = os.path.join(os.path.dirname(reversed_path), dest_folder)
full_dest_folder_path = os.path.join(os.path.dirname(path), dest_folder)
subtitles = guess_external_subtitles(full_dest_folder_path, subtitles)
except Exception as e:
logging.exception("BAZARR unable to index external subtitles.")
@ -182,11 +182,11 @@ def store_subtitles_movie(original_path, reversed_path, use_cache=True):
if not language:
continue
subtitle_path = get_external_subtitles_path(reversed_path, subtitle)
subtitle_path = get_external_subtitles_path(path, subtitle)
custom = CustomLanguage.found_external(subtitle, subtitle_path)
if custom is not None:
actual_subtitles.append([custom, path_mappings.path_replace_reverse_movie(subtitle_path)])
actual_subtitles.append([custom, subtitle_path])
elif str(language.basename) != 'und':
if language.forced:
@ -196,12 +196,12 @@ def store_subtitles_movie(original_path, reversed_path, use_cache=True):
else:
language_str = str(language)
logging.debug("BAZARR external subtitles detected: " + language_str)
actual_subtitles.append([language_str, path_mappings.path_replace_reverse_movie(subtitle_path)])
actual_subtitles.append([language_str, subtitle_path])
TableMovies.update({TableMovies.subtitles: str(actual_subtitles)})\
.where(TableMovies.path == original_path)\
.where(TableMovies.path == path)\
.execute()
matching_movies = TableMovies.select(TableMovies.radarrId).where(TableMovies.path == original_path).dicts()
matching_movies = TableMovies.select(TableMovies.radarrId).where(TableMovies.path == path).dicts()
for movie in matching_movies:
if movie:
@ -212,24 +212,24 @@ def store_subtitles_movie(original_path, reversed_path, use_cache=True):
else:
logging.debug("BAZARR this file doesn't seems to exist or isn't accessible.")
logging.debug('BAZARR ended subtitles indexing for this file: ' + reversed_path)
logging.debug('BAZARR ended subtitles indexing for this file: ' + path)
return actual_subtitles
def list_missing_subtitles(no=None, epno=None, send_event=True):
if epno is not None:
episodes_subtitles_clause = (TableEpisodes.sonarrEpisodeId == epno)
episodes_subtitles_clause = (TableEpisodes.episodeId == epno)
elif no is not None:
episodes_subtitles_clause = (TableEpisodes.sonarrSeriesId == no)
episodes_subtitles_clause = (TableEpisodes.seriesId == no)
else:
episodes_subtitles_clause = None
episodes_subtitles = TableEpisodes.select(TableShows.sonarrSeriesId,
TableEpisodes.sonarrEpisodeId,
episodes_subtitles = TableEpisodes.select(TableShows.seriesId,
TableEpisodes.episodeId,
TableEpisodes.subtitles,
TableShows.profileId,
TableEpisodes.audio_language)\
.join(TableShows, on=(TableEpisodes.sonarrSeriesId == TableShows.sonarrSeriesId))\
.join(TableShows, on=(TableEpisodes.seriesId == TableShows.seriesId))\
.where(episodes_subtitles_clause)\
.dicts()
if isinstance(episodes_subtitles, str):
@ -328,11 +328,11 @@ def list_missing_subtitles(no=None, epno=None, send_event=True):
missing_subtitles_text = str(missing_subtitles_output_list)
TableEpisodes.update({TableEpisodes.missing_subtitles: missing_subtitles_text})\
.where(TableEpisodes.sonarrEpisodeId == episode_subtitles['sonarrEpisodeId'])\
.where(TableEpisodes.episodeId == episode_subtitles['episodeId'])\
.execute()
if send_event:
event_stream(type='episode', payload=episode_subtitles['sonarrEpisodeId'])
event_stream(type='episode', payload=episode_subtitles['episodeId'])
event_stream(type='badges')
@ -458,7 +458,7 @@ def series_full_scan_subtitles():
name='Episodes subtitles',
value=i,
count=count_episodes)
store_subtitles(episode['path'], path_mappings.path_replace(episode['path']), use_cache=use_ffprobe_cache)
store_subtitles(episode['path'], use_cache=use_ffprobe_cache)
hide_progress(id='episodes_disk_scan')
@ -478,8 +478,7 @@ def movies_full_scan_subtitles():
name='Movies subtitles',
value=i,
count=count_movies)
store_subtitles_movie(movie['path'], path_mappings.path_replace_movie(movie['path']),
use_cache=use_ffprobe_cache)
store_subtitles_movie(movie['path'], use_cache=use_ffprobe_cache)
hide_progress(id='movies_disk_scan')
@ -488,13 +487,13 @@ def movies_full_scan_subtitles():
def series_scan_subtitles(no):
episodes = TableEpisodes.select(TableEpisodes.path)\
.where(TableEpisodes.sonarrSeriesId == no)\
.order_by(TableEpisodes.sonarrEpisodeId)\
.where(TableEpisodes.seriesId == no)\
.order_by(TableEpisodes.episodeId)\
.dicts()
for episode in episodes:
sleep()
store_subtitles(episode['path'], path_mappings.path_replace(episode['path']), use_cache=False)
store_subtitles(episode['path'], use_cache=False)
def movies_scan_subtitles(no):
@ -505,7 +504,7 @@ def movies_scan_subtitles(no):
for movie in movies:
sleep()
store_subtitles_movie(movie['path'], path_mappings.path_replace_movie(movie['path']), use_cache=False)
store_subtitles_movie(movie['path'], use_cache=False)
def get_external_subtitles_path(file, subtitle):

@ -23,7 +23,7 @@ os.environ["BAZARR_VERSION"] = bazarr_version.lstrip('v')
import libs
from get_args import args
from config import settings, url_sonarr, url_radarr, configure_proxy_func, base_url
from config import settings, configure_proxy_func, base_url
from init import *
from database import System
@ -38,12 +38,11 @@ from flask import make_response, request, redirect, abort, render_template, Resp
from get_series import *
from get_episodes import *
from get_movies import *
from signalr_client import sonarr_signalr_client, radarr_signalr_client
from check_update import apply_update, check_if_new_update, check_releases
from server import app, webserver
from functools import wraps
from utils import check_credentials, get_sonarr_info, get_radarr_info
from utils import check_credentials
# Install downloaded update
if bazarr_version != '':
@ -127,48 +126,14 @@ def download_log():
@check_login
@app.route('/images/series/<path:url>', methods=['GET'])
def series_images(url):
url = url.strip("/")
apikey = settings.sonarr.apikey
baseUrl = settings.sonarr.base_url
if get_sonarr_info.is_legacy():
url_image = (url_sonarr() + '/api/' + url.lstrip(baseUrl) + '?apikey=' +
apikey).replace('poster-250', 'poster-500')
else:
url_image = (url_sonarr() + '/api/v3/' + url.lstrip(baseUrl) + '?apikey=' +
apikey).replace('poster-250', 'poster-500')
try:
req = requests.get(url_image, stream=True, timeout=15, verify=False, headers=headers)
except:
return '', 404
else:
return Response(stream_with_context(req.iter_content(2048)), content_type=req.headers['content-type'])
def series_images(seriesId):
pass
@check_login
@app.route('/images/movies/<path:url>', methods=['GET'])
def movies_images(url):
apikey = settings.radarr.apikey
baseUrl = settings.radarr.base_url
if get_radarr_info.is_legacy():
url_image = url_radarr() + '/api/' + url.lstrip(baseUrl) + '?apikey=' + apikey
else:
url_image = url_radarr() + '/api/v3/' + url.lstrip(baseUrl) + '?apikey=' + apikey
try:
req = requests.get(url_image, stream=True, timeout=15, verify=False, headers=headers)
except:
return '', 404
else:
return Response(stream_with_context(req.iter_content(2048)), content_type=req.headers['content-type'])
# @app.route('/check_update')
# @authenticate
# def check_update():
# if not args.no_update:
# check_and_apply_update()
# return '', 200
def movies_images(movieId):
pass
def configured():
@ -202,12 +167,5 @@ def proxy(protocol, url):
return dict(status=False, error=result.raise_for_status())
greenlets = []
if settings.general.getboolean('use_sonarr'):
greenlets.append(Greenlet.spawn(sonarr_signalr_client.start))
if settings.general.getboolean('use_radarr'):
greenlets.append(Greenlet.spawn(radarr_signalr_client.start))
if __name__ == "__main__":
webserver.start()

@ -46,43 +46,43 @@ def get_notifier_providers():
return providers
def get_series(sonarr_series_id):
def get_series(series_id):
data = TableShows.select(TableShows.title, TableShows.year)\
.where(TableShows.sonarrSeriesId == sonarr_series_id)\
.where(TableShows.seriesId == series_id)\
.dicts()\
.get()
return {'title': data['title'], 'year': data['year']}
def get_episode_name(sonarr_episode_id):
def get_episode_name(episode_id):
data = TableEpisodes.select(TableEpisodes.title, TableEpisodes.season, TableEpisodes.episode)\
.where(TableEpisodes.sonarrEpisodeId == sonarr_episode_id)\
.where(TableEpisodes.episodeId == episode_id)\
.dicts()\
.get()
return data['title'], data['season'], data['episode']
def get_movie(radarr_id):
def get_movie(movie_id):
data = TableMovies.select(TableMovies.title, TableMovies.year)\
.where(TableMovies.radarrId == radarr_id)\
.where(TableMovies.movieId == movie_id)\
.dicts()\
.get()
return {'title': data['title'], 'year': data['year']}
def send_notifications(sonarr_series_id, sonarr_episode_id, message):
def send_notifications(series_id, episode_id, message):
providers = get_notifier_providers()
series = get_series(sonarr_series_id)
series = get_series(series_id)
series_title = series['title']
series_year = series['year']
if series_year not in [None, '', '0']:
series_year = ' ({})'.format(series_year)
else:
series_year = ''
episode = get_episode_name(sonarr_episode_id)
episode = get_episode_name(episode_id)
asset = apprise.AppriseAsset(async_mode=False)
@ -99,9 +99,9 @@ def send_notifications(sonarr_series_id, sonarr_episode_id, message):
)
def send_notifications_movie(radarr_id, message):
def send_notifications_movie(movie_id, message):
providers = get_notifier_providers()
movie = get_movie(radarr_id)
movie = get_movie(movie_id)
movie_title = movie['title']
movie_year = movie['year']
if movie_year not in [None, '', '0']:

@ -1,8 +1,7 @@
# coding=utf-8
from get_episodes import sync_episodes, update_all_episodes
from get_movies import update_movies, update_all_movies
from get_series import update_series
from get_episodes import update_all_episodes
from get_movies import update_all_movies
from config import settings
from get_subtitle import wanted_search_missing_subtitles_series, wanted_search_missing_subtitles_movies, \
upgrade_subtitles
@ -54,10 +53,6 @@ class Scheduler:
self.aps_scheduler.start()
def update_configurable_tasks(self):
self.__sonarr_update_task()
self.__radarr_update_task()
self.__sonarr_full_update_task()
self.__radarr_full_update_task()
self.__update_bazarr_task()
self.__search_wanted_subtitles_task()
self.__upgrade_subtitles_task()
@ -140,24 +135,6 @@ class Scheduler:
return task_list
def __sonarr_update_task(self):
if settings.general.getboolean('use_sonarr'):
self.aps_scheduler.add_job(
update_series, IntervalTrigger(minutes=int(settings.sonarr.series_sync)), max_instances=1,
coalesce=True, misfire_grace_time=15, id='update_series', name='Update Series list from Sonarr',
replace_existing=True)
self.aps_scheduler.add_job(
sync_episodes, IntervalTrigger(minutes=int(settings.sonarr.episodes_sync)), max_instances=1,
coalesce=True, misfire_grace_time=15, id='sync_episodes', name='Sync episodes with Sonarr',
replace_existing=True)
def __radarr_update_task(self):
if settings.general.getboolean('use_radarr'):
self.aps_scheduler.add_job(
update_movies, IntervalTrigger(minutes=int(settings.radarr.movies_sync)), max_instances=1,
coalesce=True, misfire_grace_time=15, id='update_movies', name='Update Movie list from Radarr',
replace_existing=True)
def __cache_cleanup_task(self):
self.aps_scheduler.add_job(cache_maintenance, IntervalTrigger(hours=24), max_instances=1, coalesce=True,
misfire_grace_time=15, id='cache_cleanup', name='Cache maintenance')
@ -166,45 +143,6 @@ class Scheduler:
self.aps_scheduler.add_job(check_health, IntervalTrigger(hours=6), max_instances=1, coalesce=True,
misfire_grace_time=15, id='check_health', name='Check health')
def __sonarr_full_update_task(self):
if settings.general.getboolean('use_sonarr'):
full_update = settings.sonarr.full_update
if full_update == "Daily":
self.aps_scheduler.add_job(
update_all_episodes, CronTrigger(hour=settings.sonarr.full_update_hour), max_instances=1,
coalesce=True, misfire_grace_time=15, id='update_all_episodes',
name='Update all Episode Subtitles from disk', replace_existing=True)
elif full_update == "Weekly":
self.aps_scheduler.add_job(
update_all_episodes,
CronTrigger(day_of_week=settings.sonarr.full_update_day, hour=settings.sonarr.full_update_hour),
max_instances=1, coalesce=True, misfire_grace_time=15, id='update_all_episodes',
name='Update all Episode Subtitles from disk', replace_existing=True)
elif full_update == "Manually":
self.aps_scheduler.add_job(
update_all_episodes, CronTrigger(year='2100'), max_instances=1, coalesce=True,
misfire_grace_time=15, id='update_all_episodes',
name='Update all Episode Subtitles from disk', replace_existing=True)
def __radarr_full_update_task(self):
if settings.general.getboolean('use_radarr'):
full_update = settings.radarr.full_update
if full_update == "Daily":
self.aps_scheduler.add_job(
update_all_movies, CronTrigger(hour=settings.radarr.full_update_hour), max_instances=1,
coalesce=True, misfire_grace_time=15,
id='update_all_movies', name='Update all Movie Subtitles from disk', replace_existing=True)
elif full_update == "Weekly":
self.aps_scheduler.add_job(
update_all_movies,
CronTrigger(day_of_week=settings.radarr.full_update_day, hour=settings.radarr.full_update_hour),
max_instances=1, coalesce=True, misfire_grace_time=15, id='update_all_movies',
name='Update all Movie Subtitles from disk', replace_existing=True)
elif full_update == "Manually":
self.aps_scheduler.add_job(
update_all_movies, CronTrigger(year='2100'), max_instances=1, coalesce=True, misfire_grace_time=15,
id='update_all_movies', name='Update all Movie Subtitles from disk', replace_existing=True)
def __update_bazarr_task(self):
if not args.no_update and os.environ["BAZARR_VERSION"] != '':
task_name = 'Update Bazarr'
@ -227,12 +165,12 @@ class Scheduler:
id='update_release', name='Update Release Info', replace_existing=True)
def __search_wanted_subtitles_task(self):
if settings.general.getboolean('use_sonarr'):
if settings.general.getboolean('use_series'):
self.aps_scheduler.add_job(
wanted_search_missing_subtitles_series, IntervalTrigger(hours=int(settings.general.wanted_search_frequency)),
max_instances=1, coalesce=True, misfire_grace_time=15, id='wanted_search_missing_subtitles_series',
name='Search for wanted Series Subtitles', replace_existing=True)
if settings.general.getboolean('use_radarr'):
if settings.general.getboolean('use_movies'):
self.aps_scheduler.add_job(
wanted_search_missing_subtitles_movies, IntervalTrigger(hours=int(settings.general.wanted_search_frequency_movie)),
max_instances=1, coalesce=True, misfire_grace_time=15, id='wanted_search_missing_subtitles_movies',
@ -240,7 +178,7 @@ class Scheduler:
def __upgrade_subtitles_task(self):
if settings.general.getboolean('upgrade_subs') and \
(settings.general.getboolean('use_sonarr') or settings.general.getboolean('use_radarr')):
(settings.general.getboolean('use_series') or settings.general.getboolean('use_movies')):
self.aps_scheduler.add_job(
upgrade_subtitles, IntervalTrigger(hours=int(settings.general.upgrade_frequency)), max_instances=1,
coalesce=True, misfire_grace_time=15, id='upgrade_subtitles',
@ -257,12 +195,3 @@ class Scheduler:
scheduler = Scheduler()
# Force the execution of the sync process with Sonarr and Radarr after migration to v0.9.1
if 'BAZARR_AUDIO_PROFILES_MIGRATION' in os.environ:
if settings.general.getboolean('use_sonarr'):
scheduler.aps_scheduler.modify_job('update_series', next_run_time=datetime.now())
scheduler.aps_scheduler.modify_job('sync_episodes', next_run_time=datetime.now())
if settings.general.getboolean('use_radarr'):
scheduler.aps_scheduler.modify_job('update_movies', next_run_time=datetime.now())
del os.environ['BAZARR_AUDIO_PROFILES_MIGRATION']

@ -22,7 +22,7 @@ class Server:
def __init__(self):
# Mute DeprecationWarning
warnings.simplefilter("ignore", DeprecationWarning)
# Mute Insecure HTTPS requests made to Sonarr and Radarr
# Mute Insecure outgoing HTTPS requests
warnings.filterwarnings('ignore', message='Unverified HTTPS request')
# Mute Python3 BrokenPipeError
warnings.simplefilter("ignore", BrokenPipeError)

@ -1,192 +0,0 @@
# coding=utf-8
import logging
import gevent
import json
import os
from requests import Session
from signalr import Connection
from requests.exceptions import ConnectionError
from signalrcore.hub_connection_builder import HubConnectionBuilder
from config import settings, url_sonarr, url_radarr
from get_episodes import sync_episodes, sync_one_episode
from get_series import update_series, update_one_series
from get_movies import update_movies, update_one_movie
from scheduler import scheduler
from utils import get_sonarr_info
from get_args import args
headers = {"User-Agent": os.environ["SZ_USER_AGENT"]}
class SonarrSignalrClient:
def __init__(self):
super(SonarrSignalrClient, self).__init__()
self.apikey_sonarr = None
self.session = Session()
self.session.timeout = 60
self.session.verify = False
self.session.headers = headers
self.connection = None
def start(self):
if get_sonarr_info.is_legacy():
logging.warning('BAZARR can only sync from Sonarr v3 SignalR feed to get real-time update. You should '
'consider upgrading your version({}).'.format(get_sonarr_info.version()))
raise gevent.GreenletExit
else:
logging.info('BAZARR trying to connect to Sonarr SignalR feed...')
self.configure()
while not self.connection.started:
try:
self.connection.start()
except ConnectionError:
gevent.sleep(5)
except json.decoder.JSONDecodeError:
logging.error("BAZARR cannot parse JSON returned by SignalR feed. This is caused by a permissions "
"issue when Sonarr try to access its /config/.config directory. You should fix "
"permissions on that directory and restart Sonarr. Also, if you're a Docker image "
"user, you should make sure you properly defined PUID/PGID environment variables. "
"Otherwise, please contact Sonarr support.")
raise gevent.GreenletExit
else:
logging.info('BAZARR SignalR client for Sonarr is connected and waiting for events.')
finally:
if not args.dev:
scheduler.add_job(update_series, kwargs={'send_event': True}, max_instances=1)
scheduler.add_job(sync_episodes, kwargs={'send_event': True}, max_instances=1)
def stop(self, log=True):
try:
self.connection.close()
except Exception as e:
pass
if log:
logging.info('BAZARR SignalR client for Sonarr is now disconnected.')
def restart(self):
if self.connection:
if self.connection.started:
try:
self.stop(log=False)
except:
self.connection.started = False
if settings.general.getboolean('use_sonarr'):
self.start()
def exception_handler(self, type, exception, traceback):
logging.error('BAZARR connection to Sonarr SignalR feed has been lost.')
self.restart()
def configure(self):
self.apikey_sonarr = settings.sonarr.apikey
self.connection = Connection(url_sonarr() + "/signalr", self.session)
self.connection.qs = {'apikey': self.apikey_sonarr}
sonarr_hub = self.connection.register_hub('') # Sonarr doesn't use named hub
sonarr_method = ['series', 'episode']
for item in sonarr_method:
sonarr_hub.client.on(item, dispatcher)
self.connection.exception += self.exception_handler
class RadarrSignalrClient:
def __init__(self):
super(RadarrSignalrClient, self).__init__()
self.apikey_radarr = None
self.connection = None
def start(self):
self.configure()
logging.info('BAZARR trying to connect to Radarr SignalR feed...')
while self.connection.transport.state.value not in [0, 1, 2]:
try:
self.connection.start()
except ConnectionError:
gevent.sleep(5)
def stop(self):
logging.info('BAZARR SignalR client for Radarr is now disconnected.')
self.connection.stop()
def restart(self):
if self.connection:
if self.connection.transport.state.value in [0, 1, 2]:
self.stop()
if settings.general.getboolean('use_radarr'):
self.start()
def exception_handler(self):
logging.error("BAZARR connection to Radarr SignalR feed has failed. We'll try to reconnect.")
self.restart()
@staticmethod
def on_connect_handler():
logging.info('BAZARR SignalR client for Radarr is connected and waiting for events.')
if not args.dev:
scheduler.add_job(update_movies, kwargs={'send_event': True}, max_instances=1)
def configure(self):
self.apikey_radarr = settings.radarr.apikey
self.connection = HubConnectionBuilder() \
.with_url(url_radarr() + "/signalr/messages?access_token={}".format(self.apikey_radarr),
options={
"verify_ssl": False,
"headers": headers
}) \
.with_automatic_reconnect({
"type": "raw",
"keep_alive_interval": 5,
"reconnect_interval": 180,
"max_attempts": None
}).build()
self.connection.on_open(self.on_connect_handler)
self.connection.on_reconnect(lambda: logging.error('BAZARR SignalR client for Radarr connection as been lost. '
'Trying to reconnect...'))
self.connection.on_close(lambda: logging.debug('BAZARR SignalR client for Radarr is disconnected.'))
self.connection.on_error(self.exception_handler)
self.connection.on("receiveMessage", dispatcher)
def dispatcher(data):
try:
topic = media_id = action = None
episodesChanged = None
if isinstance(data, dict):
topic = data['name']
try:
media_id = data['body']['resource']['id']
action = data['body']['action']
if 'episodesChanged' in data['body']['resource']:
episodesChanged = data['body']['resource']['episodesChanged']
except KeyError:
return
elif isinstance(data, list):
topic = data[0]['name']
try:
media_id = data[0]['body']['resource']['id']
action = data[0]['body']['action']
except KeyError:
return
if topic == 'series':
update_one_series(series_id=media_id, action=action)
if episodesChanged:
# this will happen if a season monitored status is changed.
sync_episodes(series_id=media_id, send_event=True)
elif topic == 'episode':
sync_one_episode(episode_id=media_id)
elif topic == 'movie':
update_one_movie(movie_id=media_id, action=action)
except Exception as e:
logging.debug('BAZARR an exception occurred while parsing SignalR feed: {}'.format(repr(e)))
finally:
return
sonarr_signalr_client = SonarrSignalrClient()
radarr_signalr_client = RadarrSignalrClient()

@ -4,7 +4,6 @@ from ffsubsync.ffsubsync import run, make_parser
from utils import get_binary
from utils import history_log, history_log_movie
from get_languages import language_from_alpha2
from helper import path_mappings
from config import settings
from get_args import args
@ -24,8 +23,8 @@ class SubSyncer:
self.vad = 'subs_then_webrtc'
self.log_dir_path = os.path.join(args.config_dir, 'log')
def sync(self, video_path, srt_path, srt_lang, media_type, sonarr_series_id=None, sonarr_episode_id=None,
radarr_id=None):
def sync(self, video_path, srt_path, srt_lang, media_type, series_id=None, episode_id=None,
movie_id=None):
self.reference = video_path
self.srtin = srt_path
self.srtout = '{}.synced.srt'.format(os.path.splitext(self.srtin)[0])
@ -72,13 +71,12 @@ class SubSyncer:
"{:.2f}".format(framerate_scale_factor))
if media_type == 'series':
history_log(action=5, sonarr_series_id=sonarr_series_id, sonarr_episode_id=sonarr_episode_id,
description=message, video_path=path_mappings.path_replace_reverse(self.reference),
language=srt_lang, subtitles_path=srt_path)
history_log(action=5, series_id=series_id, episode_id=episode_id,
description=message, video_path=self.reference, language=srt_lang,
subtitles_path=srt_path)
else:
history_log_movie(action=5, radarr_id=radarr_id, description=message,
video_path=path_mappings.path_replace_reverse_movie(self.reference),
language=srt_lang, subtitles_path=srt_path)
history_log_movie(action=5, movie_id=movie_id, description=message,
video_path=self.reference, language=srt_lang, subtitles_path=srt_path)
else:
logging.error('BAZARR unable to sync subtitles: {0}'.format(self.srtin))

@ -12,13 +12,12 @@ import stat
from whichcraft import which
from get_args import args
from config import settings, url_sonarr, url_radarr
from config import settings
from custom_lang import CustomLanguage
from database import TableHistory, TableHistoryMovie, TableBlacklist, TableBlacklistMovie, TableShowsRootfolder, \
TableMoviesRootfolder
from event_handler import event_stream
from get_languages import alpha2_from_alpha3, language_from_alpha3, language_from_alpha2, alpha3_from_alpha2
from helper import path_mappings
from list_subtitles import store_subtitles, store_subtitles_movie
from subliminal_patch.subtitle import Subtitle
from subliminal_patch.core import get_subtitle_path
@ -37,12 +36,12 @@ class BinaryNotFound(Exception):
pass
def history_log(action, sonarr_series_id, sonarr_episode_id, description, video_path=None, language=None, provider=None,
def history_log(action, series_id, episode_id, description, video_path=None, language=None, provider=None,
score=None, subs_id=None, subtitles_path=None):
TableHistory.insert({
TableHistory.action: action,
TableHistory.sonarrSeriesId: sonarr_series_id,
TableHistory.sonarrEpisodeId: sonarr_episode_id,
TableHistory.seriesId: series_id,
TableHistory.episodeId: episode_id,
TableHistory.timestamp: time.time(),
TableHistory.description: description,
TableHistory.video_path: video_path,
@ -55,10 +54,10 @@ def history_log(action, sonarr_series_id, sonarr_episode_id, description, video_
event_stream(type='episode-history')
def blacklist_log(sonarr_series_id, sonarr_episode_id, provider, subs_id, language):
def blacklist_log(series_id, episode_id, provider, subs_id, language):
TableBlacklist.insert({
TableBlacklist.sonarr_series_id: sonarr_series_id,
TableBlacklist.sonarr_episode_id: sonarr_episode_id,
TableBlacklist.series_id: series_id,
TableBlacklist.episode_id: episode_id,
TableBlacklist.timestamp: time.time(),
TableBlacklist.provider: provider,
TableBlacklist.subs_id: subs_id,
@ -79,11 +78,11 @@ def blacklist_delete_all():
event_stream(type='episode-blacklist', action='delete')
def history_log_movie(action, radarr_id, description, video_path=None, language=None, provider=None, score=None,
def history_log_movie(action, movie_id, description, video_path=None, language=None, provider=None, score=None,
subs_id=None, subtitles_path=None):
TableHistoryMovie.insert({
TableHistoryMovie.action: action,
TableHistoryMovie.radarrId: radarr_id,
TableHistoryMovie.movieId: movie_id,
TableHistoryMovie.timestamp: time.time(),
TableHistoryMovie.description: description,
TableHistoryMovie.video_path: video_path,
@ -96,9 +95,9 @@ def history_log_movie(action, radarr_id, description, video_path=None, language=
event_stream(type='movie-history')
def blacklist_log_movie(radarr_id, provider, subs_id, language):
def blacklist_log_movie(movie_id, provider, subs_id, language):
TableBlacklistMovie.insert({
TableBlacklistMovie.radarr_id: radarr_id,
TableBlacklistMovie.movie_id: movie_id,
TableBlacklistMovie.timestamp: time.time(),
TableBlacklistMovie.provider: provider,
TableBlacklistMovie.subs_id: subs_id,
@ -236,126 +235,8 @@ def cache_maintenance():
remove_expired(fn, pack_cache_validity)
class GetSonarrInfo:
@staticmethod
def version():
"""
Call system/status API endpoint and get the Sonarr version
@return: str
"""
sonarr_version = region.get("sonarr_version", expiration_time=datetime.timedelta(seconds=60).total_seconds())
if sonarr_version:
region.set("sonarr_version", sonarr_version)
return sonarr_version
else:
sonarr_version = ''
if settings.general.getboolean('use_sonarr'):
try:
sv = url_sonarr() + "/api/system/status?apikey=" + settings.sonarr.apikey
sonarr_json = requests.get(sv, timeout=60, verify=False, headers=headers).json()
if 'version' in sonarr_json:
sonarr_version = sonarr_json['version']
else:
sv = url_sonarr() + "/api/v3/system/status?apikey=" + settings.sonarr.apikey
sonarr_version = requests.get(sv, timeout=60, verify=False, headers=headers).json()['version']
except Exception:
logging.debug('BAZARR cannot get Sonarr version')
sonarr_version = 'unknown'
logging.debug('BAZARR got this Sonarr version from its API: {}'.format(sonarr_version))
region.set("sonarr_version", sonarr_version)
return sonarr_version
def is_legacy(self):
"""
Call self.version() and parse the result to determine if it's a legacy version of Sonarr API
@return: bool
"""
sonarr_version = self.version()
if sonarr_version.startswith(('0.', '2.')):
return True
else:
return False
get_sonarr_info = GetSonarrInfo()
def notify_sonarr(sonarr_series_id):
try:
if get_sonarr_info.is_legacy():
url = url_sonarr() + "/api/command?apikey=" + settings.sonarr.apikey
else:
url = url_sonarr() + "/api/v3/command?apikey=" + settings.sonarr.apikey
data = {
'name': 'RescanSeries',
'seriesId': int(sonarr_series_id)
}
requests.post(url, json=data, timeout=60, verify=False, headers=headers)
except Exception as e:
logging.exception('BAZARR cannot notify Sonarr')
class GetRadarrInfo:
@staticmethod
def version():
"""
Call system/status API endpoint and get the Radarr version
@return: str
"""
radarr_version = region.get("radarr_version", expiration_time=datetime.timedelta(seconds=60).total_seconds())
if radarr_version:
region.set("radarr_version", radarr_version)
return radarr_version
else:
radarr_version = ''
if settings.general.getboolean('use_radarr'):
try:
rv = url_radarr() + "/api/system/status?apikey=" + settings.radarr.apikey
radarr_json = requests.get(rv, timeout=60, verify=False, headers=headers).json()
if 'version' in radarr_json:
radarr_version = radarr_json['version']
else:
rv = url_radarr() + "/api/v3/system/status?apikey=" + settings.radarr.apikey
radarr_version = requests.get(rv, timeout=60, verify=False, headers=headers).json()['version']
except Exception as e:
logging.debug('BAZARR cannot get Radarr version')
radarr_version = 'unknown'
logging.debug('BAZARR got this Radarr version from its API: {}'.format(radarr_version))
region.set("radarr_version", radarr_version)
return radarr_version
def is_legacy(self):
"""
Call self.version() and parse the result to determine if it's a legacy version of Radarr
@return: bool
"""
radarr_version = self.version()
if radarr_version.startswith('0.'):
return True
else:
return False
get_radarr_info = GetRadarrInfo()
def notify_radarr(radarr_id):
try:
if get_radarr_info.is_legacy():
url = url_radarr() + "/api/command?apikey=" + settings.radarr.apikey
else:
url = url_radarr() + "/api/v3/command?apikey=" + settings.radarr.apikey
data = {
'name': 'RescanMovie',
'movieId': int(radarr_id)
}
requests.post(url, json=data, timeout=60, verify=False, headers=headers)
except Exception as e:
logging.exception('BAZARR cannot notify Radarr')
def delete_subtitles(media_type, language, forced, hi, media_path, subtitles_path, sonarr_series_id=None,
sonarr_episode_id=None, radarr_id=None):
def delete_subtitles(media_type, language, forced, hi, media_path, subtitles_path, series_id=None,
episode_id=None, movie_id=None):
if not subtitles_path.endswith('.srt'):
logging.error('BAZARR can only delete .srt files.')
return False
@ -373,33 +254,29 @@ def delete_subtitles(media_type, language, forced, hi, media_path, subtitles_pat
if media_type == 'series':
try:
os.remove(path_mappings.path_replace(subtitles_path))
os.remove(subtitles_path)
except OSError:
logging.exception('BAZARR cannot delete subtitles file: ' + subtitles_path)
store_subtitles(path_mappings.path_replace_reverse(media_path), media_path)
store_subtitles(media_path)
return False
else:
history_log(0, sonarr_series_id, sonarr_episode_id, result, language=language_log,
video_path=path_mappings.path_replace_reverse(media_path),
subtitles_path=path_mappings.path_replace_reverse(subtitles_path))
store_subtitles(path_mappings.path_replace_reverse(media_path), media_path)
notify_sonarr(sonarr_series_id)
event_stream(type='episode-wanted', action='update', payload=sonarr_episode_id)
history_log(0, series_id, episode_id, result, language=language_log,
video_path=media_path, subtitles_path=subtitles_path)
store_subtitles(media_path)
event_stream(type='episode-wanted', action='update', payload=episode_id)
return True
else:
try:
os.remove(path_mappings.path_replace_movie(subtitles_path))
os.remove(subtitles_path)
except OSError:
logging.exception('BAZARR cannot delete subtitles file: ' + subtitles_path)
store_subtitles_movie(path_mappings.path_replace_reverse_movie(media_path), media_path)
store_subtitles_movie(media_path)
return False
else:
history_log_movie(0, radarr_id, result, language=language_log,
video_path=path_mappings.path_replace_reverse_movie(media_path),
subtitles_path=path_mappings.path_replace_reverse_movie(subtitles_path))
store_subtitles_movie(path_mappings.path_replace_reverse_movie(media_path), media_path)
notify_radarr(radarr_id)
event_stream(type='movie-wanted', action='update', payload=radarr_id)
history_log_movie(0, movie_id, result, language=language_log, video_path=media_path,
subtitles_path=subtitles_path)
store_subtitles_movie(media_path)
event_stream(type='movie-wanted', action='update', payload=movie_id)
return True
@ -492,11 +369,11 @@ def check_credentials(user, pw):
def check_health():
from get_rootfolder import check_sonarr_rootfolder, check_radarr_rootfolder
if settings.general.getboolean('use_sonarr'):
check_sonarr_rootfolder()
if settings.general.getboolean('use_radarr'):
check_radarr_rootfolder()
from get_rootfolder import check_series_rootfolder, check_movies_rootfolder
if settings.general.getboolean('use_series'):
check_series_rootfolder()
if settings.general.getboolean('use_movies'):
check_movies_rootfolder()
event_stream(type='badges')
@ -504,26 +381,4 @@ def get_health_issues():
# this function must return a list of dictionaries consisting of to keys: object and issue
health_issues = []
# get Sonarr rootfolder issues
if settings.general.getboolean('use_sonarr'):
rootfolder = TableShowsRootfolder.select(TableShowsRootfolder.path,
TableShowsRootfolder.accessible,
TableShowsRootfolder.error)\
.where(TableShowsRootfolder.accessible == 0)\
.dicts()
for item in rootfolder:
health_issues.append({'object': path_mappings.path_replace(item['path']),
'issue': item['error']})
# get Radarr rootfolder issues
if settings.general.getboolean('use_radarr'):
rootfolder = TableMoviesRootfolder.select(TableMoviesRootfolder.path,
TableMoviesRootfolder.accessible,
TableMoviesRootfolder.error)\
.where(TableMoviesRootfolder.accessible == 0)\
.dicts()
for item in rootfolder:
health_issues.append({'object': path_mappings.path_replace_movie(item['path']),
'issue': item['error']})
return health_issues

@ -0,0 +1,179 @@
[general]
ip = 0.0.0.0
port = 6767
base_url =
debug = False
branch = master
auto_update = True
single_language = False
minimum_score = 90
use_postprocessing = False
postprocessing_cmd =
postprocessing_threshold = 90
use_postprocessing_threshold = False
postprocessing_threshold_movie = 70
use_postprocessing_threshold_movie = False
use_series = False
use_movies = False
serie_default_enabled = False
serie_default_profile =
movie_default_enabled = False
movie_default_profile =
page_size = 25
page_size_manual_search = 10
minimum_score_movie = 70
use_embedded_subs = True
embedded_subs_show_desired = True
utf8_encode = True
ignore_pgs_subs = False
ignore_vobsub_subs = False
ignore_ass_subs = False
adaptive_searching = False
enabled_providers = []
multithreading = True
chmod_enabled = False
chmod = 0640
subfolder = current
subfolder_custom =
upgrade_subs = True
upgrade_frequency = 12
days_to_upgrade_subs = 7
upgrade_manual = True
anti_captcha_provider = None
wanted_search_frequency = 3
wanted_search_frequency_movie = 3
subzero_mods = []
dont_notify_manual_actions = False
flask_secret_key = d80a91a952aca19d18b53e9c9e1eae0f
[auth]
type = None
username =
password =
apikey = 85730a918fb9e1d77e6ada8b035a88c1
[series]
full_update = Daily
full_update_day = 6
full_update_hour = 4
only_monitored = False
excluded_tags = []
excluded_series_types = []
use_ffprobe_cache = True
[movies]
full_update = Daily
full_update_day = 6
full_update_hour = 5
only_monitored = False
excluded_tags = []
use_ffprobe_cache = True
[proxy]
type = None
url =
port =
username =
password =
exclude = ["localhost","127.0.0.1"]
[anticaptcha]
anti_captcha_key =
[deathbycaptcha]
username =
password =
[analytics]
enabled = True
[subsync]
use_subsync = False
use_subsync_threshold = False
subsync_threshold = 90
use_subsync_movie_threshold = False
subsync_movie_threshold = 70
debug = False
[series_scores]
hash = 359
series = 180
year = 90
season = 30
episode = 30
release_group = 15
source = 7
audio_codec = 3
resolution = 2
video_codec = 2
hearing_impaired = 1
streaming_service = 0
edition = 0
[movie_scores]
hash = 119
title = 60
year = 30
release_group = 15
source = 7
audio_codec = 3
resolution = 2
video_codec = 2
hearing_impaired = 1
streaming_service = 0
edition = 0
[opensubtitles]
username =
password =
use_tag_search = False
vip = False
ssl = False
timeout = 15
skip_wrong_fps = False
[opensubtitlescom]
username =
password =
use_hash = True
[addic7ed]
username =
password =
[legendasdivx]
username =
password =
skip_wrong_fps = False
[ktuvit]
email =
hashed_password =
[legendastv]
username =
password =
featured_only = False
[xsubs]
username =
password =
[assrt]
token =
[napisy24]
username =
password =
[subscene]
username =
password =
[betaseries]
token =
[titlovi]
username =
password =

Binary file not shown.

Binary file not shown.

Binary file not shown.

@ -0,0 +1,15 @@
03/09/2021 00:00:10|ERROR |root |Error trying to get releases from Github. Http error.|'Traceback (most recent call last):\n File "/Users/morpheus/Desktop/bazarr/bazarr/check_update.py", line 23, in check_releases\n r.raise_for_status()\n File "/Users/morpheus/Desktop/bazarr/bazarr/../libs/requests/models.py", line 943, in raise_for_status\n raise HTTPError(http_error_msg, response=self)\nrequests.exceptions.HTTPError: 404 Client Error: Not Found for url: https://api.github.com/repos/Bazarr/Bazarr2/releases?per_page=100'|
03/09/2021 13:38:48|ERROR |root |Error trying to get releases from Github. Http error.|'Traceback (most recent call last):\n File "/Users/morpheus/Desktop/bazarr/bazarr/check_update.py", line 23, in check_releases\n r.raise_for_status()\n File "/Users/morpheus/Desktop/bazarr/bazarr/../libs/requests/models.py", line 943, in raise_for_status\n raise HTTPError(http_error_msg, response=self)\nrequests.exceptions.HTTPError: 404 Client Error: Not Found for url: https://api.github.com/repos/Bazarr/Bazarr2/releases?per_page=100'|
03/09/2021 13:39:03|ERROR |root |Error trying to get releases from Github. Http error.|'Traceback (most recent call last):\n File "/Users/morpheus/Desktop/bazarr/bazarr/check_update.py", line 23, in check_releases\n r.raise_for_status()\n File "/Users/morpheus/Desktop/bazarr/bazarr/../libs/requests/models.py", line 943, in raise_for_status\n raise HTTPError(http_error_msg, response=self)\nrequests.exceptions.HTTPError: 404 Client Error: Not Found for url: https://api.github.com/repos/Bazarr/Bazarr2/releases?per_page=100'|
03/09/2021 13:39:25|ERROR |root |Error trying to get releases from Github. Http error.|'Traceback (most recent call last):\n File "/Users/morpheus/Desktop/bazarr/bazarr/check_update.py", line 23, in check_releases\n r.raise_for_status()\n File "/Users/morpheus/Desktop/bazarr/bazarr/../libs/requests/models.py", line 943, in raise_for_status\n raise HTTPError(http_error_msg, response=self)\nrequests.exceptions.HTTPError: 404 Client Error: Not Found for url: https://api.github.com/repos/Bazarr/Bazarr2/releases?per_page=100'|
03/09/2021 13:39:48|ERROR |root |Error trying to get releases from Github. Http error.|'Traceback (most recent call last):\n File "/Users/morpheus/Desktop/bazarr/bazarr/check_update.py", line 23, in check_releases\n r.raise_for_status()\n File "/Users/morpheus/Desktop/bazarr/bazarr/../libs/requests/models.py", line 943, in raise_for_status\n raise HTTPError(http_error_msg, response=self)\nrequests.exceptions.HTTPError: 404 Client Error: Not Found for url: https://api.github.com/repos/Bazarr/Bazarr2/releases?per_page=100'|
03/09/2021 13:39:59|ERROR |root |Error trying to get releases from Github. Http error.|'Traceback (most recent call last):\n File "/Users/morpheus/Desktop/bazarr/bazarr/check_update.py", line 23, in check_releases\n r.raise_for_status()\n File "/Users/morpheus/Desktop/bazarr/bazarr/../libs/requests/models.py", line 943, in raise_for_status\n raise HTTPError(http_error_msg, response=self)\nrequests.exceptions.HTTPError: 404 Client Error: Not Found for url: https://api.github.com/repos/Bazarr/Bazarr2/releases?per_page=100'|
03/09/2021 13:40:01|ERROR |root |Error trying to get releases from Github. Http error.|'Traceback (most recent call last):\n File "/Users/morpheus/Desktop/bazarr/bazarr/check_update.py", line 23, in check_releases\n r.raise_for_status()\n File "/Users/morpheus/Desktop/bazarr/bazarr/../libs/requests/models.py", line 943, in raise_for_status\n raise HTTPError(http_error_msg, response=self)\nrequests.exceptions.HTTPError: 404 Client Error: Not Found for url: https://api.github.com/repos/Bazarr/Bazarr2/releases?per_page=100'|
03/09/2021 13:40:01|INFO |root |BAZARR is started and waiting for request on http://***.***.***.***:6767|
03/09/2021 13:40:47|ERROR |root |Error trying to get releases from Github. Http error.|'Traceback (most recent call last):\n File "/Users/morpheus/Desktop/bazarr/bazarr/check_update.py", line 23, in check_releases\n r.raise_for_status()\n File "/Users/morpheus/Desktop/bazarr/bazarr/../libs/requests/models.py", line 943, in raise_for_status\n raise HTTPError(http_error_msg, response=self)\nrequests.exceptions.HTTPError: 404 Client Error: Not Found for url: https://api.github.com/repos/Bazarr/Bazarr2/releases?per_page=100'|
03/09/2021 13:40:52|ERROR |root |Error trying to get releases from Github. Http error.|'Traceback (most recent call last):\n File "/Users/morpheus/Desktop/bazarr/bazarr/check_update.py", line 23, in check_releases\n r.raise_for_status()\n File "/Users/morpheus/Desktop/bazarr/bazarr/../libs/requests/models.py", line 943, in raise_for_status\n raise HTTPError(http_error_msg, response=self)\nrequests.exceptions.HTTPError: 404 Client Error: Not Found for url: https://api.github.com/repos/Bazarr/Bazarr2/releases?per_page=100'|
03/09/2021 13:40:52|INFO |root |BAZARR is started and waiting for request on http://***.***.***.***:6767|
03/09/2021 13:43:39|ERROR |root |Error trying to get releases from Github. Http error.|'Traceback (most recent call last):\n File "/Users/morpheus/Desktop/bazarr/bazarr/check_update.py", line 23, in check_releases\n r.raise_for_status()\n File "/Users/morpheus/Desktop/bazarr/bazarr/../libs/requests/models.py", line 943, in raise_for_status\n raise HTTPError(http_error_msg, response=self)\nrequests.exceptions.HTTPError: 404 Client Error: Not Found for url: https://api.github.com/repos/Bazarr/Bazarr2/releases?per_page=100'|
03/09/2021 13:43:41|ERROR |root |Error trying to get releases from Github. Http error.|'Traceback (most recent call last):\n File "/Users/morpheus/Desktop/bazarr/bazarr/check_update.py", line 23, in check_releases\n r.raise_for_status()\n File "/Users/morpheus/Desktop/bazarr/bazarr/../libs/requests/models.py", line 943, in raise_for_status\n raise HTTPError(http_error_msg, response=self)\nrequests.exceptions.HTTPError: 404 Client Error: Not Found for url: https://api.github.com/repos/Bazarr/Bazarr2/releases?per_page=100'|
03/09/2021 13:43:41|INFO |root |BAZARR is started and waiting for request on http://***.***.***.***:6767|
03/09/2021 13:46:48|ERROR |root |BAZARR cannot parse releases caching file: /Users/morpheus/Desktop/bazarr/data_autonomous/config/releases.txt|'Traceback (most recent call last):\n File "/Users/morpheus/Desktop/bazarr/bazarr/api.py", line 604, in get\n with io.open(os.path.join(args.config_dir, \'config\', \'releases.txt\'), \'r\', encoding=\'UTF-8\') as f:\nFileNotFoundError: [Errno 2] No such file or directory: \'/Users/morpheus/Desktop/bazarr/data_autonomous/config/releases.txt\''|

@ -0,0 +1,4 @@
02/09/2021 23:56:23|ERROR |root |Error trying to get releases from Github. Http error.|'Traceback (most recent call last):\n File "/Users/morpheus/Desktop/bazarr/bazarr/check_update.py", line 23, in check_releases\n r.raise_for_status()\n File "/Users/morpheus/Desktop/bazarr/bazarr/../libs/requests/models.py", line 943, in raise_for_status\n raise HTTPError(http_error_msg, response=self)\nrequests.exceptions.HTTPError: 404 Client Error: Not Found for url: https://api.github.com/repos/Bazarr/Bazarr2/releases?per_page=100'|
02/09/2021 23:56:57|ERROR |root |Error trying to get releases from Github. Http error.|'Traceback (most recent call last):\n File "/Users/morpheus/Desktop/bazarr/bazarr/check_update.py", line 23, in check_releases\n r.raise_for_status()\n File "/Users/morpheus/Desktop/bazarr/bazarr/../libs/requests/models.py", line 943, in raise_for_status\n raise HTTPError(http_error_msg, response=self)\nrequests.exceptions.HTTPError: 404 Client Error: Not Found for url: https://api.github.com/repos/Bazarr/Bazarr2/releases?per_page=100'|
02/09/2021 23:58:27|ERROR |root |Error trying to get releases from Github. Http error.|'Traceback (most recent call last):\n File "/Users/morpheus/Desktop/bazarr/bazarr/check_update.py", line 23, in check_releases\n r.raise_for_status()\n File "/Users/morpheus/Desktop/bazarr/bazarr/../libs/requests/models.py", line 943, in raise_for_status\n raise HTTPError(http_error_msg, response=self)\nrequests.exceptions.HTTPError: 404 Client Error: Not Found for url: https://api.github.com/repos/Bazarr/Bazarr2/releases?per_page=100'|
02/09/2021 23:59:24|ERROR |root |Error trying to get releases from Github. Http error.|'Traceback (most recent call last):\n File "/Users/morpheus/Desktop/bazarr/bazarr/check_update.py", line 23, in check_releases\n r.raise_for_status()\n File "/Users/morpheus/Desktop/bazarr/bazarr/../libs/requests/models.py", line 943, in raise_for_status\n raise HTTPError(http_error_msg, response=self)\nrequests.exceptions.HTTPError: 404 Client Error: Not Found for url: https://api.github.com/repos/Bazarr/Bazarr2/releases?per_page=100'|

@ -1,7 +1,7 @@
{
"name": "bazarr",
"version": "1.0.0",
"description": "Bazarr is a companion application to Sonarr and Radarr. It manages and downloads subtitles based on your requirements. You define your preferences by TV show or movie and Bazarr takes care of everything for you.",
"description": "Bazarr manages and downloads subtitles based on your requirements. You define your preferences by TV show or movie and Bazarr takes care of everything for you.",
"repository": {
"type": "git",
"url": "git+https://github.com/morpheus65535/bazarr.git"

@ -15,7 +15,7 @@
/>
<meta
name="description"
content="Bazarr is a companion application to Sonarr and Radarr. It manages and downloads subtitles based on your requirements. You define your preferences by TV show or movie and Bazarr takes care of everything for you."
content="Bazarr manages and downloads subtitles based on your requirements. You define your preferences by TV show or movie and Bazarr takes care of everything for you."
/>
<link rel="manifest" href="%PUBLIC_URL%/static/manifest.json" />
</head>

@ -56,7 +56,7 @@ export function useEpisodesBy(seriesId: number) {
const episodes = useReduxStore((d) => d.series.episodeList);
const newContent = useMemo(() => {
return episodes.content.filter((v) => v.sonarrSeriesId === seriesId);
return episodes.content.filter((v) => v.seriesId === seriesId);
}, [seriesId, episodes.content]);
const newList: Async.List<Item.Episode> = useMemo(

@ -23,14 +23,14 @@ export function useIsOffline() {
return useReduxStore((s) => s.site.offline);
}
export function useIsSonarrEnabled() {
export function useIsSeriesEnabled() {
const settings = useSystemSettings();
return settings.content?.general.use_sonarr ?? true;
return settings.content?.general.use_series ?? true;
}
export function useIsRadarrEnabled() {
export function useIsMoviesEnabled() {
const settings = useSystemSettings();
return settings.content?.general.use_radarr ?? true;
return settings.content?.general.use_movies ?? true;
}
export function useShowOnlyDesired() {

@ -30,8 +30,8 @@ interface Movie {
}
const defaultMovie: Movie = {
movieList: AsyncUtility.getDefaultEntity("radarrId"),
wantedMovieList: AsyncUtility.getDefaultEntity("radarrId"),
movieList: AsyncUtility.getDefaultEntity("movieId"),
wantedMovieList: AsyncUtility.getDefaultEntity("movieId"),
historyList: AsyncUtility.getDefaultEntity("id"),
blacklist: AsyncUtility.getDefaultItem(),
};

@ -36,9 +36,9 @@ interface Series {
}
const defaultSeries: Series = {
seriesList: AsyncUtility.getDefaultEntity("sonarrSeriesId"),
wantedEpisodesList: AsyncUtility.getDefaultEntity("sonarrEpisodeId"),
episodeList: AsyncUtility.getDefaultList("sonarrEpisodeId"),
seriesList: AsyncUtility.getDefaultEntity("seriesId"),
wantedEpisodesList: AsyncUtility.getDefaultEntity("episodeId"),
episodeList: AsyncUtility.getDefaultList("episodeId"),
historyList: AsyncUtility.getDefaultEntity("id"),
blacklist: AsyncUtility.getDefaultItem(),
};
@ -61,8 +61,8 @@ const reducer = createReducer(defaultSeries, (builder) => {
const episodes = state.episodeList;
const dirtyIdsSet = new Set(dirtyIds);
const dirtyEpisodeIds = episodes.content
.filter((v) => dirtyIdsSet.has(v.sonarrSeriesId.toString()))
.map((v) => String(v.sonarrEpisodeId));
.filter((v) => dirtyIdsSet.has(v.seriesId.toString()))
.map((v) => String(v.episodeId));
ReducerUtility.markDirty(episodes, dirtyEpisodeIds);
});

@ -73,11 +73,11 @@ interface TagType {
}
interface SeriesIdType {
sonarrSeriesId: number;
seriesId: number;
}
type EpisodeIdType = SeriesIdType & {
sonarrEpisodeId: number;
episodeId: number;
};
interface EpisodeTitleType {
@ -86,7 +86,7 @@ interface EpisodeTitleType {
}
interface MovieIdType {
radarrId: number;
movieId: number;
}
interface TitleType {
@ -121,7 +121,7 @@ declare namespace Item {
hearing_impaired: boolean;
episodeFileCount: number;
episodeMissingCount: number;
seriesType: SonarrSeriesType;
seriesType: SeriesType;
tvdbId: number;
};
@ -168,7 +168,7 @@ declare namespace Wanted {
EpisodeIdType &
EpisodeTitleType & {
episode_number: string;
seriesType: SonarrSeriesType;
seriesType: SeriesType;
};
type Movie = Base & MovieIdType & TitleType;

@ -1,5 +1,5 @@
// Sonarr
type SonarrSeriesType = "Standard" | "Daily" | "Anime";
// Series
type SeriesType = "Standard" | "Daily" | "Anime";
type PythonBoolean = "True" | "False";

@ -10,7 +10,7 @@ declare namespace FormType {
interface OneMovieAction {
action: "search-missing" | "scan-disk";
radarrid: number;
movieid: number;
}
interface OneSerieAction {
@ -49,8 +49,8 @@ declare namespace FormType {
language: string;
hi: boolean;
forced: boolean;
sonarrSeriesId: number;
sonarrEpisodeId: number;
seriesId: number;
episodeId: number;
title: string;
}

@ -4,8 +4,8 @@ interface Settings {
auth: Settings.Auth;
subsync: Settings.Subsync;
analytics: Settings.Analytic;
sonarr: Settings.Sonarr;
radarr: Settings.Radarr;
series: Settings.Series;
movies: Settings.Movies;
// Anitcaptcha
anticaptcha: Settings.Anticaptcha;
deathbycaptcha: Settings.DeathByCaptche;
@ -49,8 +49,6 @@ declare namespace Settings {
movie_default_profile?: number;
serie_default_enabled: boolean;
serie_default_profile?: number;
path_mappings: [string, string][];
path_mappings_movie: [string, string][];
port: number;
upgrade_subs: boolean;
postprocessing_cmd?: string;
@ -68,9 +66,8 @@ declare namespace Settings {
use_postprocessing: boolean;
use_postprocessing_threshold: boolean;
use_postprocessing_threshold_movie: boolean;
use_radarr: boolean;
use_scenename: boolean;
use_sonarr: boolean;
use_movies: boolean;
use_series: boolean;
utf8_encode: boolean;
wanted_search_frequency: number;
wanted_search_frequency_movie: number;
@ -115,36 +112,23 @@ declare namespace Settings {
url: string | null;
}
// Sonarr / Radarr
// Series / Movies
type FullUpdateOptions = "Manually" | "Daily" | "Weekly";
interface Sonarr {
ip: string;
port: number;
base_url?: string;
ssl: boolean;
apikey?: string;
interface Series {
full_update: FullUpdateOptions;
full_update_day: number;
full_update_hour: number;
only_monitored: boolean;
series_sync: number;
episodes_sync: number;
excluded_tags: string[];
excluded_series_types: SonarrSeriesType[];
excluded_series_types: SeriesType[];
}
interface Radarr {
ip: string;
port: number;
base_url?: string;
ssl: boolean;
apikey?: string;
interface Movies {
full_update: FullUpdateOptions;
full_update_day: number;
full_update_hour: number;
only_monitored: boolean;
movies_sync: number;
excluded_tags: string[];
}

@ -14,8 +14,6 @@ declare namespace System {
bazarr_version: string;
operating_system: string;
python_version: string;
radarr_version: string;
sonarr_version: string;
}
interface Health {

@ -35,10 +35,10 @@ async function SearchItem(text: string) {
return results.map<SearchResult>((v) => {
let link: string;
if (v.sonarrSeriesId) {
link = `/series/${v.sonarrSeriesId}`;
} else if (v.radarrId) {
link = `/movies/${v.radarrId}`;
if (v.seriesId) {
link = `/series/${v.seriesId}`;
} else if (v.movieId) {
link = `/movies/${v.movieId}`;
} else {
link = "";
}

@ -23,7 +23,7 @@ const Table: FunctionComponent<Props> = ({ blacklist }) => {
accessor: "title",
className: "text-nowrap",
Cell: (row) => {
const target = `/movies/${row.row.original.radarrId}`;
const target = `/movies/${row.row.original.movieId}`;
return (
<Link to={target}>
<span>{row.value}</span>

@ -23,7 +23,7 @@ const Table: FunctionComponent<Props> = ({ blacklist }) => {
accessor: "seriesTitle",
className: "text-nowrap",
Cell: (row) => {
const target = `/series/${row.row.original.sonarrSeriesId}`;
const target = `/series/${row.row.original.seriesId}`;
return (
<Link to={target}>
<span>{row.value}</span>

@ -70,7 +70,7 @@ const SeriesEpisodesView: FunctionComponent<Props> = (props) => {
const profile = useProfileBy(series.content?.profileId);
const hasTask = useIsAnyTaskRunningWithId([
...episodes.content.map((v) => v.sonarrEpisodeId),
...episodes.content.map((v) => v.episodeId),
id,
]);

@ -34,17 +34,13 @@ interface Props {
const download = (item: Item.Episode, result: SearchResultType) => {
const { language, hearing_impaired, forced, provider, subtitle } = result;
return ProvidersApi.downloadEpisodeSubtitle(
item.sonarrSeriesId,
item.sonarrEpisodeId,
{
language,
hi: hearing_impaired,
forced,
provider,
subtitle,
}
);
return ProvidersApi.downloadEpisodeSubtitle(item.seriesId, item.episodeId, {
language,
hi: hearing_impaired,
forced,
provider,
subtitle,
});
};
const Table: FunctionComponent<Props> = ({
@ -109,10 +105,10 @@ const Table: FunctionComponent<Props> = ({
Cell: ({ row }) => {
const episode = row.original;
const seriesid = episode.sonarrSeriesId;
const seriesid = episode.seriesId;
const elements = useMemo(() => {
const episodeid = episode.sonarrEpisodeId;
const episodeid = episode.episodeId;
const missing = episode.missing_subtitles.map((val, idx) => (
<SubtitleAction
@ -146,7 +142,7 @@ const Table: FunctionComponent<Props> = ({
},
{
Header: "Actions",
accessor: "sonarrEpisodeId",
accessor: "episodeId",
Cell: ({ row, update }) => {
return (
<ButtonGroup>

@ -33,7 +33,7 @@ import Table from "./table";
const download = (item: Item.Movie, result: SearchResultType) => {
const { language, hearing_impaired, forced, provider, subtitle } = result;
return ProvidersApi.downloadMovieSubtitle(item.radarrId, {
return ProvidersApi.downloadMovieSubtitle(item.movieId, {
language,
hi: hearing_impaired,
forced,
@ -92,7 +92,7 @@ const MovieDetailView: FunctionComponent<Props> = ({ match }) => {
item.title,
id,
MoviesApi.action.bind(MoviesApi),
{ action: "scan-disk", radarrid: id }
{ action: "scan-disk", movieid: id }
);
dispatchTask("Scaning Disk...", [task], "Scaning...");
}}
@ -109,7 +109,7 @@ const MovieDetailView: FunctionComponent<Props> = ({ match }) => {
MoviesApi.action.bind(MoviesApi),
{
action: "search-missing",
radarrid: id,
movieid: id,
}
);
dispatchTask("Searching subtitles...", [task], "Searching...");

@ -67,7 +67,7 @@ const Table: FunctionComponent<Props> = ({ movie, profile, disabled }) => {
<AsyncButton
disabled={disabled}
promise={() =>
MoviesApi.downloadSubtitles(movie.radarrId, {
MoviesApi.downloadSubtitles(movie.movieId, {
language: original.code2,
hi: original.hi,
forced: original.forced,
@ -86,7 +86,7 @@ const Table: FunctionComponent<Props> = ({ movie, profile, disabled }) => {
variant="light"
size="sm"
promise={() =>
MoviesApi.deleteSubtitles(movie.radarrId, {
MoviesApi.deleteSubtitles(movie.movieId, {
language: original.code2,
hi: original.hi,
forced: original.forced,

@ -40,7 +40,7 @@ const MovieView: FunctionComponent<Props> = () => {
if (select) {
return value;
} else {
const target = `/movies/${row.original.radarrId}`;
const target = `/movies/${row.original.movieId}`;
return (
<TextPopover text={row.original.sceneName} delay={1}>
<Link to={target}>
@ -91,7 +91,7 @@ const MovieView: FunctionComponent<Props> = () => {
},
},
{
accessor: "radarrId",
accessor: "movieId",
selectHide: true,
Cell: ({ row, update }) => {
return (

@ -27,7 +27,7 @@ const SeriesView: FunctionComponent<Props> = () => {
if (select) {
return value;
} else {
const target = `/series/${row.original.sonarrSeriesId}`;
const target = `/series/${row.original.seriesId}`;
return (
<Link to={target}>
<span>{value}</span>
@ -91,7 +91,7 @@ const SeriesView: FunctionComponent<Props> = () => {
},
},
{
accessor: "sonarrSeriesId",
accessor: "seriesId",
selectHide: true,
Cell: ({ row, update }) => (
<ActionBadge

@ -30,7 +30,7 @@ const MoviesHistoryView: FunctionComponent<Props> = () => {
accessor: "title",
className: "text-nowrap",
Cell: (row) => {
const target = `/movies/${row.row.original.radarrId}`;
const target = `/movies/${row.row.original.movieId}`;
return (
<Link to={target}>
@ -116,9 +116,7 @@ const MoviesHistoryView: FunctionComponent<Props> = () => {
return (
<BlacklistButton
history={original}
promise={(form) =>
MoviesApi.addBlacklist(original.radarrId, form)
}
promise={(form) => MoviesApi.addBlacklist(original.movieId, form)}
></BlacklistButton>
);
},

@ -29,7 +29,7 @@ const SeriesHistoryView: FunctionComponent<Props> = () => {
Header: "Series",
accessor: "seriesTitle",
Cell: (row) => {
const target = `/series/${row.row.original.sonarrSeriesId}`;
const target = `/series/${row.row.original.seriesId}`;
return (
<Link to={target}>
@ -121,12 +121,12 @@ const SeriesHistoryView: FunctionComponent<Props> = () => {
Cell: ({ row }) => {
const original = row.original;
const { sonarrEpisodeId, sonarrSeriesId } = original;
const { episodeId, seriesId } = original;
return (
<BlacklistButton
history={original}
promise={(form) =>
EpisodesApi.addBlacklist(sonarrSeriesId, sonarrEpisodeId, form)
EpisodesApi.addBlacklist(seriesId, episodeId, form)
}
></BlacklistButton>
);

@ -8,7 +8,7 @@ import {
faPlay,
} from "@fortawesome/free-solid-svg-icons";
import { useMemo } from "react";
import { useIsRadarrEnabled, useIsSonarrEnabled } from "../@redux/hooks";
import { useIsMoviesEnabled, useIsSeriesEnabled } from "../@redux/hooks";
import { useReduxStore } from "../@redux/hooks/base";
import BlacklistMoviesView from "../Blacklist/Movies";
import BlacklistSeriesView from "../Blacklist/Series";
@ -21,11 +21,11 @@ import SeriesHistoryView from "../History/Series";
import HistoryStats from "../History/Statistics";
import SettingsGeneralView from "../Settings/General";
import SettingsLanguagesView from "../Settings/Languages";
import SettingsMoviesView from "../Settings/Movies";
import SettingsNotificationsView from "../Settings/Notifications";
import SettingsProvidersView from "../Settings/Providers";
import SettingsRadarrView from "../Settings/Radarr";
import SettingsSchedulerView from "../Settings/Scheduler";
import SettingsSonarrView from "../Settings/Sonarr";
import SettingsSeriesView from "../Settings/Series";
import SettingsSubtitlesView from "../Settings/Subtitles";
import SettingsUIView from "../Settings/UI";
import EmptyPage, { RouterEmptyPath } from "../special-pages/404";
@ -39,8 +39,8 @@ import WantedSeriesView from "../Wanted/Series";
import { Navigation } from "./nav";
export function useNavigationItems() {
const sonarr = useIsSonarrEnabled();
const radarr = useIsRadarrEnabled();
const seriesEnabled = useIsSeriesEnabled();
const moviesEnabled = useIsMoviesEnabled();
const { movies, episodes, providers } = useReduxStore((s) => s.site.badges);
const items = useMemo<Navigation.RouteItem[]>(
@ -56,7 +56,7 @@ export function useNavigationItems() {
name: "Series",
path: "/series",
component: SeriesView,
enabled: sonarr,
enabled: seriesEnabled,
routes: [
{
name: "Episode",
@ -71,7 +71,7 @@ export function useNavigationItems() {
name: "Movies",
path: "/movies",
component: MovieView,
enabled: radarr,
enabled: moviesEnabled,
routes: [
{
name: "Movie Details",
@ -89,13 +89,13 @@ export function useNavigationItems() {
{
name: "Series",
path: "/series",
enabled: sonarr,
enabled: seriesEnabled,
component: SeriesHistoryView,
},
{
name: "Movies",
path: "/movies",
enabled: radarr,
enabled: moviesEnabled,
component: MoviesHistoryView,
},
{
@ -113,13 +113,13 @@ export function useNavigationItems() {
{
name: "Series",
path: "/series",
enabled: sonarr,
enabled: seriesEnabled,
component: BlacklistSeriesView,
},
{
name: "Movies",
path: "/movies",
enabled: radarr,
enabled: moviesEnabled,
component: BlacklistMoviesView,
},
],
@ -133,14 +133,14 @@ export function useNavigationItems() {
name: "Series",
path: "/series",
badge: episodes,
enabled: sonarr,
enabled: seriesEnabled,
component: WantedSeriesView,
},
{
name: "Movies",
path: "/movies",
badge: movies,
enabled: radarr,
enabled: moviesEnabled,
component: WantedMoviesView,
},
],
@ -171,14 +171,14 @@ export function useNavigationItems() {
component: SettingsSubtitlesView,
},
{
name: "Sonarr",
path: "/sonarr",
component: SettingsSonarrView,
name: "Series",
path: "/series",
component: SettingsSeriesView,
},
{
name: "Radarr",
path: "/radarr",
component: SettingsRadarrView,
name: "Movies",
path: "/movies",
component: SettingsMoviesView,
},
{
name: "Notifications",
@ -231,7 +231,7 @@ export function useNavigationItems() {
],
},
],
[episodes, movies, providers, radarr, sonarr]
[episodes, movies, providers, moviesEnabled, seriesEnabled]
);
return items;

@ -177,10 +177,10 @@ const SettingsGeneralView: FunctionComponent = () => {
<Message>
Send anonymous usage information, nothing that can identify you.
This includes information on which providers you use, what languages
you search for, Bazarr, Python, Sonarr, Radarr and what OS version
you are using. We will use this information to prioritize features
and bug fixes. Please, keep this enabled as this is the only way we
have to better understand how you use Bazarr.
you search for, Bazarr, Python and what OS version you are using. We
will use this information to prioritize features and bug fixes.
Please, keep this enabled as this is the only way we have to better
understand how you use Bazarr.
</Message>
</Input>
</Group>

@ -0,0 +1,56 @@
import React, { FunctionComponent } from "react";
import {
Check,
Chips,
CollapseBox,
Group,
Input,
Message,
SettingsProvider,
Slider,
} from "../components";
import { moviesEnabledKey } from "../keys";
interface Props {}
const SettingsMoviesView: FunctionComponent<Props> = () => {
return (
<SettingsProvider title="Movies - Bazarr (Settings)">
<CollapseBox>
<CollapseBox.Control>
<Group header="Use Movies">
<Input>
<Check label="Enabled" settingKey={moviesEnabledKey}></Check>
</Input>
</Group>
</CollapseBox.Control>
<CollapseBox.Content indent={false}>
<Group header="Options">
<Input name="Minimum Score">
<Slider settingKey="settings-general-minimum_score_movie"></Slider>
</Input>
<Input name="Excluded Tags">
<Chips settingKey="settings-movies-excluded_tags"></Chips>
<Message>
Movies with those tags (case sensitive) will be excluded from
automatic download of subtitles.
</Message>
</Input>
<Input>
<Check
label="Download Only Monitored"
settingKey="settings-movies-only_monitored"
></Check>
<Message>
Automatic download of subtitles will only happen for monitored
movies.
</Message>
</Input>
</Group>
</CollapseBox.Content>
</CollapseBox>
</SettingsProvider>
);
};
export default SettingsMoviesView;

@ -1,97 +0,0 @@
import React, { FunctionComponent, useCallback } from "react";
import { InputGroup } from "react-bootstrap";
import {
Check,
Chips,
CollapseBox,
Group,
Input,
Message,
SettingsProvider,
Slider,
Text,
URLTestButton,
} from "../components";
import { PathMappingTable } from "../components/pathMapper";
import { moviesEnabledKey } from "../keys";
interface Props {}
const SettingsRadarrView: FunctionComponent<Props> = () => {
const baseUrlOverride = useCallback((settings: Settings) => {
return settings.radarr.base_url?.slice(1) ?? "";
}, []);
return (
<SettingsProvider title="Radarr - Bazarr (Settings)">
<CollapseBox>
<CollapseBox.Control>
<Group header="Use Radarr">
<Input>
<Check label="Enabled" settingKey={moviesEnabledKey}></Check>
</Input>
</Group>
</CollapseBox.Control>
<CollapseBox.Content indent={false}>
<Group header="Host">
<Input name="Address">
<Text settingKey="settings-radarr-ip"></Text>
<Message>Hostname or IPv4 Address</Message>
</Input>
<Input name="Port">
<Text settingKey="settings-radarr-port"></Text>
</Input>
<Input name="Base URL">
<InputGroup>
<InputGroup.Prepend>
<InputGroup.Text>/</InputGroup.Text>
</InputGroup.Prepend>
<Text
settingKey="settings-radarr-base_url"
override={baseUrlOverride}
beforeStaged={(v) => "/" + v}
></Text>
</InputGroup>
</Input>
<Input name="API Key">
<Text settingKey="settings-radarr-apikey"></Text>
</Input>
<Input>
<Check label="SSL" settingKey="settings-radarr-ssl"></Check>
</Input>
<Input>
<URLTestButton category="radarr"></URLTestButton>
</Input>
</Group>
<Group header="Options">
<Input name="Minimum Score">
<Slider settingKey="settings-general-minimum_score_movie"></Slider>
</Input>
<Input name="Excluded Tags">
<Chips settingKey="settings-radarr-excluded_tags"></Chips>
<Message>
Movies with those tags (case sensitive) in Radarr will be
excluded from automatic download of subtitles.
</Message>
</Input>
<Input>
<Check
label="Download Only Monitored"
settingKey="settings-radarr-only_monitored"
></Check>
<Message>
Automatic download of subtitles will only happen for monitored
movies in Radarr.
</Message>
</Input>
</Group>
<Group header="Path Mappings">
<PathMappingTable type="radarr"></PathMappingTable>
</Group>
</CollapseBox.Content>
</CollapseBox>
</SettingsProvider>
);
};
export default SettingsRadarrView;

@ -8,14 +8,7 @@ import {
Selector,
SettingsProvider,
} from "../components";
import {
dayOptions,
diskUpdateOptions,
episodesSyncOptions,
moviesSyncOptions,
seriesSyncOptions,
upgradeOptions,
} from "./options";
import { dayOptions, diskUpdateOptions, upgradeOptions } from "./options";
const SettingsSchedulerView: FunctionComponent = () => {
const timeOptions = useMemo(() => {
@ -29,32 +22,12 @@ const SettingsSchedulerView: FunctionComponent = () => {
return (
<SettingsProvider title="Scheduler - Bazarr (Settings)">
<Group header="Sonarr/Radarr Sync">
<Input name="Update Series List from Sonarr">
<Selector
options={seriesSyncOptions}
settingKey="settings-sonarr-series_sync"
></Selector>
</Input>
<Input name="Update Episodes List from Sonarr">
<Selector
options={episodesSyncOptions}
settingKey="settings-sonarr-episodes_sync"
></Selector>
</Input>
<Input name="Update Movies List from Radarr">
<Selector
options={moviesSyncOptions}
settingKey="settings-radarr-movies_sync"
></Selector>
</Input>
</Group>
<Group header="Disk Indexing">
<CollapseBox>
<CollapseBox.Control>
<Input name="Update all Episode Subtitles from Disk">
<Selector
settingKey="settings-sonarr-full_update"
settingKey="settings-series-full_update"
options={diskUpdateOptions}
></Selector>
</Input>
@ -62,7 +35,7 @@ const SettingsSchedulerView: FunctionComponent = () => {
<CollapseBox.Content on={(k) => k === "Weekly"}>
<Input name="Day of The Week">
<Selector
settingKey="settings-sonarr-full_update_day"
settingKey="settings-series-full_update_day"
options={dayOptions}
></Selector>
</Input>
@ -70,7 +43,7 @@ const SettingsSchedulerView: FunctionComponent = () => {
<CollapseBox.Content on={(k) => k === "Daily" || k === "Weekly"}>
<Input name="Time of The Day">
<Selector
settingKey="settings-sonarr-full_update_hour"
settingKey="settings-series-full_update_hour"
options={timeOptions}
></Selector>
</Input>
@ -78,7 +51,7 @@ const SettingsSchedulerView: FunctionComponent = () => {
<Input>
<Check
label="Use cached ffprobe results"
settingKey="settings-sonarr-use_ffprobe_cache"
settingKey="settings-series-use_ffprobe_cache"
></Check>
<Message>
If disabled, Bazarr will use ffprobe to index video file
@ -90,7 +63,7 @@ const SettingsSchedulerView: FunctionComponent = () => {
<CollapseBox.Control>
<Input name="Update all Movie Subtitles from Disk">
<Selector
settingKey="settings-radarr-full_update"
settingKey="settings-movies-full_update"
options={diskUpdateOptions}
></Selector>
</Input>
@ -98,7 +71,7 @@ const SettingsSchedulerView: FunctionComponent = () => {
<CollapseBox.Content on={(k) => k === "Weekly"}>
<Input name="Day of The Week">
<Selector
settingKey="settings-radarr-full_update_day"
settingKey="settings-movies-full_update_day"
options={dayOptions}
></Selector>
</Input>
@ -106,7 +79,7 @@ const SettingsSchedulerView: FunctionComponent = () => {
<CollapseBox.Content on={(k) => k === "Daily" || k === "Weekly"}>
<Input name="Time of The Day">
<Selector
settingKey="settings-radarr-full_update_hour"
settingKey="settings-movies-full_update_hour"
options={timeOptions}
></Selector>
</Input>
@ -114,7 +87,7 @@ const SettingsSchedulerView: FunctionComponent = () => {
<Input>
<Check
label="Use cached ffprobe results"
settingKey="settings-radarr-use_ffprobe_cache"
settingKey="settings-movies-use_ffprobe_cache"
></Check>
<Message>
If disabled, Bazarr will use ffprobe to index video file

@ -0,0 +1,69 @@
import React, { FunctionComponent } from "react";
import {
Check,
Chips,
CollapseBox,
Group,
Input,
Message,
Selector,
SettingsProvider,
Slider,
} from "../components";
import { seriesEnabledKey } from "../keys";
import { seriesTypeOptions } from "../options";
interface Props {}
const SettingsSeriesView: FunctionComponent<Props> = () => {
return (
<SettingsProvider title="Series - Bazarr (Settings)">
<CollapseBox>
<CollapseBox.Control>
<Group header="Use Series">
<Input>
<Check label="Enabled" settingKey={seriesEnabledKey}></Check>
</Input>
</Group>
</CollapseBox.Control>
<CollapseBox.Content indent={false}>
<Group header="Options">
<Input name="Minimum Score">
<Slider settingKey="settings-general-minimum_score"></Slider>
</Input>
<Input name="Excluded Tags">
<Chips settingKey="settings-series-excluded_tags"></Chips>
<Message>
Episodes from series with those tags (case sensitive) will be
excluded from automatic download of subtitles.
</Message>
</Input>
<Input name="Excluded Series Types">
<Selector
settingKey="settings-series-excluded_series_types"
multiple
options={seriesTypeOptions}
></Selector>
<Message>
Episodes from series with those types will be excluded from
automatic download of subtitles.
</Message>
</Input>
<Input>
<Check
label="Download Only Monitored"
settingKey="settings-series-only_monitored"
></Check>
<Message>
Automatic download of subtitles will only happen for monitored
episodes.
</Message>
</Input>
</Group>
</CollapseBox.Content>
</CollapseBox>
</SettingsProvider>
);
};
export default SettingsSeriesView;

@ -1,110 +0,0 @@
import React, { FunctionComponent, useCallback } from "react";
import { InputGroup } from "react-bootstrap";
import {
Check,
Chips,
CollapseBox,
Group,
Input,
Message,
Selector,
SettingsProvider,
Slider,
Text,
URLTestButton,
} from "../components";
import { PathMappingTable } from "../components/pathMapper";
import { seriesEnabledKey } from "../keys";
import { seriesTypeOptions } from "../options";
interface Props {}
const SettingsSonarrView: FunctionComponent<Props> = () => {
const baseUrlOverride = useCallback((settings: Settings) => {
return settings.sonarr.base_url?.slice(1) ?? "";
}, []);
return (
<SettingsProvider title="Sonarr - Bazarr (Settings)">
<CollapseBox>
<CollapseBox.Control>
<Group header="Use Sonarr">
<Input>
<Check label="Enabled" settingKey={seriesEnabledKey}></Check>
</Input>
</Group>
</CollapseBox.Control>
<CollapseBox.Content indent={false}>
<Group header="Host">
<Input name="Address">
<Text settingKey="settings-sonarr-ip"></Text>
<Message>Hostname or IPv4 Address</Message>
</Input>
<Input name="Port">
<Text settingKey="settings-sonarr-port"></Text>
</Input>
<Input name="Base URL">
<InputGroup>
<InputGroup.Prepend>
<InputGroup.Text>/</InputGroup.Text>
</InputGroup.Prepend>
<Text
settingKey="settings-sonarr-base_url"
override={baseUrlOverride}
beforeStaged={(v) => "/" + v}
></Text>
</InputGroup>
</Input>
<Input name="API Key">
<Text settingKey="settings-sonarr-apikey"></Text>
</Input>
<Input>
<Check label="SSL" settingKey="settings-sonarr-ssl"></Check>
</Input>
<Input>
<URLTestButton category="sonarr"></URLTestButton>
</Input>
</Group>
<Group header="Options">
<Input name="Minimum Score">
<Slider settingKey="settings-general-minimum_score"></Slider>
</Input>
<Input name="Excluded Tags">
<Chips settingKey="settings-sonarr-excluded_tags"></Chips>
<Message>
Episodes from series with those tags (case sensitive) in Sonarr
will be excluded from automatic download of subtitles.
</Message>
</Input>
<Input name="Excluded Series Types">
<Selector
settingKey="settings-sonarr-excluded_series_types"
multiple
options={seriesTypeOptions}
></Selector>
<Message>
Episodes from series with those types in Sonarr will be excluded
from automatic download of subtitles.
</Message>
</Input>
<Input>
<Check
label="Download Only Monitored"
settingKey="settings-sonarr-only_monitored"
></Check>
<Message>
Automatic download of subtitles will only happen for monitored
episodes in Sonarr.
</Message>
</Input>
</Group>
<Group header="Path Mappings">
<PathMappingTable type="sonarr"></PathMappingTable>
</Group>
</CollapseBox.Content>
</CollapseBox>
</SettingsProvider>
);
};
export default SettingsSonarrView;

@ -461,10 +461,10 @@ const SettingsSubtitlesView: FunctionComponent = () => {
<b>{"{{subtitle_id}}"}</b> Provider ID of the subtitle file
</Message>
<Message>
<b>{"{{series_id}}"}</b> Sonarr series ID (Empty if movie)
<b>{"{{series_id}}"}</b> Series ID (Empty if movie)
</Message>
<Message>
<b>{"{{episode_id}}"}</b> Sonarr episode ID or Radarr movie ID
<b>{"{{episode_id}}"}</b> Episode ID or movie ID
</Message>
</CollapseBox.Content>
</CollapseBox>

@ -1,67 +1,3 @@
import { isBoolean, isNumber, isString } from "lodash";
import React, { FunctionComponent, useCallback, useState } from "react";
import { Button } from "react-bootstrap";
import { UtilsApi } from "../../apis";
import { useLatest } from "./hooks";
export const URLTestButton: FunctionComponent<{
category: "sonarr" | "radarr";
}> = ({ category }) => {
const [title, setTitle] = useState("Test");
const [variant, setVar] = useState("primary");
const address = useLatest<string>(`settings-${category}-ip`, isString);
const port = useLatest<number>(`settings-${category}-port`, isNumber);
const url = useLatest<string>(`settings-${category}-base_url`, isString);
const apikey = useLatest<string>(`settings-${category}-apikey`, isString);
const ssl = useLatest<boolean>(`settings-${category}-ssl`, isBoolean);
const click = useCallback(() => {
if (address && apikey && ssl !== null) {
let testUrl: string;
if (port) {
testUrl = `${address}:${port}${url ?? ""}`;
} else {
testUrl = `${address}${url ?? ""}`;
}
const request = {
protocol: ssl ? "https" : "http",
url: testUrl,
params: {
apikey: apikey,
},
};
if (!request.url.endsWith("/")) {
request.url += "/";
}
UtilsApi.urlTest(request.protocol, request.url, request.params).then(
(result) => {
if (result.status) {
setTitle(`Version: ${result.version}`);
setVar("success");
} else {
setTitle(result.error);
setVar("danger");
}
}
);
}
}, [address, port, url, apikey, ssl]);
return (
<Button
onClick={click}
variant={variant}
title={title}
className="text-truncate text-nowrap"
>
{title}
</Button>
);
};
export * from "./collapse";
export { default as CollapseBox } from "./collapse";
export * from "./container";

@ -1,176 +0,0 @@
import { faArrowCircleRight, faTrash } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { capitalize, isArray, isBoolean } from "lodash";
import React, { FunctionComponent, useCallback, useMemo } from "react";
import { Button } from "react-bootstrap";
import { Column, TableUpdater } from "react-table";
import { FilesApi } from "../../apis";
import { ActionButton, FileBrowser, SimpleTable } from "../../components";
import {
moviesEnabledKey,
pathMappingsKey,
pathMappingsMovieKey,
seriesEnabledKey,
} from "../keys";
import { Message } from "./forms";
import { useExtract, useLatest, useSingleUpdate } from "./hooks";
type SupportType = "sonarr" | "radarr";
function getSupportKey(type: SupportType) {
if (type === "sonarr") {
return pathMappingsKey;
} else {
return pathMappingsMovieKey;
}
}
function getEnabledKey(type: SupportType) {
if (type === "sonarr") {
return seriesEnabledKey;
} else {
return moviesEnabledKey;
}
}
interface PathMappingItem {
from: string;
to: string;
}
interface TableProps {
type: SupportType;
}
export const PathMappingTable: FunctionComponent<TableProps> = ({ type }) => {
const key = getSupportKey(type);
const items = useLatest<[string, string][]>(key, isArray);
const enabledKey = getEnabledKey(type);
const enabled = useExtract<boolean>(enabledKey, isBoolean);
const update = useSingleUpdate();
const updateRow = useCallback(
(newItems: PathMappingItem[]) => {
update(
newItems.map((v) => [v.from, v.to]),
key
);
},
[key, update]
);
const addRow = useCallback(() => {
if (items) {
const newItems = [...items, ["", ""]];
update(newItems, key);
}
}, [items, key, update]);
const data = useMemo<PathMappingItem[]>(
() => items?.map((v) => ({ from: v[0], to: v[1] })) ?? [],
[items]
);
const request = useMemo(() => {
if (type === "sonarr") {
return (path: string) => FilesApi.sonarr(path);
} else {
return (path: string) => FilesApi.radarr(path);
}
}, [type]);
const updateCell = useCallback<TableUpdater<PathMappingItem>>(
(row, item?: PathMappingItem) => {
const newItems = [...data];
if (item) {
newItems[row.index] = item;
} else {
newItems.splice(row.index, 1);
}
updateRow(newItems);
},
[data, updateRow]
);
const columns = useMemo<Column<PathMappingItem>[]>(
() => [
{
Header: capitalize(type),
accessor: "from",
Cell: ({ value, row, update }) => (
<FileBrowser
drop="up"
defaultValue={value}
load={request}
onChange={(path) => {
const newItem = { ...row.original };
newItem.from = path;
update && update(row, newItem);
}}
></FileBrowser>
),
},
{
id: "arrow",
className: "text-center",
Cell: () => (
<FontAwesomeIcon icon={faArrowCircleRight}></FontAwesomeIcon>
),
},
{
Header: "Bazarr",
accessor: "to",
Cell: ({ value, row, update }) => (
<FileBrowser
drop="up"
defaultValue={value}
load={(path) => FilesApi.bazarr(path)}
onChange={(path) => {
const newItem = { ...row.original };
newItem.to = path;
update && update(row, newItem);
}}
></FileBrowser>
),
},
{
id: "action",
accessor: "to",
Cell: ({ row, update }) => (
<ActionButton
icon={faTrash}
onClick={() => {
update && update(row);
}}
></ActionButton>
),
},
],
[type, request]
);
if (enabled) {
return (
<React.Fragment>
<SimpleTable
emptyText="No Mapping"
responsive={false}
columns={columns}
data={data}
update={updateCell}
></SimpleTable>
<Button block variant="light" onClick={addRow}>
Add
</Button>
</React.Fragment>
);
} else {
return (
<Message>
Path Mappings will be available after staged changes are saved
</Message>
);
}
};

@ -2,8 +2,5 @@ export const enabledLanguageKey = "languages-enabled";
export const languageProfileKey = "languages-profiles";
export const notificationsKey = "notifications-providers";
export const pathMappingsKey = "settings-general-path_mappings";
export const pathMappingsMovieKey = "settings-general-path_mappings_movie";
export const seriesEnabledKey = "settings-general-use_sonarr";
export const moviesEnabledKey = "settings-general-use_radarr";
export const seriesEnabledKey = "settings-general-use_seris";
export const moviesEnabledKey = "settings-general-use_movies";

@ -88,12 +88,6 @@ const SystemStatusView: FunctionComponent<Props> = () => {
<CRow title="Bazarr Version">
<span>{status?.bazarr_version}</span>
</CRow>
<CRow title="Sonarr Version">
<span>{status?.sonarr_version}</span>
</CRow>
<CRow title="Radarr Version">
<span>{status?.radarr_version}</span>
</CRow>
<CRow title="Operating System">
<span>{status?.operating_system}</span>
</CRow>

@ -30,7 +30,7 @@ const WantedMoviesView: FunctionComponent<Props> = () => {
Header: "Name",
accessor: "title",
Cell: (row) => {
const target = `/movies/${row.row.original.radarrId}`;
const target = `/movies/${row.row.original.movieId}`;
return (
<Link to={target}>
<span>{row.value}</span>
@ -44,7 +44,7 @@ const WantedMoviesView: FunctionComponent<Props> = () => {
Cell: ({ row, value, update }) => {
const wanted = row.original;
const hi = wanted.hearing_impaired;
const movieid = wanted.radarrId;
const movieid = wanted.movieId;
return value.map((item, idx) => (
<AsyncButton

@ -30,7 +30,7 @@ const WantedSeriesView: FunctionComponent<Props> = () => {
Header: "Name",
accessor: "seriesTitle",
Cell: (row) => {
const target = `/series/${row.row.original.sonarrSeriesId}`;
const target = `/series/${row.row.original.seriesId}`;
return (
<Link to={target}>
<span>{row.value}</span>
@ -51,8 +51,8 @@ const WantedSeriesView: FunctionComponent<Props> = () => {
Cell: ({ row, update, value }) => {
const wanted = row.original;
const hi = wanted.hearing_impaired;
const seriesid = wanted.sonarrSeriesId;
const episodeid = wanted.sonarrEpisodeId;
const seriesid = wanted.seriesId;
const episodeid = wanted.episodeId;
return value.map((item, idx) => (
<AsyncButton

@ -13,14 +13,6 @@ class FilesApi extends BaseApi {
async bazarr(path?: string) {
return this.browse("", path);
}
async sonarr(path?: string) {
return this.browse("/sonarr", path);
}
async radarr(path?: string) {
return this.browse("/radarr", path);
}
}
export default new FilesApi();

@ -12,17 +12,17 @@ class MovieApi extends BaseApi {
return response.data;
}
async addBlacklist(radarrid: number, form: FormType.AddBlacklist) {
await this.post("/blacklist", form, { radarrid });
async addBlacklist(movieid: number, form: FormType.AddBlacklist) {
await this.post("/blacklist", form, { movieid });
}
async deleteBlacklist(all?: boolean, form?: FormType.DeleteBlacklist) {
await this.delete("/blacklist", form, { all });
}
async movies(radarrid?: number[]) {
async movies(movieid?: number[]) {
const response = await this.get<AsyncDataWrapper<Item.Movie>>("", {
radarrid,
movieid,
});
return response;
}
@ -33,7 +33,7 @@ class MovieApi extends BaseApi {
}
async modify(form: FormType.ModifyItem) {
await this.post("", { radarrid: form.id, profileid: form.profileid });
await this.post("", { movieid: form.id, profileid: form.profileid });
}
async wanted(params: Parameter.Range) {
@ -44,9 +44,9 @@ class MovieApi extends BaseApi {
return response;
}
async wantedBy(radarrid: number[]) {
async wantedBy(movieid: number[]) {
const response = await this.get<AsyncDataWrapper<Wanted.Movie>>("/wanted", {
radarrid,
movieid,
});
return response;
}
@ -59,10 +59,10 @@ class MovieApi extends BaseApi {
return response;
}
async historyBy(radarrid: number) {
async historyBy(movieid: number) {
const response = await this.get<AsyncDataWrapper<History.Movie>>(
"/history",
{ radarrid }
{ movieid }
);
return response;
}
@ -71,16 +71,16 @@ class MovieApi extends BaseApi {
await this.patch("", action);
}
async downloadSubtitles(radarrid: number, form: FormType.Subtitle) {
await this.patch("/subtitles", form, { radarrid });
async downloadSubtitles(movieid: number, form: FormType.Subtitle) {
await this.patch("/subtitles", form, { movieid });
}
async uploadSubtitles(radarrid: number, form: FormType.UploadSubtitle) {
await this.post("/subtitles", form, { radarrid });
async uploadSubtitles(movieid: number, form: FormType.UploadSubtitle) {
await this.post("/subtitles", form, { movieid });
}
async deleteSubtitles(radarrid: number, form: FormType.DeleteSubtitle) {
await this.delete("/subtitles", form, { radarrid });
async deleteSubtitles(movieid: number, form: FormType.DeleteSubtitle) {
await this.delete("/subtitles", form, { movieid });
}
}

@ -19,13 +19,13 @@ class ProviderApi extends BaseApi {
async movies(id: number) {
const response = await this.get<DataWrapper<SearchResultType[]>>(
"/movies",
{ radarrid: id }
{ movieid: id }
);
return response.data;
}
async downloadMovieSubtitle(radarrid: number, form: FormType.ManualDownload) {
await this.post("/movies", form, { radarrid });
async downloadMovieSubtitle(movieid: number, form: FormType.ManualDownload) {
await this.post("/movies", form, { movieid });
}
async episodes(episodeid: number) {

@ -19,13 +19,13 @@ export const MovieHistoryModal: FunctionComponent<BaseModalProps> = (props) => {
const update = useCallback(() => {
if (movie) {
updateHistory(movie.radarrId);
updateHistory(movie.movieId);
}
}, [movie, updateHistory]);
useDidUpdate(() => {
update();
}, [movie?.radarrId]);
}, [movie?.movieId]);
const columns = useMemo<Column<History.Movie>[]>(
() => [
@ -78,9 +78,7 @@ export const MovieHistoryModal: FunctionComponent<BaseModalProps> = (props) => {
return (
<BlacklistButton
update={update}
promise={(form) =>
MoviesApi.addBlacklist(original.radarrId, form)
}
promise={(form) => MoviesApi.addBlacklist(original.movieId, form)}
history={original}
></BlacklistButton>
);
@ -118,13 +116,13 @@ export const EpisodeHistoryModal: FunctionComponent<
const update = useCallback(() => {
if (episode) {
updateHistory(episode.sonarrEpisodeId);
updateHistory(episode.episodeId);
}
}, [episode, updateHistory]);
useDidUpdate(() => {
update();
}, [episode?.sonarrEpisodeId]);
}, [episode?.episodeId]);
const columns = useMemo<Column<History.Episode>[]>(
() => [
@ -174,13 +172,13 @@ export const EpisodeHistoryModal: FunctionComponent<
accessor: "blacklisted",
Cell: ({ row }) => {
const original = row.original;
const { sonarrSeriesId, sonarrEpisodeId } = original;
const { seriesId, episodeId } = original;
return (
<BlacklistButton
history={original}
update={update}
promise={(form) =>
EpisodesApi.addBlacklist(sonarrSeriesId, sonarrEpisodeId, form)
EpisodesApi.addBlacklist(seriesId, episodeId, form)
}
></BlacklistButton>
);

@ -65,9 +65,9 @@ export function ManualSearchModal<T extends SupportType>(
setSearchState(SearchState.Searching);
let results: SearchResultType[] = [];
if (isMovie(item)) {
results = await ProvidersApi.movies(item.radarrId);
results = await ProvidersApi.movies(item.movieId);
} else {
results = await ProvidersApi.episodes(item.sonarrEpisodeId);
results = await ProvidersApi.episodes(item.episodeId);
}
setResult(results);
setSearchState(SearchState.Finished);

@ -57,7 +57,7 @@ const MovieUploadModal: FunctionComponent<BaseModalProps> = (props) => {
return;
}
const { radarrId } = payload;
const { movieId } = payload;
const tasks = items
.filter((v) => v.language !== null)
@ -66,9 +66,9 @@ const MovieUploadModal: FunctionComponent<BaseModalProps> = (props) => {
return createTask(
file.name,
radarrId,
movieId,
MoviesApi.uploadSubtitles.bind(MoviesApi),
radarrId,
movieId,
{
file,
forced,

@ -85,14 +85,14 @@ const SeriesUploadModal: FunctionComponent<SeriesProps & BaseModalProps> = ({
return;
}
const { sonarrSeriesId: seriesid } = payload;
const { seriesId: seriesid } = payload;
const tasks = items
.filter((v) => v.payload.instance !== undefined)
.map((v) => {
const { hi, forced, payload, language } = v;
const { code2 } = language!;
const { sonarrEpisodeId: episodeid } = payload.instance!;
const { episodeId: episodeid } = payload.instance!;
const form: FormType.UploadSubtitle = {
file: v.file,

@ -58,9 +58,9 @@ type TableColumnType = FormType.ModifySubtitle & {
function getIdAndType(item: SupportType): [number, "episode" | "movie"] {
if (isMovie(item)) {
return [item.radarrId, "movie"];
return [item.movieId, "movie"];
} else {
return [item.sonarrEpisodeId, "episode"];
return [item.episodeId, "episode"];
}
}

@ -27,11 +27,11 @@ export function submodProcessColor(s: string) {
export function GetItemId<T extends object>(item: T): number {
if (isMovie(item)) {
return item.radarrId;
return item.movieId;
} else if (isEpisode(item)) {
return item.sonarrEpisodeId;
return item.episodeId;
} else if (isSeries(item)) {
return item.sonarrSeriesId;
return item.seriesId;
} else {
return -1;
}

@ -14,11 +14,11 @@ export function isNonNullable(v: any): v is NonNullable<any> {
}
export function isMovie(v: any): v is Item.Movie {
return "radarrId" in v;
return "movieId" in v;
}
export function isEpisode(v: any): v is Item.Episode {
return "sonarrEpisodeId" in v;
return "episodeId" in v;
}
export function isSeries(v: any): v is Item.Series {

Loading…
Cancel
Save