Merge branch 'development' into Flask

# Conflicts:
#	bazarr.py
#	bazarr/get_series.py
#	bazarr/main.py
#	bazarr/scheduler.py
#	views/historymovies.tpl
#	views/historyseries.tpl
#	views/menu.tpl
pull/884/head
Louis Vézina 4 years ago
commit a8e27a6a79

@ -1,14 +1,11 @@
# coding=utf-8
from __future__ import absolute_import
from __future__ import print_function
import bazarr.libs
import subprocess as sp
import time
import os
import sys
import signal
import subprocess
import sys
import time
from bazarr.get_args import args
@ -53,11 +50,11 @@ class DaemonStatus(ProcessRegistry):
def unregister(self, process):
self.__processes.remove(process)
'''
Waits all the provided processes for the specified amount of time in seconds.
'''
@staticmethod
def __wait_for_processes(processes, timeout):
"""
Waits all the provided processes for the specified amount of time in seconds.
"""
reference_ts = time.time()
elapsed = 0
remaining_processes = list(processes)
@ -68,20 +65,21 @@ class DaemonStatus(ProcessRegistry):
remaining_processes.remove(ep)
else:
if remaining_time > 0:
try:
ep.wait(remaining_time)
remaining_processes.remove(ep)
except sp.TimeoutExpired:
pass
try:
ep.wait(remaining_time)
remaining_processes.remove(ep)
except subprocess.TimeoutExpired:
pass
elapsed = time.time() - reference_ts
remaining_time = timeout - elapsed
return remaining_processes
'''
Sends to every single of the specified processes the given signal and (if live_processes is not None) append to it processes which are still alive.
'''
@staticmethod
def __send_signal(processes, signal_no, live_processes=None):
"""
Sends to every single of the specified processes the given signal and (if live_processes is not None) append to
it processes which are still alive.
"""
for ep in processes:
if ep.poll() is None:
if live_processes is not None:
@ -89,13 +87,14 @@ class DaemonStatus(ProcessRegistry):
try:
ep.send_signal(signal_no)
except Exception as e:
print('Failed sending signal %s to process %s because of an unexpected error: %s' % (signal_no, ep.pid, e))
print('Failed sending signal %s to process %s because of an unexpected error: %s' % (
signal_no, ep.pid, e))
return live_processes
'''
Flags this instance as should stop and terminates as smoothly as possible children processes.
'''
def stop(self):
"""
Flags this instance as should stop and terminates as smoothly as possible children processes.
"""
self.__should_stop = True
live_processes = DaemonStatus.__send_signal(self.__processes, signal.SIGINT, list())
live_processes = DaemonStatus.__wait_for_processes(live_processes, 120)
@ -107,8 +106,8 @@ class DaemonStatus(ProcessRegistry):
def start_bazarr(process_registry=ProcessRegistry()):
script = [sys.executable, "-u", os.path.normcase(os.path.join(dir_name, 'bazarr', 'main.py'))] + sys.argv[1:]
ep = sp.Popen(script, stdout=sp.PIPE, stderr=sp.STDOUT, stdin=sp.PIPE)
ep = subprocess.Popen(script, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.PIPE)
process_registry.register(ep)
print("Bazarr starting...")
try:
@ -127,39 +126,39 @@ def start_bazarr(process_registry=ProcessRegistry()):
if __name__ == '__main__':
restartfile = os.path.normcase(os.path.join(args.config_dir, 'bazarr.restart'))
stopfile = os.path.normcase(os.path.join(args.config_dir, 'bazarr.stop'))
try:
os.remove(restartfile)
except:
except Exception:
pass
try:
os.remove(stopfile)
except:
except Exception:
pass
def daemon(bazarr_runner = lambda: start_bazarr()):
def daemon(bazarr_runner=lambda: start_bazarr()):
if os.path.exists(stopfile):
try:
os.remove(stopfile)
except:
except Exception:
print('Unable to delete stop file.')
else:
print('Bazarr exited.')
os._exit(0)
sys.exit(0)
if os.path.exists(restartfile):
try:
os.remove(restartfile)
except:
except Exception:
print('Unable to delete restart file.')
else:
bazarr_runner()
bazarr_runner = lambda: start_bazarr()
should_stop = lambda: False
daemonStatus = DaemonStatus()
@ -174,7 +173,6 @@ if __name__ == '__main__':
should_stop = lambda: daemonStatus.should_stop()
bazarr_runner = lambda: start_bazarr(daemonStatus)
bazarr_runner()
# Keep the script running forever until stop is requested through term or keyboard interrupt

