Merge pull request #72 from morpheus65535/radarr

Radarr integration
pull/115/merge
morpheus65535 7 years ago committed by GitHub
commit 9efdcde095
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,4 +1,4 @@
bazarr_version = '0.4.1'
bazarr_version = '0.5.0'
import gc
gc.enable()
@ -62,6 +62,8 @@ from update_modules import *
from bottle import route, run, template, static_file, request, redirect, response
import bottle
bottle.TEMPLATE_PATH.insert(0,os.path.join(os.path.dirname(__file__), 'views/'))
bottle.debug(True)
bottle.TEMPLATES.clear()
from json import dumps
import itertools
@ -127,14 +129,53 @@ def image_proxy(url):
url_sonarr_short = get_sonarr_settings()[1]
apikey = get_sonarr_settings()[2]
url_image = url_sonarr_short + '/' + url + '?apikey=' + apikey
img_pil = Image.open(BytesIO(requests.get(url_sonarr_short + '/api' + url_image.split(url_sonarr)[1]).content))
try:
img_pil = Image.open(BytesIO(requests.get(url_sonarr_short + '/api' + url_image.split(url_sonarr)[1]).content))
except:
return None
else:
img_buffer = BytesIO()
img_pil.tobytes()
img_pil.save(img_buffer, img_pil.format)
img_buffer.seek(0)
return send_file(img_buffer, ctype=img_pil.format)
@route(base_url + 'image_proxy_movies/<url:path>', method='GET')
def image_proxy_movies(url):
from get_radarr_settings import get_radarr_settings
url_radarr = get_radarr_settings()[0]
url_radarr_short = get_radarr_settings()[1]
apikey = get_radarr_settings()[2]
try:
url_image = (url_radarr_short + '/' + url + '?apikey=' + apikey).replace('/fanart.jpg', '/banner.jpg')
img_pil = Image.open(BytesIO(requests.get(url_radarr_short + '/api' + url_image.split(url_radarr)[1]).content))
except:
url_image = url_radarr_short + '/' + url + '?apikey=' + apikey
img_pil = Image.open(BytesIO(requests.get(url_radarr_short + '/api' + url_image.split(url_radarr)[1]).content))
img_buffer = BytesIO()
img_pil.tobytes()
img_pil.save(img_buffer, img_pil.format)
img_buffer.seek(0)
return send_file(img_buffer, ctype=img_pil.format)
@route(base_url)
def redirect_root():
conn = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db'), timeout=30)
c = conn.cursor()
integration = c.execute("SELECT use_sonarr, use_radarr FROM table_settings_general").fetchone()
c.close()
if integration[0] == "True":
redirect(base_url + 'series')
elif integration[1] == "True":
redirect(base_url + 'movies')
else:
redirect(base_url + 'settings')
@route(base_url + 'series')
def series():
import update_db
single_language = get_general_settings()[7]
@ -184,20 +225,26 @@ def serieseditor():
output = template('serieseditor', __file__=__file__, bazarr_version=bazarr_version, rows=data, languages=languages, missing_count=missing_count, base_url=base_url, single_language=single_language)
return output
@route(base_url + 'series_json/<query>', method='GET')
def series_json(query):
@route(base_url + 'search_json/<query>', method='GET')
def search_json(query):
db = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db'), timeout=30)
c = db.cursor()
c.execute("SELECT title, sonarrSeriesId FROM table_shows WHERE title LIKE ? ORDER BY title", ('%'+query+'%',))
data = c.fetchall()
series = c.fetchall()
c.execute("SELECT title, radarrId FROM table_movies WHERE title LIKE ? ORDER BY title", ('%' + query + '%',))
movies = c.fetchall()
search_list = []
for serie in series:
search_list.append(dict([('name', serie[0]), ('url', base_url + 'episodes/' + str(serie[1]))]))
series_list = []
for serie in data:
series_list.append(dict([('name', serie[0]), ('url', base_url + 'episodes/' + str(serie[1]))]))
for movie in movies:
search_list.append(dict([('name', movie[0]), ('url', base_url + 'movie/' + str(movie[1]))]))
response.content_type = 'application/json'
return dict(items=series_list)
return dict(items=search_list)
@route(base_url + 'edit_series/<no:int>', method='POST')
@ -284,6 +331,131 @@ def episodes(no):
return template('episodes', __file__=__file__, 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)
@route(base_url + 'movies')
def movies():
import update_db
single_language = get_general_settings()[7]
db = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db'), timeout=30)
db.create_function("path_substitution", 1, path_replace)
c = db.cursor()
c.execute("SELECT COUNT(*) FROM table_movies")
missing_count = c.fetchone()
missing_count = missing_count[0]
page = request.GET.page
if page == "":
page = "1"
offset = (int(page) - 1) * 15
max_page = int(math.ceil(missing_count / 15.0))
c.execute("SELECT tmdbId, title, path_substitution(path), languages, hearing_impaired, radarrId, poster, audio_language FROM table_movies ORDER BY title ASC LIMIT 15 OFFSET ?", (offset,))
data = c.fetchall()
c.execute("SELECT code2, name FROM table_settings_languages WHERE enabled = 1")
languages = c.fetchall()
c.close()
output = template('movies', __file__=__file__, bazarr_version=bazarr_version, rows=data, languages=languages, missing_count=missing_count, page=page, max_page=max_page, base_url=base_url, single_language=single_language)
return output
@route(base_url + 'movieseditor')
def movieseditor():
single_language = get_general_settings()[7]
db = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db'), timeout=30)
db.create_function("path_substitution", 1, path_replace)
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()
c.execute("SELECT code2, name FROM table_settings_languages WHERE enabled = 1")
languages = c.fetchall()
c.close()
output = template('movieseditor', __file__=__file__, bazarr_version=bazarr_version, rows=data, languages=languages, missing_count=missing_count, base_url=base_url, single_language=single_language)
return output
@route(base_url + 'edit_movieseditor', method='POST')
def edit_movieseditor():
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(os.path.dirname(__file__), 'data/db/bazarr.db'), timeout=30)
c = conn.cursor()
for movie in movies:
if str(lang) != "[]" and str(lang) != "['']":
if str(lang) == "['None']":
lang = 'None'
else:
lang = str(lang)
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)
@route(base_url + 'edit_movie/<no:int>', method='POST')
def edit_movie(no):
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(os.path.dirname(__file__), 'data/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)
@route(base_url + 'movie/<no:int>', method='GET')
def movie(no):
from get_radarr_settings import get_radarr_settings
single_language = get_general_settings()[7]
url_radarr_short = get_radarr_settings()[1]
conn = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db'), timeout=30)
conn.create_function("path_substitution", 1, path_replace)
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 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', __file__=__file__, bazarr_version=bazarr_version, no=no, details=movies_details, languages=languages, url_radarr_short=url_radarr_short, base_url=base_url, tmdbid=tmdbid)
@route(base_url + 'scan_disk/<no:int>', method='GET')
def scan_disk(no):
ref = request.environ['HTTP_REFERER']
@ -292,6 +464,14 @@ def scan_disk(no):
redirect(ref)
@route(base_url + 'scan_disk_movie/<no:int>', method='GET')
def scan_disk_movie(no):
ref = request.environ['HTTP_REFERER']
movies_scan_subtitles(no)
redirect(ref)
@route(base_url + 'search_missing_subtitles/<no:int>', method='GET')
def search_missing_subtitles(no):
ref = request.environ['HTTP_REFERER']
@ -300,8 +480,20 @@ def search_missing_subtitles(no):
redirect(ref)
@route(base_url + 'search_missing_subtitles_movie/<no:int>', method='GET')
def search_missing_subtitles_movie(no):
ref = request.environ['HTTP_REFERER']
movies_download_subtitles(no)
redirect(ref)
@route(base_url + 'history')
def history():
return template('history', __file__=__file__, bazarr_version=bazarr_version, base_url=base_url)
@route(base_url + 'historyseries')
def historyseries():
db = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db'), timeout=30)
c = db.cursor()
@ -333,10 +525,49 @@ def history():
data = c.fetchall()
c.close()
data = reversed(sorted(data, key=operator.itemgetter(4)))
return template('history', __file__=__file__, bazarr_version=bazarr_version, rows=data, row_count=row_count, page=page, max_page=max_page, stats=stats, base_url=base_url)
return template('historyseries', __file__=__file__, bazarr_version=bazarr_version, rows=data, row_count=row_count, page=page, max_page=max_page, stats=stats, base_url=base_url)
@route(base_url + 'historymovies')
def historymovies():
db = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/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]
page = request.GET.page
if page == "":
page = "1"
offset = (int(page) - 1) * 15
max_page = int(math.ceil(row_count / 15.0))
now = datetime.now()
today = []
thisweek = []
thisyear = []
stats = c.execute("SELECT timestamp FROM table_history_movie WHERE action LIKE '1'").fetchall()
total = len(stats)
for stat in stats:
if now - timedelta(hours=24) <= datetime.fromtimestamp(stat[0]) <= now:
today.append(datetime.fromtimestamp(stat[0]).date())
if now - timedelta(weeks=1) <= datetime.fromtimestamp(stat[0]) <= now:
thisweek.append(datetime.fromtimestamp(stat[0]).date())
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 15 OFFSET ?", (offset,))
data = c.fetchall()
c.close()
data = reversed(sorted(data, key=operator.itemgetter(2)))
return template('historymovies', __file__=__file__, bazarr_version=bazarr_version, rows=data, row_count=row_count, page=page, max_page=max_page, stats=stats, base_url=base_url)
@route(base_url + 'wanted')
def wanted():
return template('wanted', __file__=__file__, bazarr_version=bazarr_version, base_url=base_url)
@route(base_url + 'wantedseries')
def wantedseries():
db = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db'), timeout=30)
db.create_function("path_substitution", 1, path_replace)
c = db.cursor()
@ -353,7 +584,27 @@ def wanted():
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 FROM table_episodes INNER JOIN table_shows on table_shows.sonarrSeriesId = table_episodes.sonarrSeriesId WHERE table_episodes.missing_subtitles != '[]' ORDER BY table_episodes._rowid_ DESC LIMIT 15 OFFSET ?", (offset,))
data = c.fetchall()
c.close()
return template('wanted', __file__=__file__, bazarr_version=bazarr_version, rows=data, missing_count=missing_count, page=page, max_page=max_page, base_url=base_url)
return template('wantedseries', __file__=__file__, bazarr_version=bazarr_version, rows=data, missing_count=missing_count, page=page, max_page=max_page, base_url=base_url)
@route(base_url + 'wantedmovies')
def wantedmovies():
db = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db'), timeout=30)
db.create_function("path_substitution", 1, path_replace)
c = db.cursor()
c.execute("SELECT COUNT(*) FROM table_movies WHERE missing_subtitles != '[]'")
missing_count = c.fetchone()
missing_count = missing_count[0]
page = request.GET.page
if page == "":
page = "1"
offset = (int(page) - 1) * 15
max_page = int(math.ceil(missing_count / 15.0))
c.execute("SELECT title, missing_subtitles, radarrId, path_substitution(path), hearing_impaired, sceneName FROM table_movies WHERE missing_subtitles != '[]' ORDER BY _rowid_ DESC LIMIT 15 OFFSET ?", (offset,))
data = c.fetchall()
c.close()
return template('wantedmovies', __file__=__file__, bazarr_version=bazarr_version, rows=data, missing_count=missing_count, page=page, max_page=max_page, base_url=base_url)
@route(base_url + 'wanted_search_missing_subtitles')
def wanted_search_missing_subtitles_list():
@ -375,10 +626,12 @@ def settings():
settings_providers = c.fetchall()
c.execute("SELECT * FROM table_settings_sonarr")
settings_sonarr = c.fetchone()
c.execute("SELECT * FROM table_settings_radarr")
settings_radarr = c.fetchone()
c.execute("SELECT * FROM table_settings_notifier")
settings_notifier = c.fetchall()
c.close()
return template('settings', __file__=__file__, bazarr_version=bazarr_version, settings_general=settings_general, settings_languages=settings_languages, settings_providers=settings_providers, settings_sonarr=settings_sonarr, settings_notifier=settings_notifier, base_url=base_url)
return template('settings', __file__=__file__, bazarr_version=bazarr_version, settings_general=settings_general, settings_languages=settings_languages, settings_providers=settings_providers, settings_sonarr=settings_sonarr, settings_radarr=settings_radarr, settings_notifier=settings_notifier, base_url=base_url)
@route(base_url + 'save_settings', method='POST')
def save_settings():
@ -418,10 +671,20 @@ def save_settings():
else:
settings_general_use_postprocessing = 'True'
settings_general_postprocessing_cmd = request.forms.get('settings_general_postprocessing_cmd')
settings_general_use_sonarr = request.forms.get('settings_general_use_sonarr')
if settings_general_use_sonarr is None:
settings_general_use_sonarr = 'False'
else:
settings_general_use_sonarr = 'True'
settings_general_use_radarr = request.forms.get('settings_general_use_radarr')
if settings_general_use_radarr is None:
settings_general_use_radarr = 'False'
else:
settings_general_use_radarr = 'True'
before = c.execute("SELECT ip, port, base_url FROM table_settings_general").fetchone()
after = (unicode(settings_general_ip), int(settings_general_port), unicode(settings_general_baseurl))
c.execute("UPDATE table_settings_general SET ip = ?, port = ?, base_url = ?, path_mapping = ?, log_level = ?, branch=?, auto_update=?, single_language=?, minimum_score=?, use_scenename=?, use_postprocessing=?, postprocessing_cmd=?", (unicode(settings_general_ip), int(settings_general_port), unicode(settings_general_baseurl), unicode(settings_general_pathmapping), unicode(settings_general_loglevel), unicode(settings_general_branch), unicode(settings_general_automatic), unicode(settings_general_single_language), unicode(settings_general_minimum_score), unicode(settings_general_scenename), unicode(settings_general_use_postprocessing), unicode(settings_general_postprocessing_cmd) ))
before = c.execute("SELECT ip, port, base_url, log_level, path_mapping, use_sonarr, use_radarr FROM table_settings_general").fetchone()
after = (unicode(settings_general_ip), int(settings_general_port), unicode(settings_general_baseurl), unicode(settings_general_loglevel), unicode(settings_general_pathmapping), unicode(settings_general_use_sonarr), unicode(settings_general_use_radarr))
c.execute("UPDATE table_settings_general SET ip = ?, port = ?, base_url = ?, path_mapping = ?, log_level = ?, branch=?, auto_update=?, single_language=?, minimum_score=?, use_scenename=?, use_postprocessing=?, postprocessing_cmd=?, use_sonarr=?, use_radarr=?", (unicode(settings_general_ip), int(settings_general_port), unicode(settings_general_baseurl), unicode(settings_general_pathmapping), unicode(settings_general_loglevel), unicode(settings_general_branch), unicode(settings_general_automatic), unicode(settings_general_single_language), unicode(settings_general_minimum_score), unicode(settings_general_scenename), unicode(settings_general_use_postprocessing), unicode(settings_general_postprocessing_cmd), unicode(settings_general_use_sonarr), unicode(settings_general_use_radarr)))
conn.commit()
if after != before:
configured()
@ -439,6 +702,19 @@ def save_settings():
settings_sonarr_sync = request.forms.get('settings_sonarr_sync')
c.execute("UPDATE table_settings_sonarr SET ip = ?, port = ?, base_url = ?, ssl = ?, apikey = ?, full_update = ?", (settings_sonarr_ip, settings_sonarr_port, settings_sonarr_baseurl, settings_sonarr_ssl, settings_sonarr_apikey, 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')
settings_radarr_ssl = request.forms.get('settings_radarr_ssl')
if settings_radarr_ssl is None:
settings_radarr_ssl = 'False'
else:
settings_radarr_ssl = 'True'
settings_radarr_apikey = request.forms.get('settings_radarr_apikey')
settings_radarr_sync = request.forms.get('settings_radarr_sync')
c.execute("UPDATE table_settings_radarr SET ip = ?, port = ?, base_url = ?, ssl = ?, apikey = ?, full_update = ?", (settings_radarr_ip, settings_radarr_port, settings_radarr_baseurl, settings_radarr_ssl, settings_radarr_apikey, settings_radarr_sync))
settings_subliminal_providers = request.forms.getall('settings_subliminal_providers')
c.execute("UPDATE table_settings_providers SET enabled = 0")
for item in settings_subliminal_providers:
@ -744,6 +1020,7 @@ def execute_task(taskid):
redirect(ref)
@route(base_url + 'remove_subtitles', method='POST')
def remove_subtitles():
episodePath = request.forms.get('episodePath')
@ -751,7 +1028,6 @@ def remove_subtitles():
subtitlesPath = request.forms.get('subtitlesPath')
sonarrSeriesId = request.forms.get('sonarrSeriesId')
sonarrEpisodeId = request.forms.get('sonarrEpisodeId')
tvdbid = request.forms.get('tvdbid')
try:
os.remove(subtitlesPath)
@ -762,6 +1038,24 @@ def remove_subtitles():
store_subtitles(unicode(episodePath))
list_missing_subtitles(sonarrSeriesId)
@route(base_url + 'remove_subtitles_movie', method='POST')
def remove_subtitles_movie():
moviePath = request.forms.get('moviePath')
language = request.forms.get('language')
subtitlesPath = request.forms.get('subtitlesPath')
radarrId = request.forms.get('radarrId')
try:
os.remove(subtitlesPath)
result = pycountry.languages.lookup(language).name + " subtitles deleted from disk."
history_log_movie(0, radarrId, result)
except OSError:
pass
store_subtitles_movie(unicode(moviePath))
list_missing_subtitles_movies(radarrId)
@route(base_url + 'get_subtitle', method='POST')
def get_subtitle():
ref = request.environ['HTTP_REFERER']
@ -798,7 +1092,7 @@ def get_subtitle():
providers_auth = None
try:
result = download_subtitle(episodePath, language, hi, providers_list, providers_auth, sceneName)
result = download_subtitle(episodePath, language, hi, providers_list, providers_auth, sceneName, 'series')
if result is not None:
history_log(1, sonarrSeriesId, sonarrEpisodeId, result)
send_notifications(sonarrSeriesId, sonarrEpisodeId, result)
@ -808,6 +1102,51 @@ def get_subtitle():
except OSError:
pass
@route(base_url + 'get_subtitle_movie', method='POST')
def get_subtitle_movie():
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')
radarrId = request.forms.get('radarrId')
tmdbid = request.forms.get('tmdbid')
db = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db'), timeout=30)
c = db.cursor()
c.execute("SELECT * FROM table_settings_providers WHERE enabled = 1")
enabled_providers = c.fetchall()
c.close()
providers_list = []
providers_auth = {}
if len(enabled_providers) > 0:
for provider in enabled_providers:
providers_list.append(provider[0])
try:
if provider[2] is not '' and provider[3] is not '':
provider_auth = providers_auth.append(provider[0])
provider_auth.update({'username':providers[2], 'password':providers[3]})
else:
providers_auth = None
except:
providers_auth = None
else:
providers_list = None
providers_auth = None
try:
result = download_subtitle(moviePath, language, hi, providers_list, providers_auth, sceneName, 'movies')
if result is not None:
history_log_movie(1, radarrId, result)
send_notifications_movie(radarrId, result)
store_subtitles_movie(unicode(moviePath))
list_missing_subtitles_movies(radarrId)
redirect(ref)
except OSError:
pass
def configured():
conn = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db'), timeout=30)
c = conn.cursor()

@ -38,9 +38,18 @@ CREATE TABLE "table_settings_general" (
`path_mapping` TEXT,
`log_level` TEXT,
`branch` TEXT,
`auto_update` INTEGER
`auto_update` INTEGER,
`configured` INTEGER,
`updated` INTEGER,
`single_language` TEXT,
`minimum_score` TEXT,
`use_scenename` TEXT,
`use_postprocessing` TEXT,
`postprocessing_cmd` TEXT,
`use_sonarr` TEXT,
`use_radarr` TEXT
);
INSERT INTO `table_settings_general` (ip,port,base_url,path_mapping,log_level, branch, auto_update) VALUES ('0.0.0.0',6767,'/',Null,'INFO','master','True');
INSERT INTO `table_settings_general` (ip,port,base_url,path_mapping,log_level, branch, auto_update, configured, updated, single_language, minimum_score, use_scenename, use_postprocessing, postprocessing_cmd, use_sonarr, use_radarr) VALUES ('0.0.0.0',6767,'/',Null,'INFO','master','True',0,0,'False','0','False','False',Null,'False','False');
CREATE TABLE "table_history" (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
`action` INTEGER NOT NULL,

@ -7,7 +7,13 @@ from get_general_settings import *
from list_subtitles import *
def update_all_episodes():
full_scan_subtitles()
series_full_scan_subtitles()
logging.info('All existing subtitles indexed from disk.')
list_missing_subtitles()
logging.info('All missing subtitles updated in database.')
def update_all_movies():
movies_full_scan_subtitles()
logging.info('All existing subtitles indexed from disk.')
list_missing_subtitles()
logging.info('All missing subtitles updated in database.')

@ -1,6 +1,7 @@
import sqlite3
import os
import ast
import re
def get_general_settings():
# Open database connection
@ -31,14 +32,16 @@ def get_general_settings():
use_scenename = general_settings[11]
use_postprocessing = general_settings[12]
postprocessing_cmd = general_settings[13]
use_sonarr = general_settings[14]
use_radarr = general_settings[15]
return [ip, port, base_url, path_mappings, log_level, branch, automatic, single_language, minimum_score, use_scenename, use_postprocessing, postprocessing_cmd]
return [ip, port, base_url, path_mappings, log_level, branch, automatic, single_language, minimum_score, use_scenename, use_postprocessing, postprocessing_cmd, use_sonarr, use_radarr]
def path_replace(path):
for path_mapping in path_mappings:
if path_mapping[0] in path:
path = path.replace(path_mapping[0], path_mapping[1])
if path.startswith('\\\\'):
if path.startswith('\\\\') or re.match(r'^[a-zA-Z]:\\', path):
path = path.replace('/', '\\')
elif path.startswith('/'):
path = path.replace('\\', '/')
@ -49,7 +52,7 @@ def path_replace_reverse(path):
for path_mapping in path_mappings:
if path_mapping[1] in path:
path = path.replace(path_mapping[1], path_mapping[0])
if path.startswith('\\\\'):
if path.startswith('\\\\') or re.match(r'^[a-zA-Z]:\\', path):
path = path.replace('/', '\\')
elif path.startswith('/'):
path = path.replace('\\', '/')
@ -78,4 +81,6 @@ single_language = result[7]
minimum_score = result[8]
use_scenename = result[9]
use_processing = result[10]
postprocessing_cmd = result[11]
postprocessing_cmd = result[11]
use_sonarr = result[12]
use_radarr = result[13]

@ -0,0 +1,108 @@
import os
import sqlite3
import requests
from get_general_settings import *
from list_subtitles import *
def update_movies():
from get_radarr_settings import get_radarr_settings
url_radarr = get_radarr_settings()[0]
url_radarr_short = get_radarr_settings()[1]
apikey_radarr = get_radarr_settings()[2]
# Open database connection
db = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db'), timeout=30)
c = db.cursor()
if apikey_radarr == None:
pass
else:
get_profile_list()
# Get movies data from radarr
url_radarr_api_movies = url_radarr + "/api/movie?apikey=" + apikey_radarr
r = requests.get(url_radarr_api_movies)
# Get current movies in DB
current_movies_db = c.execute('SELECT tmdbId FROM table_movies').fetchall()
current_movies_db_list = [x[0] for x in current_movies_db]
current_movies_radarr = []
for movie in r.json():
if movie['hasFile'] is True:
try:
overview = unicode(movie['overview'])
except:
overview = ""
try:
poster_big = movie['images'][0]['url']
poster = os.path.splitext(poster_big)[0] + '-500' + os.path.splitext(poster_big)[1]
except:
poster = ""
try:
fanart = movie['images'][1]['url']
except:
fanart = ""
if 'sceneName' in movie['movieFile']:
sceneName = movie['movieFile']['sceneName']
else:
sceneName = None
# Add movies in radarr to current movies list
current_movies_radarr.append(unicode(movie['tmdbId']))
# Detect file separator
if movie['path'][0] == "/":
separator = "/"
else:
separator == "\\"
# Update or insert movies list in database table
try:
c.execute('''INSERT INTO table_movies(title, path, tmdbId, languages,`hearing_impaired`, radarrId, overview, poster, fanart, `audio_language`, sceneName) VALUES (?,?,?,(SELECT languages FROM table_movies WHERE tmdbId = ?),(SELECT `hearing_impaired` FROM table_movies WHERE tmdbId = ?), ?, ?, ?, ?, ?, ?)''', (movie["title"], movie["path"] + separator + movie['movieFile']['relativePath'], movie["tmdbId"], movie["tmdbId"], movie["tmdbId"], movie["id"], overview, poster, fanart, profile_id_to_language(movie['qualityProfileId']), sceneName))
except:
c.execute('''UPDATE table_movies SET title = ?, path = ?, tmdbId = ?, radarrId = ?, overview = ?, poster = ?, fanart = ?, `audio_language` = ?, sceneName = ? WHERE tmdbid = ?''', (movie["title"],movie["path"] + separator + movie['movieFile']['relativePath'],movie["tmdbId"],movie["id"],overview,poster,fanart,profile_id_to_language(movie['qualityProfileId']),sceneName,movie["tmdbId"]))
# Commit changes to database table
db.commit()
# Delete movies not in radarr anymore
added_movies = list(set(current_movies_radarr) - set(current_movies_db_list))
removed_movies = list(set(current_movies_db_list) - set(current_movies_radarr))
for removed_movie in removed_movies:
c.execute('DELETE FROM table_movies WHERE radarrId = ?', (removed_movie,))
db.commit()
for added_movie in added_movies:
added_path = c.execute('SELECT path FROM table_movies WHERE tmdbId = ?', (added_movie,)).fetchone()
store_subtitles_movie(path_replace(added_path[0]))
# Close database connection
db.close()
def get_profile_list():
from get_radarr_settings import get_radarr_settings
url_radarr = get_radarr_settings()[0]
url_radarr_short = get_radarr_settings()[1]
apikey_radarr = get_radarr_settings()[2]
# Get profiles data from radarr
url_radarr_api_movies = url_radarr + "/api/profile?apikey=" + apikey_radarr
profiles_json = requests.get(url_radarr_api_movies)
global profiles_list
profiles_list = []
# Parsing data returned from radarr
for profile in profiles_json.json():
profiles_list.append([profile['id'], profile['language'].capitalize()])
def profile_id_to_language(id):
for profile in profiles_list:
if id == profile[0]:
return profile[1]
if __name__ == '__main__':
update_movies()

@ -0,0 +1,40 @@
import sqlite3
import os
import ast
def get_radarr_settings():
# Open database connection
db = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db'), timeout=30)
c = db.cursor()
# Get Radarr API URL from database config table
c.execute('''SELECT * FROM table_settings_radarr''')
config_radarr = c.fetchone()
# Close database connection
db.close()
# Build radarr URL
ip_radarr = config_radarr[0]
port_radarr = str(config_radarr[1])
baseurl_radarr = config_radarr[2]
ssl_radarr = config_radarr[3]
apikey_radarr = config_radarr[4]
full_update = config_radarr[5]
if ssl_radarr == 1:
protocol_radarr = "https"
else:
protocol_radarr = "http"
if baseurl_radarr == None:
baseurl_radarr = "/"
if baseurl_radarr.startswith("/") == False:
baseurl_radarr = "/" + baseurl_radarr
if baseurl_radarr.endswith("/"):
baseurl_radarr = baseurl_radarr[:-1]
url_radarr = protocol_radarr + "://" + ip_radarr + ":" + port_radarr + baseurl_radarr
url_radarr_short = protocol_radarr + "://" + ip_radarr + ":" + port_radarr
return [url_radarr, url_radarr_short, apikey_radarr, full_update]

@ -10,13 +10,17 @@ from bs4 import UnicodeDammit
from get_general_settings import *
from list_subtitles import *
from utils import *
from notifier import send_notifications
from notifier import send_notifications, send_notifications_movie
# configure the cache
region.configure('dogpile.cache.memory')
def download_subtitle(path, language, hi, providers, providers_auth, sceneName):
minimum_score = float(get_general_settings()[8]) / 100 * 359
def download_subtitle(path, language, hi, providers, providers_auth, sceneName, type):
if type == 'series':
type_of_score = 359
elif type == 'movies':
type_of_score = 119
minimum_score = float(get_general_settings()[8]) / 100 * type_of_score
use_scenename = get_general_settings()[9]
use_postprocessing = get_general_settings()[10]
postprocessing_cmd = get_general_settings()[11]
@ -45,7 +49,7 @@ def download_subtitle(path, language, hi, providers, providers_auth, sceneName):
else:
single = get_general_settings()[7]
try:
score = round(float(compute_score(best_subtitle, video)) / 359 * 100, 2)
score = round(float(compute_score(best_subtitle, video)) / type_of_score * 100, 2)
if used_sceneName == True:
video = scan_video(path)
if single == 'True':
@ -122,13 +126,47 @@ def series_download_subtitles(no):
for episode in episodes_details:
for language in ast.literal_eval(episode[1]):
message = download_subtitle(path_replace(episode[0]), str(pycountry.languages.lookup(language).alpha_3), series_details[0], providers_list, providers_auth, episode[3])
message = download_subtitle(path_replace(episode[0]), str(pycountry.languages.lookup(language).alpha_3), series_details[0], providers_list, providers_auth, episode[3], 'series')
if message is not None:
store_subtitles(path_replace(episode[0]))
history_log(1, no, episode[2], message)
send_notifications(no, episode[2], message)
list_missing_subtitles(no)
def movies_download_subtitles(no):
conn_db = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db'), timeout=30)
c_db = conn_db.cursor()
movie = c_db.execute("SELECT path, missing_subtitles, radarrId, sceneName, hearing_impaired FROM table_movies WHERE radarrId = ?", (no,)).fetchone()
enabled_providers = c_db.execute("SELECT * FROM table_settings_providers WHERE enabled = 1").fetchall()
c_db.close()
providers_list = []
providers_auth = {}
if len(enabled_providers) > 0:
for provider in enabled_providers:
providers_list.append(provider[0])
try:
if provider[2] is not '' and provider[3] is not '':
provider_auth = providers_auth.append(provider[0])
provider_auth.update({'username': providers[2], 'password': providers[3]})
else:
providers_auth = None
except:
providers_auth = None
else:
providers_list = None
providers_auth = None
for language in ast.literal_eval(movie[1]):
message = download_subtitle(path_replace(movie[0]), str(pycountry.languages.lookup(language).alpha_3), movie[4], providers_list, providers_auth, movie[3], 'movies')
if message is not None:
store_subtitles_movie(path_replace(movie[0]))
history_log_movie(1, no, message)
send_notifications_movie(no, message)
list_missing_subtitles_movies(no)
def wanted_download_subtitles(path):
conn_db = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db'), timeout=30)
c_db = conn_db.cursor()
@ -155,7 +193,7 @@ def wanted_download_subtitles(path):
for episode in episodes_details:
for language in ast.literal_eval(episode[1]):
message = download_subtitle(path_replace(episode[0]), str(pycountry.languages.lookup(language).alpha_3), episode[4], providers_list, providers_auth, episode[5])
message = download_subtitle(path_replace(episode[0]), str(pycountry.languages.lookup(language).alpha_3), episode[4], providers_list, providers_auth, episode[5], 'series')
if message is not None:
store_subtitles(path_replace(episode[0]))
list_missing_subtitles(episode[3])
@ -167,11 +205,15 @@ def wanted_search_missing_subtitles():
db.create_function("path_substitution", 1, path_replace)
c = db.cursor()
c.execute("SELECT path_substitution(path) FROM table_episodes WHERE table_episodes.missing_subtitles != '[]'")
data = c.fetchall()
c.execute("SELECT path_substitution(path) FROM table_episodes WHERE missing_subtitles != '[]'")
episodes = c.fetchall()
c.execute("SELECT path_substitution(path) FROM table_movies WHERE missing_subtitles != '[]'")
movies = c.fetchall()
c.close()
for episode in data:
for episode in episodes:
wanted_download_subtitles(episode[0])
logging.info('Finished searching for missing subtitles. Check history for more information.')
logging.info('Finished searching for missing subtitles. Check histories for more information.')

@ -60,7 +60,55 @@ def store_subtitles(file):
c_db.close()
return actual_subtitles
def store_subtitles_movie(file):
languages = []
actual_subtitles = []
if os.path.exists(file):
if os.path.splitext(file)[1] == '.mkv':
try:
with open(file, 'rb') as f:
mkv = enzyme.MKV(f)
for subtitle_track in mkv.subtitle_tracks:
try:
actual_subtitles.append([str(pycountry.languages.lookup(subtitle_track.language).alpha_2), None])
except:
pass
except:
pass
subtitles = core.search_external_subtitles(file)
for subtitle, language in subtitles.iteritems():
if str(language) != 'und':
actual_subtitles.append([str(language), path_replace_reverse(os.path.join(os.path.dirname(file), subtitle))])
else:
with open(path_replace(os.path.join(os.path.dirname(file), subtitle)), 'r') as f:
text = list(islice(f, 100))
text = ' '.join(text)
encoding = UnicodeDammit(text)
try:
text = text.decode(encoding.original_encoding)
except Exception as e:
logging.exception('Error trying to detect character encoding for this subtitles file: ' + path_replace(os.path.join(os.path.dirname(file), subtitle)) + ' You should try to delete this subtitles file manually and ask Bazarr to download it again.')
else:
detected_language = langdetect.detect(text)
if len(detected_language) > 0:
actual_subtitles.append([str(detected_language), path_replace_reverse(os.path.join(os.path.dirname(file), subtitle))])
conn_db = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db'), timeout=30)
c_db = conn_db.cursor()
c_db.execute("UPDATE table_movies SET subtitles = ? WHERE path = ?", (str(actual_subtitles), path_replace_reverse(file)))
conn_db.commit()
c_db.close()
return actual_subtitles
def list_missing_subtitles(*no):
query_string = ''
try:
@ -97,7 +145,44 @@ def list_missing_subtitles(*no):
conn_db.commit()
c_db.close()
def full_scan_subtitles():
def list_missing_subtitles_movies(*no):
query_string = ''
try:
query_string = " WHERE table_movies.radarrId = " + str(no[0])
except:
pass
conn_db = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db'), timeout=30)
c_db = conn_db.cursor()
movies_subtitles = c_db.execute("SELECT radarrId, subtitles, languages FROM table_movies" + query_string).fetchall()
c_db.close()
missing_subtitles_global = []
for movie_subtitles in movies_subtitles:
actual_subtitles = []
desired_subtitles = []
missing_subtitles = []
if movie_subtitles[1] != None:
actual_subtitles = ast.literal_eval(movie_subtitles[1])
if movie_subtitles[2] != None:
desired_subtitles = ast.literal_eval(movie_subtitles[2])
actual_subtitles_list = []
if desired_subtitles == None:
missing_subtitles_global.append(tuple(['[]', movie_subtitles[0]]))
else:
for item in actual_subtitles:
actual_subtitles_list.append(item[0])
missing_subtitles = list(set(desired_subtitles) - set(actual_subtitles_list))
missing_subtitles_global.append(tuple([str(missing_subtitles), movie_subtitles[0]]))
conn_db = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db'), timeout=30)
c_db = conn_db.cursor()
c_db.executemany("UPDATE table_movies SET missing_subtitles = ? WHERE radarrId = ?", (missing_subtitles_global))
conn_db.commit()
c_db.close()
def series_full_scan_subtitles():
conn_db = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db'), timeout=30)
c_db = conn_db.cursor()
episodes = c_db.execute("SELECT path FROM table_episodes").fetchall()
@ -108,6 +193,17 @@ def full_scan_subtitles():
gc.collect()
def movies_full_scan_subtitles():
conn_db = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db'), timeout=30)
c_db = conn_db.cursor()
movies = c_db.execute("SELECT path FROM table_movies").fetchall()
c_db.close()
for movie in movies:
store_subtitles_movie(path_replace(movie[0]))
gc.collect()
def series_scan_subtitles(no):
conn_db = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db'), timeout=30)
c_db = conn_db.cursor()
@ -119,11 +215,14 @@ def series_scan_subtitles(no):
list_missing_subtitles(no)
def new_scan_subtitles():
def movies_scan_subtitles(no):
conn_db = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db'), timeout=30)
c_db = conn_db.cursor()
episodes = c_db.execute("SELECT path FROM table_episodes WHERE subtitles is null").fetchall()
movies = c_db.execute("SELECT path FROM table_movies WHERE radarrId = ?", (no,)).fetchall()
c_db.close()
for episode in episodes:
store_subtitles(path_replace(episode[0]))
for movie in movies:
store_subtitles_movie(path_replace(movie[0]))
list_missing_subtitles_movies(no)

@ -26,6 +26,14 @@ def get_episode_name(sonarrEpisodeId):
return data[0]
def get_movies_name(radarrId):
conn_db = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db'), timeout=30)
c_db = conn_db.cursor()
data = c_db.execute('SELECT title FROM table_movies WHERE radarrId = ?', (radarrId,)).fetchone()
c_db.close()
return data[0]
def send_notifications(sonarrSeriesId, sonarrEpisodeId, message):
providers = get_notifier_providers()
series = get_series_name(sonarrSeriesId)
@ -40,4 +48,20 @@ def send_notifications(sonarrSeriesId, sonarrEpisodeId, message):
apobj.notify(
title='Bazarr notification',
body=series + ' - ' + episode + ' : ' + message,
)
def send_notifications_movie(radarrId, message):
providers = get_notifier_providers()
movie = get_movies_name(radarrId)
apobj = apprise.Apprise()
for provider in providers:
if provider[1] is not None:
apobj.add(provider[1])
apobj.notify(
title='Bazarr notification',
body=movie + ' : ' + message,
)

@ -1,7 +1,10 @@
from get_general_settings import *
from get_sonarr_settings import get_sonarr_settings
from get_radarr_settings import get_radarr_settings
from get_general_settings import get_general_settings
from get_series import *
from get_episodes import *
from get_movies import *
from list_subtitles import *
from get_subtitle import *
from check_update import *
@ -11,18 +14,29 @@ from datetime import datetime
import pytz
from tzlocal import get_localzone
integration = get_general_settings()
def sonarr_full_update():
full_update = get_sonarr_settings()[3]
if full_update == "Daily":
scheduler.add_job(update_all_episodes, 'cron', hour=4, max_instances=1, coalesce=True, misfire_grace_time=15, id='update_all_episodes', name='Update all episodes from Sonarr', replace_existing=True)
scheduler.add_job(update_all_episodes, 'cron', hour=4, max_instances=1, coalesce=True, misfire_grace_time=15, id='update_all_episodes', name='Update all episodes subtitles from disk', replace_existing=True)
elif full_update == "Weekly":
scheduler.add_job(update_all_episodes, 'cron', day_of_week='sun', hour=4, max_instances=1, coalesce=True, misfire_grace_time=15, id='update_all_episodes', name='Update all episodes subtitles from disk', replace_existing=True)
elif full_update == "Manually":
scheduler.add_job(update_all_episodes, 'cron', year='2100', hour=4, max_instances=1, coalesce=True, misfire_grace_time=15, id='update_all_episodes', name='Update all episodes subtitles from disk', replace_existing=True)
def radarr_full_update():
full_update = get_radarr_settings()[3]
if full_update == "Daily":
scheduler.add_job(update_all_movies, 'cron', hour=4, max_instances=1, coalesce=True, misfire_grace_time=15, id='update_all_movies', name='Update all movies subtitles from disk', replace_existing=True)
elif full_update == "Weekly":
scheduler.add_job(update_all_episodes, 'cron', day_of_week='sun', hour=4, max_instances=1, coalesce=True, misfire_grace_time=15, id='update_all_episodes', name='Update all episodes from Sonarr', replace_existing=True)
scheduler.add_job(update_all_movies, 'cron', day_of_week='sun', hour=4, max_instances=1, coalesce=True, misfire_grace_time=15, id='update_all_movies', name='Update all movies subtitles from disk', replace_existing=True)
elif full_update == "Manually":
scheduler.add_job(update_all_episodes, 'cron', year='2100', hour=4, max_instances=1, coalesce=True, misfire_grace_time=15, id='update_all_episodes', name='Update all episodes from Sonarr', replace_existing=True)
scheduler.add_job(update_all_movies, 'cron', year='2100', hour=4, max_instances=1, coalesce=True, misfire_grace_time=15, id='update_all_movies', name='Update all movies subtitles from disk', replace_existing=True)
def execute_now(taskid):
scheduler.modify_job(taskid, jobstore=None, next_run_time=datetime.now())
scheduler.modify_job(taskid, next_run_time=datetime.now())
if str(get_localzone()) == "local":
@ -34,8 +48,15 @@ if automatic == 'True':
scheduler.add_job(check_and_apply_update, 'interval', hours=6, max_instances=1, coalesce=True, misfire_grace_time=15, id='update_bazarr', name='Update bazarr from source on Github')
else:
scheduler.add_job(check_and_apply_update, 'cron', year='2100', hour=4, id='update_bazarr', name='Update bazarr from source on Github')
scheduler.add_job(update_series, 'interval', 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, 'interval', minutes=5, max_instances=1, coalesce=True, misfire_grace_time=15, id='sync_episodes', name='Sync episodes with Sonarr')
scheduler.add_job(wanted_search_missing_subtitles, 'interval', hours=3, max_instances=1, coalesce=True, misfire_grace_time=15, id='wanted_search_missing_subtitles', name='Search for wanted subtitles')
sonarr_full_update()
if integration[12] == "True":
scheduler.add_job(update_series, 'interval', 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, 'interval', minutes=5, max_instances=1, coalesce=True, misfire_grace_time=15, id='sync_episodes', name='Sync episodes with Sonarr')
if integration[13] == "True":
scheduler.add_job(update_movies, 'interval', minutes=5, max_instances=1, coalesce=True, misfire_grace_time=15, id='update_movies', name='Update movies list from Radarr')
if integration[12] == "True" or integration[13] == "True":
scheduler.add_job(wanted_search_missing_subtitles, 'interval', hours=3, max_instances=1, coalesce=True, misfire_grace_time=15, id='wanted_search_missing_subtitles', name='Search for wanted subtitles')
scheduler.start()

@ -87,6 +87,37 @@ if os.path.exists(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db'))
except:
pass
try:
c.execute('CREATE TABLE "table_settings_radarr" ( `ip` TEXT NOT NULL, `port` INTEGER NOT NULL, `base_url` TEXT, `ssl` INTEGER, `apikey` TEXT , "full_update" TEXT)')
except:
pass
else:
c.execute('INSERT INTO `table_settings_radarr` (ip, port, base_url, ssl, apikey, full_update) VALUES ("127.0.0.1", "7878", "/", "False", Null, "Daily")')
try:
c.execute('CREATE TABLE "table_movies" ( `tmdbId` TEXT NOT NULL UNIQUE, `title` TEXT NOT NULL, `path` TEXT NOT NULL UNIQUE, `languages` TEXT, `subtitles` TEXT, `missing_subtitles` TEXT, `hearing_impaired` TEXT, `radarrId` INTEGER NOT NULL UNIQUE, `overview` TEXT, `poster` TEXT, `fanart` TEXT, "audio_language" "text", `sceneName` TEXT, PRIMARY KEY(`tmdbId`) )')
except:
pass
try:
c.execute('CREATE TABLE "table_history_movie" ( `id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, `action` INTEGER NOT NULL, `radarrId` INTEGER NOT NULL, `timestamp` INTEGER NOT NULL, `description` TEXT NOT NULL )')
except:
pass
try:
c.execute('alter table table_settings_general add column "use_sonarr" "text"')
except:
pass
else:
c.execute('UPDATE table_settings_general SET use_sonarr="True"')
try:
c.execute('alter table table_settings_general add column "use_radarr" "text"')
except:
pass
else:
c.execute('UPDATE table_settings_general SET use_radarr="False"')
# Commit change to db
db.commit()
@ -94,10 +125,14 @@ if os.path.exists(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db'))
c.execute('alter table table_episodes add column "scene_name" TEXT')
db.commit()
except:
db.close()
pass
else:
db.close()
from scheduler import execute_now
execute_now('update_all_episodes')
# Close database connection
db.close()
from get_general_settings import get_general_settings
integration = get_general_settings()
if integration[12] == "True":
execute_now('update_all_episodes')
if integration[13] == "True":
execute_now('update_all_movies')

@ -15,3 +15,17 @@ def history_log(action, sonarrSeriesId, sonarrEpisodeId, description):
# Close database connection
db.close()
def history_log_movie(action, radarrId, description):
# Open database connection
db = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db'), timeout=30)
c = db.cursor()
history = c.execute('''INSERT INTO table_history_movie(action, radarrId, timestamp, description) VALUES (?, ?, ?, ?)''', (action, radarrId, time.time(), description))
# Commit changes to DB
db.commit()
# Close database connection
db.close()

@ -85,7 +85,7 @@
<div style='padding-left: 2em; padding-right: 2em;' class='ui container'>
<div id="divdetails" class="ui container">
<img class="left floated ui image" src="{{base_url}}image_proxy{{details[2]}}">
<img class="left floated ui image" style="max-height:250px;" src="{{base_url}}image_proxy{{details[2]}}">
<div class="ui right floated basic icon buttons">
<button id="scan_disk" class="ui button" data-tooltip="Scan disk for subtitles" data-inverted=""><i class="ui inverted large compact refresh icon"></i></button>
<button id="search_missing_subtitles" class="ui button" data-tooltip="Download missing subtitles" data-inverted=""><i class="ui inverted huge compact search icon"></i></button>

@ -1,214 +1,138 @@
<html>
<head>
<!DOCTYPE html>
<script src="{{base_url}}static/jquery/jquery-latest.min.js"></script>
<script src="{{base_url}}static/semantic/semantic.min.js"></script>
<script src="{{base_url}}static/jquery/tablesort.js"></script>
<link rel="stylesheet" href="{{base_url}}static/semantic/semantic.min.css">
<link rel="apple-touch-icon" sizes="120x120" href="{{base_url}}static/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="{{base_url}}static/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="{{base_url}}static/favicon-16x16.png">
<link rel="manifest" href="{{base_url}}static/manifest.json">
<link rel="mask-icon" href="{{base_url}}static/safari-pinned-tab.svg" color="#5bbad5">
<link rel="shortcut icon" href="{{base_url}}static/favicon.ico">
<meta name="msapplication-config" content="{{base_url}}static/browserconfig.xml">
<meta name="theme-color" content="#ffffff">
<title>History - Bazarr</title>
<style>
body {
background-color: #272727;
}
#fondblanc {
background-color: #ffffff;
border-radius: 0px;
box-shadow: 0px 0px 5px 5px #ffffff;
margin-top: 32px;
margin-bottom: 3em;
padding: 3em;
}
.fast.backward, .backward, .forward, .fast.forward {
cursor: pointer;
}
.fast.backward, .backward, .forward, .fast.forward { pointer-events: auto; }
.fast.backward.disabled, .backward.disabled, .forward.disabled, .fast.forward.disabled { pointer-events: none; }
#bottommenu {
background-color: #333333;
box-shadow: 0 0 10px 1px #333;
padding: 10px;
margin-bottom: -2em !important;
}
.label, .value {
color: white !important;
}
</style>
</head>
<body>
<div id='loader' class="ui page dimmer">
<div class="ui indeterminate text loader">Loading...</div>
</div>
% include('menu.tpl')
<div id="fondblanc" class="ui container">
<table id="tablehistory" class="ui very basic selectable table">
<thead>
<tr>
<th></th>
<th>Series</th>
<th>Episode</th>
<th>Episode Title</th>
<th>Date</th>
<th>Description</th>
</tr>
</thead>
<tbody>
%import time
%import pretty
%for row in rows:
<tr class="selectable">
<td class="collapsing">
%if row[0] == 0:
<div class="ui inverted basic compact icon" data-tooltip="Subtitles file have been erased." data-inverted="">
<i class="ui trash icon"></i>
</div>
%elif row[0] == 1:
<div class="ui inverted basic compact icon" data-tooltip="Subtitles file have been downloaded." data-inverted="">
<i class="ui download icon"></i>
</div>
%end
</td>
<td>
<a href="{{base_url}}episodes/{{row[6]}}">{{row[1]}}</a>
</td>
<td class="collapsing">
%if row[2] is not None:
% episode = row[2].split('x')
{{episode[0] + 'x' + episode[1].zfill(2)}}
%end
</td>
<td>
%if row[3] is not None:
{{row[3]}}
%else:
<em>Deleted episode</em>
%end
</td>
<td class="collapsing">
<div class="ui inverted" data-tooltip="{{time.strftime('%Y/%m/%d %H:%M', time.localtime(row[4]))}}" data-inverted="">
{{pretty.date(int(row[4]))}}
</div>
</td>
<td>{{row[5]}}</td>
</tr>
%end
</tbody>
</table>
<div class="ui grid">
<div class="three column row">
<div class="column"></div>
<div class="center aligned column">
<i class="\\
%if page == '1':
disabled\\
%end
fast backward icon"></i>
<i class="\\
%if page == '1':
disabled\\
%end
backward icon"></i>
{{page}} / {{max_page}}
<i class="\\
%if int(page) == int(max_page):
disabled\\
%end
forward icon"></i>
<i class="\\
%if int(page) == int(max_page):
disabled\\
%end
fast forward icon"></i>
</div>
<div class="right floated right aligned column">Total records: {{row_count}}</div>
</div>
</div>
</div>
<div id='bottommenu' class="ui fluid inverted bottom fixed five item menu">
<div class="ui statistics">
<div class="statistic">
<div class="text value">
<br>
Statistics
</div>
<div class="label">
</div>
</div>
<div class="statistic">
<div class="value">
{{stats[0]}}
</div>
<div class="label">
Since 24 hours
</div>
</div>
<div class="statistic">
<div class="value">
{{stats[1]}}
</div>
<div class="label">
Since one week
</div>
</div>
<div class="statistic">
<div class="value">
{{stats[2]}}
</div>
<div class="label">
Since one year
</div>
</div>
<div class="statistic">
<div class="value">
{{stats[3]}}
</div>
<div class="label">
Total
</div>
</div>
</div>
</div>
% include('footer.tpl')
<br><br><br><br>
</body>
</html>
<script>
if (sessionStorage.scrolly) {
$(window).scrollTop(sessionStorage.scrolly);
sessionStorage.clear();
}
$('a, i').click(function(){
sessionStorage.scrolly=$(window).scrollTop();
$('#loader').addClass('active');
})
$('.backward').click(function(){
location.href="?page={{int(page)-1}}";
})
$('.fast.backward').click(function(){
location.href="?page=1";
})
$('.forward').click(function(){
location.href="?page={{int(page)+1}}";
})
$('.fast.forward').click(function(){
location.href="?page={{int(max_page)}}";
})
<html>
<head>
<!DOCTYPE html>
<script src="{{base_url}}static/jquery/jquery-latest.min.js"></script>
<script src="{{base_url}}static/semantic/semantic.min.js"></script>
<script src="{{base_url}}static/jquery/tablesort.js"></script>
<link rel="stylesheet" href="{{base_url}}static/semantic/semantic.min.css">
<link rel="apple-touch-icon" sizes="120x120" href="{{base_url}}static/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="{{base_url}}static/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="{{base_url}}static/favicon-16x16.png">
<link rel="manifest" href="{{base_url}}static/manifest.json">
<link rel="mask-icon" href="{{base_url}}static/safari-pinned-tab.svg" color="#5bbad5">
<link rel="shortcut icon" href="{{base_url}}static/favicon.ico">
<meta name="msapplication-config" content="{{base_url}}static/browserconfig.xml">
<meta name="theme-color" content="#ffffff">
<title>History - Bazarr</title>
<style>
body {
background-color: #272727;
}
#fondblanc {
background-color: #ffffff;
border-radius: 0px;
box-shadow: 0px 0px 5px 5px #ffffff;
margin-top: 32px;
margin-bottom: 3em;
padding: 1em;
}
#logs {
margin-top: 4em;
}
.fast.backward, .backward, .forward, .fast.forward {
cursor: pointer;
}
.fast.backward, .backward, .forward, .fast.forward { pointer-events: auto; }
.fast.backward.disabled, .backward.disabled, .forward.disabled, .fast.forward.disabled { pointer-events: none; }
.ui.tabular.menu > .disabled.item {
opacity: 0.45 !important;
pointer-events: none !important;
}
</style>
</head>
<body>
<div id='loader' class="ui page dimmer">
<div class="ui indeterminate text loader">Loading...</div>
</div>
% include('menu.tpl')
% import os
% import sqlite3
% conn = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db'), timeout=30)
% c = conn.cursor()
% integration = c.execute("SELECT use_sonarr, use_radarr FROM table_settings_general").fetchone()
% c.close()
<div id="fondblanc" class="ui container">
<div class="ui top attached tabular menu">
<a id="series_tab" class="tabs item active" data-enabled="{{integration[0]}}" data-tab="series">Series</a>
<a id="movies_tab" class="tabs item" data-enabled="{{integration[1]}}" data-tab="movies">Movies</a>
</div>
<div class="ui bottom attached tab segment" data-tab="series">
<div class="content">
<div id="content_series"></div>
</div>
</div>
<div class="ui bottom attached tab segment" data-tab="movies">
<div class="content">
<div id="content_movies"></div>
</div>
</div>
</div>
% include('footer.tpl')
</body>
</html>
<script>
$('.menu .item')
.tab()
;
$('#series_tab').click(function() {
loadURLseries(1);
})
$('#movies_tab').click(function() {
loadURLmovies(1);
})
function loadURLseries(page) {
$.ajax({
url: "{{base_url}}historyseries?page=" + page,
beforeSend: function() { $('#loader').addClass('active'); },
complete: function() { $('#loader').removeClass('active'); },
cache: false
}).done(function(data) {
$("#content_series").html(data);
});
}
function loadURLmovies(page) {
$.ajax({
url: "{{base_url}}historymovies?page=" + page,
beforeSend: function() { $('#loader').addClass('active'); },
complete: function() { $('#loader').removeClass('active'); },
cache: false
}).done(function(data) {
$("#content_movies").html(data);
});
}
$('a:not(.tabs), button:not(.cancel, #download_log)').click(function(){
$('#loader').addClass('active');
})
if ($('#series_tab').data("enabled") == "True") {
$("#series_tab").removeClass('disabled');
} else {
$("#series_tab").addClass('disabled');
}
if ($('#movies_tab').data("enabled") == "True") {
$("#movies_tab").removeClass('disabled');
} else {
$("#movies_tab").addClass('disabled');
}
if ($('#series_tab').data("enabled") == "True") {
$( "#series_tab" ).trigger( "click" );
}
if ($('#series_tab').data("enabled") == "False" && $('#movies_tab').data("enabled") == "True") {
$( "#movies_tab" ).trigger( "click" );
}
</script>

@ -0,0 +1,199 @@
<html>
<head>
<!DOCTYPE html>
<script src="{{base_url}}static/jquery/jquery-latest.min.js"></script>
<script src="{{base_url}}static/semantic/semantic.min.js"></script>
<script src="{{base_url}}static/jquery/tablesort.js"></script>
<link rel="stylesheet" href="{{base_url}}static/semantic/semantic.min.css">
<link rel="apple-touch-icon" sizes="120x120" href="{{base_url}}static/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="{{base_url}}static/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="{{base_url}}static/favicon-16x16.png">
<link rel="manifest" href="{{base_url}}static/manifest.json">
<link rel="mask-icon" href="{{base_url}}static/safari-pinned-tab.svg" color="#5bbad5">
<link rel="shortcut icon" href="{{base_url}}static/favicon.ico">
<meta name="msapplication-config" content="{{base_url}}static/browserconfig.xml">
<meta name="theme-color" content="#ffffff">
<title>History - Bazarr</title>
<style>
body {
background-color: #272727;
}
#fondblanc {
background-color: #ffffff;
border-radius: 0px;
box-shadow: 0px 0px 5px 5px #ffffff;
margin-top: 32px;
margin-bottom: 3em;
padding: 3em;
}
.fast.backward, .backward, .forward, .fast.forward {
cursor: pointer;
}
.fast.backward, .backward, .forward, .fast.forward { pointer-events: auto; }
.fast.backward.disabled, .backward.disabled, .forward.disabled, .fast.forward.disabled { pointer-events: none; }
#bottommenu {
background-color: #333333;
box-shadow: 0 0 10px 1px #333;
padding: 10px;
margin-bottom: -2em !important;
}
.label, .value {
color: white !important;
}
</style>
</head>
<body>
<div id='loader' class="ui page dimmer">
<div class="ui indeterminate text loader">Loading...</div>
</div>
<div class="ui container">
<table id="tablehistory" class="ui very basic selectable table">
<thead>
<tr>
<th></th>
<th>Name</th>
<th>Date</th>
<th>Description</th>
</tr>
</thead>
<tbody>
%import time
%import pretty
%for row in rows:
<tr class="selectable">
<td class="collapsing">
%if row[0] == 0:
<div class="ui inverted basic compact icon" data-tooltip="Subtitles file have been erased." data-inverted="">
<i class="ui trash icon"></i>
</div>
%elif row[0] == 1:
<div class="ui inverted basic compact icon" data-tooltip="Subtitles file have been downloaded." data-inverted="">
<i class="ui download icon"></i>
</div>
%end
</td>
<td>
<a href="{{base_url}}movie/{{row[4]}}">{{row[1]}}</a>
</td>
<td class="collapsing">
<div class="ui inverted" data-tooltip="{{time.strftime('%Y/%m/%d %H:%M', time.localtime(row[2]))}}" data-inverted="">
{{pretty.date(int(row[2]))}}
</div>
</td>
<td>{{row[3]}}</td>
</tr>
%end
</tbody>
</table>
<div class="ui grid">
<div class="three column row">
<div class="column"></div>
<div class="center aligned column">
<i class="\\
%if page == '1':
disabled\\
%end
fast backward icon"></i>
<i class="\\
%if page == '1':
disabled\\
%end
backward icon"></i>
{{page}} / {{max_page}}
<i class="\\
%if int(page) == int(max_page):
disabled\\
%end
forward icon"></i>
<i class="\\
%if int(page) == int(max_page):
disabled\\
%end
fast forward icon"></i>
</div>
<div class="right floated right aligned column">Total records: {{row_count}}</div>
</div>
</div>
</div>
<div id='bottommenu' class="ui fluid inverted bottom fixed five item menu">
<div class="ui small statistics">
<div class="statistic">
<div class="text value">
<br>
Movies
<br>
statistics
</div>
<div class="label">
</div>
</div>
<div class="statistic">
<div class="value">
{{stats[0]}}
</div>
<div class="label">
Since 24 hours
</div>
</div>
<div class="statistic">
<div class="value">
{{stats[1]}}
</div>
<div class="label">
Since one week
</div>
</div>
<div class="statistic">
<div class="value">
{{stats[2]}}
</div>
<div class="label">
Since one year
</div>
</div>
<div class="statistic">
<div class="value">
{{stats[3]}}
</div>
<div class="label">
Total
</div>
</div>
</div>
</div>
<br><br><br><br>
</body>
</html>
<script>
if (sessionStorage.scrolly) {
$(window).scrollTop(sessionStorage.scrolly);
sessionStorage.clear();
}
$('a, i').click(function(){
sessionStorage.scrolly=$(window).scrollTop();
$('#loader').addClass('active');
})
$('.fast.backward').click(function(){
loadURLseries(1);
})
$('.backward:not(.fast)').click(function(){
loadURLseries({{int(page)-1}});
})
$('.forward:not(.fast)').click(function(){
loadURLseries({{int(page)+1}});
})
$('.fast.forward').click(function(){
loadURLseries({{int(max_page)}});
})
</script>

