Merge development into master

This commit is contained in:
github-actions[bot] 2021-08-13 12:06:07 +00:00 committed by GitHub
commit 87bc9ecd29
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 536 additions and 315 deletions

0
.github/scripts/build_test.sh vendored Normal file → Executable file
View File

View File

@ -23,6 +23,6 @@ done
cp VERSION $to_dist
pushd __builds__/bazarr
zip -r ../bazarr.zip . -x '*.map' -b $(mktemp -d)
zip -r ../bazarr.zip . -b $(mktemp -d)
popd
rm -rf $to_dist

View File

@ -12,10 +12,10 @@ from pyga.entities import CustomVariable
from get_args import args
from config import settings
from utils import get_sonarr_version, get_radarr_version
from utils import get_sonarr_info, get_radarr_info
sonarr_version = get_sonarr_version()
radarr_version = get_radarr_version()
sonarr_version = get_sonarr_info.version()
radarr_version = get_radarr_info.version()
def track_event(category=None, action=None, label=None):

View File

@ -1,5 +1,7 @@
# coding=utf-8
import sys
import os
import ast
from datetime import timedelta
from dateutil import rrule
@ -15,6 +17,8 @@ import hashlib
import apprise
import gc
from peewee import fn, Value
import requests
from bs4 import BeautifulSoup as bso
from get_args import args
from config import settings, base_url, save_settings, get_settings
@ -35,7 +39,7 @@ from notifier import send_notifications, send_notifications_movie
from list_subtitles import store_subtitles, store_subtitles_movie, series_scan_subtitles, movies_scan_subtitles, \
list_missing_subtitles, list_missing_subtitles_movies
from utils import history_log, history_log_movie, blacklist_log, blacklist_delete, blacklist_delete_all, \
blacklist_log_movie, blacklist_delete_movie, blacklist_delete_all_movie, get_sonarr_version, get_radarr_version, \
blacklist_log_movie, blacklist_delete_movie, blacklist_delete_all_movie, get_sonarr_info, get_radarr_info, \
delete_subtitles, subtitles_apply_mods, translate_subtitles_file, check_credentials, get_health_issues
from get_providers import get_providers, get_providers_auth, list_throttled_providers, reset_throttled_providers, \
get_throttled_providers, set_throttled_providers
@ -596,8 +600,8 @@ class SystemStatus(Resource):
def get(self):
system_status = {}
system_status.update({'bazarr_version': os.environ["BAZARR_VERSION"]})
system_status.update({'sonarr_version': get_sonarr_version()})
system_status.update({'radarr_version': get_radarr_version()})
system_status.update({'sonarr_version': get_sonarr_info.version()})
system_status.update({'radarr_version': get_radarr_info.version()})
system_status.update({'operating_system': platform.platform()})
system_status.update({'python_version': platform.python_version()})
system_status.update({'bazarr_directory': os.path.dirname(os.path.dirname(__file__))})
@ -716,6 +720,15 @@ class Series(Resource):
list_missing_subtitles(no=seriesId, send_event=False)
event_stream(type='series', payload=seriesId)
episode_id_list = TableEpisodes\
.select(TableEpisodes.sonarrEpisodeId)\
.where(TableEpisodes.sonarrSeriesId == seriesId)\
.dicts()
for item in episode_id_list:
event_stream(type='episode-wanted', payload=item['sonarrEpisodeId'])
event_stream(type='badges')
return '', 204
@ -963,6 +976,7 @@ class Movies(Resource):
list_missing_subtitles_movies(no=radarrId, send_event=False)
event_stream(type='movie', payload=radarrId)
event_stream(type='movie-wanted', payload=radarrId)
event_stream(type='badges')
return '', 204
@ -1516,7 +1530,7 @@ class MoviesHistory(Resource):
if int(upgradable_movie['score']) < 120:
upgradable_movies_not_perfect.append(upgradable_movie)
query_conditions = [(TableMovies is not None)]
query_conditions = [(TableMovies.title is not None)]
if radarrid:
query_conditions.append((TableMovies.radarrId == radarrid))
query_condition = reduce(operator.and_, query_conditions)
@ -1696,6 +1710,7 @@ class EpisodesWanted(Resource):
TableShows.seriesType)\
.join(TableShows, on=(TableEpisodes.sonarrSeriesId == TableShows.sonarrSeriesId))\
.where(wanted_condition)\
.order_by(TableEpisodes.rowid.desc())\
.limit(length)\
.offset(start)\
.dicts()
@ -1749,7 +1764,7 @@ class MoviesWanted(Resource):
TableMovies.tags,
TableMovies.monitored)\
.where(wanted_condition)\
.order_by(TableMovies.radarrId.desc())\
.order_by(TableMovies.rowid.desc())\
.limit(length)\
.offset(start)\
.dicts()
@ -1966,6 +1981,12 @@ class Subtitles(Resource):
else:
subtitles_apply_mods(language, subtitles_path, [action])
# apply chmod if required
chmod = int(settings.general.chmod, 8) if not sys.platform.startswith(
'win') and settings.general.getboolean('chmod_enabled') else None
if chmod:
os.chmod(subtitles_path, chmod)
return '', 204
@ -2045,6 +2066,68 @@ class BrowseRadarrFS(Resource):
return jsonify(data)
class WebHooksPlex(Resource):
@authenticate
def post(self):
json_webhook = request.form.get('payload')
parsed_json_webhook = json.loads(json_webhook)
event = parsed_json_webhook['event']
if event not in ['media.play']:
return '', 204
media_type = parsed_json_webhook['Metadata']['type']
if media_type == 'episode':
season = parsed_json_webhook['Metadata']['parentIndex']
episode = parsed_json_webhook['Metadata']['index']
else:
season = episode = None
ids = []
for item in parsed_json_webhook['Metadata']['Guid']:
splitted_id = item['id'].split('://')
if len(splitted_id) == 2:
ids.append({splitted_id[0]: splitted_id[1]})
if not ids:
return '', 404
if media_type == 'episode':
try:
episode_imdb_id = [x['imdb'] for x in ids if 'imdb' in x][0]
r = requests.get('https://imdb.com/title/{}'.format(episode_imdb_id),
headers={"User-Agent": os.environ["SZ_USER_AGENT"]})
soup = bso(r.content, "html.parser")
series_imdb_id = soup.find('a', {'class': re.compile(r'SeriesParentLink__ParentTextLink')})['href'].split('/')[2]
except:
return '', 404
else:
sonarrEpisodeId = TableEpisodes.select(TableEpisodes.sonarrEpisodeId) \
.join(TableShows, on=(TableEpisodes.sonarrSeriesId == TableShows.sonarrSeriesId)) \
.where(TableShows.imdbId == series_imdb_id,
TableEpisodes.season == season,
TableEpisodes.episode == episode) \
.dicts() \
.get()
if sonarrEpisodeId:
episode_download_subtitles(no=sonarrEpisodeId['sonarrEpisodeId'], send_progress=True)
else:
try:
movie_imdb_id = [x['imdb'] for x in ids if 'imdb' in x][0]
except:
return '', 404
else:
radarrId = TableMovies.select(TableMovies.radarrId)\
.where(TableMovies.imdbId == movie_imdb_id)\
.dicts()\
.get()
if radarrId:
movies_download_subtitles(no=radarrId['radarrId'])
return '', 200
api.add_resource(Badges, '/badges')
api.add_resource(Providers, '/providers')
@ -2086,3 +2169,5 @@ api.add_resource(HistoryStats, '/history/stats')
api.add_resource(BrowseBazarrFS, '/files')
api.add_resource(BrowseSonarrFS, '/files/sonarr')
api.add_resource(BrowseRadarrFS, '/files/radarr')
api.add_resource(WebHooksPlex, '/webhooks/plex')