@ -6,15 +6,11 @@ import os
import requests
import logging
from queueconfig import notifications
from collections import OrderedDict
import datetime
from get_args import args
from config import settings, url_sonarr
from list_subtitles import list_missing_subtitles
from database import database, dict_converter
from utils import get_sonarr_version
import six
from helper import path_replace
from websocket_handler import event_stream
@ -22,146 +18,145 @@ from websocket_handler import event_stream
def update_series():
notifications.write(msg="Update series list from Sonarr is running...", queue='get_series')
apikey_sonarr = settings.sonarr.apikey
if apikey_sonarr is None:
return
sonarr_version = get_sonarr_version()
serie_default_enabled = settings.general.getboolean('serie_default_enabled')
serie_default_language = settings.general.serie_default_language
serie_default_hi = settings.general.serie_default_hi
serie_default_forced = settings.general.serie_default_forced
if apikey_sonarr is None:
pass
else:
audio_profiles = get_profile_list()
# Get shows data from Sonarr
url_sonarr_api_series = url_sonarr() + "/api/series?apikey=" + apikey_sonarr
try:
r = requests.get(url_sonarr_api_series, timeout=60, verify=False)
r.raise_for_status()
except requests.exceptions.HTTPError as errh:
logging.exception("BAZARR Error trying to get series from Sonarr. Http error.")
return
except requests.exceptions.ConnectionError as errc:
logging.exception("BAZARR Error trying to get series from Sonarr. Connection Error.")
return
except requests.exceptions.Timeout as errt:
logging.exception("BAZARR Error trying to get series from Sonarr. Timeout Error.")
return
except requests.exceptions.RequestException as err:
logging.exception("BAZARR Error trying to get series from Sonarr.")
return
audio_profiles = get_profile_list()
# Get shows data from Sonarr
url_sonarr_api_series = url_sonarr() + "/api/series?apikey=" + apikey_sonarr
try:
r = requests.get(url_sonarr_api_series, timeout=60, verify=False)
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
# Get current shows in DB
current_shows_db = database.execute("SELECT sonarrSeriesId FROM table_shows")
current_shows_db_list = [x['sonarrSeriesId'] for x in current_shows_db]
current_shows_sonarr = []
series_to_update = []
series_to_add = []
series_list_length = len(r.json())
for i, show in enumerate(r.json(), 1):
notifications.write(msg="Getting series data from Sonarr...", queue='get_series', item=i,
length=series_list_length)
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']])
if sonarr_version.startswith('2'):
audio_language = profile_id_to_language(show['qualityProfileId'], audio_profiles)
else:
audio_language = profile_id_to_language(show['languageProfileId'], audio_profiles)
# 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({'title': show["title"],
'path': show["path"],
'tvdbId': int(show["tvdbId"]),
'sonarrSeriesId': int(show["id"]),
'overview': overview,
'poster': poster,
'fanart': fanart,
'audio_language': audio_language,
'sortTitle': show['sortTitle'],
'year': str(show['year']),
'alternateTitles': alternate_titles})
else:
# Get current shows in DB
current_shows_db = database.execute("SELECT sonarrSeriesId FROM table_shows")
current_shows_db_list = [x['sonarrSeriesId'] for x in current_shows_db]
current_shows_sonarr = []
series_to_update = []
series_to_add = []
altered_series = []
seriesListLength = len(r.json())
for i, show in enumerate(r.json(), 1):
notifications.write(msg="Getting series data from Sonarr...", queue='get_series', item=i, length=seriesListLength)
if 'overview' in show:
overview = show['overview']
else:
overview = ''
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]
if show['alternateTitles'] != None:
alternateTitles = str([item['title'] for item in show['alternateTitles']])
else:
alternateTitles = None
# 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({'title': show["title"],
'path': show["path"],
'tvdbId': int(show["tvdbId"]),
'sonarrSeriesId': int(show["id"]),
'overview': overview,
'poster': poster,
'fanart': fanart,
'audio_language': profile_id_to_language((show['qualityProfileId'] if get_sonarr_version().startswith('2') else show['languageProfileId']), audio_profiles),
'sortTitle': show['sortTitle'],
'year': str(show['year']),
'alternateTitles': alternateTitles})
else:
if serie_default_enabled is True:
series_to_add.append({'title': show["title"],
'path': show["path"],
'tvdbId': show["tvdbId"],
'languages': serie_default_language,
'hearing_impaired': serie_default_hi,
'sonarrSeriesId': show["id"],
'overview': overview,
'poster': poster,
'fanart': fanart,
'audio_language': profile_id_to_language((show['qualityProfileId'] if sonarr_version.startswith('2') else show['languageProfileId']), audio_profiles),
'sortTitle': show['sortTitle'],
'year': show['year'],
'alternateTitles': alternateTitles,
'forced': serie_default_forced})
else:
series_to_add.append({'title': show["title"],
'path': show["path"],
'tvdbId': show["tvdbId"],
'sonarrSeriesId': show["id"],
'overview': overview,
'poster': poster,
'fanart': fanart,
'audio_language': profile_id_to_language((show['qualityProfileId'] if sonarr_version.startswith('2') else show['languageProfileId']), audio_profiles),
'sortTitle': show['sortTitle'],
'year': show['year'],
'alternateTitles': alternateTitles})
# Remove old series from DB
removed_series = list(set(current_shows_db_list) - set(current_shows_sonarr))
for series in removed_series:
database.execute("DELETE FROM table_shows WHERE sonarrSeriesId=?",(series,))
if serie_default_enabled is True:
series_to_add.append({'title': show["title"],
'path': show["path"],
'tvdbId': show["tvdbId"],
'languages': serie_default_language,
'hearing_impaired': serie_default_hi,
'sonarrSeriesId': show["id"],
'overview': overview,
'poster': poster,
'fanart': fanart,
'audio_language': audio_language,
'sortTitle': show['sortTitle'],
'year': show['year'],
'alternateTitles': alternate_titles,
'forced': serie_default_forced})
else:
series_to_add.append({'title': show["title"],
'path': show["path"],
'tvdbId': show["tvdbId"],
'sonarrSeriesId': show["id"],
'overview': overview,
'poster': poster,
'fanart': fanart,
'audio_language': audio_language,
'sortTitle': show['sortTitle'],
'year': show['year'],
'alternateTitles': alternate_titles})
# Remove old series from DB
removed_series = list(set(current_shows_db_list) - set(current_shows_sonarr))
for series in removed_series:
database.execute("DELETE FROM table_shows WHERE sonarrSeriesId=?",(series,))
event_stream.write(type='series', action='delete', series=series)
# Update existing series in DB
series_in_db_list = []
series_in_db = database.execute("SELECT title, path, tvdbId, sonarrSeriesId, overview, poster, fanart, "
"audio_language, sortTitle, year, alternateTitles FROM table_shows")
# Update existing series in DB
series_in_db_list = []
series_in_db = database.execute("SELECT title, path, tvdbId, sonarrSeriesId, overview, poster, fanart, "
"audio_language, sortTitle, year, alternateTitles FROM table_shows")
for item in series_in_db:
series_in_db_list.append(item)
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]
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:
query = dict_converter.convert(updated_series)
database.execute('''UPDATE table_shows SET ''' + query.keys_update + ''' WHERE sonarrSeriesId = ?''',
query.values + (updated_series['sonarrSeriesId'],))
for updated_series in series_to_update_list:
query = dict_converter.convert(updated_series)
database.execute('''UPDATE table_shows SET ''' + query.keys_update + ''' WHERE sonarrSeriesId = ?''',
query.values + (updated_series['sonarrSeriesId'],))
event_stream.write(type='series', action='update', series=updated_series['sonarrSeriesId'])
# Insert new series in DB
for added_series in series_to_add:
query = dict_converter.convert(added_series)
result = database.execute(
'''INSERT OR IGNORE INTO table_shows(''' + query.keys_insert + ''') VALUES(''' +
query.question_marks + ''')''', query.values)
if result:
list_missing_subtitles(no=added_series['sonarrSeriesId'])
else:
logging.debug('BAZARR unable to insert this series into the database:',
path_replace(added_series['path']))
# Insert new series in DB
for added_series in series_to_add:
query = dict_converter.convert(added_series)
result = database.execute(
'''INSERT OR IGNORE INTO table_shows(''' + query.keys_insert + ''') VALUES(''' +
query.question_marks + ''')''', query.values)
if result:
list_missing_subtitles(no=added_series['sonarrSeriesId'])
else:
logging.debug('BAZARR unable to insert this series into the database:',
path_replace(added_series['path']))
event_stream.write(type='series', action='insert', series=added_series['sonarrSeriesId'])
@ -172,36 +167,37 @@ def get_profile_list():
apikey_sonarr = settings.sonarr.apikey
sonarr_version = get_sonarr_version()
profiles_list = []
# Get profiles data from Sonarr
# Get profiles data from Sonarr
if sonarr_version.startswith('2'):
url_sonarr_api_series = url_sonarr() + "/api/profile?apikey=" + apikey_sonarr
elif sonarr_version.startswith('3'):
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)
except requests.exceptions.ConnectionError as errc:
except requests.exceptions.ConnectionError:
logging.exception("BAZARR Error trying to get profiles from Sonarr. Connection Error.")
except requests.exceptions.Timeout as errt:
return None
except requests.exceptions.Timeout:
logging.exception("BAZARR Error trying to get profiles from Sonarr. Timeout Error.")
except requests.exceptions.RequestException as err:
return None
except requests.exceptions.RequestException:
logging.exception("BAZARR Error trying to get profiles from Sonarr.")
else:
# Parsing data returned from Sonarr
if sonarr_version.startswith('2'):
for profile in profiles_json.json():
profiles_list.append([profile['id'], profile['language'].capitalize()])
elif sonarr_version.startswith('3'):
for profile in profiles_json.json():
profiles_list.append([profile['id'], profile['name'].capitalize()])
return None
return profiles_list
# Parsing data returned from Sonarr
if sonarr_version.startswith('2'):
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 None
return profiles_list
def profile_id_to_language(id, profiles):
def profile_id_to_language(id_, profiles):
for profile in profiles:
if id == profile[0]:
if id_ == profile[0]:
return profile[1]