@ -0,0 +1,214 @@
<html>
<head>
<!DOCTYPE html>
<script src="{{base_url}}static/jquery/jquery-latest.min.js"></script>
<script src="{{base_url}}static/semantic/semantic.min.js"></script>
<script src="{{base_url}}static/jquery/tablesort.js"></script>
<link rel="stylesheet" href="{{base_url}}static/semantic/semantic.min.css">
<link rel="apple-touch-icon" sizes="120x120" href="{{base_url}}static/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="{{base_url}}static/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="{{base_url}}static/favicon-16x16.png">
<link rel="manifest" href="{{base_url}}static/manifest.json">
<link rel="mask-icon" href="{{base_url}}static/safari-pinned-tab.svg" color="#5bbad5">
<link rel="shortcut icon" href="{{base_url}}static/favicon.ico">
<meta name="msapplication-config" content="{{base_url}}static/browserconfig.xml">
<meta name="theme-color" content="#ffffff">
<title>History - Bazarr</title>
<style>
body {
background-color: #272727;
}
#fondblanc {
background-color: #ffffff;
border-radius: 0px;
box-shadow: 0px 0px 5px 5px #ffffff;
margin-top: 32px;
margin-bottom: 3em;
padding: 3em;
}
.fast.backward, .backward, .forward, .fast.forward {
cursor: pointer;
}
.fast.backward, .backward, .forward, .fast.forward { pointer-events: auto; }
.fast.backward.disabled, .backward.disabled, .forward.disabled, .fast.forward.disabled { pointer-events: none; }
#bottommenu {
background-color: #333333;
box-shadow: 0 0 10px 1px #333;
padding: 10px;
margin-bottom: -2em !important;
}
.label, .value {
color: white !important;
}
</style>
</head>
<body>
<div id='loader' class="ui page dimmer">
<div class="ui indeterminate text loader">Loading...</div>
</div>
<div class="ui container">
<table id="tablehistory" class="ui very basic selectable table">
<thead>
<tr>
<th></th>
<th>Name</th>
<th>Episode</th>
<th>Episode Title</th>
<th>Date</th>
<th>Description</th>
</tr>
</thead>
<tbody>
%import time
%import pretty
%for row in rows:
<tr class="selectable">
<td class="collapsing">
%if row[0] == 0:
<div class="ui inverted basic compact icon" data-tooltip="Subtitles file have been erased." data-inverted="">
<i class="ui trash icon"></i>
</div>
%elif row[0] == 1:
<div class="ui inverted basic compact icon" data-tooltip="Subtitles file have been downloaded." data-inverted="">
<i class="ui download icon"></i>
</div>
%end
</td>
<td>
<a href="{{base_url}}episodes/{{row[6]}}">{{row[1]}}</a>
</td>
<td class="collapsing">
%if row[2] is not None:
% episode = row[2].split('x')
{{episode[0] + 'x' + episode[1].zfill(2)}}
%end
</td>
<td>
%if row[3] is not None:
{{row[3]}}
%else:
<em>Deleted episode</em>
%end
</td>
<td class="collapsing">
<div class="ui inverted" data-tooltip="{{time.strftime('%Y/%m/%d %H:%M', time.localtime(row[4]))}}" data-inverted="">
{{pretty.date(int(row[4]))}}
</div>
</td>
<td>{{row[5]}}</td>
</tr>
%end
</tbody>
</table>
<div class="ui grid">
<div class="three column row">
<div class="column"></div>
<div class="center aligned column">
<i class="\\
%if page == '1':
disabled\\
%end
fast backward icon"></i>
<i class="\\
%if page == '1':
disabled\\
%end
backward icon"></i>
{{page}} / {{max_page}}
<i class="\\
%if int(page) == int(max_page):
disabled\\
%end
forward icon"></i>
<i class="\\
%if int(page) == int(max_page):
disabled\\
%end
fast forward icon"></i>
</div>
<div class="right floated right aligned column">Total records: {{row_count}}</div>
</div>
</div>
</div>
<div id='bottommenu' class="ui fluid inverted bottom fixed five item menu">
<div class="ui small statistics">
<div class="statistic">
<div class="text value">
<br>
Series
<br>
statistics
</div>
<div class="label">
</div>
</div>
<div class="statistic">
<div class="value">
{{stats[0]}}
</div>
<div class="label">
Since 24 hours
</div>
</div>
<div class="statistic">
<div class="value">
{{stats[1]}}
</div>
<div class="label">
Since one week
</div>
</div>
<div class="statistic">
<div class="value">
{{stats[2]}}
</div>
<div class="label">
Since one year
</div>
</div>
<div class="statistic">
<div class="value">
{{stats[3]}}
</div>
<div class="label">
Total
</div>
</div>
</div>
</div>
<br><br><br><br>
</body>
</html>
<script>
if (sessionStorage.scrolly) {
$(window).scrollTop(sessionStorage.scrolly);
sessionStorage.clear();
}
$('a, i').click(function(){
sessionStorage.scrolly=$(window).scrollTop();
$('#loader').addClass('active');
})
$('.fast.backward').click(function(){
loadURLseries(1);
})
$('.backward:not(.fast)').click(function(){
loadURLseries({{int(page)-1}});
})
$('.forward:not(.fast)').click(function(){
loadURLseries({{int(page)+1}});
})
$('.fast.forward').click(function(){
loadURLseries({{int(max_page)}});
})
</script>