View File

@ -1,6 +1,8 @@
# coding=utf-8
import os
import shutil
import re
import logging
import json
import requests
@ -19,13 +21,13 @@ def check_releases():
logging.debug('BAZARR getting releases from Github: {}'.format(url_releases))
r = requests.get(url_releases, allow_redirects=True)
r.raise_for_status()
except requests.exceptions.HTTPError as errh:
except requests.exceptions.HTTPError:
logging.exception("Error trying to get releases from Github. Http error.")
except requests.exceptions.ConnectionError as errc:
except requests.exceptions.ConnectionError:
logging.exception("Error trying to get releases from Github. Connection Error.")
except requests.exceptions.Timeout as errt:
except requests.exceptions.Timeout:
logging.exception("Error trying to get releases from Github. Timeout Error.")
except requests.exceptions.RequestException as err:
except requests.exceptions.RequestException:
logging.exception("Error trying to get releases from Github.")
else:
for release in r.json():
@ -151,6 +153,14 @@ def apply_update():
logging.exception('BAZARR unable to unzip release')
else:
is_updated = True
try:
logging.debug('BAZARR successfully unzipped new release and will now try to delete the leftover '
'files.')
update_cleaner(zipfile=bazarr_zip, bazarr_dir=bazarr_dir, config_dir=args.config_dir)
except:
logging.exception('BAZARR unable to cleanup leftover files after upgrade.')
else:
logging.debug('BAZARR successfully deleted leftover files.')
finally:
logging.debug('BAZARR now deleting release archive')
os.remove(bazarr_zip)
@ -161,3 +171,76 @@ def apply_update():
logging.debug('BAZARR new release have been installed, now we restart')
from server import webserver
webserver.restart()
def update_cleaner(zipfile, bazarr_dir, config_dir):
with ZipFile(zipfile, 'r') as archive:
file_in_zip = archive.namelist()
logging.debug('BAZARR zip file contain {} directories and files'.format(len(file_in_zip)))
separator = os.path.sep
if os.path.sep == '\\':
logging.debug('BAZARR upgrade leftover cleaner is running on Windows. We\'ll fix the zip file separator '
'accordingly.')
for i, item in enumerate(file_in_zip):
file_in_zip[i] = item.replace('/', '\\')
separator += os.path.sep
else:
logging.debug('BAZARR upgrade leftover cleaner is running on something else than Windows. The zip file '
'separator are fine.')
dir_to_ignore = ['^.' + separator,
'^bin' + separator,
'^venv' + separator,
'^WinPython' + separator,
separator + '__pycache__' + separator + '$']
if os.path.abspath(bazarr_dir) in os.path.abspath(config_dir):
dir_to_ignore.append('^' + os.path.relpath(config_dir, bazarr_dir) + os.path.sep)
dir_to_ignore_regex = re.compile('(?:% s)' % '|'.join(dir_to_ignore))
logging.debug('BAZARR upgrade leftover cleaner will ignore directories matching this regex: '
'{}'.format(dir_to_ignore_regex))
file_to_ignore = ['nssm.exe', '7za.exe']
logging.debug('BAZARR upgrade leftover cleaner will ignore those files: {}'.format(', '.join(file_to_ignore)))
extension_to_ignore = ['.pyc']
logging.debug('BAZARR upgrade leftover cleaner will ignore files with those extensions: '
'{}'.format(', '.join(extension_to_ignore)))
file_on_disk = []
folder_list = []
for foldername, subfolders, filenames in os.walk(bazarr_dir):
relative_foldername = os.path.relpath(foldername, bazarr_dir) + os.path.sep
if not dir_to_ignore_regex.findall(relative_foldername):
if relative_foldername not in folder_list:
folder_list.append(relative_foldername)
for file in filenames:
if file in file_to_ignore:
continue
elif os.path.splitext(file)[1] in extension_to_ignore:
continue
elif foldername == bazarr_dir:
file_on_disk.append(file)
else:
current_dir = relative_foldername
filepath = os.path.join(current_dir, file)
if not dir_to_ignore_regex.findall(filepath):
file_on_disk.append(filepath)
logging.debug('BAZARR directory contain {} files'.format(len(file_on_disk)))
logging.debug('BAZARR directory contain {} directories'.format(len(folder_list)))
file_on_disk += folder_list
logging.debug('BAZARR directory contain {} directories and files'.format(len(file_on_disk)))
file_to_remove = list(set(file_on_disk) - set(file_in_zip))
logging.debug('BAZARR will delete {} directories and files'.format(len(file_to_remove)))
logging.debug('BAZARR will delete this: {}'.format(', '.join(file_to_remove)))
for file in file_to_remove:
filepath = os.path.join(bazarr_dir, file)
try:
if os.path.isdir(filepath):
rmtree(filepath, ignore_errors=True)
else:
os.remove(filepath)
except Exception as e:
logging.debug('BAZARR upgrade leftover cleaner cannot delete {}'.format(filepath))

View File