@ -1010,7 +1010,7 @@ def upgrade_subtitles():
except ValueError:
pass
else:
if int(upgradable_movie['score']) < 360:
if int(upgradable_movie['score']) < 120:
upgradable_movies_not_perfect.append(upgradable_movie)
movies_to_upgrade = []
@ -1025,6 +1025,8 @@ def upgrade_subtitles():
if settings.general.getboolean('use_sonarr'):
for i, episode in enumerate(episodes_to_upgrade, 1):
if episode['languages'] in [None, 'None', '[]']:
continue
providers = get_providers()
if not providers:
notifications.write(msg='BAZARR All providers are throttled', queue='get_subtitle', duration='long')
@ -1074,6 +1076,8 @@ def upgrade_subtitles():
if settings.general.getboolean('use_radarr'):
for i, movie in enumerate(movies_to_upgrade, 1):
if movie['languages'] in [None, 'None', '[]']:
continue
providers = get_providers()
if not providers:
notifications.write(msg='BAZARR All providers are throttled', queue='get_subtitle', duration='long')

@ -26,7 +26,7 @@ import warnings
import queueconfig
import platform
import apprise
from calendar import day_name
import operator
from get_args import args
@ -59,7 +59,7 @@ from get_subtitle import download_subtitle, series_download_subtitles, movies_do
manual_search, manual_download_subtitle, manual_upload_subtitle
from utils import history_log, history_log_movie, get_sonarr_version, get_radarr_version
from helper import path_replace, path_replace_movie, path_replace_reverse, path_replace_reverse_movie
from scheduler import *
from scheduler import Scheduler
from notifier import send_notifications, send_notifications_movie
from subliminal_patch.extensions import provider_registry as provider_manager
from subliminal_patch.core import SUBTITLE_EXTENSIONS
@ -71,6 +71,7 @@ app = create_app()
from api import api_bp
app.register_blueprint(api_bp)
scheduler = Scheduler()
# Check and install update on startup when running on Windows from installer
if args.release_update:
@ -686,8 +687,7 @@ def search_missing_subtitles_movie(no):
ref = request.environ['HTTP_REFERER']
add_job(movies_download_subtitles, args=[no], name=('movies_download_subtitles_' + str(no)))
scheduler.add_job(movies_download_subtitles, args=[no], name=('movies_download_subtitles_' + str(no)))
redirect(ref)
@ -781,8 +781,7 @@ def wanted_search_missing_subtitles_list():
ref = request.environ['HTTP_REFERER']
add_job(wanted_search_missing_subtitles, name='manual_wanted_search_missing_subtitles')
scheduler.add_job(wanted_search_missing_subtitles, name='manual_wanted_search_missing_subtitles')
redirect(ref)
@ -1195,12 +1194,7 @@ def save_settings():
database.execute("UPDATE table_settings_notifier SET enabled=?, url=? WHERE name=?",
(enabled,notifier_url,notifier['name']))
schedule_update_job()
sonarr_full_update()
radarr_full_update()
schedule_wanted_search()
schedule_upgrade_subs()
scheduler.update_configurable_tasks()
logging.info('BAZARR Settings saved succesfully.')
if ref.find('saved=true') > 0:
@ -1226,60 +1220,7 @@ def check_update():
def system():
def get_time_from_interval(td_object):
seconds = int(td_object.total_seconds())
periods = [
('year', 60 * 60 * 24 * 365),
('month', 60 * 60 * 24 * 30),
('day', 60 * 60 * 24),
('hour', 60 * 60),
('minute', 60),
('second', 1)
]
strings = []
for period_name, period_seconds in periods:
if seconds > period_seconds:
period_value, seconds = divmod(seconds, period_seconds)
has_s = 's' if period_value > 1 else ''
strings.append("%s %s%s" % (period_value, period_name, has_s))
return ", ".join(strings)
def get_time_from_cron(cron):
year = str(cron[0])
if year == "2100":
return "Never"
day = str(cron[4])
hour = str(cron[5])
text = ""
if day == "*":
text = "everyday"
else:
text = "every " + day_name[int(day)]
if hour != "*":
text += " at " + hour + ":00"
return text
task_list = []
for job in scheduler.get_jobs():
if isinstance(job.trigger, CronTrigger):
if str(job.trigger.__getstate__()['fields'][0]) == "2100":
next_run = 'Never'
else:
next_run = pretty.date(job.next_run_time.replace(tzinfo=None))
else:
next_run = pretty.date(job.next_run_time.replace(tzinfo=None))
if isinstance(job.trigger, IntervalTrigger):
interval = "every " + get_time_from_interval(job.trigger.__getstate__()['interval'])
task_list.append([job.name, interval, next_run, job.id])
elif isinstance(job.trigger, CronTrigger):
task_list.append([job.name, get_time_from_cron(job.trigger.fields), next_run, job.id])
task_list = scheduler.get_task_list()
throttled_providers = list_throttled_providers()
@ -1323,7 +1264,7 @@ def get_logs():
@app.route('/execute/<taskid>')
@login_required
def execute_task(taskid):
execute_now(taskid)
scheduler.execute_now(taskid)
return '', 200
@ -1587,7 +1528,7 @@ def notifications():
@login_required
def running_tasks_list():
return dict(tasks=running_tasks)
return dict(tasks=scheduler.get_running_tasks())
@app.route('/episode_history/<int:no>')