@ -26,7 +26,9 @@
% conn = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db'), timeout=30)
% c = conn.cursor()
% wanted = c.execute("SELECT COUNT(*) FROM table_episodes WHERE missing_subtitles != '[]'").fetchone()
% wanted_series = c.execute("SELECT COUNT(*) FROM table_episodes WHERE missing_subtitles != '[]'").fetchone()
% wanted_movies = c.execute("SELECT COUNT(*) FROM table_movies WHERE missing_subtitles != '[]'").fetchone()
% integration = c.execute("SELECT use_sonarr, use_radarr FROM table_settings_general").fetchone()
<div id="divmenu" class="ui container">
<div class="ui grid">
@ -39,23 +41,36 @@
<div class="ui grid">
<div class="row">
<div class="sixteen wide column">
<div class="ui inverted borderless labeled icon massive menu five item">
<div class="ui inverted borderless labeled icon massive menu six item">
<div class="ui container">
<a class="item" href="{{base_url}}">
% if integration[0] == "True":
<a class="item" href="{{base_url}}series">
<i class="play icon"></i>
Series
</a>
% end
% if integration[1] == "True":
<a class="item" href="{{base_url}}movies">
<i class="film icon"></i>
Movies
</a>
% end
<a class="item" href="{{base_url}}history">
<i class="wait icon"></i>
History
</a>
<a class="item" href="{{base_url}}wanted">
<i class="warning sign icon">
% if wanted[0] > 0:
<div class="floating ui tiny yellow label">
{{wanted[0]}}
% if integration[0] == "True":
<div class="floating ui tiny yellow label" style="left:90% !important;top:0.5em !important;">
{{wanted_series[0]}}
</div>
% end
% if integration[1] == "True":
<div class="floating ui tiny green label" style="left:90% !important;top:3em !important;">
{{wanted_movies[0]}}
</div>
% end
% end
</i>
Wanted
</a>
@ -93,7 +108,6 @@
</div>
% restart_required = c.execute("SELECT updated, configured FROM table_settings_general").fetchone()
% conn.commit()
% c.close()
% if restart_required[0] == 1 and restart_required[1] == 1:
@ -110,7 +124,7 @@
$('.ui.search')
.search({
apiSettings: {
url: '{{base_url}}series_json/{query}',
url: '{{base_url}}search_json/{query}',
onResponse: function(results) {
var response = {
results : []

@ -0,0 +1,315 @@
<html>
<head>
<!DOCTYPE html>
<script src="{{base_url}}static/jquery/jquery-latest.min.js"></script>
<script src="{{base_url}}static/semantic/semantic.min.js"></script>
<script src="{{base_url}}static/jquery/tablesort.js"></script>
<link rel="stylesheet" href="{{base_url}}static/semantic/semantic.min.css">
<link rel="apple-touch-icon" sizes="120x120" href="{{base_url}}static/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="{{base_url}}static/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="{{base_url}}static/favicon-16x16.png">
<link rel="manifest" href="{{base_url}}static/manifest.json">
<link rel="mask-icon" href="{{base_url}}static/safari-pinned-tab.svg" color="#5bbad5">
<link rel="shortcut icon" href="{{base_url}}static/favicon.ico">
<meta name="msapplication-config" content="{{base_url}}static/browserconfig.xml">
<meta name="theme-color" content="#ffffff">
<title>{{details[0]}} - Bazarr</title>
<style>
body {
background-color: #1b1c1d;
background-image: url("{{base_url}}image_proxy_movies{{details[3]}}");
background-repeat: no-repeat;
background-attachment: fixed;
background-size: cover;
background-position:center center;
}
#divdetails {
background-color: #000000;
opacity: 0.9;
color: #ffffff;
margin-top: 6em;
margin-bottom: 3em;
padding: 2em;
border-radius: 1px;
box-shadow: 0px 0px 5px 5px #000000;
min-height: calc(250px + 4em);
}
#fondblanc {
background-color: #ffffff;
opacity: 0.9;
border-radius: 1px;
box-shadow: 0px 0px 3px 3px #ffffff;
margin-top: 32px;
margin-bottom: 3em;
padding-top: 2em;
padding-left: 2em;
padding-right: 2em;
padding-bottom: 1em;
text-color: black;
}
.ui.basic.button:hover, .ui.basic.buttons .button:hover {
background: transparent !important;
}
.ui.basic.button:active, .ui.basic.buttons .button:active {
background: transparent !important;
}
.ui.basic.button:focus, .ui.basic.buttons .button:focus {
background: transparent !important;
}
.ui.basic.button:visited, .ui.basic.buttons .button:visited {
background: transparent !important;
}
</style>
</head>
<body>
%import ast
%import pycountry
%from get_general_settings import *
%single_language = get_general_settings()[7]
<div style="display: none;"><img src="{{base_url}}image_proxy_movies{{details[3]}}"></div>
<div id='loader' class="ui page dimmer">
<div class="ui indeterminate text loader">Loading...</div>
</div>
% include('menu.tpl')
<div style='padding-left: 2em; padding-right: 2em;' class='ui container'>
<div id="divdetails" class="ui container">
<img class="left floated ui image" style="max-height:250px;" src="{{base_url}}image_proxy_movies{{details[2]}}">
<div class="ui right floated basic icon buttons">
<button id="scan_disk" class="ui button" data-tooltip="Scan disk for subtitles" data-inverted=""><i class="ui inverted large compact refresh icon"></i></button>
<button id="search_missing_subtitles" class="ui button" data-tooltip="Download missing subtitles" data-inverted=""><i class="ui inverted huge compact search icon"></i></button>
<%
subs_languages = ast.literal_eval(str(details[7]))
subs_languages_list = []
if subs_languages is not None:
for subs_language in subs_languages:
subs_languages_list.append(subs_language)
end
end
%>
<button id="config" class="ui button" data-tooltip="Edit movie" data-inverted="" data-tmdbid="{{details[5]}}" data-title="{{details[0]}}" data-poster="{{details[2]}}" data-audio="{{details[6]}}" data-languages="{{!subs_languages_list}}" data-hearing-impaired="{{details[4]}}"><i class="ui inverted large compact configure icon"></i></button>
</div>
<h2>{{details[0]}}</h2>
<p>{{details[1]}}</p>
<p style='margin-top: 3em;'>
<div class="ui tiny inverted label" style='background-color: #777777;'>{{details[6]}}</div>
<div class="ui tiny inverted label" style='background-color: #35c5f4;'>{{details[8]}}</div>
</p>
<p style='margin-top: 2em;'>
%for language in subs_languages_list:
<div class="ui tiny inverted label" style='background-color: #35c5f4;'>{{language}}</div>
%end
</p>
<div style='clear:both;'></div>
<div id="fondblanc" class="ui container">
<table class="ui very basic single line selectable table">
<thead>
<tr>
<th>Subtitles path</th>
<th>Language</th>
<th></th>
</tr>
</thead>
<tbody>
<%
subtitles_files = ast.literal_eval(str(details[9]))
if subtitles_files is not None:
for subtitles_file in subtitles_files:
%>
<tr>
<td>{{path_replace(subtitles_file[1]) if subtitles_file[1] is not None else 'Video file subtitles track'}}</td>
<td><div class="ui tiny inverted label" style='background-color: #777777;'>{{pycountry.languages.lookup(str(subtitles_file[0])).name}}</div></td>
<td>
%if subtitles_file[1] is not None:
<a class="remove_subtitles ui inverted basic compact icon" data-tooltip="Delete subtitles file from disk" data-inverted="" data-moviePath="{{details[8]}}" data-subtitlesPath="{{path_replace(subtitles_file[1])}}" data-language="{{pycountry.languages.lookup(str(subtitles_file[0])).alpha_3}}" data-radarrId={{details[10]}}>
<i class="ui black delete icon"></i>
</a>
%end
</td>
</tr>
<%
end
if len(subtitles_files) == 0:
%>
<tr><td colspan="3">No subtitles detected for this movie.</td></tr>
<%
end
end
%>
</tbody>
</table>
<%
missing_subs_languages = ast.literal_eval(str(details[11]))
missing_subs_languages_list = []
if missing_subs_languages is not None:
%>
<table class="ui very basic single line selectable table">
<thead>
<tr>
<th>Missing subtitles</th>
</tr>
</thead>
</table>
<%
for missing_subs_language in missing_subs_languages:
%>
<a class="get_subtitle ui small blue label" data-moviePath="{{details[8]}}" data-scenename="{{details[12]}}" data-language="{{pycountry.languages.lookup(str(missing_subs_language)).alpha_3}}" data-hi="{{details[4]}}" data-radarrId={{details[10]}}>
{{pycountry.languages.lookup(str(missing_subs_language)).name}}
<i style="margin-left:3px; margin-right:0px" class="search icon"></i>
</a>
<%
end
end
%>
</div>
</div>
</div>
<div class="ui small modal">
<i class="close icon"></i>
<div class="header">
<div id="movie_title"></div>
</div>
<div class="content">
<form name="movie_form" id="movie_form" action="" method="post" class="ui form">
<div class="ui grid">
<div class="four wide column">
<img id="movie_poster" class="ui image" src="">
</div>
<div class="twelve wide column">
<div class="ui grid">
<div class="middle aligned row">
<div class="right aligned five wide column">
<label>Audio language</label>
</div>
<div class="nine wide column">
<div id="movie_audio_language"></div>
</div>
</div>
<div class="middle aligned row">
<div class="right aligned five wide column">
<label>Subtitles languages</label>
</div>
<div class="nine wide column">
<select name="languages" id="movie_languages" {{!'multiple="" ' if single_language == 'False' else ''}} class="ui fluid selection dropdown">
<option value="">Languages</option>
%for language in languages:
<option value="{{language[0]}}">{{language[1]}}</option>
%end
</select>
</div>
</div>
<div class="middle aligned row">
<div class="right aligned five wide column">
<label>Hearing-impaired</label>
</div>
<div class="nine wide column">
<div id="movie_hearing-impaired_div" class="ui toggle checkbox">
<input name="hearing_impaired" id="movie_hearing-impaired" type="checkbox">
<label></label>
</div>
</div>
</div>
</div>
</div>
</div>
</form>
</div>
<div class="actions">
<button class="ui cancel button" >Cancel</button>
<button type="submit" name="save" value="save" form="movie_form" class="ui blue approve button">Save</button>
</div>
</div>
% include('footer.tpl')
</body>
</html>
<script>
$('#scan_disk').click(function(){
window.location = '{{base_url}}scan_disk_movie/{{no}}';
})
$('#search_missing_subtitles').click(function(){
window.location = '{{base_url}}search_missing_subtitles_movie/{{no}}';
})
$('.remove_subtitles').click(function(){
var values = {
moviePath: $(this).attr("data-moviePath"),
language: $(this).attr("data-language"),
subtitlesPath: $(this).attr("data-subtitlesPath"),
radarrId: $(this).attr("data-radarrId"),
tmdbid: {{tmdbid}}
};
$.ajax({
url: "{{base_url}}remove_subtitles_movie",
type: "POST",
dataType: "json",
data: values
});
$(document).ajaxStart(function(){
$('#loader').addClass('active');
});
$(document).ajaxStop(function(){
window.location.reload();
});
})
$('.get_subtitle').click(function(){
var values = {
moviePath: $(this).attr("data-moviePath"),
sceneName: $(this).attr("data-sceneName"),
language: $(this).attr("data-language"),
hi: $(this).attr("data-hi"),
radarrId: $(this).attr("data-radarrId"),
tmdbid: {{tmdbid}}
};
$.ajax({
url: "{{base_url}}get_subtitle_movie",
type: "POST",
dataType: "json",
data: values
});
$(document).ajaxStart(function(){
$('#loader').addClass('active');
});
$(document).ajaxStop(function(){
window.location.reload();
});
})
$('a, .menu .item, button:not(#config, .cancel)').click(function(){
$('#loader').addClass('active');
})
$('.modal')
.modal({
autofocus: false
})
;
$('#config').click(function(){
$('#movie_form').attr('action', '{{base_url}}edit_movie/{{no}}');
$("#movie_title").html($(this).data("title"));
$("#movie_poster").attr("src", "{{base_url}}image_proxy_movies" + $(this).data("poster"));
$("#movie_audio_language").html($(this).data("audio"));
$('#movie_languages').dropdown('clear');
var languages_array = eval($(this).data("languages"));
$('#movie_languages').dropdown('set selected',languages_array);
if ($(this).data("hearing-impaired") == "True") {
$("#movie_hearing-impaired_div").checkbox('check');
} else {
$("#movie_hearing-impaired_div").checkbox('uncheck');
}
$('.small.modal').modal('show');
})
</script>

@ -0,0 +1,258 @@
<html>
<head>
<!DOCTYPE html>
<script src="{{base_url}}static/jquery/jquery-latest.min.js"></script>
<script src="{{base_url}}static/semantic/semantic.min.js"></script>
<script src="{{base_url}}static/jquery/tablesort.js"></script>
<link rel="stylesheet" href="{{base_url}}static/semantic/semantic.min.css">
<link rel="apple-touch-icon" sizes="120x120" href="{{base_url}}static/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="{{base_url}}static/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="{{base_url}}static/favicon-16x16.png">
<link rel="manifest" href="{{base_url}}static/manifest.json">
<link rel="mask-icon" href="{{base_url}}static/safari-pinned-tab.svg" color="#5bbad5">
<link rel="shortcut icon" href="{{base_url}}static/favicon.ico">
<meta name="msapplication-config" content="{{base_url}}static/browserconfig.xml">
<meta name="theme-color" content="#ffffff">
<title>Movies - Bazarr</title>
<style>
body {
background-color: #272727;
}
#fondblanc {
background-color: #ffffff;
border-radius: 0px;
box-shadow: 0px 0px 5px 5px #ffffff;
margin-top: 32px;
margin-bottom: 3em;
padding: 2em 3em 2em 3em;
}
#tablemovies {
padding-top: 1em;
}
#divdetails {
min-height: 250px;
}
.fast.backward, .backward, .forward, .fast.forward {
cursor: pointer;
}
.fast.backward, .backward, .forward, .fast.forward { pointer-events: auto; }
.fast.backward.disabled, .backward.disabled, .forward.disabled, .fast.forward.disabled { pointer-events: none; }
</style>
</head>
<body>
<div id='loader' class="ui page dimmer">
<div class="ui indeterminate text loader">Loading...</div>
</div>
% include('menu.tpl')
<div id="fondblanc" class="ui container">
<div class="ui basic buttons">
<button id="movieseditor" class="ui button"><i class="configure icon"></i>Movies Editor</button>
</div>
<table id="tablemovies" class="ui very basic selectable sortable table">
<thead>
<tr>
<th class="sorted ascending">Name</th>
<th>Path</th>
<th>Audio language</th>
<th>Subtitles languages</th>
<th>Hearing-impaired</th>
<th class="no-sort"></th>
</tr>
</thead>
<tbody>
%import ast
%import os
%for row in rows:
<tr class="selectable">
<td><a href="{{base_url}}movie/{{row[5]}}">{{row[1]}}</a></td>
<td>
%if os.path.isfile(row[2]):
<span data-tooltip="This path seems to be valid." data-inverted=""><i class="checkmark icon"></i></span>
%else:
<span data-tooltip="This path doesn't seems to be valid." data-inverted=""><i class="warning sign icon"></i></span>
%end
{{row[2]}}
</td>
<td>{{row[7]}}</td>
<td>
%subs_languages = ast.literal_eval(str(row[3]))
%if subs_languages is not None:
%for subs_language in subs_languages:
<div class="ui tiny label">{{subs_language}}</div>
%end
%end
</td>
<td>{{!"" if row[4] == None else row[4]}}</td>
<td {{!"style='background-color: #e8e8e8;'" if row[4] == None else ""}}>
<%
subs_languages_list = []
if subs_languages is not None:
for subs_language in subs_languages:
subs_languages_list.append(subs_language)
end
end
%>
<div class="config ui inverted basic compact icon" data-tooltip="Edit movies" data-inverted="" data-no="{{row[5]}}" data-title="{{row[1]}}" data-poster="{{row[6]}}" data-languages="{{!subs_languages_list}}" data-hearing-impaired="{{row[4]}}" data-audio="{{row[7]}}">
<i class="ui black configure icon"></i>
</div>
</td>
</tr>
%end
</tbody>
</table>
<div class="ui grid">
<div class="three column row">
<div class="column"></div>
<div class="center aligned column">
<i class="\\
%if page == "1":
disabled\\
%end
fast backward icon"></i>
<i class="\\
%if page == "1":
disabled\\
%end
backward icon"></i>
{{page}} / {{max_page}}
<i class="\\
%if int(page) == int(max_page):
disabled\\
%end
forward icon"></i>
<i class="\\
%if int(page) == int(max_page):
disabled\\
%end
fast forward icon"></i>
</div>
<div class="right floated right aligned column">Total records: {{missing_count}}</div>
</div>
</div>
</div>
<div class="ui small modal">
<i class="close icon"></i>
<div class="header">
<div id="movies_title"></div>
</div>
<div class="content">
<form name="movies_form" id="movies_form" action="" method="post" class="ui form">
<div id="divdetails" class="ui grid">
<div class="four wide column">
<img id="movies_poster" class="ui image" src="">
</div>
<div class="twelve wide column">
<div class="ui grid">
<div class="middle aligned row">
<div class="right aligned five wide column">
<label>Audio language</label>
</div>
<div class="nine wide column">
<div id="movies_audio_language"></div>
</div>
</div>
<div class="middle aligned row">
<div class="right aligned five wide column">
<label>Subtitles languages</label>
</div>
<div class="nine wide column">
<select name="languages" id="movies_languages" {{!'multiple="" ' if single_language == 'False' else ''}}class="ui fluid selection dropdown">
<option value="">Languages</option>
%for language in languages:
<option value="{{language[0]}}">{{language[1]}}</option>
%end
</select>
</div>
</div>
<div class="middle aligned row">
<div class="right aligned five wide column">
<label>Hearing-impaired</label>
</div>
<div class="nine wide column">
<div id="movies_hearing-impaired_div" class="ui toggle checkbox">
<input name="hearing_impaired" id="movies_hearing-impaired" type="checkbox">
<label></label>
</div>
</div>
</div>
</div>
</div>
</div>
</form>
</div>
<div class="actions">
<button class="ui cancel button" >Cancel</button>
<button type="submit" name="save" value="save" form="movies_form" class="ui blue approve button">Save</button>
</div>
</div>
% include('footer.tpl')
</body>
</html>
<script>
if (sessionStorage.scrolly) {
$(window).scrollTop(sessionStorage.scrolly);
sessionStorage.clear();
}
$('table').tablesort();
$('a, button:not(.cancel)').click(function(){
$('#loader').addClass('active');
})
$('.fast.backward').click(function(){
location.href="?page=1";
})
$('.backward:not(.fast)').click(function(){
location.href="?page={{int(page)-1}}";
})
$('.forward:not(.fast)').click(function(){
location.href="?page={{int(page)+1}}";
})
$('.fast.forward').click(function(){
location.href="?page={{int(max_page)}}";
})
$('#movieseditor').click(function(){
window.location = '{{base_url}}movieseditor';
})
$('.modal')
.modal({
autofocus: false
})
;
$('.config').click(function(){
sessionStorage.scrolly=$(window).scrollTop();
$('#movies_form').attr('action', '{{base_url}}edit_movie/' + $(this).data("no"));
$("#movies_title").html($(this).data("title"));
$("#movies_poster").attr("src", "{{base_url}}image_proxy_movies" + $(this).data("poster"));
$("#movies_audio_language").html($(this).data("audio"));
$('#movies_languages').dropdown('clear');
var languages_array = eval($(this).data("languages"));
$('#movies_languages').dropdown('set selected',languages_array);
if ($(this).data("hearing-impaired") == "True") {
$("#movies_hearing-impaired_div").checkbox('check');
} else {
$("#movies_hearing-impaired_div").checkbox('uncheck');
}
$('.small.modal').modal('show');
})
$('#movies_languages').dropdown();
</script>