@ -139,7 +139,8 @@ defaults = {
},
'legendastv': {
'username': '',
'password': ''
'password': '',
'featured_only': 'False'
},
'xsubs': {
'username': '',
@ -317,7 +318,7 @@ def save_settings(settings_items):
# Make sure that text based form values aren't pass as list
if isinstance(value, list) and len(value) == 1 and settings_keys[-1] not in array_keys:
value = value[0]
if value in empty_values:
if value in empty_values and value != '':
value = None
# Make sure empty language list are stored correctly
@ -442,11 +443,17 @@ def save_settings(settings_items):
if sonarr_changed:
from signalr_client import sonarr_signalr_client
sonarr_signalr_client.restart()
try:
sonarr_signalr_client.restart()
except:
pass
if radarr_changed:
from signalr_client import radarr_signalr_client
radarr_signalr_client.restart()
try:
radarr_signalr_client.restart()
except:
pass
if update_path_map:
from helper import path_mappings

View File

@ -8,6 +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
@ -69,6 +70,7 @@ class TableBlacklistMovie(BaseModel):
class TableEpisodes(BaseModel):
rowid = RowIDField()
audio_codec = TextField(null=True)
audio_language = TextField(null=True)
episode = IntegerField()
@ -140,6 +142,7 @@ class TableLanguagesProfiles(BaseModel):
class TableMovies(BaseModel):
rowid = RowIDField()
alternativeTitles = TextField(null=True)
audio_codec = TextField(null=True)
audio_language = TextField(null=True)
@ -163,7 +166,7 @@ class TableMovies(BaseModel):
subtitles = TextField(null=True)
tags = TextField(null=True)
title = TextField()
tmdbId = TextField(primary_key=True)
tmdbId = TextField(unique=True)
video_codec = TextField(null=True)
year = TextField(null=True)

View File

@ -6,7 +6,7 @@ import logging
import string
from config import settings, url_sonarr, url_radarr
from utils import get_sonarr_version, get_radarr_version
from utils import get_sonarr_info, get_radarr_info
headers = {"User-Agent": os.environ["SZ_USER_AGENT"]}
@ -46,10 +46,9 @@ def browse_bazarr_filesystem(path='#'):
def browse_sonarr_filesystem(path='#'):
sonarr_version = get_sonarr_version()
if path == '#':
path = ''
if sonarr_version.startswith('2'):
if get_sonarr_info.is_legacy():
url_sonarr_api_filesystem = url_sonarr() + "/api/filesystem?path=" + path + \
"&allowFoldersWithoutTrailingSlashes=true&includeFiles=false&apikey=" + \
settings.sonarr.apikey
@ -77,11 +76,10 @@ def browse_sonarr_filesystem(path='#'):
def browse_radarr_filesystem(path='#'):
radarr_version = get_radarr_version()
if path == '#':
path = ''
if radarr_version.startswith('0'):
if get_radarr_info.is_legacy():
url_radarr_api_filesystem = url_radarr() + "/api/filesystem?path=" + path + \
"&allowFoldersWithoutTrailingSlashes=true&includeFiles=false&apikey=" + \
settings.radarr.apikey

View File

@ -12,7 +12,7 @@ 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_version
from utils import get_sonarr_info
headers = {"User-Agent": os.environ["SZ_USER_AGENT"]}
@ -25,7 +25,6 @@ def update_all_episodes():
def sync_episodes(series_id=None, send_event=True):
logging.debug('BAZARR Starting episodes sync from Sonarr.')
apikey_sonarr = settings.sonarr.apikey
sonarr_version = get_sonarr_version()
# Get current episodes id in DB
current_episodes_db = TableEpisodes.select(TableEpisodes.sonarrEpisodeId,
@ -42,8 +41,7 @@ def sync_episodes(series_id=None, send_event=True):
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,
sonarr_version=sonarr_version)
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, 1):
@ -57,13 +55,12 @@ def sync_episodes(series_id=None, send_event=True):
# 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'],
sonarr_version=sonarr_version)
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 sonarr_version.startswith('3'):
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:
@ -166,7 +163,6 @@ 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
sonarr_version = get_sonarr_version()
# Check if there's a row in database for this episode ID
try:
@ -181,13 +177,13 @@ def sync_one_episode(episode_id):
# 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, sonarr_version=sonarr_version)
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 sonarr_version.startswith('3'):
if not get_sonarr_info.is_legacy():
episodeFile = get_episodesFiles_from_sonarr_api(url=url, apikey_sonarr=apikey_sonarr,
episode_file_id=existing_episode['episode_file_id'])
if episode_data['hasFile']:
@ -292,7 +288,7 @@ def episodeParser(episode):
if 'name' in item:
audio_language.append(item['name'])
else:
audio_language = TableShows.get(TableShows == episode['seriesId']).audio_language
audio_language = TableShows.get(TableShows.sonarrSeriesId == episode['seriesId']).audio_language
if 'mediaInfo' in episode['episodeFile']:
if 'videoCodec' in episode['episodeFile']['mediaInfo']:
@ -336,13 +332,13 @@ def episodeParser(episode):
'file_size': episode['episodeFile']['size']}
def get_series_from_sonarr_api(series_id, url, apikey_sonarr, sonarr_version):
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 sonarr_version.startswith('2') else 'v3/', series_id, apikey_sonarr)
'' 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 sonarr_version.startswith('2') else 'v3/', apikey_sonarr)
'' 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()
@ -372,13 +368,13 @@ def get_series_from_sonarr_api(series_id, url, apikey_sonarr, sonarr_version):
return series_list
def get_episodes_from_sonarr_api(url, apikey_sonarr, sonarr_version, series_id=None, episode_id=None):
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 sonarr_version.startswith('2') else 'v3/', series_id, apikey_sonarr)
'' 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 sonarr_version.startswith('2') else 'v3/', episode_id, apikey_sonarr)
'' if get_sonarr_info.is_legacy() else 'v3/', episode_id, apikey_sonarr)
else:
return

View File