@ -2,23 +2,21 @@
from __future__ import absolute_import
import apprise
import os
import logging
from get_args import args
from database import database
def update_notifier():
# define apprise object
a = apprise.Apprise()
# Retrieve all of the details
results = a.details()
notifiers_new = []
notifiers_old = []
notifiers_current_db = database.execute("SELECT name FROM table_settings_notifier")
notifiers_current = []
@ -31,66 +29,68 @@ def update_notifier():
logging.debug('Adding new notifier agent: ' + x['service_name'])
else:
notifiers_old.append([x['service_name']])
notifiers_to_delete = [item for item in notifiers_current if item not in notifiers_old]
database.execute("INSERT INTO table_settings_notifier (name, enabled) VALUES (?, ?)", notifiers_new, execute_many=True)
database.execute("INSERT INTO table_settings_notifier (name, enabled) VALUES (?, ?)", notifiers_new,
execute_many=True)
database.execute("DELETE FROM table_settings_notifier WHERE name=?", notifiers_to_delete, execute_many=True)
def get_notifier_providers():
providers = database.execute("SELECT name, url FROM table_settings_notifier WHERE enabled=1")
return providers
def get_series_name(sonarrSeriesId):
data = database.execute("SELECT title FROM table_shows WHERE sonarrSeriesId=?", (sonarrSeriesId,), only_one=True)
def get_series_name(sonarr_series_id):
data = database.execute("SELECT title FROM table_shows WHERE sonarrSeriesId=?", (sonarr_series_id,), only_one=True)
return data['title'] or None
def get_episode_name(sonarrEpisodeId):
def get_episode_name(sonarr_episode_id):
data = database.execute("SELECT title, season, episode FROM table_episodes WHERE sonarrEpisodeId=?",
(sonarrEpisodeId,), only_one=True)
(sonarr_episode_id,), only_one=True)
return data['title'], data['season'], data['episode']
def get_movies_name(radarrId):
data = database.execute("SELECT title FROM table_movies WHERE radarrId=?", (radarrId,), only_one=True)
def get_movies_name(radarr_id):
data = database.execute("SELECT title FROM table_movies WHERE radarrId=?", (radarr_id,), only_one=True)
return data['title']
def send_notifications(sonarrSeriesId, sonarrEpisodeId, message):
def send_notifications(sonarr_series_id, sonarr_episode_id, message):
providers = get_notifier_providers()
series = get_series_name(sonarrSeriesId)
episode = get_episode_name(sonarrEpisodeId)
series = get_series_name(sonarr_series_id)
episode = get_episode_name(sonarr_episode_id)
apobj = apprise.Apprise()
for provider in providers:
if provider['url'] is not None:
apobj.add(provider['url'])
apobj.notify(
title='Bazarr notification',
body=(series + ' - S' + str(episode[1]).zfill(2) + 'E' + str(episode[2]).zfill(2) + ' - ' + episode[0] + ' : ' + message),
body="{} - S{:02d}E{:02d} - {} : {}".format(series, episode[1], episode[2], episode[0], message),
)
def send_notifications_movie(radarrId, message):
def send_notifications_movie(radarr_id, message):
providers = get_notifier_providers()
movie = get_movies_name(radarrId)
movie = get_movies_name(radarr_id)
apobj = apprise.Apprise()
for provider in providers:
if provider['url'] is not None:
apobj.add(provider['url'])
apobj.notify(
title='Bazarr notification',
body=movie + ' : ' + message,
body="{} : {}".format(movie, message),
)