@ -0,0 +1,181 @@
<html>
<head>
<!DOCTYPE html>
<script src="{{base_url}}static/jquery/jquery-latest.min.js"></script>
<script src="{{base_url}}static/semantic/semantic.min.js"></script>
<script src="{{base_url}}static/jquery/tablesort.js"></script>
<link rel="stylesheet" href="{{base_url}}static/semantic/semantic.min.css">
<link rel="apple-touch-icon" sizes="120x120" href="{{base_url}}static/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="{{base_url}}static/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="{{base_url}}static/favicon-16x16.png">
<link rel="manifest" href="{{base_url}}static/manifest.json">
<link rel="mask-icon" href="{{base_url}}static/safari-pinned-tab.svg" color="#5bbad5">
<link rel="shortcut icon" href="{{base_url}}static/favicon.ico">
<meta name="msapplication-config" content="{{base_url}}static/browserconfig.xml">
<meta name="theme-color" content="#ffffff">
<title>Movies Editor - Bazarr</title>
<style>
body {
background-color: #272727;
}
#fondblanc {
background-color: #ffffff;
border-radius: 0px;
box-shadow: 0px 0px 5px 5px #ffffff;
margin-top: 32px;
margin-bottom: 3em;
padding: 2em 3em 2em 3em;
}
#tablemovies {
padding-top: 1em;
}
#divdetails {
min-height: 250px;
}
#bottommenu {
background-color: #333333;
box-shadow: 0 0 10px 1px #333;
padding: 10px;
}
#bottomform {
width: 100%;
padding-left: 8em;
margin-bottom: -1em !important;
}
</style>
</head>
<body>
<div id='loader' class="ui page dimmer">
<div class="ui indeterminate text loader">Loading...</div>
</div>
% include('menu.tpl')
<div id="fondblanc" class="ui container">
<table id="tablemovies" class="ui very basic selectable sortable table">
<thead>
<tr>
<th class="no-sort collapsing">
<div class="ui checkbox">
<input id='selectall' type="checkbox">
<label></label>
</div>
</th>
<th class="sorted ascending">Name</th>
<th>Audio language</th>
<th>Subtitles languages</th>
<th>Hearing-impaired</th>
</tr>
</thead>
<tbody>
%import ast
%import os
%for row in rows:
<tr class="selectable">
<td class="collapsing">
<div class="ui checkbox">
<input id='{{row[5]}}' type="checkbox" class="selected">
<label></label>
</div>
</td>
<td><a href="{{base_url}}episodes/{{row[5]}}">{{row[1]}}</a></td>
<td>{{row[7]}}</td>
<td>
%subs_languages = ast.literal_eval(str(row[3]))
%if subs_languages is not None:
%for subs_language in subs_languages:
<div class="ui tiny label">{{subs_language}}</div>
%end
%end
</td>
<td>{{!"" if row[4] == None else row[4]}}</td>
</tr>
%end
</tbody>
</table>
</div>
<div id='bottommenu' class="ui inverted bottom fixed menu">
<form id='bottomform' action="{{base_url}}edit_movieseditor" method="POST" class="ui form">
<input type="hidden" name="movies" id="checked" />
<div class="fields">
<div class="eight wide field">
<label style='color: white;'>Subtitles languages</label>
<select name="languages" {{!'multiple="" ' if single_language == 'False' else ''}}class="select ui disabled selection dropdown">
<option value="">No change</option>
<option value="None">None</option>
%for language in languages:
<option value="{{language[0]}}">{{language[1]}}</option>
%end
</select>
</div>
<div class="field">
<label style='color: white;'>Hearing-impaired</label>
<select name="hearing_impaired" class="select ui disabled selection dropdown">
<option value="">No change</option>
<option value="True">True</option>
<option value="False">False</option>
</select>
</div>
<div class='field'>
<label style='color: white;'><span id='count'>0</span> movies selected</label>
<button type="submit" id="save" name="save" value="save" class="ui disabled blue approve button">Save</button>
</div>
</div>
</form>
</div>
% include('footer.tpl')
<br><br><br><br>
</body>
</html>
<script>
if (sessionStorage.scrolly) {
$(window).scrollTop(sessionStorage.scrolly);
sessionStorage.clear();
}
$('table').tablesort();
$('a, button').click(function(){
$('#loader').addClass('active');
})
$('.modal')
.modal({
autofocus: false
})
;
$('.selected').change(function() {
$("#count").text($('.selected:checked').length);
if ( $('.selected:checked').length > 0 ) {
$('.select').removeClass('disabled');
$('#save').removeClass('disabled');
}
else {
$('.select').addClass('disabled');
$('#save').addClass('disabled');
}
var result = [];
$('.selected:checked').each(function(i){
result.push($(this).attr('id'));
});
$("#checked").val(result);
});
$('#selectall').change(function() {
if ( $('#selectall').is(":checked") ) {
$('.selected').prop('checked', true).change();
}
else {
$('.selected').prop('checked', false).change();
}
});
$('.select').dropdown();
</script>