@ -10,7 +10,7 @@ from peewee import DoesNotExist
from config import settings, url_radarr
from helper import path_mappings
from utils import get_radarr_version
from utils import get_radarr_info
from list_subtitles import store_subtitles_movie, movies_full_scan_subtitles
from get_rootfolder import check_radarr_rootfolder
@ -31,7 +31,6 @@ def update_movies(send_event=True):
logging.debug('BAZARR Starting movie sync from Radarr.')
apikey_radarr = settings.radarr.apikey
radarr_version = get_radarr_version()
movie_default_enabled = settings.general.getboolean('movie_default_enabled')
if movie_default_enabled is True:
@ -45,11 +44,10 @@ def update_movies(send_event=True):
pass
else:
audio_profiles = get_profile_list()
tagsDict = get_tags(radarr_version=radarr_version)
tagsDict = get_tags()
# Get movies data from radarr
movies = get_movies_from_radarr_api(radarr_version=radarr_version, url=url_radarr(),
apikey_radarr=apikey_radarr)
movies = get_movies_from_radarr_api(url=url_radarr(), apikey_radarr=apikey_radarr)
if not movies:
return
else:
@ -82,13 +80,11 @@ def update_movies(send_event=True):
if str(movie['tmdbId']) in current_movies_db_list:
movies_to_update.append(movieParser(movie, action='update',
radarr_version=radarr_version,
tags_dict=tagsDict,
movie_default_profile=movie_default_profile,
audio_profiles=audio_profiles))
else:
movies_to_add.append(movieParser(movie, action='insert',
radarr_version=radarr_version,
tags_dict=tagsDict,
movie_default_profile=movie_default_profile,
audio_profiles=audio_profiles))
@ -190,7 +186,6 @@ def update_one_movie(movie_id, action):
existing_movie['path'])))
return
radarr_version = get_radarr_version()
movie_default_enabled = settings.general.getboolean('movie_default_enabled')
if movie_default_enabled is True:
@ -201,24 +196,22 @@ def update_one_movie(movie_id, action):
movie_default_profile = None
audio_profiles = get_profile_list()
tagsDict = get_tags(radarr_version=radarr_version)
tagsDict = get_tags()
try:
# Get movie data from radarr api
movie = None
movie_data = get_movies_from_radarr_api(radarr_version=radarr_version, url=url_radarr(),
apikey_radarr=settings.radarr.apikey, radarr_id=movie_id)
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', radarr_version=radarr_version,
tags_dict=tagsDict, movie_default_profile=movie_default_profile,
audio_profiles=audio_profiles)
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', radarr_version=radarr_version,
tags_dict=tagsDict, movie_default_profile=movie_default_profile,
audio_profiles=audio_profiles)
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
@ -262,10 +255,9 @@ def update_one_movie(movie_id, action):
def get_profile_list():
apikey_radarr = settings.radarr.apikey
radarr_version = get_radarr_version()
profiles_list = []
# Get profiles data from radarr
if radarr_version.startswith('0'):
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
@ -280,7 +272,7 @@ def get_profile_list():
logging.exception("BAZARR Error trying to get profiles from Radarr.")
else:
# Parsing data returned from radarr
if radarr_version.startswith('0'):
if get_radarr_info.is_legacy():
for profile in profiles_json.json():
profiles_list.append([profile['id'], profile['language'].capitalize()])
else:
@ -346,12 +338,12 @@ def RadarrFormatVideoCodec(videoFormat, videoCodecID, videoCodecLibrary):
return videoFormat
def get_tags(radarr_version):
def get_tags():
apikey_radarr = settings.radarr.apikey
tagsDict = []
# Get tags data from Radarr
if radarr_version.startswith('0'):
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
@ -371,7 +363,7 @@ def get_tags(radarr_version):
return tagsDict.json()
def movieParser(movie, action, radarr_version, tags_dict, movie_default_profile, audio_profiles):
def movieParser(movie, action, tags_dict, movie_default_profile, audio_profiles):
if 'movieFile' in movie:
# Detect file separator
if movie['path'][0] == "/":
@ -399,7 +391,7 @@ def movieParser(movie, action, radarr_version, tags_dict, movie_default_profile,
sceneName = None
alternativeTitles = None
if radarr_version.startswith('0'):
if get_radarr_info.is_legacy():
if 'alternativeTitles' in movie:
alternativeTitles = str([item['title'] for item in movie['alternativeTitles']])
else:
@ -422,7 +414,7 @@ def movieParser(movie, action, radarr_version, tags_dict, movie_default_profile,
if 'mediaInfo' in movie['movieFile']:
videoFormat = videoCodecID = videoProfile = videoCodecLibrary = None
if radarr_version.startswith('0'):
if get_radarr_info.is_legacy():
if 'videoFormat' in movie['movieFile']['mediaInfo']: videoFormat = \
movie['movieFile']['mediaInfo']['videoFormat']
else:
@ -437,7 +429,7 @@ def movieParser(movie, action, radarr_version, tags_dict, movie_default_profile,
videoCodec = RadarrFormatVideoCodec(videoFormat, videoCodecID, videoCodecLibrary)
audioFormat = audioCodecID = audioProfile = audioAdditionalFeatures = None
if radarr_version.startswith('0'):
if get_radarr_info.is_legacy():
if 'audioFormat' in movie['movieFile']['mediaInfo']: audioFormat = \
movie['movieFile']['mediaInfo']['audioFormat']
else:
@ -456,7 +448,7 @@ def movieParser(movie, action, radarr_version, tags_dict, movie_default_profile,
audioCodec = None
audio_language = []
if radarr_version.startswith('0'):
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('/')
@ -522,8 +514,8 @@ def movieParser(movie, action, radarr_version, tags_dict, movie_default_profile,
'file_size': movie['movieFile']['size']}
def get_movies_from_radarr_api(radarr_version, url, apikey_radarr, radarr_id=None):
if radarr_version.startswith('0'):
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:

View File

@ -164,6 +164,9 @@ def get_providers_auth():
'legendastv' : {
'username': settings.legendastv.username,
'password': settings.legendastv.password,
'featured_only': settings.legendastv.getboolean(
'featured_only'
),
},
'xsubs' : {
'username': settings.xsubs.username,

View File

@ -7,7 +7,7 @@ import logging
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_version, get_radarr_version
from utils import get_sonarr_info, get_radarr_info
headers = {"User-Agent": os.environ["SZ_USER_AGENT"]}
@ -15,10 +15,9 @@ headers = {"User-Agent": os.environ["SZ_USER_AGENT"]}
def get_sonarr_rootfolder():
apikey_sonarr = settings.sonarr.apikey
sonarr_rootfolder = []
sonarr_version = get_sonarr_version()
# Get root folder data from Sonarr
if sonarr_version.startswith('2'):
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
@ -63,8 +62,11 @@ def check_sonarr_rootfolder():
rootfolder = TableShowsRootfolder.select(TableShowsRootfolder.id, TableShowsRootfolder.path).dicts()
for item in rootfolder:
root_path = item['path']
if not root_path.endswith(os.path.sep):
root_path += os.path.sep
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 '
@ -87,10 +89,9 @@ def check_sonarr_rootfolder():
def get_radarr_rootfolder():
apikey_radarr = settings.radarr.apikey
radarr_rootfolder = []
radarr_version = get_radarr_version()
# Get root folder data from Radarr
if radarr_version.startswith('0'):
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
@ -134,8 +135,11 @@ def check_radarr_rootfolder():
rootfolder = TableMoviesRootfolder.select(TableMoviesRootfolder.id, TableMoviesRootfolder.path).dicts()
for item in rootfolder:
root_path = item['path']
if not root_path.endswith(os.path.sep):
root_path += os.path.sep
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 '

View File

@ -11,7 +11,7 @@ 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_version
from utils import get_sonarr_info
from helper import path_mappings
from event_handler import event_stream, show_progress, hide_progress
@ -24,7 +24,6 @@ def update_series(send_event=True):
if apikey_sonarr is None:
return
sonarr_version = get_sonarr_version()
serie_default_enabled = settings.general.getboolean('serie_default_enabled')
if serie_default_enabled is True:
@ -38,8 +37,7 @@ def update_series(send_event=True):
tagsDict = get_tags()
# Get shows data from Sonarr
series = get_series_from_sonarr_api(url=url_sonarr(), apikey_sonarr=apikey_sonarr,
sonarr_version=sonarr_version)
series = get_series_from_sonarr_api(url=url_sonarr(), apikey_sonarr=apikey_sonarr)
if not series:
return
else:
@ -65,12 +63,12 @@ def update_series(send_event=True):
current_shows_sonarr.append(show['id'])
if show['id'] in current_shows_db_list:
series_to_update.append(seriesParser(show, action='update', sonarr_version=sonarr_version,
tags_dict=tagsDict, serie_default_profile=serie_default_profile,
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', sonarr_version=sonarr_version,
tags_dict=tagsDict, serie_default_profile=serie_default_profile,
series_to_add.append(seriesParser(show, action='insert', tags_dict=tagsDict,
serie_default_profile=serie_default_profile,
audio_profiles=audio_profiles))
if send_event:
@ -137,7 +135,7 @@ def update_series(send_event=True):
def update_one_series(series_id, action):
logging.debug('BAZARR syncing this specific series from RSonarr: {}'.format(series_id))
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:
@ -155,7 +153,6 @@ def update_one_series(series_id, action):
event_stream(type='series', action='delete', payload=int(series_id))
return
sonarr_version = get_sonarr_version()
serie_default_enabled = settings.general.getboolean('serie_default_enabled')
if serie_default_enabled is True:
@ -173,18 +170,19 @@ def update_one_series(series_id, action):
series = None
series_data = get_series_from_sonarr_api(url=url_sonarr(), apikey_sonarr=settings.sonarr.apikey,
sonarr_series_id=int(series_id), sonarr_version=get_sonarr_version())
sonarr_series_id=int(series_id),
sonarr_version=get_sonarr_info.version())
if not series_data:
return
else:
if action == 'updated' and existing_series:
series = seriesParser(series_data, action='update', sonarr_version=sonarr_version,
tags_dict=tagsDict, serie_default_profile=serie_default_profile,
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', sonarr_version=sonarr_version,
tags_dict=tagsDict, serie_default_profile=serie_default_profile,
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.')
@ -208,11 +206,10 @@ def update_one_series(series_id, action):
def get_profile_list():
apikey_sonarr = settings.sonarr.apikey
sonarr_version = get_sonarr_version()
profiles_list = []
# Get profiles data from Sonarr
if sonarr_version.startswith('2'):
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
@ -230,7 +227,7 @@ def get_profile_list():
return None
# Parsing data returned from Sonarr
if sonarr_version.startswith('2'):
if get_sonarr_info.is_legacy():
for profile in profiles_json.json():
profiles_list.append([profile['id'], profile['language'].capitalize()])
else:
@ -253,7 +250,7 @@ def get_tags():
tagsDict = []
# Get tags data from Sonarr
if get_sonarr_version().startswith('2'):
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
@ -273,7 +270,7 @@ def get_tags():
return tagsDict.json()
def seriesParser(show, action, sonarr_version, tags_dict, serie_default_profile, audio_profiles):
def seriesParser(show, action, tags_dict, serie_default_profile, audio_profiles):
overview = show['overview'] if 'overview' in show else ''
poster = ''
fanart = ''
@ -290,7 +287,7 @@ def seriesParser(show, action, sonarr_version, tags_dict, serie_default_profile,
alternate_titles = str([item['title'] for item in show['alternateTitles']])
audio_language = []
if sonarr_version.startswith('2'):
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)
@ -332,9 +329,9 @@ def seriesParser(show, action, sonarr_version, tags_dict, serie_default_profile,
'profileId': serie_default_profile}
def get_series_from_sonarr_api(url, apikey_sonarr, sonarr_version, sonarr_series_id=None):
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 sonarr_version.startswith('2') else 'v3/', sonarr_series_id if sonarr_series_id else "", apikey_sonarr)
'' 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()

View File

@ -806,7 +806,7 @@ def series_download_subtitles(no):
hide_progress(id='series_search_progress_{}'.format(no))
def episode_download_subtitles(no):
def episode_download_subtitles(no, send_progress=False):
conditions = [(TableEpisodes.sonarrEpisodeId == no)]
conditions += get_exclusion_clause('series')
episodes_details = TableEpisodes.select(TableEpisodes.path,
@ -818,7 +818,10 @@ def episode_download_subtitles(no):
TableShows.title,
TableShows.sonarrSeriesId,
TableEpisodes.audio_language,
TableShows.seriesType)\
TableShows.seriesType,
TableEpisodes.title.alias('episodeTitle'),
TableEpisodes.season,
TableEpisodes.episode)\
.join(TableShows, on=(TableEpisodes.sonarrSeriesId == TableShows.sonarrSeriesId))\
.where(reduce(operator.and_, conditions))\
.dicts()
@ -831,6 +834,15 @@ def episode_download_subtitles(no):
for episode in episodes_details:
if providers_list:
if send_progress:
show_progress(id='episode_search_progress_{}'.format(no),
header='Searching missing subtitles...',
name='{0} - S{1:02d}E{2:02d} - {3}'.format(episode['title'],
episode['season'],
episode['episode'],
episode['episodeTitle']),
value=1,
count=1)
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) \
@ -875,6 +887,8 @@ def episode_download_subtitles(no):
history_log(1, episode['sonarrSeriesId'], episode['sonarrEpisodeId'], message, path,
language_code, provider, score, subs_id, subs_path)
send_notifications(episode['sonarrSeriesId'], episode['sonarrEpisodeId'], message)
if send_progress:
hide_progress(id='episode_search_progress_{}'.format(no))
else:
logging.info("BAZARR All providers are throttled")
break

View File

@ -16,7 +16,7 @@ import subliminal
import datetime
# set subliminal_patch user agent
os.environ["SZ_USER_AGENT"] = "Bazarr/1"
os.environ["SZ_USER_AGENT"] = "Bazarr/{}".format(os.environ["BAZARR_VERSION"])
# set anti-captcha provider and key
configure_captcha_func()
@ -42,6 +42,13 @@ if not os.path.exists(os.path.join(args.config_dir, 'cache')):
configure_logging(settings.general.getboolean('debug') or args.debug)
import logging
def is_virtualenv():
# return True if Bazarr have been start from within a virtualenv or venv
base_prefix = getattr(sys, "base_prefix", None) or getattr(sys, "real_prefix", None) or sys.prefix
return base_prefix != sys.prefix
# deploy requirements.txt
if not args.no_update:
try:
@ -57,10 +64,12 @@ if not args.no_update:
else:
logging.info('BAZARR installing requirements...')
try:
subprocess.check_output([sys.executable, '-m', 'pip', 'install', '--user', '-qq',
'--disable-pip-version-check', '--no-color', '-r',
os.path.join(os.path.dirname(__file__), '..', 'requirements.txt')],
stderr=subprocess.STDOUT)
pip_command = [sys.executable, '-m', 'pip', 'install', '-qq', '--disable-pip-version-check',
'-r', os.path.join(os.path.dirname(os.path.dirname(__file__)), 'requirements.txt')]
if not is_virtualenv():
# --user only make sense if not running under venv
pip_command.insert(4, '--user')
subprocess.check_output(pip_command, stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e:
logging.exception('BAZARR requirements.txt installation result: {}'.format(e.stdout))
os._exit(1)

View File

@ -2,7 +2,7 @@
# Gevent monkey patch if gevent available. If not, it will be installed on during the init process.
try:
from gevent import monkey, Greenlet
from gevent import monkey, Greenlet, joinall
except ImportError:
pass
else:
@ -43,7 +43,7 @@ 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_version, get_radarr_version
from utils import check_credentials, get_sonarr_info, get_radarr_info
# Install downloaded update
if bazarr_version != '':
@ -131,8 +131,7 @@ def series_images(url):
url = url.strip("/")
apikey = settings.sonarr.apikey
baseUrl = settings.sonarr.base_url
sonarr_version = get_sonarr_version()
if sonarr_version.startswith('2'):
if get_sonarr_info.is_legacy():
url_image = (url_sonarr() + '/api/' + url.lstrip(baseUrl) + '?apikey=' +
apikey).replace('poster-250', 'poster-500')
else:
@ -151,8 +150,7 @@ def series_images(url):
def movies_images(url):
apikey = settings.radarr.apikey
baseUrl = settings.radarr.base_url
radarr_version = get_radarr_version()
if radarr_version.startswith('0'):
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
@ -204,10 +202,12 @@ def proxy(protocol, url):
return dict(status=False, error=result.raise_for_status())
greenlets = []
if settings.general.getboolean('use_sonarr'):
Greenlet.spawn(sonarr_signalr_client.start)
greenlets.append(Greenlet.spawn(sonarr_signalr_client.start))
if settings.general.getboolean('use_radarr'):
Greenlet.spawn(radarr_signalr_client.start)
greenlets.append(Greenlet.spawn(radarr_signalr_client.start))
joinall(greenlets)
if __name__ == "__main__":

View File

@ -15,7 +15,7 @@ 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_version
from utils import get_sonarr_info
from get_args import args
@ -33,26 +33,31 @@ class SonarrSignalrClient:
self.connection = None
def start(self):
if get_sonarr_version().startswith('2.'):
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.')
return
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 a known issue when Sonarr "
"doesn't have write permission to it's /config/xdg directory.")
self.stop()
logging.info('BAZARR SignalR client for Sonarr is connected and waiting for events.')
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)
'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:

View File

@ -58,6 +58,8 @@ class SubSyncer:
logging.exception('BAZARR an exception occurs during the synchronization process for this subtitles: '
'{0}'.format(self.srtin))
else:
if settings.subsync.getboolean('debug'):
return result
if os.path.isfile(self.srtout):
if not settings.subsync.getboolean('debug'):
os.remove(self.srtin)

View File

@ -236,44 +236,53 @@ def cache_maintenance():
remove_expired(fn, pack_cache_validity)
def get_sonarr_version():
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'
return sonarr_version
def get_sonarr_platform():
sonarr_platform = ''
if settings.general.getboolean('use_sonarr'):
try:
if get_sonarr_version().startswith('2'):
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
else:
sv = url_sonarr() + "/api/v3/system/status?apikey=" + settings.sonarr.apikey
response = requests.get(sv, timeout=60, verify=False, headers=headers).json()
if response['isLinux'] or response['isOsx']:
sonarr_platform = 'posix'
elif response['isWindows']:
sonarr_platform = 'nt'
except Exception:
logging.debug('BAZARR cannot get Sonarr platform')
return sonarr_platform
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_version().startswith('2'):
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
@ -283,47 +292,56 @@ def notify_sonarr(sonarr_series_id):
}
requests.post(url, json=data, timeout=60, verify=False, headers=headers)
except Exception as e:
logging.debug('BAZARR notify Sonarr')
logging.exception('BAZARR cannot notify Sonarr')
def get_radarr_version():
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'
return radarr_version
def get_radarr_platform():
radarr_platform = ''
if settings.general.getboolean('use_radarr'):
try:
if get_radarr_version().startswith('0'):
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
else:
rv = url_radarr() + "/api/v3/system/status?apikey=" + settings.radarr.apikey
response = requests.get(rv, timeout=60, verify=False, headers=headers).json()
if response['isLinux'] or response['isOsx']:
radarr_platform = 'posix'
elif response['isWindows']:
radarr_platform = 'nt'
except Exception:
logging.debug('BAZARR cannot get Radarr platform')
return radarr_platform
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_version().startswith('0'):
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
@ -333,7 +351,7 @@ def notify_radarr(radarr_id):
}
requests.post(url, json=data, timeout=60, verify=False, headers=headers)
except Exception as e:
logging.debug('BAZARR notify Radarr')
logging.exception('BAZARR cannot notify Radarr')
def delete_subtitles(media_type, language, forced, hi, media_path, subtitles_path, sonarr_series_id=None,

View File

@ -1,12 +1,12 @@
{
"name": "bazarr",
"version": "1",
"version": "1.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "bazarr",
"version": "1",
"version": "1.0.0",
"license": "GPL-3",
"dependencies": {
"@fontsource/roboto": "^4.2.2",
@ -19,9 +19,8 @@
"@types/lodash": "^4",
"@types/node": "^15",
"@types/react": "^16",
"@types/react-dom": "^16",
"@types/react-dom": "^17",
"@types/react-helmet": "^6.1",
"@types/react-redux": "^7",
"@types/react-router-dom": "^5",
"@types/react-select": "^4.0.3",
"@types/react-table": "^7",
@ -33,9 +32,9 @@
"http-proxy-middleware": "^0.19",
"lodash": "^4",
"rc-slider": "^9.7",
"react": "^16",
"react": "^17",
"react-bootstrap": "^1",
"react-dom": "^16",
"react-dom": "^17",
"react-helmet": "^6.1",
"react-redux": "^7.2",
"react-router-dom": "^5.2",
@ -2969,11 +2968,11 @@
}
},
"node_modules/@types/react-dom": {
"version": "16.9.12",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.12.tgz",
"integrity": "sha512-i7NPZZpPte3jtVOoW+eLB7G/jsX5OM6GqQnH+lC0nq0rqwlK0x8WcMEvYDgFWqWhWMlTltTimzdMax6wYfZssA==",
"version": "17.0.9",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.9.tgz",
"integrity": "sha512-wIvGxLfgpVDSAMH5utdL9Ngm5Owu0VsGmldro3ORLXV8CShrL8awVj06NuEXFQ5xyaYfdca7Sgbk/50Ri1GdPg==",
"dependencies": {
"@types/react": "^16"
"@types/react": "*"
}
},
"node_modules/@types/react-helmet": {
@ -2985,9 +2984,9 @@
}
},
"node_modules/@types/react-redux": {
"version": "7.1.16",
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.16.tgz",
"integrity": "sha512-f/FKzIrZwZk7YEO9E1yoxIuDNRiDducxkFlkw/GNMGEnK9n4K8wJzlJBghpSuOVDgEUHoDkDF7Gi9lHNQR4siw==",
"version": "7.1.18",
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.18.tgz",
"integrity": "sha512-9iwAsPyJ9DLTRH+OFeIrm9cAbIj1i2ANL3sKQFATqnPWRbg+jEFXyZOKHiQK/N86pNRXbb4HRxAxo0SIX1XwzQ==",
"dependencies": {
"@types/hoist-non-react-statics": "^3.3.0",
"@types/react": "*",
@ -7071,9 +7070,9 @@
}
},
"node_modules/engine.io-client": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-5.0.1.tgz",
"integrity": "sha512-CQtGN3YwfvbxVwpPugcsHe5rHT4KgT49CEcQppNtu9N7WxbPN0MAG27lGaem7bvtCFtGNLSL+GEqXsFSz36jTg==",
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-5.1.2.tgz",
"integrity": "sha512-blRrgXIE0A/eurWXRzvfCLG7uUFJqfTGFsyJzXSK71srMMGJ2VraBLg8Mdw28uUxSpVicepBN9X7asqpD1mZcQ==",
"dependencies": {
"base64-arraybuffer": "0.1.4",
"component-emitter": "~1.3.0",
@ -15776,13 +15775,12 @@
}
},
"node_modules/react": {
"version": "16.14.0",
"resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz",
"integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==",
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",
"integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==",
"dependencies": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
"prop-types": "^15.6.2"
"object-assign": "^4.1.1"
},
"engines": {
"node": ">=0.10.0"
@ -16037,17 +16035,16 @@
}
},
"node_modules/react-dom": {
"version": "16.14.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz",
"integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==",
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
"integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==",
"dependencies": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
"prop-types": "^15.6.2",
"scheduler": "^0.19.1"
"scheduler": "^0.20.2"
},
"peerDependencies": {
"react": "^16.14.0"
"react": "17.0.2"
}
},
"node_modules/react-error-overlay": {
@ -17711,9 +17708,9 @@
}
},
"node_modules/scheduler": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz",
"integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz",
"integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==",
"dependencies": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1"
@ -18223,15 +18220,15 @@
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"node_modules/socket.io-client": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.0.1.tgz",
"integrity": "sha512-6AkaEG5zrVuSVW294cH1chioag9i1OqnCYjKwTc3EBGXbnyb98Lw7yMa40ifLjFj3y6fsFKsd0llbUZUCRf3Qw==",
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.1.3.tgz",
"integrity": "sha512-hISFn6PDpgDifVUiNklLHVPTMv1LAk8poHArfIUdXa+gKgbr0MZbAlquDFqCqsF30yBqa+jg42wgos2FK50BHA==",
"dependencies": {
"@types/component-emitter": "^1.2.10",
"backo2": "~1.0.2",
"component-emitter": "~1.3.0",
"debug": "~4.3.1",
"engine.io-client": "~5.0.0",
"engine.io-client": "~5.1.2",
"parseuri": "0.0.6",
"socket.io-parser": "~4.0.4"
},
@ -24411,11 +24408,11 @@
}
},
"@types/react-dom": {
"version": "16.9.12",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.12.tgz",
"integrity": "sha512-i7NPZZpPte3jtVOoW+eLB7G/jsX5OM6GqQnH+lC0nq0rqwlK0x8WcMEvYDgFWqWhWMlTltTimzdMax6wYfZssA==",
"version": "17.0.9",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.9.tgz",
"integrity": "sha512-wIvGxLfgpVDSAMH5utdL9Ngm5Owu0VsGmldro3ORLXV8CShrL8awVj06NuEXFQ5xyaYfdca7Sgbk/50Ri1GdPg==",
"requires": {
"@types/react": "^16"
"@types/react": "*"
}
},
"@types/react-helmet": {
@ -24427,9 +24424,9 @@
}
},
"@types/react-redux": {
"version": "7.1.16",
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.16.tgz",
"integrity": "sha512-f/FKzIrZwZk7YEO9E1yoxIuDNRiDducxkFlkw/GNMGEnK9n4K8wJzlJBghpSuOVDgEUHoDkDF7Gi9lHNQR4siw==",
"version": "7.1.18",
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.18.tgz",
"integrity": "sha512-9iwAsPyJ9DLTRH+OFeIrm9cAbIj1i2ANL3sKQFATqnPWRbg+jEFXyZOKHiQK/N86pNRXbb4HRxAxo0SIX1XwzQ==",
"requires": {
"@types/hoist-non-react-statics": "^3.3.0",
"@types/react": "*",
@ -27764,9 +27761,9 @@
}
},
"engine.io-client": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-5.0.1.tgz",
"integrity": "sha512-CQtGN3YwfvbxVwpPugcsHe5rHT4KgT49CEcQppNtu9N7WxbPN0MAG27lGaem7bvtCFtGNLSL+GEqXsFSz36jTg==",
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-5.1.2.tgz",
"integrity": "sha512-blRrgXIE0A/eurWXRzvfCLG7uUFJqfTGFsyJzXSK71srMMGJ2VraBLg8Mdw28uUxSpVicepBN9X7asqpD1mZcQ==",
"requires": {
"base64-arraybuffer": "0.1.4",
"component-emitter": "~1.3.0",
@ -34534,13 +34531,12 @@
}
},
"react": {
"version": "16.14.0",
"resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz",
"integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==",
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",
"integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==",
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
"prop-types": "^15.6.2"
"object-assign": "^4.1.1"
}
},
"react-app-polyfill": {
@ -34742,14 +34738,13 @@
}
},
"react-dom": {
"version": "16.14.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz",
"integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==",
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
"integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==",
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
"prop-types": "^15.6.2",
"scheduler": "^0.19.1"
"scheduler": "^0.20.2"
}
},
"react-error-overlay": {
@ -36048,9 +36043,9 @@
}
},
"scheduler": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz",
"integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz",
"integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==",
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1"
@ -36477,15 +36472,15 @@
}
},
"socket.io-client": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.0.1.tgz",
"integrity": "sha512-6AkaEG5zrVuSVW294cH1chioag9i1OqnCYjKwTc3EBGXbnyb98Lw7yMa40ifLjFj3y6fsFKsd0llbUZUCRf3Qw==",
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.1.3.tgz",
"integrity": "sha512-hISFn6PDpgDifVUiNklLHVPTMv1LAk8poHArfIUdXa+gKgbr0MZbAlquDFqCqsF30yBqa+jg42wgos2FK50BHA==",
"requires": {
"@types/component-emitter": "^1.2.10",
"backo2": "~1.0.2",
"component-emitter": "~1.3.0",
"debug": "~4.3.1",
"engine.io-client": "~5.0.0",
"engine.io-client": "~5.1.2",
"parseuri": "0.0.6",
"socket.io-parser": "~4.0.4"
}

