Merge remote-tracking branch 'origin/subliminal_patch' into subliminal_patch

# Conflicts:
#	bazarr/main.py
pull/489/head
Halali 6 years ago
commit 9fe6e8595e

@ -3,6 +3,13 @@ Bazarr is a companion application to Sonarr and Radarr. It manages and downloads
Be aware that Bazarr doesn't scan disk to detect series and movies: It only takes care of the series and movies that are indexed in Sonarr and Radarr.
## Support on Beerpay
At the request of some, here is a way to demonstrate your appreciation for the efforts made in the development of Bazarr:
[![Beerpay](https://beerpay.io/morpheus65535/bazarr/badge.svg?style=beer-square)](https://beerpay.io/morpheus65535/bazarr)
You can also make a wish but keep in mind that we do not commit to make it happen:
[![Beerpay](https://beerpay.io/morpheus65535/bazarr/make-wish.svg?style=flat-square)](https://beerpay.io/morpheus65535/bazarr?focus=wish)
# Status
[![GitHub issues](https://img.shields.io/github/issues/morpheus65535/bazarr.svg?style=flat-square)](https://github.com/morpheus65535/bazarr/issues)
[![GitHub stars](https://img.shields.io/github/stars/morpheus65535/bazarr.svg?style=flat-square)](https://github.com/morpheus65535/bazarr/stargazers)

@ -4,12 +4,11 @@ import os
import sqlite3
import logging
import time
import platform
import rarfile
from cork import Cork
try:
from configparser import ConfigParser
except ImportError:
from configparser2 import ConfigParser
from ConfigParser2 import ConfigParser
from config import settings
from check_update import check_releases
from get_args import args
@ -147,3 +146,41 @@ if not os.path.exists(os.path.normpath(os.path.join(args.config_dir, 'config', '
'creation_date': tstamp
}
cork._store.save_users()
def init_binaries():
binaries_dir = os.path.realpath(os.path.join(os.path.dirname(__file__), '..', 'bin'))
unrar_exe = None
exe = None
if platform.system() == "Windows": # Windows
unrar_exe = os.path.abspath(os.path.join(binaries_dir, "Windows", "i386", "UnRAR", "UnRAR.exe"))
elif platform.system() == "Darwin": # MacOSX
unrar_exe = os.path.abspath(os.path.join(binaries_dir, "MacOSX", "i386", "UnRAR", "unrar"))
elif platform.system() == "Linux": # Linux
unrar_exe = os.path.abspath(os.path.join(binaries_dir, "Linux", platform.machine(), "UnRAR", "unrar"))
else:
unrar_exe = "unrar"
if unrar_exe and os.path.isfile(unrar_exe):
exe = unrar_exe
rarfile.UNRAR_TOOL = exe
rarfile.ORIG_UNRAR_TOOL = exe
try:
rarfile.custom_check([rarfile.UNRAR_TOOL], True)
except:
logging.debug("custom check failed for: %s", exe)
rarfile.OPEN_ARGS = rarfile.ORIG_OPEN_ARGS
rarfile.EXTRACT_ARGS = rarfile.ORIG_EXTRACT_ARGS
rarfile.TEST_ARGS = rarfile.ORIG_TEST_ARGS
logging.info("Using UnRAR from: %s", exe)
unrar = exe
return unrar
init_binaries()

@ -1,4 +1,4 @@
# coding=utf-8
bazarr_version = '0.7.0.5'
import gc
import sys
@ -131,9 +131,9 @@ def custom_auth_basic(check):
return func(*a, **ka)
else:
return func(*a, **ka)
return wrapper
return decorator
@ -335,31 +335,31 @@ def save_wizard():
settings_subliminal_providers = request.forms.getall('settings_subliminal_providers')
settings.general.enabled_providers = u'' if not settings_subliminal_providers else ','.join(
settings_subliminal_providers)
settings_addic7ed_random_agents = request.forms.get('settings_addic7ed_random_agents')
if settings_addic7ed_random_agents is None:
settings_addic7ed_random_agents = 'False'
else:
settings_addic7ed_random_agents = 'True'
settings_opensubtitles_vip = request.forms.get('settings_opensubtitles_vip')
if settings_opensubtitles_vip is None:
settings_opensubtitles_vip = 'False'
else:
settings_opensubtitles_vip = 'True'
settings_opensubtitles_ssl = request.forms.get('settings_opensubtitles_ssl')
if settings_opensubtitles_ssl is None:
settings_opensubtitles_ssl = 'False'
else:
settings_opensubtitles_ssl = 'True'
settings_opensubtitles_skip_wrong_fps = request.forms.get('settings_opensubtitles_skip_wrong_fps')
if settings_opensubtitles_skip_wrong_fps is None:
settings_opensubtitles_skip_wrong_fps = 'False'
else:
settings_opensubtitles_skip_wrong_fps = 'True'
settings.addic7ed.username = request.forms.get('settings_addic7ed_username')
settings.addic7ed.password = request.forms.get('settings_addic7ed_password')
settings.addic7ed.random_agents = text_type(settings_addic7ed_random_agents)
@ -417,7 +417,7 @@ def save_wizard():
with open(os.path.join(args.config_dir, 'config', 'config.ini'), 'w+') as handle:
settings.write(handle)
conn.commit()
c.close()
@ -436,10 +436,10 @@ def static(path):
def emptylog():
authorize()
ref = request.environ['HTTP_REFERER']
empty_log()
logging.info('BAZARR Log file emptied')
redirect(ref)
@ -553,15 +553,15 @@ def series():
def serieseditor():
authorize()
single_language = settings.general.getboolean('single_language')
db = sqlite3.connect(os.path.join(args.config_dir, 'db', 'bazarr.db'), timeout=30)
db.create_function("path_substitution", 1, path_replace)
c = db.cursor()
c.execute("SELECT COUNT(*) FROM table_shows")
missing_count = c.fetchone()
missing_count = missing_count[0]
c.execute(
"SELECT tvdbId, title, path_substitution(path), languages, hearing_impaired, sonarrSeriesId, poster, audio_language FROM table_shows ORDER BY title ASC")
data = c.fetchall()
@ -605,13 +605,13 @@ def search_json(query):
def edit_series(no):
authorize()
ref = request.environ['HTTP_REFERER']
lang = request.forms.getall('languages')
if len(lang) > 0:
pass
else:
lang = 'None'
single_language = settings.general.getboolean('single_language')
if single_language is True:
if str(lang) == "['None']":
@ -621,23 +621,23 @@ def edit_series(no):
else:
if str(lang) == "['']":
lang = '[]'
hi = request.forms.get('hearing_impaired')
if hi == "on":
hi = "True"
else:
hi = "False"
conn = sqlite3.connect(os.path.join(args.config_dir, 'db', 'bazarr.db'), timeout=30)
c = conn.cursor()
c.execute("UPDATE table_shows SET languages = ?, hearing_impaired = ? WHERE sonarrSeriesId LIKE ?",
(str(lang), hi, no))
conn.commit()
c.close()
list_missing_subtitles(no)
redirect(ref)
@ -646,15 +646,15 @@ def edit_series(no):
def edit_serieseditor():
authorize()
ref = request.environ['HTTP_REFERER']
series = request.forms.get('series')
series = ast.literal_eval(str('[' + series + ']'))
lang = request.forms.getall('languages')
hi = request.forms.get('hearing_impaired')
conn = sqlite3.connect(os.path.join(args.config_dir, 'db', 'bazarr.db'), timeout=30)
c = conn.cursor()
for serie in series:
if str(lang) != "[]" and str(lang) != "['']":
if str(lang) == "['None']":
@ -664,13 +664,13 @@ def edit_serieseditor():
c.execute("UPDATE table_shows SET languages = ? WHERE sonarrSeriesId LIKE ?", (lang, serie))
if hi != '':
c.execute("UPDATE table_shows SET hearing_impaired = ? WHERE sonarrSeriesId LIKE ?", (hi, serie))
conn.commit()
c.close()
for serie in series:
list_missing_subtitles(serie)
redirect(ref)
@ -679,17 +679,17 @@ def edit_serieseditor():
def episodes(no):
authorize()
# single_language = settings.general.getboolean('single_language')
conn = sqlite3.connect(os.path.join(args.config_dir, 'db', 'bazarr.db'), timeout=30)
conn.create_function("path_substitution", 1, path_replace)
c = conn.cursor()
series_details = []
series_details = c.execute(
"SELECT title, overview, poster, fanart, hearing_impaired, tvdbid, audio_language, languages, path_substitution(path) FROM table_shows WHERE sonarrSeriesId LIKE ?",
(str(no),)).fetchone()
tvdbid = series_details[5]
episodes = c.execute(
"SELECT title, path_substitution(path), season, episode, subtitles, sonarrSeriesId, missing_subtitles, sonarrEpisodeId, scene_name, monitored, failedAttempts FROM table_episodes WHERE sonarrSeriesId LIKE ? ORDER BY episode ASC",
(str(no),)).fetchall()
@ -700,7 +700,7 @@ def episodes(no):
seasons_list = []
for key, season in itertools.groupby(episodes, operator.itemgetter(2)):
seasons_list.append(list(season))
return template('episodes', bazarr_version=bazarr_version, no=no, details=series_details,
languages=languages, seasons=seasons_list, url_sonarr_short=url_sonarr_short, base_url=base_url,
tvdbid=tvdbid, number=number, current_port=settings.general.port)
@ -711,11 +711,11 @@ def episodes(no):
def movies():
authorize()
single_language = settings.general.getboolean('single_language')
db = sqlite3.connect(os.path.join(args.config_dir, 'db', 'bazarr.db'), timeout=30)
db.create_function("path_substitution", 1, path_replace_movie)
c = db.cursor()
c.execute("SELECT COUNT(*) FROM table_movies")
missing_count = c.fetchone()
missing_count = missing_count[0]
@ -725,7 +725,7 @@ def movies():
page_size = int(settings.general.page_size)
offset = (int(page) - 1) * page_size
max_page = int(math.ceil(missing_count / (page_size + 0.0)))
c.execute(
"SELECT tmdbId, title, path_substitution(path), languages, hearing_impaired, radarrId, poster, audio_language, monitored, sceneName FROM table_movies ORDER BY sortTitle ASC LIMIT ? OFFSET ?",
(page_size, offset,))
@ -744,15 +744,15 @@ def movies():
def movieseditor():
authorize()
single_language = settings.general.getboolean('single_language')
db = sqlite3.connect(os.path.join(args.config_dir, 'db', 'bazarr.db'), timeout=30)
db.create_function("path_substitution", 1, path_replace_movie)
c = db.cursor()
c.execute("SELECT COUNT(*) FROM table_movies")
missing_count = c.fetchone()
missing_count = missing_count[0]
c.execute(
"SELECT tmdbId, title, path_substitution(path), languages, hearing_impaired, radarrId, poster, audio_language FROM table_movies ORDER BY title ASC")
data = c.fetchall()
@ -770,15 +770,15 @@ def movieseditor():
def edit_movieseditor():
authorize()
ref = request.environ['HTTP_REFERER']
movies = request.forms.get('movies')
movies = ast.literal_eval(str('[' + movies + ']'))
lang = request.forms.getall('languages')
hi = request.forms.get('hearing_impaired')
conn = sqlite3.connect(os.path.join(args.config_dir, 'db', 'bazarr.db'), timeout=30)
c = conn.cursor()
for movie in movies:
if str(lang) != "[]" and str(lang) != "['']":
if str(lang) == "['None']":
@ -788,13 +788,13 @@ def edit_movieseditor():
c.execute("UPDATE table_movies SET languages = ? WHERE radarrId LIKE ?", (lang, movie))
if hi != '':
c.execute("UPDATE table_movies SET hearing_impaired = ? WHERE radarrId LIKE ?", (hi, movie))
conn.commit()
c.close()
for movie in movies:
list_missing_subtitles_movies(movie)
redirect(ref)
@ -803,31 +803,31 @@ def edit_movieseditor():
def edit_movie(no):
authorize()
ref = request.environ['HTTP_REFERER']
lang = request.forms.getall('languages')
if len(lang) > 0:
pass
else:
lang = 'None'
if str(lang) == "['']":
lang = '[]'
hi = request.forms.get('hearing_impaired')
if hi == "on":
hi = "True"
else:
hi = "False"
conn = sqlite3.connect(os.path.join(args.config_dir, 'db', 'bazarr.db'), timeout=30)
c = conn.cursor()
c.execute("UPDATE table_movies SET languages = ?, hearing_impaired = ? WHERE radarrId LIKE ?", (str(lang), hi, no))
conn.commit()
c.close()
list_missing_subtitles_movies(no)
redirect(ref)
@ -836,20 +836,20 @@ def edit_movie(no):
def movie(no):
authorize()
# single_language = settings.general.getboolean('single_language')
conn = sqlite3.connect(os.path.join(args.config_dir, 'db', 'bazarr.db'), timeout=30)
conn.create_function("path_substitution", 1, path_replace_movie)
c = conn.cursor()
movies_details = []
movies_details = c.execute(
"SELECT title, overview, poster, fanart, hearing_impaired, tmdbid, audio_language, languages, path_substitution(path), subtitles, radarrId, missing_subtitles, sceneName, monitored, failedAttempts FROM table_movies WHERE radarrId LIKE ?",
(str(no),)).fetchone()
tmdbid = movies_details[5]
languages = c.execute("SELECT code2, name FROM table_settings_languages WHERE enabled = 1").fetchall()
c.close()
return template('movie', bazarr_version=bazarr_version, no=no, details=movies_details,
languages=languages, url_radarr_short=url_radarr_short, base_url=base_url, tmdbid=tmdbid,
current_port=settings.general.port)
@ -860,9 +860,9 @@ def movie(no):
def scan_disk(no):
authorize()
ref = request.environ['HTTP_REFERER']
series_scan_subtitles(no)
redirect(ref)
@ -871,9 +871,9 @@ def scan_disk(no):
def scan_disk_movie(no):
authorize()
ref = request.environ['HTTP_REFERER']
movies_scan_subtitles(no)
redirect(ref)
@ -882,9 +882,9 @@ def scan_disk_movie(no):
def search_missing_subtitles(no):
authorize()
ref = request.environ['HTTP_REFERER']
series_download_subtitles(no)
redirect(ref)
@ -893,9 +893,9 @@ def search_missing_subtitles(no):
def search_missing_subtitles_movie(no):
authorize()
ref = request.environ['HTTP_REFERER']
movies_download_subtitles(no)
redirect(ref)
@ -912,7 +912,7 @@ def historyseries():
authorize()
db = sqlite3.connect(os.path.join(args.config_dir, 'db', 'bazarr.db'), timeout=30)
c = db.cursor()
c.execute("SELECT COUNT(*) FROM table_history")
row_count = c.fetchone()
row_count = row_count[0]
@ -922,7 +922,7 @@ def historyseries():
page_size = int(settings.general.page_size)
offset = (int(page) - 1) * page_size
max_page = int(math.ceil(row_count / (page_size + 0.0)))
now = datetime.now()
today = []
thisweek = []
@ -937,7 +937,7 @@ def historyseries():
if now - timedelta(weeks=52) <= datetime.fromtimestamp(stat[0]) <= now:
thisyear.append(datetime.fromtimestamp(stat[0]).date())
stats = [len(today), len(thisweek), len(thisyear), total]
c.execute(
"SELECT table_history.action, table_shows.title, table_episodes.season || 'x' || table_episodes.episode, table_episodes.title, table_history.timestamp, table_history.description, table_history.sonarrSeriesId FROM table_history LEFT JOIN table_shows on table_shows.sonarrSeriesId = table_history.sonarrSeriesId LEFT JOIN table_episodes on table_episodes.sonarrEpisodeId = table_history.sonarrEpisodeId ORDER BY id DESC LIMIT ? OFFSET ?",
(page_size, offset,))
@ -955,7 +955,7 @@ def historymovies():
authorize()
db = sqlite3.connect(os.path.join(args.config_dir, 'db', 'bazarr.db'), timeout=30)
c = db.cursor()
c.execute("SELECT COUNT(*) FROM table_history_movie")
row_count = c.fetchone()
row_count = row_count[0]
@ -965,7 +965,7 @@ def historymovies():
page_size = int(settings.general.page_size)
offset = (int(page) - 1) * page_size
max_page = int(math.ceil(row_count / (page_size + 0.0)))
now = datetime.now()
today = []
thisweek = []
@ -980,7 +980,7 @@ def historymovies():
if now - timedelta(weeks=52) <= datetime.fromtimestamp(stat[0]) <= now:
thisyear.append(datetime.fromtimestamp(stat[0]).date())
stats = [len(today), len(thisweek), len(thisyear), total]
c.execute(
"SELECT table_history_movie.action, table_movies.title, table_history_movie.timestamp, table_history_movie.description, table_history_movie.radarrId FROM table_history_movie LEFT JOIN table_movies on table_movies.radarrId = table_history_movie.radarrId ORDER BY id DESC LIMIT ? OFFSET ?",
(page_size, offset,))
@ -1006,12 +1006,12 @@ def wantedseries():
db = sqlite3.connect(os.path.join(args.config_dir, 'db', 'bazarr.db'), timeout=30)
db.create_function("path_substitution", 1, path_replace)
c = db.cursor()
if settings.sonarr.getboolean('only_monitored'):
monitored_only_query_string = ' AND monitored = "True"'
else:
monitored_only_query_string = ""
c.execute("SELECT COUNT(*) FROM table_episodes WHERE missing_subtitles != '[]'" + monitored_only_query_string)
missing_count = c.fetchone()
missing_count = missing_count[0]
@ -1021,7 +1021,7 @@ def wantedseries():
page_size = int(settings.general.page_size)
offset = (int(page) - 1) * page_size
max_page = int(math.ceil(missing_count / (page_size + 0.0)))
c.execute(
"SELECT table_shows.title, table_episodes.season || 'x' || table_episodes.episode, table_episodes.title, table_episodes.missing_subtitles, table_episodes.sonarrSeriesId, path_substitution(table_episodes.path), table_shows.hearing_impaired, table_episodes.sonarrEpisodeId, table_episodes.scene_name, table_episodes.failedAttempts FROM table_episodes INNER JOIN table_shows on table_shows.sonarrSeriesId = table_episodes.sonarrSeriesId WHERE table_episodes.missing_subtitles != '[]'" + monitored_only_query_string + " ORDER BY table_episodes._rowid_ DESC LIMIT ? OFFSET ?",
(page_size, offset,))
@ -1039,12 +1039,12 @@ def wantedmovies():
db = sqlite3.connect(os.path.join(args.config_dir, 'db', 'bazarr.db'), timeout=30)
db.create_function("path_substitution", 1, path_replace_movie)
c = db.cursor()
if settings.radarr.getboolean('only_monitored'):
monitored_only_query_string = ' AND monitored = "True"'
else:
monitored_only_query_string = ""
c.execute("SELECT COUNT(*) FROM table_movies WHERE missing_subtitles != '[]'" + monitored_only_query_string)
missing_count = c.fetchone()
missing_count = missing_count[0]
@ -1054,7 +1054,7 @@ def wantedmovies():
page_size = int(settings.general.page_size)
offset = (int(page) - 1) * page_size
max_page = int(math.ceil(missing_count / (page_size + 0.0)))
c.execute(
"SELECT title, missing_subtitles, radarrId, path_substitution(path), hearing_impaired, sceneName, failedAttempts FROM table_movies WHERE missing_subtitles != '[]'" + monitored_only_query_string + " ORDER BY _rowid_ DESC LIMIT ? OFFSET ?",
(page_size, offset,))
@ -1233,11 +1233,11 @@ def save_settings():
settings.proxy.username = text_type(settings_proxy_username)
settings.proxy.password = text_type(settings_proxy_password)
settings.proxy.exclude = text_type(settings_proxy_exclude)
settings_auth_type = request.forms.get('settings_auth_type')
settings_auth_username = request.forms.get('settings_auth_username')
settings_auth_password = request.forms.get('settings_auth_password')
if settings.auth.type != settings_auth_type:
configured()
if settings.auth.password == settings_auth_password:
@ -1270,7 +1270,7 @@ def save_settings():
pass
else:
aaa._beaker_session.delete()
settings_sonarr_ip = request.forms.get('settings_sonarr_ip')
settings_sonarr_port = request.forms.get('settings_sonarr_port')
settings_sonarr_baseurl = request.forms.get('settings_sonarr_baseurl')
@ -1286,7 +1286,7 @@ def save_settings():
else:
settings_sonarr_only_monitored = 'True'
settings_sonarr_sync = request.forms.get('settings_sonarr_sync')
settings.sonarr.ip = text_type(settings_sonarr_ip)
settings.sonarr.port = text_type(settings_sonarr_port)
settings.sonarr.base_url = text_type(settings_sonarr_baseurl)
@ -1294,7 +1294,7 @@ def save_settings():
settings.sonarr.apikey = text_type(settings_sonarr_apikey)
settings.sonarr.only_monitored = text_type(settings_sonarr_only_monitored)
settings.sonarr.full_update = text_type(settings_sonarr_sync)
settings_radarr_ip = request.forms.get('settings_radarr_ip')
settings_radarr_port = request.forms.get('settings_radarr_port')
settings_radarr_baseurl = request.forms.get('settings_radarr_baseurl')
@ -1310,7 +1310,7 @@ def save_settings():
else:
settings_radarr_only_monitored = 'True'
settings_radarr_sync = request.forms.get('settings_radarr_sync')
settings.radarr.ip = text_type(settings_radarr_ip)
settings.radarr.port = text_type(settings_radarr_port)
settings.radarr.base_url = text_type(settings_radarr_baseurl)
@ -1318,35 +1318,35 @@ def save_settings():
settings.radarr.apikey = text_type(settings_radarr_apikey)
settings.radarr.only_monitored = text_type(settings_radarr_only_monitored)
settings.radarr.full_update = text_type(settings_radarr_sync)
settings_subliminal_providers = request.forms.getall('settings_subliminal_providers')
settings.general.enabled_providers = u'' if not settings_subliminal_providers else ','.join(
settings_subliminal_providers)
settings_addic7ed_random_agents = request.forms.get('settings_addic7ed_random_agents')
if settings_addic7ed_random_agents is None:
settings_addic7ed_random_agents = 'False'
else:
settings_addic7ed_random_agents = 'True'
settings_opensubtitles_vip = request.forms.get('settings_opensubtitles_vip')
if settings_opensubtitles_vip is None:
settings_opensubtitles_vip = 'False'
else:
settings_opensubtitles_vip = 'True'
settings_opensubtitles_ssl = request.forms.get('settings_opensubtitles_ssl')
if settings_opensubtitles_ssl is None:
settings_opensubtitles_ssl = 'False'
else:
settings_opensubtitles_ssl = 'True'
settings_opensubtitles_skip_wrong_fps = request.forms.get('settings_opensubtitles_skip_wrong_fps')
if settings_opensubtitles_skip_wrong_fps is None:
settings_opensubtitles_skip_wrong_fps = 'False'
else:
settings_opensubtitles_skip_wrong_fps = 'True'
settings.addic7ed.username = request.forms.get('settings_addic7ed_username')
settings.addic7ed.password = request.forms.get('settings_addic7ed_password')
settings.addic7ed.random_agents = text_type(settings_addic7ed_random_agents)
@ -1358,55 +1358,55 @@ def save_settings():
settings.opensubtitles.vip = text_type(settings_opensubtitles_vip)
settings.opensubtitles.ssl = text_type(settings_opensubtitles_ssl)
settings.opensubtitles.skip_wrong_fps = text_type(settings_opensubtitles_skip_wrong_fps)
settings_subliminal_languages = request.forms.getall('settings_subliminal_languages')
c.execute("UPDATE table_settings_languages SET enabled = 0")
for item in settings_subliminal_languages:
c.execute("UPDATE table_settings_languages SET enabled = '1' WHERE code2 = ?", (item,))
settings_serie_default_enabled = request.forms.get('settings_serie_default_enabled')
if settings_serie_default_enabled is None:
settings_serie_default_enabled = 'False'
else:
settings_serie_default_enabled = 'True'
settings.general.serie_default_enabled = text_type(settings_serie_default_enabled)
settings_serie_default_languages = str(request.forms.getall('settings_serie_default_languages'))
if settings_serie_default_languages == "['None']":
settings_serie_default_languages = 'None'
settings.general.serie_default_language = text_type(settings_serie_default_languages)
settings_serie_default_hi = request.forms.get('settings_serie_default_hi')
if settings_serie_default_hi is None:
settings_serie_default_hi = 'False'
else:
settings_serie_default_hi = 'True'
settings.general.serie_default_hi = text_type(settings_serie_default_hi)
settings_movie_default_enabled = request.forms.get('settings_movie_default_enabled')
if settings_movie_default_enabled is None:
settings_movie_default_enabled = 'False'
else:
settings_movie_default_enabled = 'True'
settings.general.movie_default_enabled = text_type(settings_movie_default_enabled)
settings_movie_default_languages = str(request.forms.getall('settings_movie_default_languages'))
if settings_movie_default_languages == "['None']":
settings_movie_default_languages = 'None'
settings.general.movie_default_language = text_type(settings_movie_default_languages)
settings_movie_default_hi = request.forms.get('settings_movie_default_hi')
if settings_movie_default_hi is None:
settings_movie_default_hi = 'False'
else:
settings_movie_default_hi = 'True'
settings.general.movie_default_hi = text_type(settings_movie_default_hi)
with open(os.path.join(args.config_dir, 'config', 'config.ini'), 'w+') as handle:
settings.write(handle)
configure_logging(settings.general.getboolean('debug'))
notifiers = c.execute("SELECT * FROM table_settings_notifier ORDER BY name").fetchall()
for notifier in notifiers:
enabled = request.forms.get('settings_notifier_' + notifier[0] + '_enabled')
@ -1417,18 +1417,18 @@ def save_settings():
notifier_url = request.forms.get('settings_notifier_' + notifier[0] + '_url')
c.execute("UPDATE table_settings_notifier SET enabled = ?, url = ? WHERE name = ?",
(enabled, notifier_url, notifier[0]))
conn.commit()
c.close()
sonarr_full_update()
radarr_full_update()
logging.info('BAZARR Settings saved succesfully.')
# reschedule full update task according to settings
sonarr_full_update()
if ref.find('saved=true') > 0:
redirect(ref)
else:
@ -1440,10 +1440,10 @@ def save_settings():
def check_update():
authorize()
ref = request.environ['HTTP_REFERER']
if not args.no_update:
check_and_apply_update()
redirect(ref)
@ -1456,11 +1456,11 @@ def system():
interval_clean = interval.split('[')
interval_clean = interval_clean[1][:-1]
interval_split = interval_clean.split(':')
hour = interval_split[0]
minute = interval_split[1].lstrip("0")
second = interval_split[2].lstrip("0")
text = "every "
if hour != "0":
text = text + hour
@ -1468,7 +1468,7 @@ def system():
text = text + " hour"
else:
text = text + " hours"
if minute != "" and second != "":
text = text + ", "
elif minute == "" and second != "":
@ -1481,7 +1481,7 @@ def system():
text = text + " minute"
else:
text = text + " minutes"
if second != "":
text = text + " and "
if second != "":
@ -1490,22 +1490,22 @@ def system():
text = text + " second"
else:
text = text + " seconds"
return text
def get_time_from_cron(cron):
text = "at "
hour = str(cron[5])
minute = str(cron[6])
second = str(cron[7])
if hour != "0" and hour != "*":
text = text + hour
if hour == "0" or hour == "1":
text = text + " hour"
else:
text = text + " hours"
if minute != "*" and second != "0":
text = text + ", "
elif minute == "*" and second != "0":
@ -1518,7 +1518,7 @@ def system():
text = text + " minute"
else:
text = text + " minutes"
if second != "0" and second != "*":
text = text + " and "
if second != "0" and second != "*":
@ -1527,21 +1527,21 @@ def system():
text = text + " second"
else:
text = text + " seconds"
return text
task_list = []
for job in scheduler.get_jobs():
if job.next_run_time is not None:
next_run = pretty.date(job.next_run_time.replace(tzinfo=None))
else:
next_run = "Never"
if job.trigger.__str__().startswith('interval'):
task_list.append([job.name, get_time_from_interval(str(job.trigger)), next_run, job.id])
elif job.trigger.__str__().startswith('cron'):
task_list.append([job.name, get_time_from_cron(job.trigger.fields), next_run, job.id])
i = 0
with open(os.path.join(args.config_dir, 'log', 'bazarr.log')) as f:
for i, l in enumerate(f, 1):
@ -1549,10 +1549,10 @@ def system():
row_count = i
page_size = int(settings.general.page_size)
max_page = int(math.ceil(row_count / (page_size + 0.0)))
with open(os.path.join(args.config_dir, 'config', 'releases.txt'), 'r') as f:
releases = ast.literal_eval(f.read())
use_sonarr = settings.general.getboolean('use_sonarr')
apikey_sonarr = settings.sonarr.apikey
sv = url_sonarr + "/api/system/status?apikey=" + apikey_sonarr
@ -1562,7 +1562,7 @@ def system():
sonarr_version = requests.get(sv, timeout=15, verify=False).json()['version']
except:
pass
use_radarr = settings.general.getboolean('use_radarr')
apikey_radarr = settings.radarr.apikey
rv = url_radarr + "/api/system/status?apikey=" + apikey_radarr
@ -1572,7 +1572,7 @@ def system():
radarr_version = requests.get(rv, timeout=15, verify=False).json()['version']
except:
pass
return template('system', bazarr_version=bazarr_version,
sonarr_version=sonarr_version, radarr_version=radarr_version,
operating_system=platform.platform(), python_version=platform.python_version(),
@ -1592,7 +1592,7 @@ def get_logs(page):
for line in reversed(open(os.path.join(args.config_dir, 'log', 'bazarr.log')).readlines()):
logs_complete.append(line.rstrip())
logs = logs_complete[begin:end]
return template('logs', logs=logs, base_url=base_url, current_port=settings.general.port)
@ -1601,9 +1601,9 @@ def get_logs(page):
def execute_task(taskid):
authorize()
ref = request.environ['HTTP_REFERER']
execute_now(taskid)
redirect(ref)
@ -1615,13 +1615,14 @@ def remove_subtitles():
language = request.forms.get('language')
subtitlesPath = request.forms.get('subtitlesPath')
sonarrSeriesId = request.forms.get('sonarrSeriesId')
sonarrEpisodeId = request.forms.get('sonarrEpisodeId')
subfolder = ('\\' + get_subtitle_destination_folder() + '\\') if get_subtitle_destination_folder() else '\\'
subtitlesPath = os.path.split(subtitlesPath)
try:
os.remove(subtitlesPath[0] + subfolder + subtitlesPath[1])
result = language_from_alpha3(language) + " subtitles deleted from disk."
history_log_movie(0, radarrId, result)
history_log(0, sonarrSeriesId, sonarrEpisodeId, result)
except OSError:
pass
store_subtitles(unicode(episodePath))
@ -1654,7 +1655,7 @@ def remove_subtitles_movie():
def get_subtitle():
authorize()
ref = request.environ['HTTP_REFERER']
episodePath = request.forms.get('episodePath')
sceneName = request.forms.get('sceneName')
language = request.forms.get('language')
@ -1663,10 +1664,10 @@ def get_subtitle():
sonarrEpisodeId = request.forms.get('sonarrEpisodeId')
title = request.forms.get('title')
# tvdbid = request.forms.get('tvdbid')
providers_list = get_providers()
providers_auth = get_providers_auth()
try:
result = download_subtitle(episodePath, language, hi, providers_list, providers_auth, sceneName, title,
'series')
@ -1685,16 +1686,16 @@ def get_subtitle():
def manual_search_json():
authorize()
ref = request.environ['HTTP_REFERER']
episodePath = request.forms.get('episodePath')
sceneName = request.forms.get('sceneName')
language = request.forms.get('language')
hi = request.forms.get('hi')
title = request.forms.get('title')
providers_list = get_providers()
providers_auth = get_providers_auth()
data = manual_search(episodePath, language, hi, providers_list, providers_auth, sceneName, title, 'series')
return dict(data=data)
@ -1704,7 +1705,7 @@ def manual_search_json():
def manual_get_subtitle():
authorize()
ref = request.environ['HTTP_REFERER']
episodePath = request.forms.get('episodePath')
sceneName = request.forms.get('sceneName')
language = request.forms.get('language')
@ -1714,10 +1715,10 @@ def manual_get_subtitle():
sonarrSeriesId = request.forms.get('sonarrSeriesId')
sonarrEpisodeId = request.forms.get('sonarrEpisodeId')
title = request.forms.get('title')
providers_list = get_providers()
providers_auth = get_providers_auth()
try:
result = manual_download_subtitle(episodePath, language, hi, subtitle, selected_provider, providers_auth,
sceneName, title, 'series')
@ -1736,7 +1737,7 @@ def manual_get_subtitle():
def get_subtitle_movie():
authorize()
ref = request.environ['HTTP_REFERER']
moviePath = request.forms.get('moviePath')
sceneName = request.forms.get('sceneName')
language = request.forms.get('language')
@ -1744,10 +1745,10 @@ def get_subtitle_movie():
radarrId = request.forms.get('radarrId')
# tmdbid = request.forms.get('tmdbid')
title = request.forms.get('title')
providers_list = get_providers()
providers_auth = get_providers_auth()
try:
result = download_subtitle(moviePath, language, hi, providers_list, providers_auth, sceneName, title, 'movie')
if result is not None:
@ -1765,16 +1766,16 @@ def get_subtitle_movie():
def manual_search_movie_json():
authorize()
ref = request.environ['HTTP_REFERER']
moviePath = request.forms.get('moviePath')
sceneName = request.forms.get('sceneName')
language = request.forms.get('language')
hi = request.forms.get('hi')
title = request.forms.get('title')
providers_list = get_providers()
providers_auth = get_providers_auth()
data = manual_search(moviePath, language, hi, providers_list, providers_auth, sceneName, title, 'movie')
return dict(data=data)
@ -1784,7 +1785,7 @@ def manual_search_movie_json():
def manual_get_subtitle_movie():
authorize()
ref = request.environ['HTTP_REFERER']
moviePath = request.forms.get('moviePath')
sceneName = request.forms.get('sceneName')
language = request.forms.get('language')
@ -1793,10 +1794,10 @@ def manual_get_subtitle_movie():
subtitle = request.forms.get('subtitle')
radarrId = request.forms.get('radarrId')
title = request.forms.get('title')
providers_list = get_providers()
providers_auth = get_providers_auth()
try:
result = manual_download_subtitle(moviePath, language, hi, subtitle, selected_provider, providers_auth,
sceneName, title, 'movie')
@ -1876,7 +1877,7 @@ def test_notification(protocol, provider):
provider = urllib.unquote(provider)
apobj = apprise.Apprise()
apobj.add(protocol + "://" + provider)
apobj.notify(
title='Bazarr test notification',
body=('Test notification')
@ -1889,14 +1890,16 @@ def handle_websocket():
wsock = request.environ.get('wsgi.websocket')
if not wsock:
abort(400, 'Expected WebSocket request.')
queueconfig.q4ws.clear()
while True:
try:
if len(queueconfig.q4ws) > 0:
if queueconfig.q4ws:
wsock.send(queueconfig.q4ws.popleft())
gevent.sleep(0)
gevent.sleep(0.1)
else:
gevent.sleep(0.5)
except WebSocketError:
break

@ -67,9 +67,9 @@ else:
scheduler = BackgroundScheduler()
if not args.no_update:
if settings.sonarr.auto_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')
misfire_grace_time=15, id='update_bazarr', name='Update bazarr from source on Github')
else:
scheduler.add_job(check_and_apply_update, CronTrigger(year='2100'), hour=4, id='update_bazarr',
name='Update bazarr from source on Github')

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

@ -0,0 +1,34 @@
****** ***** ****** UnRAR - free utility for RAR archives
** ** ** ** ** ** ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
****** ******* ****** License for use and distribution of
** ** ** ** ** ** ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
** ** ** ** ** ** FREEWARE version
~~~~~~~~~~~~~~~~
The UnRAR utility is freeware. This means:
1. All copyrights to RAR and the utility UnRAR are exclusively
owned by the author - Alexander Roshal.
2. The UnRAR utility may be freely distributed. It is allowed
to distribute UnRAR inside of other software packages.
3. THE RAR ARCHIVER AND THE UnRAR UTILITY ARE DISTRIBUTED "AS IS".
NO WARRANTY OF ANY KIND IS EXPRESSED OR IMPLIED. YOU USE AT
YOUR OWN RISK. THE AUTHOR WILL NOT BE LIABLE FOR DATA LOSS,
DAMAGES, LOSS OF PROFITS OR ANY OTHER KIND OF LOSS WHILE USING
OR MISUSING THIS SOFTWARE.
4. Neither RAR binary code, WinRAR binary code, UnRAR source or UnRAR
binary code may be used or reverse engineered to re-create the RAR
compression algorithm, which is proprietary, without written
permission of the author.
5. If you don't agree with terms of the license you must remove
UnRAR files from your storage devices and cease to use the
utility.
Thank you for your interest in RAR and UnRAR.
Alexander L. Roshal

@ -0,0 +1,797 @@
"""Configuration file parser.
A setup file consists of sections, lead by a "[section]" header,
and followed by "name: value" entries, with continuations and such in
the style of RFC 822.
The option values can contain format strings which refer to other values in
the same section, or values in a special [DEFAULT] section.
For example:
something: %(dir)s/whatever
would resolve the "%(dir)s" to the value of dir. All reference
expansions are done late, on demand.
Intrinsic defaults can be specified by passing them into the
ConfigParser constructor as a dictionary.
class:
ConfigParser -- responsible for parsing a list of
configuration files, and managing the parsed database.
methods:
__init__(defaults=None)
create the parser and specify a dictionary of intrinsic defaults. The
keys must be strings, the values must be appropriate for %()s string
interpolation. Note that `__name__' is always an intrinsic default;
its value is the section's name.
sections()
return all the configuration section names, sans DEFAULT
has_section(section)
return whether the given section exists
has_option(section, option)
return whether the given option exists in the given section
options(section)
return list of configuration options for the named section
read(filenames)
read and parse the list of named configuration files, given by
name. A single filename is also allowed. Non-existing files
are ignored. Return list of successfully read files.
readfp(fp, filename=None)
read and parse one configuration file, given as a file object.
The filename defaults to fp.name; it is only used in error
messages (if fp has no `name' attribute, the string `<???>' is used).
get(section, option, raw=False, vars=None)
return a string value for the named option. All % interpolations are
expanded in the return values, based on the defaults passed into the
constructor and the DEFAULT section. Additional substitutions may be
provided using the `vars' argument, which must be a dictionary whose
contents override any pre-existing defaults.
getint(section, options)
like get(), but convert value to an integer
getfloat(section, options)
like get(), but convert value to a float
getboolean(section, options)
like get(), but convert value to a boolean (currently case
insensitively defined as 0, false, no, off for False, and 1, true,
yes, on for True). Returns False or True.
items(section, raw=False, vars=None)
return a list of tuples with (name, value) for each option
in the section.
remove_section(section)
remove the given file section and all its options
remove_option(section, option)
remove the given option from the given section
set(section, option, value)
set the given option
write(fp)
write the configuration state in .ini format
"""
try:
from collections import OrderedDict as _default_dict
except ImportError:
# fallback for setup.py which hasn't yet built _collections
_default_dict = dict
import re
__all__ = ["NoSectionError", "DuplicateSectionError", "NoOptionError",
"InterpolationError", "InterpolationDepthError",
"InterpolationSyntaxError", "ParsingError",
"MissingSectionHeaderError",
"ConfigParser", "SafeConfigParser", "RawConfigParser",
"DEFAULTSECT", "MAX_INTERPOLATION_DEPTH"]
DEFAULTSECT = "DEFAULT"
MAX_INTERPOLATION_DEPTH = 10
# exception classes
class Error(Exception):
"""Base class for ConfigParser exceptions."""
def _get_message(self):
"""Getter for 'message'; needed only to override deprecation in
BaseException."""
return self.__message
def _set_message(self, value):
"""Setter for 'message'; needed only to override deprecation in
BaseException."""
self.__message = value
# BaseException.message has been deprecated since Python 2.6. To prevent
# DeprecationWarning from popping up over this pre-existing attribute, use
# a new property that takes lookup precedence.
message = property(_get_message, _set_message)
def __init__(self, msg=''):
self.message = msg
Exception.__init__(self, msg)
def __repr__(self):
return self.message
__str__ = __repr__
class NoSectionError(Error):
"""Raised when no section matches a requested option."""
def __init__(self, section):
Error.__init__(self, 'No section: %r' % (section,))
self.section = section
self.args = (section, )
class DuplicateSectionError(Error):
"""Raised when a section is multiply-created."""
def __init__(self, section):
Error.__init__(self, "Section %r already exists" % section)
self.section = section
self.args = (section, )
class NoOptionError(Error):
"""A requested option was not found."""
def __init__(self, option, section):
Error.__init__(self, "No option %r in section: %r" %
(option, section))
self.option = option
self.section = section
self.args = (option, section)
class InterpolationError(Error):
"""Base class for interpolation-related exceptions."""
def __init__(self, option, section, msg):
Error.__init__(self, msg)
self.option = option
self.section = section
self.args = (option, section, msg)
class InterpolationMissingOptionError(InterpolationError):
"""A string substitution required a setting which was not available."""
def __init__(self, option, section, rawval, reference):
msg = ("Bad value substitution:\n"
"\tsection: [%s]\n"
"\toption : %s\n"
"\tkey : %s\n"
"\trawval : %s\n"
% (section, option, reference, rawval))
InterpolationError.__init__(self, option, section, msg)
self.reference = reference
self.args = (option, section, rawval, reference)
class InterpolationSyntaxError(InterpolationError):
"""Raised when the source text into which substitutions are made
does not conform to the required syntax."""
class InterpolationDepthError(InterpolationError):
"""Raised when substitutions are nested too deeply."""
def __init__(self, option, section, rawval):
msg = ("Value interpolation too deeply recursive:\n"
"\tsection: [%s]\n"
"\toption : %s\n"
"\trawval : %s\n"
% (section, option, rawval))
InterpolationError.__init__(self, option, section, msg)
self.args = (option, section, rawval)
class ParsingError(Error):
"""Raised when a configuration file does not follow legal syntax."""
def __init__(self, filename):
Error.__init__(self, 'File contains parsing errors: %s' % filename)
self.filename = filename
self.errors = []
self.args = (filename, )
def append(self, lineno, line):
self.errors.append((lineno, line))
self.message += '\n\t[line %2d]: %s' % (lineno, line)
class MissingSectionHeaderError(ParsingError):
"""Raised when a key-value pair is found before any section header."""
def __init__(self, filename, lineno, line):
Error.__init__(
self,
'File contains no section headers.\nfile: %s, line: %d\n%r' %
(filename, lineno, line))
self.filename = filename
self.lineno = lineno
self.line = line
self.args = (filename, lineno, line)
class RawConfigParser:
def __init__(self, defaults=None, dict_type=_default_dict,
allow_no_value=False):
self._dict = dict_type
self._sections = self._dict()
self._defaults = self._dict()
if allow_no_value:
self._optcre = self.OPTCRE_NV
else:
self._optcre = self.OPTCRE
if defaults:
for key, value in defaults.items():
self._defaults[self.optionxform(key)] = value
self.comment_store = None ## used for storing comments in ini
def defaults(self):
return self._defaults
def sections(self):
"""Return a list of section names, excluding [DEFAULT]"""
# self._sections will never have [DEFAULT] in it
return self._sections.keys()
def add_section(self, section):
"""Create a new section in the configuration.
Raise DuplicateSectionError if a section by the specified name
already exists. Raise ValueError if name is DEFAULT or any of it's
case-insensitive variants.
"""
if section.lower() == "default":
raise ValueError, 'Invalid section name: %s' % section
if section in self._sections:
raise DuplicateSectionError(section)
self._sections[section] = self._dict()
def has_section(self, section):
"""Indicate whether the named section is present in the configuration.
The DEFAULT section is not acknowledged.
"""
return section in self._sections
def options(self, section):
"""Return a list of option names for the given section name."""
try:
opts = self._sections[section].copy()
except KeyError:
raise NoSectionError(section)
opts.update(self._defaults)
if '__name__' in opts:
del opts['__name__']
return opts.keys()
def read(self, filenames):
"""Read and parse a filename or a list of filenames.
Files that cannot be opened are silently ignored; this is
designed so that you can specify a list of potential
configuration file locations (e.g. current directory, user's
home directory, systemwide directory), and all existing
configuration files in the list will be read. A single
filename may also be given.
Return list of successfully read files.
"""
if isinstance(filenames, basestring):
filenames = [filenames]
read_ok = []
for filename in filenames:
try:
fp = open(filename)
except IOError:
continue
self._read(fp, filename)
fp.close()
read_ok.append(filename)
return read_ok
def readfp(self, fp, filename=None):
"""Like read() but the argument must be a file-like object.
The `fp' argument must have a `readline' method. Optional
second argument is the `filename', which if not given, is
taken from fp.name. If fp has no `name' attribute, `<???>' is
used.
"""
if filename is None:
try:
filename = fp.name
except AttributeError:
filename = '<???>'
self._read(fp, filename)
def get(self, section, option):
opt = self.optionxform(option)
if section not in self._sections:
if section != DEFAULTSECT:
raise NoSectionError(section)
if opt in self._defaults:
return self._defaults[opt]
else:
raise NoOptionError(option, section)
elif opt in self._sections[section]:
return self._sections[section][opt]
elif opt in self._defaults:
return self._defaults[opt]
else:
raise NoOptionError(option, section)
def items(self, section):
try:
d2 = self._sections[section]
except KeyError:
if section != DEFAULTSECT:
raise NoSectionError(section)
d2 = self._dict()
d = self._defaults.copy()
d.update(d2)
if "__name__" in d:
del d["__name__"]
return d.items()
def _get(self, section, conv, option):
return conv(self.get(section, option))
def getint(self, section, option):
return self._get(section, int, option)
def getfloat(self, section, option):
return self._get(section, float, option)
_boolean_states = {'1': True, 'yes': True, 'true': True, 'on': True,
'0': False, 'no': False, 'false': False, 'off': False}
def getboolean(self, section, option):
v = self.get(section, option)
if v.lower() not in self._boolean_states:
raise ValueError, 'Not a boolean: %s' % v
return self._boolean_states[v.lower()]
def optionxform(self, optionstr):
return optionstr.lower()
def has_option(self, section, option):
"""Check for the existence of a given option in a given section."""
if not section or section == DEFAULTSECT:
option = self.optionxform(option)
return option in self._defaults
elif section not in self._sections:
return False
else:
option = self.optionxform(option)
return (option in self._sections[section]
or option in self._defaults)
def set(self, section, option, value=None):
"""Set an option."""
if not section or section == DEFAULTSECT:
sectdict = self._defaults
else:
try:
sectdict = self._sections[section]
except KeyError:
raise NoSectionError(section)
sectdict[self.optionxform(option)] = value
def write(self, fp):
"""Write an .ini-format representation of the configuration state."""
if self._defaults:
fp.write("[%s]\n" % DEFAULTSECT)
for (key, value) in self._defaults.items():
fp.write("%s = %s\n" % (key, str(value).replace('\n', '\n\t')))
fp.write("\n")
for section in self._sections:
fp.write("[%s]\n" % section)
for (key, value) in self._sections[section].items():
if key == "__name__":
continue
if (value is not None) or (self._optcre == self.OPTCRE):
key = " = ".join((key, str(value).replace('\n', '\n\t')))
fp.write("%s\n" % (key))
fp.write("\n")
def remove_option(self, section, option):
"""Remove an option."""
if not section or section == DEFAULTSECT:
sectdict = self._defaults
else:
try:
sectdict = self._sections[section]
except KeyError:
raise NoSectionError(section)
option = self.optionxform(option)
existed = option in sectdict
if existed:
del sectdict[option]
return existed
def remove_section(self, section):
"""Remove a file section."""
existed = section in self._sections
if existed:
del self._sections[section]
return existed
#
# Regular expressions for parsing section headers and options.
#
SECTCRE = re.compile(
r'\[' # [
r'(?P<header>[^]]+)' # very permissive!
r'\]' # ]
)
OPTCRE = re.compile(
r'(?P<option>[^:=\s][^:=]*)' # very permissive!
r'\s*(?P<vi>[:=])\s*' # any number of space/tab,
# followed by separator
# (either : or =), followed
# by any # space/tab
r'(?P<value>.*)$' # everything up to eol
)
OPTCRE_NV = re.compile(
r'(?P<option>[^:=\s][^:=]*)' # very permissive!
r'\s*(?:' # any number of space/tab,
r'(?P<vi>[:=])\s*' # optionally followed by
# separator (either : or
# =), followed by any #
# space/tab
r'(?P<value>.*))?$' # everything up to eol
)
def _read(self, fp, fpname):
"""Parse a sectioned setup file.
The sections in setup file contains a title line at the top,
indicated by a name in square brackets (`[]'), plus key/value
options lines, indicated by `name: value' format lines.
Continuations are represented by an embedded newline then
leading whitespace. Blank lines, lines beginning with a '#',
and just about everything else are ignored.
"""
comment_store = {}
cursect = None # None, or a dictionary
optname = None
lineno = 0
e = None # None, or an exception
while True:
line = fp.readline()
if not line:
break
lineno = lineno + 1
# comment or blank line?
if line.strip() == '' :
continue
### store comments for doc purposes
### Deal with cases of sections and options being there or not
if line[0] in '#;' and cursect is not None:
if optname is None:
comment_store.setdefault(cursect['__name__'] +
"::" + "global",[]).append(line)
else:
comment_store.setdefault(cursect['__name__'] +
"::" + optname,[]).append(line)
continue
elif line[0] in '#;' and cursect is None:
comment_store.setdefault("global" +
"::" + optname,[]).append(line)
continue
if line.split(None, 1)[0].lower() == 'rem' and line[0] in "rR":
# no leading whitespace
continue
# continuation line?
if line[0].isspace() and cursect is not None and optname:
value = line.strip()
if value:
cursect[optname].append(value)
# a section header or option header?
else:
# is it a section header?
mo = self.SECTCRE.match(line)
if mo:
sectname = mo.group('header')
if sectname in self._sections:
cursect = self._sections[sectname]
elif sectname == DEFAULTSECT:
cursect = self._defaults
else:
cursect = self._dict()
cursect['__name__'] = sectname
self._sections[sectname] = cursect
# So sections can't start with a continuation line
optname = None
# no section header in the file?
elif cursect is None:
raise MissingSectionHeaderError(fpname, lineno, line)
# an option line?
else:
mo = self._optcre.match(line)
if mo:
optname, vi, optval = mo.group('option', 'vi', 'value')
optname = self.optionxform(optname.rstrip())
# This check is fine because the OPTCRE cannot
# match if it would set optval to None
if optval is not None:
if vi in ('=', ':') and ';' in optval:
# ';' is a comment delimiter only if it follows
# a spacing character
pos = optval.find(';')
if pos != -1 and optval[pos-1].isspace():
optval = optval[:pos]
optval = optval.strip()
# allow empty values
if optval == '""':
optval = ''
cursect[optname] = [optval]
else:
# valueless option handling
cursect[optname] = optval
else:
# a non-fatal parsing error occurred. set up the
# exception but keep going. the exception will be
# raised at the end of the file and will contain a
# list of all bogus lines
if not e:
e = ParsingError(fpname)
e.append(lineno, repr(line))
# if any parsing errors occurred, raise an exception
if e:
raise e
# join the multi-line values collected while reading
all_sections = [self._defaults]
all_sections.extend(self._sections.values())
for options in all_sections:
for name, val in options.items():
if isinstance(val, list):
options[name] = '\n'.join(val)
self.comment_store = comment_store
def ini_as_rst(self):
"""trivial helper function to putput comment_stroe as rest
.. todo:: write actual doctests with string input
>> p = ConfigParser2.SafeConfigParser()
>> p.read(f)
['/usr/home/pbrian/src/public/configparser2/example.ini']
>> open("/tmp/foo.rst", "w").write(p.ini_as_rst())
"""
outstr = ".. rst version of ini file\n\n"
_cursectname = None
for item in sorted(self.comment_store.keys()):
_sect, _opt = item.split("::")
if _sect != _cursectname:
outstr += "\n%s\n%s\n" % (_sect, "-"* len(_sect))
_cursectname = _sect
txt = " ".join(self.comment_store[item])
txt = txt.replace("#", "").replace(";","")
outstr += ":%s: %s" % (_opt, txt)
return outstr
import UserDict as _UserDict
class _Chainmap(_UserDict.DictMixin):
"""Combine multiple mappings for successive lookups.
For example, to emulate Python's normal lookup sequence:
import __builtin__
pylookup = _Chainmap(locals(), globals(), vars(__builtin__))
"""
def __init__(self, *maps):
self._maps = maps
def __getitem__(self, key):
for mapping in self._maps:
try:
return mapping[key]
except KeyError:
pass
raise KeyError(key)
def keys(self):
result = []
seen = set()
for mapping in self._maps:
for key in mapping:
if key not in seen:
result.append(key)
seen.add(key)
return result
class ConfigParser(RawConfigParser):
def get(self, section, option, raw=False, vars=None):
"""Get an option value for a given section.
If `vars' is provided, it must be a dictionary. The option is looked up
in `vars' (if provided), `section', and in `defaults' in that order.
All % interpolations are expanded in the return values, unless the
optional argument `raw' is true. Values for interpolation keys are
looked up in the same manner as the option.
The section DEFAULT is special.
"""
sectiondict = {}
try:
sectiondict = self._sections[section]
except KeyError:
if section != DEFAULTSECT:
raise NoSectionError(section)
# Update with the entry specific variables
vardict = {}
if vars:
for key, value in vars.items():
vardict[self.optionxform(key)] = value
d = _Chainmap(vardict, sectiondict, self._defaults)
option = self.optionxform(option)
try:
value = d[option]
except KeyError:
raise NoOptionError(option, section)
if raw or value is None:
return value
else:
return self._interpolate(section, option, value, d)
def items(self, section, raw=False, vars=None):
"""Return a list of tuples with (name, value) for each option
in the section.
All % interpolations are expanded in the return values, based on the
defaults passed into the constructor, unless the optional argument
`raw' is true. Additional substitutions may be provided using the
`vars' argument, which must be a dictionary whose contents overrides
any pre-existing defaults.
The section DEFAULT is special.
"""
d = self._defaults.copy()
try:
d.update(self._sections[section])
except KeyError:
if section != DEFAULTSECT:
raise NoSectionError(section)
# Update with the entry specific variables
if vars:
for key, value in vars.items():
d[self.optionxform(key)] = value
options = d.keys()
if "__name__" in options:
options.remove("__name__")
if raw:
return [(option, d[option])
for option in options]
else:
return [(option, self._interpolate(section, option, d[option], d))
for option in options]
def _interpolate(self, section, option, rawval, vars):
# do the string interpolation
value = rawval
depth = MAX_INTERPOLATION_DEPTH
while depth: # Loop through this until it's done
depth -= 1
if value and "%(" in value:
value = self._KEYCRE.sub(self._interpolation_replace, value)
try:
value = value % vars
except KeyError, e:
raise InterpolationMissingOptionError(
option, section, rawval, e.args[0])
else:
break
if value and "%(" in value:
raise InterpolationDepthError(option, section, rawval)
return value
_KEYCRE = re.compile(r"%\(([^)]*)\)s|.")
def _interpolation_replace(self, match):
s = match.group(1)
if s is None:
return match.group()
else:
return "%%(%s)s" % self.optionxform(s)
class SafeConfigParser(ConfigParser):
def _interpolate(self, section, option, rawval, vars):
# do the string interpolation
L = []
self._interpolate_some(option, L, rawval, section, vars, 1)
return ''.join(L)
_interpvar_re = re.compile(r"%\(([^)]+)\)s")
def _interpolate_some(self, option, accum, rest, section, map, depth):
if depth > MAX_INTERPOLATION_DEPTH:
raise InterpolationDepthError(option, section, rest)
while rest:
p = rest.find("%")
if p < 0:
accum.append(rest)
return
if p > 0:
accum.append(rest[:p])
rest = rest[p:]
# p is no longer used
c = rest[1:2]
if c == "%":
accum.append("%")
rest = rest[2:]
elif c == "(":
m = self._interpvar_re.match(rest)
if m is None:
raise InterpolationSyntaxError(option, section,
"bad interpolation variable reference %r" % rest)
var = self.optionxform(m.group(1))
rest = rest[m.end():]
try:
v = map[var]
except KeyError:
raise InterpolationMissingOptionError(
option, section, rest, var)
if "%" in v:
self._interpolate_some(option, accum, v,
section, map, depth + 1)
else:
accum.append(v)
else:
raise InterpolationSyntaxError(
option, section,
"'%%' must be followed by '%%' or '(', found: %r" % (rest,))
def set(self, section, option, value=None):
"""Set an option. Extend ConfigParser.set: check for string values."""
# The only legal non-string value if we allow valueless
# options is None, so we need to check if the value is a
# string if:
# - we do not allow valueless options, or
# - we allow valueless options but the value is not None
if self._optcre is self.OPTCRE or value:
if not isinstance(value, basestring):
raise TypeError("option values must be strings")
if value is not None:
# check for bad percent signs:
# first, replace all "good" interpolations
tmp_value = value.replace('%%', '')
tmp_value = self._interpvar_re.sub('', tmp_value)
# then, check if there's a lone percent sign left
if '%' in tmp_value:
raise ValueError("invalid interpolation syntax in %r at "
"position %d" % (value, tmp_value.find('%')))
ConfigParser.set(self, section, option, value)

File diff suppressed because it is too large Load Diff

@ -1,171 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from collections import MutableMapping
try:
from collections import UserDict
except ImportError:
from UserDict import UserDict
try:
from collections import OrderedDict
except ImportError:
from ordereddict import OrderedDict
from io import open
import sys
try:
from thread import get_ident
except ImportError:
try:
from _thread import get_ident
except ImportError:
from _dummy_thread import get_ident
PY2 = sys.version_info[0] == 2
PY3 = sys.version_info[0] == 3
str = type('str')
def from_none(exc):
"""raise from_none(ValueError('a')) == raise ValueError('a') from None"""
exc.__cause__ = None
exc.__suppress_context__ = True
return exc
# from reprlib 3.2.1
def recursive_repr(fillvalue='...'):
'Decorator to make a repr function return fillvalue for a recursive call'
def decorating_function(user_function):
repr_running = set()
def wrapper(self):
key = id(self), get_ident()
if key in repr_running:
return fillvalue
repr_running.add(key)
try:
result = user_function(self)
finally:
repr_running.discard(key)
return result
# Can't use functools.wraps() here because of bootstrap issues
wrapper.__module__ = getattr(user_function, '__module__')
wrapper.__doc__ = getattr(user_function, '__doc__')
wrapper.__name__ = getattr(user_function, '__name__')
wrapper.__annotations__ = getattr(user_function, '__annotations__', {})
return wrapper
return decorating_function
# from collections 3.2.1
class _ChainMap(MutableMapping):
''' A ChainMap groups multiple dicts (or other mappings) together
to create a single, updateable view.
The underlying mappings are stored in a list. That list is public and can
accessed or updated using the *maps* attribute. There is no other state.
Lookups search the underlying mappings successively until a key is found.
In contrast, writes, updates, and deletions only operate on the first
mapping.
'''
def __init__(self, *maps):
'''Initialize a ChainMap by setting *maps* to the given mappings.
If no mappings are provided, a single empty dictionary is used.
'''
self.maps = list(maps) or [{}] # always at least one map
def __missing__(self, key):
raise KeyError(key)
def __getitem__(self, key):
for mapping in self.maps:
try:
return mapping[key] # can't use 'key in mapping' with defaultdict
except KeyError:
pass
return self.__missing__(key) # support subclasses that define __missing__
def get(self, key, default=None):
return self[key] if key in self else default
def __len__(self):
return len(set().union(*self.maps)) # reuses stored hash values if possible
def __iter__(self):
return iter(set().union(*self.maps))
def __contains__(self, key):
return any(key in m for m in self.maps)
@recursive_repr()
def __repr__(self):
return '{0.__class__.__name__}({1})'.format(
self, ', '.join(map(repr, self.maps)))
@classmethod
def fromkeys(cls, iterable, *args):
'Create a ChainMap with a single dict created from the iterable.'
return cls(dict.fromkeys(iterable, *args))
def copy(self):
'New ChainMap or subclass with a new copy of maps[0] and refs to maps[1:]'
return self.__class__(self.maps[0].copy(), *self.maps[1:])
__copy__ = copy
def new_child(self): # like Django's Context.push()
'New ChainMap with a new dict followed by all previous maps.'
return self.__class__({}, *self.maps)
@property
def parents(self): # like Django's Context.pop()
'New ChainMap from maps[1:].'
return self.__class__(*self.maps[1:])
def __setitem__(self, key, value):
self.maps[0][key] = value
def __delitem__(self, key):
try:
del self.maps[0][key]
except KeyError:
raise KeyError('Key not found in the first mapping: {!r}'.format(key))
def popitem(self):
'Remove and return an item pair from maps[0]. Raise KeyError is maps[0] is empty.'
try:
return self.maps[0].popitem()
except KeyError:
raise KeyError('No keys found in the first mapping.')
def pop(self, key, *args):
'Remove *key* from maps[0] and return its value. Raise KeyError if *key* not in maps[0].'
try:
return self.maps[0].pop(key, *args)
except KeyError:
raise KeyError('Key not found in the first mapping: {!r}'.format(key))
def clear(self):
'Clear maps[0], leaving maps[1:] intact.'
self.maps[0].clear()
try:
from collections import ChainMap
except ImportError:
ChainMap = _ChainMap

@ -1,40 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Convenience module importing everything from backports.configparser."""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from backports.configparser2 import (
RawConfigParser,
ConfigParser,
SafeConfigParser,
SectionProxy,
Interpolation,
BasicInterpolation,
ExtendedInterpolation,
LegacyInterpolation,
Error,
NoSectionError,
DuplicateSectionError,
DuplicateOptionError,
NoOptionError,
InterpolationError,
InterpolationMissingOptionError,
InterpolationSyntaxError,
InterpolationDepthError,
ParsingError,
MissingSectionHeaderError,
_UNSET,
DEFAULTSECT,
MAX_INTERPOLATION_DEPTH,
_default_dict,
_ChainMap,
)

@ -23,10 +23,12 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
try:
from configparser2 import ConfigParser as configparser, NoOptionError, NoSectionError
except ImportError:
from ConfigParser import SafeConfigParser as configparser, NoOptionError, NoSectionError
# Bazarr patch to use custom ConfigParser2:
from ConfigParser2 import ConfigParser as configparser, NoOptionError, NoSectionError
#try:
# from configparser2 import ConfigParser as configparser, NoOptionError, NoSectionError
#except ImportError:
# from ConfigParser import SafeConfigParser as configparser, NoOptionError, NoSectionError
class simpleconfigparser(configparser):

@ -9,7 +9,7 @@ chardet=3.0.4
configparser2=4.0.0
dogpile.cache=0.6.5
enzyme=0.4.1
geventwebsocker=0.10.1
gevent-websocker=0.10.1
gitpython=2.1.9
guessit=2.1.4
langdetect=1.0.7
@ -24,5 +24,4 @@ SimpleConfigParser=0.1.0
stevedore=1.28.0
subliminal=2.1.0dev
tzlocal=1.5.1
urllib3=1.23
waitress=1.1.0
urllib3=1.23

@ -56,7 +56,7 @@
<div class="ui grid">
<div class="row">
<div class="sixteen wide column">
<div class="ui inverted borderless labeled icon massive menu six item">
<div class="ui inverted borderless labeled icon massive menu seven item">
<div class="ui container">
% if settings.general.getboolean('use_sonarr'):
<a class="item" href="{{base_url}}series">
@ -97,6 +97,10 @@
<i class="laptop icon"></i>
System
</a>
<a id="donate" class="item" href="https://beerpay.io/morpheus65535/bazarr">
<i class="red heart icon"></i>
Donate
</a>
</div>
</div>
</div>

@ -1169,28 +1169,6 @@
<div class="ui dividing header">Subtitles providers</div>
<div class="twelve wide column">
<div class="ui grid">
<div class="middle aligned row">
<div class="right aligned four wide column">
<label>Argenteam</label>
</div>
<div class="one wide column">
<div id="argenteam" class="ui toggle checkbox provider">
<input type="checkbox">
<label></label>
</div>
</div>
<div class="collapsed column">
<div class="collapsed center aligned column">
<div class="ui basic icon" data-tooltip="Spanish" data-inverted="">
<i class="help circle large icon"></i>
</div>
</div>
</div>
</div>
<div id="argenteam_option" class="ui grid container">
</div>
<div class="middle aligned row">
<div class="right aligned four wide column">
<label>Addic7ed</label>
@ -1243,6 +1221,28 @@
</div>
</div>
<div class="middle aligned row">
<div class="right aligned four wide column">
<label>Argenteam</label>
</div>
<div class="one wide column">
<div id="argenteam" class="ui toggle checkbox provider">
<input type="checkbox">
<label></label>
</div>
</div>
<div class="collapsed column">
<div class="collapsed center aligned column">
<div class="ui basic icon" data-tooltip="Spanish subtitles provider." data-inverted="">
<i class="help circle large icon"></i>
</div>
</div>
</div>
</div>
<div id="argenteam_option" class="ui grid container">
</div>
<div class="middle aligned row">
<div class="right aligned four wide column">
<label>Assrt</label>
@ -1255,7 +1255,7 @@
</div>
<div class="collapsed column">
<div class="collapsed center aligned column">
<div class="ui basic icon" data-tooltip="Chinese" data-inverted="">
<div class="ui basic icon" data-tooltip="Chinese subtitles provider." data-inverted="">
<i class="help circle large icon"></i>
</div>
</div>
@ -1286,7 +1286,7 @@
</div>
<div class="collapsed column">
<div class="collapsed center aligned column">
<div class="ui basic icon" data-tooltip="Hungarian" data-inverted="">
<div class="ui basic icon" data-tooltip="Hungarian subtitles provider." data-inverted="">
<i class="help circle large icon"></i>
</div>
</div>
@ -1308,7 +1308,7 @@
</div>
<div class="collapsed column">
<div class="collapsed center aligned column">
<div class="ui basic icon" data-tooltip="mostly pt-BR; UNRAR NEEDED" data-inverted="">
<div class="ui basic icon" data-tooltip="Brazilian Portuguese subtitles provider." data-inverted="">
<i class="help circle large icon"></i>
</div>
</div>
@ -1349,7 +1349,7 @@
</div>
<div class="collapsed column">
<div class="collapsed center aligned column">
<div class="ui basic icon" data-tooltip="Polish" data-inverted="">
<div class="ui basic icon" data-tooltip="Polish subtitles provider." data-inverted="">
<i class="help circle large icon"></i>
</div>
</div>
@ -1421,7 +1421,7 @@
</div>
<div class="collapsed column">
<div class="collapsed center aligned column">
<div class="ui basic icon" data-tooltip="Use SSL connection to OS" data-inverted="">
<div class="ui basic icon" data-tooltip="Use SSL to connect to OpenSubtitles" data-inverted="">
<i class="help circle large icon"></i>
</div>
</div>
@ -1439,7 +1439,7 @@
</div>
<div class="collapsed column">
<div class="collapsed center aligned column">
<div class="ui basic icon" data-tooltip="Skip subtitles with a mismatched FPS value; might lead to more results when disabled but also to more false-positives" data-inverted="">
<div class="ui basic icon" data-tooltip="Skip subtitles with a mismatched FPS value; might lead to more results when disabled but also to more false-positives." data-inverted="">
<i class="help circle large icon"></i>
</div>
</div>
@ -1462,6 +1462,51 @@
</div>
<div class="middle aligned row">
<div class="right aligned four wide column">
<label>Shooter</label>
</div>
<div class="one wide column">
<div id="shooter" class="ui toggle checkbox provider">
<input type="checkbox">
<label></label>
</div>
</div>
</div>
<div id="shooter_option" class="ui grid container">
</div>
<div class="middle aligned row">
<div class="right aligned four wide column">
<label>Subscene</label>
</div>
<div class="one wide column">
<div id="subscene" class="ui toggle checkbox provider">
<input type="checkbox">
<label></label>
</div>
</div>
</div>
<div id="subscene_option" class="ui grid container">
</div>
<div class="middle aligned row">
<div class="right aligned four wide column">
<label>Subscenter</label>
</div>
<div class="one wide column">
<div id="subscenter" class="ui toggle checkbox provider">
<input type="checkbox">
<label></label>
</div>
</div>
</div>
<div id="subscenter_option" class="ui grid container">
</div>
<div class="middle aligned row">
<div class="right aligned four wide column">
<label>Supersubtitles</label>
@ -1925,7 +1970,7 @@
$('#settings_use_radarr').checkbox({
onChecked: function() {
$("#radarr_tab").removeClass('disabled');
$('#sonarr_validated').checkbox('uncheck');
$('#radarr_validated').checkbox('uncheck');
$('.form').form('validate form');
$('#loader').removeClass('active');
},
@ -2419,14 +2464,14 @@
$('.radarr_config').on('keyup', function() {
$('#radarr_validated').checkbox('uncheck');
$('#radarr_validation_result').text('You must test your Sonarr connection settings before saving settings.').css('color', 'red');
$('#radarr_validation_result').text('You must test your Radarr connection settings before saving settings.').css('color', 'red');
$('.form').form('validate form');
$('#loader').removeClass('active');
});
$('#settings_radarr_ssl').on('change', function() {
$('#radarr_validated').checkbox('uncheck');
$('#radarr_validation_result').text('You must test your Sonarr connection settings before saving settings.').css('color', 'red');
$('#radarr_validation_result').text('You must test your Radarr connection settings before saving settings.').css('color', 'red');
$('.form').form('validate form');
$('#loader').removeClass('active');
});

Loading…
Cancel
Save