@ -14,7 +14,7 @@
<meta name="msapplication-config" content="{{base_url}}static/browserconfig.xml">
<meta name="theme-color" content="#ffffff">
<title>Bazarr</title>
<title>Series - Bazarr</title>
<style>
body {

@ -29,6 +29,10 @@
margin-bottom: 3em;
padding: 1em;
}
.ui.tabular.menu > .disabled.item {
opacity: 0.45 !important;
pointer-events: none !important;
}
</style>
</head>
<body>
@ -40,11 +44,12 @@
<div id="fondblanc" class="ui container">
<form name="settings_form" id="settings_form" action="{{base_url}}save_settings" method="post" class="ui form">
<div id="form_validation_error" class="ui error message">
<p>Some fields are in error and you can't save settings until you have corrected them.</p>
<p>Some fields are in error and you can't save settings until you have corrected them. Be sure to check in every tabs.</p>
</div>
<div class="ui top attached tabular menu">
<a class="tabs item active" data-tab="general">General</a>
<a class="tabs item" data-tab="sonarr">Sonarr</a>
<a class="tabs item" id="sonarr_tab" data-tab="sonarr">Sonarr</a>
<a class="tabs item" id="radarr_tab" data-tab="radarr">Radarr</a>
<a class="tabs item" data-tab="subliminal">Subliminal</a>
<a class="tabs item" data-tab="notifier">Notifications</a>
</div>
@ -157,6 +162,49 @@
</div>
</div>
<div class="ui dividing header">Integration settings</div>
<div class="twelve wide column">
<div class="ui grid">
<div class="middle aligned row">
<div class="right aligned four wide column">
<label>Use Sonarr</label>
</div>
<div class="one wide column">
<div id="settings_use_sonarr" class="ui toggle checkbox" data-enabled={{settings_general[14]}}>
<input name="settings_general_use_sonarr" type="checkbox">
<label></label>
</div>
</div>
<div class="collapsed column">
<div class="collapsed center aligned column">
<div class="ui basic icon" data-tooltip="Enable Sonarr integration." data-inverted="">
<i class="help circle large icon"></i>
</div>
</div>
</div>
</div>
<div class="middle aligned row">
<div class="right aligned four wide column">
<label>Use Radarr</label>
</div>
<div class="one wide column">
<div id="settings_use_radarr" class="ui toggle checkbox" data-enabled={{settings_general[15]}}>
<input name="settings_general_use_radarr" type="checkbox">
<label></label>
</div>
</div>
<div class="collapsed column">
<div class="collapsed center aligned column">
<div class="ui basic icon" data-tooltip="Enable Radarr integration." data-inverted="">
<i class="help circle large icon"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="ui dividing header">Path Mappings</div>
<div class="twelve wide column">
<div class="ui grid">
@ -449,7 +497,7 @@
</div>
<div class="five wide column">
<div class='field'>
<select name="settings_sonarr_sync" id="settings_sync" class="ui fluid selection dropdown">
<select name="settings_sonarr_sync" id="settings_sonarr_sync" class="ui fluid selection dropdown">
<option value="Manually">Manually</option>
<option value="Daily">Daily (at 4am)</option>
<option value="Weekly">Weekly (sunday at 4am)</option>
@ -460,6 +508,115 @@
</div>
</div>
</div>
<div class="ui bottom attached tab segment" data-tab="radarr">
<div class="ui container"><button class="submit ui blue right floated button" type="submit" value="Submit" form="settings_form">Save</button></div>
<div class="ui dividing header">Connection settings</div>
<div class="twelve wide column">
<div class="ui grid">
<div class="middle aligned row">
<div class="right aligned four wide column">
<label>Hostname or IP address</label>
</div>
<div class="five wide column">
<div class='field'>
<div class="ui fluid input">
<input name="settings_radarr_ip" type="text" value="{{settings_radarr[0]}}">
</div>
</div>
</div>
<div class="collapsed center aligned column">
<div class="ui basic icon" data-tooltip="Hostname or IP4 address of Radarr" data-inverted="">
<i class="help circle large icon"></i>
</div>
</div>
</div>
<div class="middle aligned row">
<div class="right aligned four wide column">
<label>Listening port</label>
</div>
<div class="five wide column">
<div class='field'>
<div class="ui fluid input">
<input name="settings_radarr_port" type="text" value="{{settings_radarr[1]}}">
</div>
</div>
</div>
<div class="collapsed center aligned column">
<div class="ui basic icon" data-tooltip="TCP port of Radarr" data-inverted="">
<i class="help circle large icon"></i>
</div>
</div>
</div>
<div class="middle aligned row">
<div class="right aligned four wide column">
<label>Base URL</label>
</div>
<div class="five wide column">
<div class="ui fluid input">
<input name="settings_radarr_baseurl" type="text" value="{{settings_radarr[2]}}">
</div>
</div>
<div class="collapsed center aligned column">
<div class="ui basic icon" data-tooltip="Base URL for Radarr (default: '/')" data-inverted="">
<i class="help circle large icon"></i>
</div>
</div>
</div>
<div class="middle aligned row">
<div class="right aligned four wide column">
<label>SSL enabled</label>
</div>
<div class="one wide column">
<div id="radarr_ssl_div" class="ui toggle checkbox" data-ssl={{settings_radarr[3]}}>
<input name="settings_radarr_ssl" type="checkbox">
<label></label>
</div>
</div>
</div>
<div class="middle aligned row">
<div class="right aligned four wide column">
<label>API key</label>
</div>
<div class="five wide column">
<div class='field'>
<div class="ui fluid input">
<input name="settings_radarr_apikey" type="text" value="{{settings_radarr[4]}}">
</div>
</div>
</div>
<div class="collapsed center aligned column">
<div class="ui basic icon" data-tooltip="API key for Radarr (32 alphanumeric characters)" data-inverted="">
<i class="help circle large icon"></i>
</div>
</div>
</div>
</div>
</div>
<div class="ui dividing header">Synchronization</div>
<div class="twelve wide column">
<div class="ui grid">
<div class="middle aligned row">
<div class="right aligned four wide column">
<label>Full sync frequency</label>
</div>
<div class="five wide column">
<div class='field'>
<select name="settings_radarr_sync" id="settings_radarr_sync" class="ui fluid selection dropdown">
<option value="Manually">Manually</option>
<option value="Daily">Daily (at 5am)</option>
<option value="Weekly">Weekly (sunday at 5am)</option>
</select>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="ui bottom attached tab segment" data-tab="subliminal">
<div class="ui container"><button class="submit ui blue right floated button" type="submit" value="Submit" form="settings_form">Save</button></div>
<br><br><br>
@ -473,7 +630,7 @@
<div class="ui grid">
<div class="middle aligned row">
<div class="right aligned four wide column">
<label>Use Sonarr scene naming</label>
<label>Use scene name when available</label>
</div>
<div class="one wide column">
<div id="settings_scenename" class="ui toggle checkbox" data-scenename={{settings_general[11]}}>
@ -483,7 +640,7 @@
</div>
<div class="collapsed column">
<div class="collapsed center aligned column">
<div class="ui basic icon" data-tooltip="Use the scene name from Sonarr if available to circumvent usage of episode file renaming." data-inverted="">
<div class="ui basic icon" data-tooltip="Use the scene name from Sonarr/Radarr if available to circumvent usage of episode file renaming." data-inverted="">
<i class="help circle large icon"></i>
</div>
</div>
@ -725,6 +882,12 @@
$("#sonarr_ssl_div").checkbox('uncheck');
}
if ($('#radarr_ssl_div').data("ssl") == "True") {
$("#radarr_ssl_div").checkbox('check');
} else {
$("#radarr_ssl_div").checkbox('uncheck');
}
if ($('#settings_automatic_div').data("automatic") == "True") {
$("#settings_automatic_div").checkbox('check');
} else {
@ -759,6 +922,40 @@
}
});
if ($('#settings_use_sonarr').data("enabled") == "True") {
$("#settings_use_sonarr").checkbox('check');
$("#sonarr_tab").removeClass('disabled');
} else {
$("#settings_use_sonarr").checkbox('uncheck');
$("#sonarr_tab").addClass('disabled');
}
$('#settings_use_sonarr').checkbox({
onChecked: function() {
$("#sonarr_tab").removeClass('disabled');
},
onUnchecked: function() {
$("#sonarr_tab").addClass('disabled');
}
});
if ($('#settings_use_radarr').data("enabled") == "True") {
$("#settings_use_radarr").checkbox('check');
$("#radarr_tab").removeClass('disabled');
} else {
$("#settings_use_radarr").checkbox('uncheck');
$("#radarr_tab").addClass('disabled');
}
$('#settings_use_radarr').checkbox({
onChecked: function() {
$("#radarr_tab").removeClass('disabled');
},
onUnchecked: function() {
$("#radarr_tab").addClass('disabled');
}
});
$('.notifier_enabled').each(function(i, obj) {
if ($(this).data("enabled") == 1) {
$(this).checkbox('check');
@ -786,14 +983,17 @@
$('#settings_languages').dropdown('set selected',{{!enabled_languages}});
$('#settings_branch').dropdown('clear');
$('#settings_branch').dropdown('set selected','{{!settings_general[5]}}');
$('#settings_sync').dropdown('clear');
$('#settings_sync').dropdown('set selected','{{!settings_sonarr[5]}}');
$('#settings_sonarr_sync').dropdown('clear');
$('#settings_sonarr_sync').dropdown('set selected','{{!settings_sonarr[5]}}');
$('#settings_radarr_sync').dropdown('clear');
$('#settings_radarr_sync').dropdown('set selected','{{!settings_radarr[5]}}');
$('#settings_loglevel').dropdown();
$('#settings_providers').dropdown();
$('#settings_languages').dropdown();
$('#settings_branch').dropdown();
$('#settings_sync').dropdown();
$('#settings_sonarr_sync').dropdown();
$('#settings_radarr_sync').dropdown();
</script>
<script>
@ -822,6 +1022,7 @@
]
},
settings_sonarr_ip : {
depends: 'settings_general_use_sonarr',
rules : [
{
type : 'empty'
@ -829,6 +1030,7 @@
]
},
settings_sonarr_port : {
depends: 'settings_general_use_sonarr',
rules : [
{
type : 'integer[1..65535]'
@ -839,6 +1041,37 @@
]
},
settings_sonarr_apikey : {
depends: 'settings_general_use_sonarr',
rules : [
{
type : 'exactLength[32]'
},
{
type : 'empty'
}
]
},
settings_radarr_ip : {
depends: 'settings_general_use_radarr',
rules : [
{
type : 'empty'
}
]
},
settings_radarr_port : {
depends: 'settings_general_use_radarr',
rules : [
{
type : 'integer[1..65535]'
},
{
type : 'empty'
}
]
},
settings_radarr_apikey : {
depends: 'settings_general_use_radarr',
rules : [
{
type : 'exactLength[32]'

@ -27,92 +27,67 @@
box-shadow: 0px 0px 5px 5px #ffffff;
margin-top: 32px;
margin-bottom: 3em;
padding: 2em 3em 2em 3em;
padding: 1em;
}
#tablehistory {
padding-top: 2em;
#logs {
margin-top: 4em;
}
.fast.backward, .backward, .forward, .fast.forward {
cursor: pointer;
}
.fast.backward, .backward, .forward, .fast.forward { pointer-events: auto; }
.fast.backward.disabled, .backward.disabled, .forward.disabled, .fast.forward.disabled { pointer-events: none; }
.ui.tabular.menu > .disabled.item {
opacity: 0.45 !important;
pointer-events: none !important;
}
</style>
</head>
<body>
%import ast
%import pycountry
% import os
% import sqlite3
% conn = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db'), timeout=30)
% c = conn.cursor()
% wanted_series = c.execute("SELECT COUNT(*) FROM table_episodes WHERE missing_subtitles != '[]'").fetchone()
% wanted_movies = c.execute("SELECT COUNT(*) FROM table_movies WHERE missing_subtitles != '[]'").fetchone()
% c.close()
<div id='loader' class="ui page dimmer">
<div class="ui indeterminate text loader">Loading...</div>
</div>
% include('menu.tpl')
% import os
% import sqlite3
% conn = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db'), timeout=30)
% c = conn.cursor()
% integration = c.execute("SELECT use_sonarr, use_radarr FROM table_settings_general").fetchone()
% c.close()
<div id="fondblanc" class="ui container">
<div class="ui right floated basic buttons">
<button id="wanted_search_missing_subtitles" class="ui button"><i class="download icon"></i>Download wanted subtitles</button>
<div class="ui top attached tabular menu">
<a id="series_tab" class="tabs item active" data-enabled="{{integration[0]}}" data-tab="series">Series
<div class="ui tiny yellow label">
{{wanted_series[0]}}
</div>
</a>
<a id="movies_tab" class="tabs item" data-enabled="{{integration[1]}}" data-tab="movies">Movies
<div class="ui tiny green label">
{{wanted_movies[0]}}
</div>
</a>
</div>
<table id="tablehistory" class="ui very basic selectable table">
<thead>
<tr>
<th>Series</th>
<th>Episode</th>
<th>Episode Title</th>
<th>Missing subtitles</th>
</tr>
</thead>
<tbody>
%import time
%import pretty
%for row in rows:
<tr class="selectable">
<td><a href="{{base_url}}episodes/{{row[4]}}">{{row[0]}}</a></td>
<td class="collapsing">
<%episode = row[1].split('x')%>
{{episode[0] + 'x' + episode[1].zfill(2)}}
</td>
<td>{{row[2]}}</td>
<td>
%missing_languages = ast.literal_eval(row[3])
%if missing_languages is not None:
%for language in missing_languages:
<a data-episodePath="{{row[5]}}" data-sceneName="{{row[8]}}" data-language="{{pycountry.languages.lookup(str(language)).alpha_3}}" data-hi="{{row[6]}}" data-sonarrSeriesId={{row[4]}} data-sonarrEpisodeId={{row[7]}} class="get_subtitle ui tiny label">
{{language}}
<i style="margin-left:3px; margin-right:0px" class="search icon"></i>
</a>
%end
%end
</td>
</tr>
%end
</tbody>
</table>
<div class="ui grid">
<div class="three column row">
<div class="column"></div>
<div class="center aligned column">
<i class="\\
%if page == "1":
disabled\\
%end
fast backward icon"></i>
<i class="\\
%if page == "1":
disabled\\
%end
backward icon"></i>
{{page}} / {{max_page}}
<i class="\\
%if int(page) == int(max_page):
disabled\\
%end
forward icon"></i>
<i class="\\
%if int(page) == int(max_page):
disabled\\
%end
fast forward icon"></i>
</div>
<div class="right floated right aligned column">Total records: {{missing_count}}</div>
<div class="ui bottom attached tab segment" data-tab="series">
<div class="content">
<div id="content_series"></div>
</div>
</div>
<div class="ui bottom attached tab segment" data-tab="movies">
<div class="content">
<div id="content_movies"></div>
</div>
</div>
</div>
@ -122,44 +97,59 @@
<script>
$('a, button').click(function(){
$('#loader').addClass('active');
})
$('.menu .item')
.tab()
;
$('.fast.backward').click(function(){
location.href="?page=1";
})
$('.backward:not(.fast)').click(function(){
location.href="?page={{int(page)-1}}";
})
$('.forward:not(.fast)').click(function(){
location.href="?page={{int(page)+1}}";
})
$('.fast.forward').click(function(){
location.href="?page={{int(max_page)}}";
$('#series_tab').click(function() {
loadURLseries(1);
})
$('#wanted_search_missing_subtitles').click(function(){
window.location = '{{base_url}}wanted_search_missing_subtitles';
$('#movies_tab').click(function() {
loadURLmovies(1);
})
$('.get_subtitle').click(function(){
var values = {
episodePath: $(this).attr("data-episodePath"),
sceneName: $(this).attr("data-sceneName"),
language: $(this).attr("data-language"),
hi: $(this).attr("data-hi"),
sonarrSeriesId: $(this).attr("data-sonarrSeriesId"),
sonarrEpisodeId: $(this).attr("data-sonarrEpisodeId")
};
$('#loader').addClass('active');
$.ajax({
url: "{{base_url}}get_subtitle",
type: "POST",
dataType: "json",
data: values
}).always(function () {
window.location.reload();
});
function loadURLseries(page) {
$.ajax({
url: "{{base_url}}wantedseries?page=" + page,
beforeSend: function() { $('#loader').addClass('active'); },
complete: function() { $('#loader').removeClass('active'); },
cache: false
}).done(function(data) {
$("#content_series").html(data);
});
}
function loadURLmovies(page) {
$.ajax({
url: "{{base_url}}wantedmovies?page=" + page,
beforeSend: function() { $('#loader').addClass('active'); },
complete: function() { $('#loader').removeClass('active'); },
cache: false
}).done(function(data) {
$("#content_movies").html(data);
});
}
$('a:not(.tabs), button:not(.cancel, #download_log)').click(function(){
$('#loader').addClass('active');
})
if ($('#series_tab').data("enabled") == "True") {
$("#series_tab").removeClass('disabled');
} else {
$("#series_tab").addClass('disabled');
}
if ($('#movies_tab').data("enabled") == "True") {
$("#movies_tab").removeClass('disabled');
} else {
$("#movies_tab").addClass('disabled');
}
if ($('#series_tab').data("enabled") == "True") {
$( "#series_tab" ).trigger( "click" );
}
if ($('#series_tab').data("enabled") == "False" && $('#movies_tab').data("enabled") == "True") {
$( "#movies_tab" ).trigger( "click" );
}
</script>

@ -0,0 +1,160 @@
<html>
<head>
<!DOCTYPE html>
<script src="{{base_url}}static/jquery/jquery-latest.min.js"></script>
<script src="{{base_url}}static/semantic/semantic.min.js"></script>
<script src="{{base_url}}static/jquery/tablesort.js"></script>
<link rel="stylesheet" href="{{base_url}}static/semantic/semantic.min.css">
<link rel="apple-touch-icon" sizes="120x120" href="{{base_url}}static/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="{{base_url}}static/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="{{base_url}}static/favicon-16x16.png">
<link rel="manifest" href="{{base_url}}static/manifest.json">
<link rel="mask-icon" href="{{base_url}}static/safari-pinned-tab.svg" color="#5bbad5">
<link rel="shortcut icon" href="{{base_url}}static/favicon.ico">
<meta name="msapplication-config" content="{{base_url}}static/browserconfig.xml">
<meta name="theme-color" content="#ffffff">
<title>Wanted - Bazarr</title>
<style>
body {
background-color: #272727;
}
#fondblanc {
background-color: #ffffff;
border-radius: 0px;
box-shadow: 0px 0px 5px 5px #ffffff;
margin-top: 32px;
margin-bottom: 3em;
padding: 2em 3em 2em 3em;
}
#tablehistory {
padding-top: 2em;
}
.fast.backward, .backward, .forward, .fast.forward {
cursor: pointer;
}
.fast.backward, .backward, .forward, .fast.forward { pointer-events: auto; }
.fast.backward.disabled, .backward.disabled, .forward.disabled, .fast.forward.disabled { pointer-events: none; }
</style>
</head>
<body>
%import ast
%import pycountry
<div id='loader' class="ui page dimmer">
<div class="ui indeterminate text loader">Loading...</div>
</div>
<div class="ui container">
<div class="ui right floated basic buttons">
<button id="wanted_search_missing_subtitles_movies" class="ui button"><i class="download icon"></i>Download wanted subtitles</button>
</div>
<table id="tablehistory" class="ui very basic selectable table">
<thead>
<tr>
<th>Movies</th>
<th>Missing subtitles</th>
</tr>
</thead>
<tbody>
%import time
%import pretty
%if len(rows) == 0:
<tr>
<td colspan="2">No missing movie subtitles.</td>
</tr>
%end
%for row in rows:
<tr class="selectable">
<td><a href="{{base_url}}movie/{{row[2]}}">{{row[0]}}</a></td>
<td>
%missing_languages = ast.literal_eval(row[1])
%if missing_languages is not None:
%for language in missing_languages:
<a data-moviePath="{{row[3]}}" data-sceneName="{{row[5]}}" data-language="{{pycountry.languages.lookup(str(language)).alpha_3}}" data-hi="{{row[4]}}" data-radarrId={{row[2]}} class="get_subtitle ui tiny label">
{{language}}
<i style="margin-left:3px; margin-right:0px" class="search icon"></i>
</a>
%end
%end
</td>
</tr>
%end
</tbody>
</table>
<div class="ui grid">
<div class="three column row">
<div class="column"></div>
<div class="center aligned column">
<i class="\\
%if page == "1":
disabled\\
%end
fast backward icon"></i>
<i class="\\
%if page == "1":
disabled\\
%end
backward icon"></i>
{{page}} / {{max_page}}
<i class="\\
%if int(page) == int(max_page):
disabled\\
%end
forward icon"></i>
<i class="\\
%if int(page) == int(max_page):
disabled\\
%end
fast forward icon"></i>
</div>
<div class="right floated right aligned column">Total records: {{missing_count}}</div>
</div>
</div>
</div>
</body>
</html>
<script>
$('a, button').click(function(){
$('#loader').addClass('active');
})
$('.fast.backward').click(function(){
loadURLmovies(1);
})
$('.backward:not(.fast)').click(function(){
loadURLmovies({{int(page)-1}});
})
$('.forward:not(.fast)').click(function(){
loadURLmovies({{int(page)+1}});
})
$('.fast.forward').click(function(){
loadURLmovies({{int(max_page)}});
})
$('#wanted_search_missing_subtitles_movies').click(function(){
window.location = '{{base_url}}wanted_search_missing_subtitles';
})
$('.get_subtitle').click(function(){
var values = {
moviePath: $(this).attr("data-moviePath"),
sceneName: $(this).attr("data-sceneName"),
language: $(this).attr("data-language"),
hi: $(this).attr("data-hi"),
radarrId: $(this).attr("data-radarrId")
};
$('#loader').addClass('active');
$.ajax({
url: "{{base_url}}get_subtitle_movie",
type: "POST",
dataType: "json",
data: values
}).always(function () {
window.location.reload();
});
})
</script>

@ -0,0 +1,168 @@
<html>
<head>
<!DOCTYPE html>
<script src="{{base_url}}static/jquery/jquery-latest.min.js"></script>
<script src="{{base_url}}static/semantic/semantic.min.js"></script>
<script src="{{base_url}}static/jquery/tablesort.js"></script>
<link rel="stylesheet" href="{{base_url}}static/semantic/semantic.min.css">
<link rel="apple-touch-icon" sizes="120x120" href="{{base_url}}static/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="{{base_url}}static/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="{{base_url}}static/favicon-16x16.png">
<link rel="manifest" href="{{base_url}}static/manifest.json">
<link rel="mask-icon" href="{{base_url}}static/safari-pinned-tab.svg" color="#5bbad5">
<link rel="shortcut icon" href="{{base_url}}static/favicon.ico">
<meta name="msapplication-config" content="{{base_url}}static/browserconfig.xml">
<meta name="theme-color" content="#ffffff">
<title>Wanted - Bazarr</title>
<style>
body {
background-color: #272727;
}
#fondblanc {
background-color: #ffffff;
border-radius: 0px;
box-shadow: 0px 0px 5px 5px #ffffff;
margin-top: 32px;
margin-bottom: 3em;
padding: 2em 3em 2em 3em;
}
#tablehistory {
padding-top: 2em;
}
.fast.backward, .backward, .forward, .fast.forward {
cursor: pointer;
}
.fast.backward, .backward, .forward, .fast.forward { pointer-events: auto; }
.fast.backward.disabled, .backward.disabled, .forward.disabled, .fast.forward.disabled { pointer-events: none; }
</style>
</head>
<body>
%import ast
%import pycountry
<div id='loader' class="ui page dimmer">
<div class="ui indeterminate text loader">Loading...</div>
</div>
<div class="ui container">
<div class="ui right floated basic buttons">
<button id="wanted_search_missing_subtitles" class="ui button"><i class="download icon"></i>Download wanted subtitles</button>
</div>
<table id="tablehistory" class="ui very basic selectable table">
<thead>
<tr>
<th>Series</th>
<th>Episode</th>
<th>Episode Title</th>
<th>Missing subtitles</th>
</tr>
</thead>
<tbody>
%import time
%import pretty
%if len(rows) == 0:
<tr>
<td colspan="4">No missing episode subtitles.</td>
</tr>
%end
%for row in rows:
<tr class="selectable">
<td><a href="{{base_url}}episodes/{{row[4]}}">{{row[0]}}</a></td>
<td class="collapsing">
<%episode = row[1].split('x')%>
{{episode[0] + 'x' + episode[1].zfill(2)}}
</td>
<td>{{row[2]}}</td>
<td>
%missing_languages = ast.literal_eval(row[3])
%if missing_languages is not None:
%for language in missing_languages:
<a data-episodePath="{{row[5]}}" data-sceneName="{{row[8]}}" data-language="{{pycountry.languages.lookup(str(language)).alpha_3}}" data-hi="{{row[6]}}" data-sonarrSeriesId={{row[4]}} data-sonarrEpisodeId={{row[7]}} class="get_subtitle ui tiny label">
{{language}}
<i style="margin-left:3px; margin-right:0px" class="search icon"></i>
</a>
%end
%end
</td>
</tr>
%end
</tbody>
</table>
<div class="ui grid">
<div class="three column row">
<div class="column"></div>
<div class="center aligned column">
<i class="\\
%if page == "1":
disabled\\
%end
fast backward icon"></i>
<i class="\\
%if page == "1":
disabled\\
%end
backward icon"></i>
{{page}} / {{max_page}}
<i class="\\
%if int(page) == int(max_page):
disabled\\
%end
forward icon"></i>
<i class="\\
%if int(page) == int(max_page):
disabled\\
%end
fast forward icon"></i>
</div>
<div class="right floated right aligned column">Total records: {{missing_count}}</div>
</div>
</div>
</div>
</body>
</html>
<script>
$('a, button').click(function(){
$('#loader').addClass('active');
})
$('.fast.backward').click(function(){
loadURLseries(1);
})
$('.backward:not(.fast)').click(function(){
loadURLseries({{int(page)-1}});
})
$('.forward:not(.fast)').click(function(){
loadURLseries({{int(page)+1}});
})
$('.fast.forward').click(function(){
loadURLseries({{int(max_page)}});
})
$('#wanted_search_missing_subtitles').click(function(){
window.location = '{{base_url}}wanted_search_missing_subtitles';
})
$('.get_subtitle').click(function(){
var values = {
episodePath: $(this).attr("data-episodePath"),
sceneName: $(this).attr("data-sceneName"),
language: $(this).attr("data-language"),
hi: $(this).attr("data-hi"),
sonarrSeriesId: $(this).attr("data-sonarrSeriesId"),
sonarrEpisodeId: $(this).attr("data-sonarrEpisodeId")
};
$('#loader').addClass('active');
$.ajax({
url: "{{base_url}}get_subtitle",
type: "POST",
dataType: "json",
data: values
}).always(function () {
window.location.reload();
});
})
</script>
Loading…
Cancel
Save