View File

@ -1,6 +1,6 @@
{
"name": "bazarr",
"version": "1",
"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.",
"repository": {
"type": "git",
@ -23,9 +23,8 @@
"@types/lodash": "^4",
"@types/node": "^15",
"@types/react": "^16",
"@types/react-dom": "^16",
"@types/react-dom": "^17",
"@types/react-helmet": "^6.1",
"@types/react-redux": "^7",
"@types/react-router-dom": "^5",
"@types/react-select": "^4.0.3",
"@types/react-table": "^7",
@ -37,9 +36,9 @@
"http-proxy-middleware": "^0.19",
"lodash": "^4",
"rc-slider": "^9.7",
"react": "^16",
"react": "^17",
"react-bootstrap": "^1",
"react-dom": "^16",
"react-dom": "^17",
"react-helmet": "^6.1",
"react-redux": "^7.2",
"react-router-dom": "^5.2",

View File

@ -5,13 +5,11 @@ import {
episodeDeleteItems,
episodeUpdateBy,
episodeUpdateById,
movieDeleteWantedItems,
movieUpdateBlacklist,
movieUpdateHistoryList,
movieUpdateList,
movieUpdateWantedList,
providerUpdateList,
seriesDeleteWantedItems,
seriesUpdateBlacklist,
seriesUpdateHistoryList,
seriesUpdateList,
@ -324,18 +322,6 @@ export function useWantedSeries() {
const update = useReduxAction(seriesUpdateWantedList);
const items = useReduxStore((d) => d.series.wantedEpisodesList);
const updateAction = useWrapToOptionalId(update);
const deleteAction = useReduxAction(seriesDeleteWantedItems);
const reducer = useMemo<SocketIO.Reducer>(
() => ({
key: "episode-wanted",
update: updateAction,
delete: deleteAction,
}),
[updateAction, deleteAction]
);
useSocketIOReducer(reducer);
return stateBuilder(items, update);
}
@ -343,18 +329,6 @@ export function useWantedMovies() {
const update = useReduxAction(movieUpdateWantedList);
const items = useReduxStore((d) => d.movie.wantedMovieList);
const updateAction = useWrapToOptionalId(update);
const deleteAction = useReduxAction(movieDeleteWantedItems);
const reducer = useMemo<SocketIO.Reducer>(
() => ({
key: "movie-wanted",
update: updateAction,
delete: deleteAction,
}),
[updateAction, deleteAction]
);
useSocketIOReducer(reducer);
return stateBuilder(items, update);
}

View File

@ -4,7 +4,7 @@ export function defaultAOS(): AsyncOrderState<any> {
data: {
items: [],
order: [],
fetched: false,
dirty: false,
},
};
}

View File

@ -36,7 +36,7 @@ export function updateOrderIdState<T extends LooseObject>(
return {
data: {
...state.data,
fetched: true,
dirty: true,
},
updating: true,
};
@ -44,7 +44,7 @@ export function updateOrderIdState<T extends LooseObject>(
return {
data: {
...state.data,
fetched: true,
dirty: true,
},
updating: false,
error: action.payload.item as Error,
@ -107,7 +107,7 @@ export function updateOrderIdState<T extends LooseObject>(
return {
updating: false,
data: {
fetched: true,
dirty: true,
items: newItems,
order: newOrder,
},
@ -131,7 +131,7 @@ export function deleteOrderListItemBy<T extends LooseObject>(
return {
...state,
data: {
fetched: true,
dirty: true,
items: newItems,
order: newOrder,
},

View File

@ -3,9 +3,13 @@ import {
badgeUpdateAll,
bootstrap,
movieDeleteItems,
movieDeleteWantedItems,
movieUpdateList,
movieUpdateWantedList,
seriesDeleteItems,
seriesDeleteWantedItems,
seriesUpdateList,
seriesUpdateWantedList,
siteAddNotifications,
siteAddProgress,
siteInitializationFailed,
@ -91,6 +95,24 @@ export function createDefaultReducer(): SocketIO.Reducer[] {
update: bindToReduxStore(movieUpdateList),
delete: bindToReduxStore(movieDeleteItems),
},
{
key: "episode-wanted",
update: (ids: number[] | undefined) => {
if (ids) {
reduxStore.dispatch(seriesUpdateWantedList(ids) as any);
}
},
delete: bindToReduxStore(seriesDeleteWantedItems),
},
{
key: "movie-wanted",
update: (ids: number[] | undefined) => {
if (ids) {
reduxStore.dispatch(movieUpdateWantedList(ids) as any);
}
},
delete: bindToReduxStore(movieDeleteWantedItems),
},
{
key: "settings",
any: bindToReduxStore(systemUpdateSettings),

View File

@ -14,7 +14,7 @@ type StorageType = string | null;
interface OrderIdState<T> {
items: IdState<T>;
order: (number | null)[];
fetched: boolean;
dirty: boolean;
}
interface AsyncState<T> {

View File

@ -181,7 +181,9 @@ namespace Settings {
skip_wrong_fps: boolean;
}
interface Legendastv extends BaseProvider {}
interface Legendastv extends BaseProvider {
featured_only: boolean;
}
interface XSubs extends BaseProvider {}

View File

@ -77,6 +77,10 @@ export const ProviderList: Readonly<ProviderInfo[]> = [
defaultKey: {
username: "",
password: "",
featured_only: false,
},
keyNameOverride: {
featured_only: "Only Download Featured",
},
},
{ key: "napiprojekt", description: "Polish Subtitles Provider" },

View File

@ -21,7 +21,7 @@ export default function AsyncPageTable<T extends object>(props: Props<T>) {
const {
updating,
data: { order, items, fetched },
data: { order, items, dirty },
} = aos;
const allPlugins: PluginHook<T>[] = [useDefaultSettings];
@ -85,12 +85,12 @@ export default function AsyncPageTable<T extends object>(props: Props<T>) {
}, [pageIndex]);
useEffect(() => {
const needInit = visibleItemIds.length === 0 && fetched === false;
const needFetch = visibleItemIds.length === 0 && dirty === false;
const needRefresh = !visibleItemIds.every(isNonNullable);
if (needInit || needRefresh) {
if (needFetch || needRefresh) {
loader(pageStart, pageSize);
}
}, [visibleItemIds, pageStart, pageSize, loader, fetched]);
}, [visibleItemIds, pageStart, pageSize, loader, dirty]);
const showLoading = useMemo(
() =>

View File

@ -75,12 +75,12 @@ class GreekSubsProvider(Provider):
r = self.session.get(search_link, timeout=30)
# 404 is returned if the imdb_id was not found
if r.status_code != 404:
r.raise_for_status()
if r.status_code == 404:
logger.debug('IMDB id {} not found on greeksubs'.format(imdb_id))
return subtitles
if r.status_code != 200:
logger.debug('No subtitles found')
return subtitles
r.raise_for_status()
soup_page = ParserBeautifulSoup(r.content.decode('utf-8', 'ignore'), ['html.parser'])

View File

@ -70,7 +70,7 @@ class LegendasTVProvider(_LegendasTVProvider):
languages = {Language(*l) for l in language_converters['legendastv'].to_legendastv.keys()}
subtitle_class = LegendasTVSubtitle
def __init__(self, username=None, password=None):
def __init__(self, username=None, password=None, featured_only=False):
# Provider needs UNRAR installed. If not available raise ConfigurationError
try:
@ -85,6 +85,7 @@ class LegendasTVProvider(_LegendasTVProvider):
self.password = password
self.logged_in = False
self.session = None
self.featured_only = featured_only
@staticmethod
def is_valid_title(title, title_id, sanitized_title, season, year, imdb_id):
@ -209,6 +210,11 @@ class LegendasTVProvider(_LegendasTVProvider):
# iterate over title's archives
for a in archives:
# Check if featured
if self.featured_only and a.featured == False:
logger.info('Subtitle is not featured, skipping')
continue
# compute an expiration time based on the archive timestamp
expiration_time = (datetime.utcnow().replace(tzinfo=pytz.utc) - a.timestamp).total_seconds()

View File

@ -124,6 +124,9 @@ class OpenSubtitlesComProvider(ProviderRetryMixin, Provider):
languages.update(set(Language.rebuild(l, forced=True) for l in languages))
def __init__(self, username=None, password=None, use_hash=True, api_key=None):
if not all((username, password)):
raise ConfigurationError('Username and password must be specified')
if not api_key:
raise ConfigurationError('Api_key must be specified')

View File

@ -152,12 +152,12 @@ class TitulkyProvider(Provider):
"""Titulky Provider."""
languages = {Language(l) for l in ['ces', 'slk']}
server_url = 'https://premium.titulky.com'
server_url = 'https://oldpremium.titulky.com'
sign_out_url = '?Logoff=true'
search_url_series = '?Fulltext={}'
search_url_movies = '?Searching=AdvancedResult&ARelease={}'
dn_url = 'https://premium.titulky.com'
download_url = 'https://premium.titulky.com/idown.php?titulky='
dn_url = 'https://oldpremium.titulky.com'
download_url = 'https://oldpremium.titulky.com/idown.php?titulky='
UserAgent = 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)'