@ -20,118 +20,210 @@ from apscheduler.events import EVENT_JOB_SUBMITTED, EVENT_JOB_EXECUTED, EVENT_JO
from datetime import datetime
import pytz
from tzlocal import get_localzone
from calendar import day_name
import pretty
def sonarr_full_update():
if settings.general.getboolean('use_sonarr'):
full_update = settings.sonarr.full_update
if full_update == "Daily":
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":
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":
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():
if settings.general.getboolean('use_radarr'):
full_update = settings.radarr.full_update
if full_update == "Daily":
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":
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":
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 execute_now(taskid):
scheduler.modify_job(taskid, next_run_time=datetime.now())
if str(get_localzone()) == "local":
scheduler = BackgroundScheduler(timezone=pytz.timezone('UTC'))
else:
scheduler = BackgroundScheduler()
global running_tasks
running_tasks = []
def task_listener_add(event):
if event.job_id not in running_tasks:
running_tasks.append(event.job_id)
class Scheduler:
def __init__(self):
self.__running_tasks = []
def task_listener_remove(event):
if event.job_id in running_tasks:
running_tasks.remove(event.job_id)
scheduler.add_listener(task_listener_add, EVENT_JOB_SUBMITTED)
scheduler.add_listener(task_listener_remove, EVENT_JOB_EXECUTED | EVENT_JOB_ERROR)
if str(get_localzone()) == "local":
self.aps_scheduler = BackgroundScheduler(timezone=pytz.timezone('UTC'))
else:
self.aps_scheduler = BackgroundScheduler()
# task listener
def task_listener_add(event):
if event.job_id not in self.__running_tasks:
self.__running_tasks.append(event.job_id)
def task_listener_remove(event):
if event.job_id in self.__running_tasks:
self.__running_tasks.remove(event.job_id)
self.aps_scheduler.add_listener(task_listener_add, EVENT_JOB_SUBMITTED)
self.aps_scheduler.add_listener(task_listener_remove, EVENT_JOB_EXECUTED | EVENT_JOB_ERROR)
# configure all tasks
self.__sonarr_update_task()
self.__radarr_update_task()
self.__cache_cleanup_task()
self.update_configurable_tasks()
self.aps_scheduler.start()
def update_configurable_tasks(self):
self.__sonarr_full_update_task()
self.__radarr_full_update_task()
self.__update_bazarr_task()
self.__search_wanted_subtitles_task()
self.__upgrade_subtitles_task()
def add_job(self, job, name=None, max_instances=1, coalesce=True, args=None):
self.aps_scheduler.add_job(
job, DateTrigger(run_date=datetime.now()), name=name, id=name, max_instances=max_instances,
coalesce=coalesce, args=args)
def execute_job_now(self, taskid):
self.aps_scheduler.modify_job(taskid, next_run_time=datetime.now())
def get_running_tasks(self):
return self.__running_tasks
def get_task_list(self):
def get_time_from_interval(td_object):
seconds = int(td_object.total_seconds())
periods = [
('year', 60 * 60 * 24 * 365),
('month', 60 * 60 * 24 * 30),
('day', 60 * 60 * 24),
('hour', 60 * 60),
('minute', 60),
('second', 1)
]
strings = []
for period_name, period_seconds in periods:
if seconds > period_seconds:
period_value, seconds = divmod(seconds, period_seconds)
has_s = 's' if period_value > 1 else ''
strings.append("%s %s%s" % (period_value, period_name, has_s))
return ", ".join(strings)
def get_time_from_cron(cron):
year = str(cron[0])
if year == "2100":
return "Never"
day = str(cron[4])
hour = str(cron[5])
if day == "*":
text = "everyday"
else:
text = "every " + day_name[int(day)]
if hour != "*":
text += " at " + hour + ":00"
return text
task_list = []
for job in self.aps_scheduler.get_jobs():
if isinstance(job.trigger, CronTrigger):
if str(job.trigger.__getstate__()['fields'][0]) == "2100":
next_run = 'Never'
else:
next_run = pretty.date(job.next_run_time.replace(tzinfo=None))
else:
next_run = pretty.date(job.next_run_time.replace(tzinfo=None))
if isinstance(job.trigger, IntervalTrigger):
interval = "every " + get_time_from_interval(job.trigger.__getstate__()['interval'])
task_list.append([job.name, interval, next_run, job.id])
elif isinstance(job.trigger, CronTrigger):
task_list.append([job.name, get_time_from_cron(job.trigger.fields), next_run, job.id])
return task_list
def __sonarr_update_task(self):
if settings.general.getboolean('use_sonarr'):
self.aps_scheduler.add_job(
update_series, IntervalTrigger(minutes=1), max_instances=1, coalesce=True, misfire_grace_time=15,
id='update_series', name='Update Series list from Sonarr')
self.aps_scheduler.add_job(
sync_episodes, IntervalTrigger(minutes=5), max_instances=1, coalesce=True, misfire_grace_time=15,
id='sync_episodes', name='Sync episodes with Sonarr')
def __radarr_update_task(self):
if settings.general.getboolean('use_radarr'):
self.aps_scheduler.add_job(
update_movies, IntervalTrigger(minutes=5), max_instances=1, coalesce=True, misfire_grace_time=15,
id='update_movies', name='Update Movie list from Radarr')
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')
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:
task_name = 'Update Bazarr from source on Github'
if args.release_update:
task_name = 'Update Bazarr from release on Github'
if settings.general.getboolean('auto_update'):
self.aps_scheduler.add_job(
check_and_apply_update, IntervalTrigger(hours=6), max_instances=1, coalesce=True,
misfire_grace_time=15, id='update_bazarr', name=task_name, replace_existing=True)
else:
self.aps_scheduler.add_job(
check_and_apply_update, CronTrigger(year='2100'), hour=4, id='update_bazarr', name=task_name,
replace_existing=True)
self.aps_scheduler.add_job(
check_releases, IntervalTrigger(hours=6), max_instances=1, coalesce=True, misfire_grace_time=15,
id='update_release', name='Update Release Info', replace_existing=True)
def schedule_update_job():
if not args.no_update:
if settings.general.getboolean('auto_update'):
scheduler.add_job(check_and_apply_update, IntervalTrigger(hours=6), max_instances=1, coalesce=True,
misfire_grace_time=15, id='update_bazarr',
name='Update Bazarr from source on Github' if not args.release_update else 'Update Bazarr from release on Github',
replace_existing=True)
else:
scheduler.add_job(check_and_apply_update, CronTrigger(year='2100'), hour=4, id='update_bazarr',
name='Update Bazarr from source on Github' if not args.release_update else 'Update Bazarr from release on Github',
replace_existing=True)
scheduler.add_job(check_releases, IntervalTrigger(hours=6), max_instances=1, coalesce=True,
misfire_grace_time=15, id='update_release', name='Update Release Info',
replace_existing=True)
else:
scheduler.add_job(check_releases, IntervalTrigger(hours=6), max_instances=1, coalesce=True,
misfire_grace_time=15,
id='update_release', name='Update Release Info', replace_existing=True)
if settings.general.getboolean('use_sonarr'):
scheduler.add_job(update_series, IntervalTrigger(minutes=1), max_instances=1, coalesce=True, misfire_grace_time=15,
id='update_series', name='Update Series list from Sonarr')
scheduler.add_job(sync_episodes, IntervalTrigger(minutes=5), max_instances=1, coalesce=True, misfire_grace_time=15,
id='sync_episodes', name='Sync episodes with Sonarr')
if settings.general.getboolean('use_radarr'):
scheduler.add_job(update_movies, IntervalTrigger(minutes=5), max_instances=1, coalesce=True, misfire_grace_time=15,
id='update_movies', name='Update Movie list from Radarr')
def schedule_wanted_search():
if settings.general.getboolean('use_sonarr') or settings.general.getboolean('use_radarr'):
scheduler.add_job(wanted_search_missing_subtitles,
IntervalTrigger(hours=int(settings.general.wanted_search_frequency)), max_instances=1,
coalesce=True, misfire_grace_time=15, id='wanted_search_missing_subtitles',
name='Search for wanted Subtitles', replace_existing=True)
def schedule_upgrade_subs():
if settings.general.getboolean('upgrade_subs') and (settings.general.getboolean('use_sonarr') or
settings.general.getboolean('use_radarr')):
scheduler.add_job(upgrade_subtitles, IntervalTrigger(hours=int(settings.general.upgrade_frequency)),
max_instances=1, coalesce=True, misfire_grace_time=15, id='upgrade_subtitles',
name='Upgrade previously downloaded Subtitles', replace_existing=True)
self.aps_scheduler.add_job(
check_releases, IntervalTrigger(hours=6), max_instances=1, coalesce=True, misfire_grace_time=15,
id='update_release', name='Update Release Info', replace_existing=True)
def __search_wanted_subtitles_task(self):
if settings.general.getboolean('use_sonarr') or settings.general.getboolean('use_radarr'):
self.aps_scheduler.add_job(
wanted_search_missing_subtitles, IntervalTrigger(hours=int(settings.general.wanted_search_frequency)),
max_instances=1, coalesce=True, misfire_grace_time=15, id='wanted_search_missing_subtitles',
name='Search for wanted Subtitles', replace_existing=True)
def __upgrade_subtitles_task(self):
if settings.general.getboolean('upgrade_subs') and \
(settings.general.getboolean('use_sonarr') or settings.general.getboolean('use_radarr')):
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',
name='Upgrade previously downloaded Subtitles', replace_existing=True)
scheduler.add_job(cache_maintenance, IntervalTrigger(hours=24), max_instances=1, coalesce=True,
misfire_grace_time=15, id='cache_cleanup', name='Cache maintenance')

@ -4,7 +4,6 @@ from __future__ import absolute_import
import os
import time
import platform
import sys
import logging
import requests
@ -17,22 +16,20 @@ import datetime
import glob
def history_log(action, sonarrSeriesId, sonarrEpisodeId, description, video_path=None, language=None, provider=None,
score=None, forced=False):
def history_log(action, sonarr_series_id, sonarr_episode_id, description, video_path=None, language=None, provider=None,
score=None):
from database import database
database.execute("INSERT INTO table_history (action, sonarrSeriesId, sonarrEpisodeId, timestamp, description,"
"video_path, language, provider, score) VALUES (?,?,?,?,?,?,?,?,?)", (action, sonarrSeriesId,
sonarrEpisodeId, time.time(),
description, video_path,
language, provider, score))
"video_path, language, provider, score) VALUES (?,?,?,?,?,?,?,?,?)",
(action, sonarr_series_id, sonarr_episode_id, time.time(), description, video_path, language,
provider, score))
def history_log_movie(action, radarrId, description, video_path=None, language=None, provider=None, score=None,
forced=False):
def history_log_movie(action, radarr_id, description, video_path=None, language=None, provider=None, score=None):
from database import database
database.execute("INSERT INTO table_history_movie (action, radarrId, timestamp, description, video_path, language, "
"provider, score) VALUES (?,?,?,?,?,?,?,?)", (action, radarrId, time.time(), description,
video_path, language, provider, score))
"provider, score) VALUES (?,?,?,?,?,?,?,?)",
(action, radarr_id, time.time(), description, video_path, language, provider, score))
def get_binary(name):
@ -46,10 +43,8 @@ def get_binary(name):
else:
if platform.system() == "Windows": # Windows
exe = os.path.abspath(os.path.join(binaries_dir, "Windows", "i386", name, "%s.exe" % name))
elif platform.system() == "Darwin": # MacOSX
exe = os.path.abspath(os.path.join(binaries_dir, "MacOSX", "i386", name, name))
elif platform.system() == "Linux": # Linux
exe = os.path.abspath(os.path.join(binaries_dir, "Linux", platform.machine(), name, name))
@ -82,62 +77,52 @@ def cache_maintenance():
def get_sonarr_version():
use_sonarr = settings.general.getboolean('use_sonarr')
apikey_sonarr = settings.sonarr.apikey
sv = url_sonarr() + "/api/system/status?apikey=" + apikey_sonarr
sonarr_version = ''
if use_sonarr:
if settings.general.getboolean('use_sonarr'):
try:
sv = url_sonarr() + "/api/system/status?apikey=" + settings.sonarr.apikey
sonarr_version = requests.get(sv, timeout=60, verify=False).json()['version']
except Exception as e:
except Exception:
logging.debug('BAZARR cannot get Sonarr version')
return sonarr_version
def get_sonarr_platform():
use_sonarr = settings.general.getboolean('use_sonarr')
apikey_sonarr = settings.sonarr.apikey
sv = url_sonarr() + "/api/system/status?apikey=" + apikey_sonarr
sonarr_platform = ''
if use_sonarr:
if settings.general.getboolean('use_sonarr'):
try:
if requests.get(sv, timeout=60, verify=False).json()['isLinux'] or requests.get(sv, timeout=60, verify=False).json()['isOsx']:
sv = url_sonarr() + "/api/system/status?apikey=" + settings.sonarr.apikey
response = requests.get(sv, timeout=60, verify=False).json()
if response['isLinux'] or response['isOsx']:
sonarr_platform = 'posix'
elif requests.get(sv, timeout=60, verify=False).json()['isWindows']:
elif response['isWindows']:
sonarr_platform = 'nt'
except Exception as e:
logging.DEBUG('BAZARR cannot get Sonarr platform')
except Exception:
logging.debug('BAZARR cannot get Sonarr platform')
return sonarr_platform
def get_radarr_version():
use_radarr = settings.general.getboolean('use_radarr')
apikey_radarr = settings.radarr.apikey
rv = url_radarr() + "/api/system/status?apikey=" + apikey_radarr
radarr_version = ''
if use_radarr:
if settings.general.getboolean('use_radarr'):
try:
rv = url_radarr() + "/api/system/status?apikey=" + settings.radarr.apikey
radarr_version = requests.get(rv, timeout=60, verify=False).json()['version']
except Exception as e:
except Exception:
logging.debug('BAZARR cannot get Radarr version')
return radarr_version
def get_radarr_platform():
use_radarr = settings.general.getboolean('use_radarr')
apikey_radarr = settings.radarr.apikey
rv = url_radarr() + "/api/system/status?apikey=" + apikey_radarr
radarr_platform = ''
if use_radarr:
if settings.general.getboolean('use_radarr'):
try:
if requests.get(rv, timeout=60, verify=False).json()['isLinux'] or requests.get(rv, timeout=60, verify=False).json()['isOsx']:
rv = url_radarr() + "/api/system/status?apikey=" + settings.radarr.apikey
response = requests.get(rv, timeout=60, verify=False).json()
if response['isLinux'] or response['isOsx']:
radarr_platform = 'posix'
elif requests.get(rv, timeout=60, verify=False).json()['isWindows']:
elif response['isWindows']:
radarr_platform = 'nt'
except Exception as e:
logging.DEBUG('BAZARR cannot get Radarr platform')
except Exception:
logging.debug('BAZARR cannot get Radarr platform')
return radarr_platform

@ -0,0 +1 @@
# coding=utf-8
Loading…
Cancel
Save