summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--bazarr.dbbin0 -> 1200128 bytes
-rw-r--r--bazarr.py144
-rw-r--r--create_db.sql.old49
-rw-r--r--get_episodes.py43
-rw-r--r--get_general_settings.py48
-rw-r--r--get_general_settings.pycbin0 -> 1378 bytes
-rw-r--r--get_languages.py20
-rw-r--r--get_languages.pycbin0 -> 590 bytes
-rw-r--r--get_series.py41
-rw-r--r--get_sonarr_settings.py33
-rw-r--r--get_sonarr_settings.pycbin0 -> 871 bytes
-rw-r--r--init_db.py23
-rw-r--r--init_db.pycbin0 -> 491 bytes
-rw-r--r--list_subtitles.py73
-rw-r--r--list_subtitles.pycbin0 -> 2733 bytes
-rw-r--r--static/android-chrome-96x96.pngbin0 -> 8357 bytes
-rw-r--r--static/apple-touch-icon.pngbin0 -> 11628 bytes
-rw-r--r--static/browserconfig.xml9
-rw-r--r--static/favicon-16x16.pngbin0 -> 1148 bytes
-rw-r--r--static/favicon-32x32.pngbin0 -> 2192 bytes
-rw-r--r--static/favicon.icobin0 -> 15086 bytes
-rw-r--r--static/logo128.pngbin0 -> 10302 bytes
-rw-r--r--static/logo64.pngbin0 -> 10302 bytes
-rw-r--r--static/manifest.json13
-rw-r--r--static/mstile-150x150.pngbin0 -> 13519 bytes
-rw-r--r--static/safari-pinned-tab.svg27
-rw-r--r--utils.py44
-rw-r--r--utils.pycbin0 -> 1352 bytes
-rw-r--r--views/episodes.tpl200
-rw-r--r--views/history.tpl124
-rw-r--r--views/series.tpl225
-rw-r--r--views/settings.tpl105
-rw-r--r--views/system.tpl97
33 files changed, 1318 insertions, 0 deletions
diff --git a/bazarr.db b/bazarr.db
new file mode 100644
index 000000000..3763b5dd9
--- /dev/null
+++ b/bazarr.db
Binary files differ
diff --git a/bazarr.py b/bazarr.py
new file mode 100644
index 000000000..cbffdca0b
--- /dev/null
+++ b/bazarr.py
@@ -0,0 +1,144 @@
+from bottle import route, run, template, static_file, request, redirect
+import bottle
+bottle.debug(True)
+bottle.TEMPLATES.clear()
+
+application = bottle.default_app()
+
+from paste import httpserver
+
+import sqlite3
+import itertools
+import operator
+import requests
+from PIL import Image
+from io import BytesIO
+from fdsend import send_file
+import urllib
+
+from init_db import *
+from get_languages import *
+from get_general_settings import *
+from get_sonarr_settings import *
+from list_subtitles import *
+
+@route('/static/:path#.+#', name='static')
+def static(path):
+ return static_file(path, root='static')
+
+@route('/image_proxy/<url:path>', method='GET')
+def image_proxy(url):
+ img_pil = Image.open(BytesIO(requests.get(url_sonarr_short + '/' + url).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('/')
+def series():
+ db = sqlite3.connect('bazarr.db')
+ db.create_function("path_substitution", 1, path_replace)
+ c = db.cursor()
+ c.execute("SELECT tvdbId, title, path_substitution(path), languages, hearing_impaired, sonarrSeriesId, poster FROM table_shows ORDER BY title")
+ data = c.fetchall()
+ c.execute("SELECT code2, name FROM table_settings_languages WHERE enabled = 1")
+ languages = c.fetchall()
+ c.close()
+ output = template('series', rows=data, languages=languages, url_sonarr_short=url_sonarr_short)
+ return output
+
+@route('/edit_series/<no:int>', method='POST')
+def edit_series(no):
+ lang = request.forms.getall('languages')
+ if len(lang) > 0:
+ if lang[0] == '':
+ lang = None
+ else:
+ pass
+ else:
+ lang = None
+ hi = request.forms.get('hearing_impaired')
+
+ if hi == "on":
+ hi = "True"
+ else:
+ hi = "False"
+
+ conn = sqlite3.connect('bazarr.db')
+ c = conn.cursor()
+ c.execute("UPDATE table_shows SET languages = ?, hearing_impaired = ? WHERE tvdbId LIKE ?", (str(lang), hi, no))
+ conn.commit()
+ c.close()
+
+ redirect('/')
+
+@route('/episodes/<no:int>', method='GET')
+def episodes(no):
+ conn = sqlite3.connect('bazarr.db')
+ conn.create_function("path_substitution", 1, path_replace)
+ conn.create_function("missing_subtitles", 1, list_missing_subtitles)
+ c = conn.cursor()
+
+ series_details = []
+ series_details = c.execute("SELECT title, overview, poster, fanart FROM table_shows WHERE sonarrSeriesId LIKE ?", (str(no),)).fetchone()
+
+ sqlite3.enable_callback_tracebacks(True)
+ episodes = c.execute("SELECT title, path_substitution(path), season, episode, subtitles, sonarrSeriesId, missing_subtitles(path) FROM table_episodes WHERE sonarrSeriesId LIKE ?", (str(no),)).fetchall()
+ episodes=reversed(sorted(episodes, key=operator.itemgetter(2)))
+ seasons_list = []
+ for key,season in itertools.groupby(episodes,operator.itemgetter(2)):
+ seasons_list.append(list(season))
+ c.close()
+
+ return template('episodes', details=series_details, seasons=seasons_list, url_sonarr_short=url_sonarr_short)
+
+@route('/history')
+def history():
+ db = sqlite3.connect('bazarr.db')
+ c = db.cursor()
+ c.execute("SELECT table_history.action, table_shows.title, table_episodes.season || 'x' || table_episodes.episode, table_episodes.title, table_history.timestamp, table_history.description, table_history.sonarrSeriesId FROM table_history INNER JOIN table_shows on table_shows.sonarrSeriesId = table_history.sonarrSeriesId INNER JOIN table_episodes on table_episodes.sonarrEpisodeId = table_history.sonarrEpisodeId ORDER BY id LIMIT 15")
+ data = c.fetchall()
+ c.close()
+ return template('history', rows=data)
+
+@route('/settings')
+def settings():
+ db = sqlite3.connect('bazarr.db')
+ c = db.cursor()
+ c.execute("SELECT * FROM table_settings_general")
+ settings_general = c.fetchone()
+ c.execute("SELECT * FROM table_settings_languages")
+ settings_languages = c.fetchall()
+ c.execute("SELECT * FROM table_settings_providers")
+ settings_providers = c.fetchall()
+ c.execute("SELECT * FROM table_settings_sonarr")
+ settings_sonarr = c.fetchone()
+ c.execute("SELECT * FROM table_settings_subliminal")
+ settings_subliminal = c.fetchone()
+ c.close()
+ return template('settings', settings_general=settings_general, settings_languages=settings_languages, settings_providers=settings_providers, settings_sonarr=settings_sonarr, settings_subliminal=settings_subliminal)
+
+@route('/system')
+def system():
+ db = sqlite3.connect('bazarr.db')
+ c = db.cursor()
+ c.execute("SELECT * FROM table_scheduler")
+ data = c.fetchall()
+ c.close()
+ return template('system', rows=data)
+
+@route('/remove_subtitles', method='GET')
+def remove_subtitles():
+ episodePath = request.GET.episodePath
+ subtitlesPath = request.GET.subtitlesPath
+ sonarrSeriesId = request.GET.sonarrSeriesId
+
+ try:
+ os.remove(subtitlesPath)
+ store_subtitles(episodePath)
+ redirect('/episodes/' + sonarrSeriesId)
+ except OSError:
+ redirect('/episodes/' + sonarrSeriesId + '?error=1')
+
+httpserver.serve(application, host=ip, port=port)
diff --git a/create_db.sql.old b/create_db.sql.old
new file mode 100644
index 000000000..2c88fac02
--- /dev/null
+++ b/create_db.sql.old
@@ -0,0 +1,49 @@
+CREATE TABLE `table_shows` (
+ `tvdbId` INTEGER NOT NULL PRIMARY KEY UNIQUE,
+ `title` TEXT NOT NULL,
+ `path` TEXT NOT NULL UNIQUE,
+ `languages` TEXT,
+ `hearing_impaired` TEXT
+);
+CREATE TABLE `table_settings_subliminal` (
+ `age` TEXT,
+ `max-workers` INTEGER
+);
+CREATE TABLE `table_settings_providers` (
+ `name` TEXT NOT NULL UNIQUE,
+ `username` TEXT,
+ `password` TEXT,
+ `enabled` INTEGER,
+ PRIMARY KEY(`name`)
+);
+CREATE TABLE `table_settings_languages` (
+ `code` TEXT NOT NULL UNIQUE,
+ `name` TEXT NOT NULL,
+ `enabled` INTEGER,
+ PRIMARY KEY(`code`)
+);
+CREATE TABLE `table_settings_general` (
+ `ip` TEXT NOT NULL,
+ `port` INTEGER NOT NULL,
+ `base_url` TEXT,
+ `ssl` INTEGER
+);
+INSERT INTO `table_settings_general` (ip,port,base_url,ssl) VALUES ('0.0.0.0',6767,NULL,NULL);
+CREATE TABLE `table_settings_connect` (
+ `ip` TEXT NOT NULL,
+ `port` INTEGER NOT NULL,
+ `base_url` TEXT,
+ `ssl` INTEGER,
+ `apikey` TEXT NOT NULL
+);
+CREATE TABLE `table_scheduler` (
+ `id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
+ `name` TEXT NOT NULL,
+ `frequency` TEXT NOT NULL
+);
+CREATE TABLE `table_history` (
+ `id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
+ `timestamp` TEXT NOT NULL,
+ `file` TEXT NOT NULL,
+ `provider` TEXT NOT NULL
+);
diff --git a/get_episodes.py b/get_episodes.py
new file mode 100644
index 000000000..93aa414bd
--- /dev/null
+++ b/get_episodes.py
@@ -0,0 +1,43 @@
+import sqlite3
+import requests
+
+# Open database connection
+db = sqlite3.connect('bazarr.db')
+c = db.cursor()
+
+# Get Sonarr API URL from database config table
+c.execute('''SELECT * FROM table_settings_sonarr''')
+config_sonarr = c.fetchone()
+if config_sonarr[3] == 1:
+ protocol_sonarr = "https"
+else:
+ protocol_sonarr = "http"
+if config_sonarr[2] == "":
+ base_url_sonarr = ""
+else:
+ base_url_sonarr = "/" + config_sonarr[2].strip("/")
+
+# Get sonarrId for each series from database
+c.execute("SELECT sonarrSeriesId FROM table_shows")
+seriesIdList = c.fetchall()
+for seriesId in seriesIdList:
+ # Get episodes data for a series from Sonarr
+ url_sonarr_api_episode = protocol_sonarr + "://" + config_sonarr[0] + ":" + str(config_sonarr[1]) + base_url_sonarr + "/api/episode?seriesId=" + str(seriesId[0]) + "&apikey=" + config_sonarr[4]
+ r = requests.get(url_sonarr_api_episode)
+ for episode in r.json():
+ if episode['hasFile']:
+ try:
+ c.execute('''INSERT INTO table_episodes(sonarrSeriesId, sonarrEpisodeId, title, path, season, episode) VALUES (?, ?, ?, ?, ?, ?)''', (episode['seriesId'], episode['id'], episode['title'], episode['episodeFile']['path'], episode['seasonNumber'], episode['episodeNumber']))
+ except:
+ c.execute('''UPDATE table_episodes SET sonarrSeriesId = ?, sonarrEpisodeId = ?, title = ?, path = ?, season = ?, episode = ? WHERE path = ?''', (episode['seriesId'], episode['id'], episode['title'], episode['episodeFile']['path'], episode['seasonNumber'], episode['episodeNumber'], episode['episodeFile']['path']))
+ else:
+ continue
+ continue
+
+# Commit changes to database table
+db.commit()
+
+# Close database connection
+c.close()
+
+
diff --git a/get_general_settings.py b/get_general_settings.py
new file mode 100644
index 000000000..9826eac1d
--- /dev/null
+++ b/get_general_settings.py
@@ -0,0 +1,48 @@
+import sqlite3
+import os
+import ast
+
+# Open database connection
+db = sqlite3.connect('bazarr.db')
+c = db.cursor()
+
+# Get general settings from database table
+c.execute("SELECT * FROM table_settings_general")
+general_settings = c.fetchone()
+
+# Close database connection
+db.close()
+
+ip = general_settings[0]
+port = general_settings[1]
+base_url = general_settings[2]
+ssl = general_settings[3]
+path_mappings = ast.literal_eval(general_settings[4])
+
+def path_replace(path):
+ for path_mapping in path_mappings:
+ path = path.replace(path_mapping[0], path_mapping[1], 1)
+
+ if '\\' in path:
+ path = path.replace('/', '\\')
+
+ return path
+
+def path_replace_reverse(path):
+ for path_mapping in path_mappings:
+ if path.startswith('\\\\\\\\'):
+ if '\\\\' in path:
+ path = path.replace('\\\\', '\\')
+ elif '\\' in path:
+ path = path.replace('\\\\', '\\')
+
+ path = path.replace(path_mapping[1], path_mapping[0], 1)
+ elif path.startswith('\\\\'):
+ path = path.replace(path_mapping[1], path_mapping[0], 1)
+
+ if '\\' in path:
+ path = path.replace('\\', '/')
+
+ return path
+
+#print path_replace_reverse(r'\\\\serveur\\media\\Series TV\\Vikings\\Season 03\\Vikings.S03E01.720p.HDTV.x264-KILLERS.mkv')
diff --git a/get_general_settings.pyc b/get_general_settings.pyc
new file mode 100644
index 000000000..ad1bef5a3
--- /dev/null
+++ b/get_general_settings.pyc
Binary files differ
diff --git a/get_languages.py b/get_languages.py
new file mode 100644
index 000000000..b48fa4931
--- /dev/null
+++ b/get_languages.py
@@ -0,0 +1,20 @@
+import sqlite3
+import pycountry
+
+# Get languages list in langs tuple
+langs = [[lang.alpha_3,lang.alpha_2,lang.name]
+ for lang in pycountry.languages
+ if hasattr(lang, 'alpha_2')]
+
+# Open database connection
+db = sqlite3.connect('bazarr.db')
+c = db.cursor()
+
+# Insert languages in database table
+c.executemany('''INSERT OR IGNORE INTO table_settings_languages(code3, code2, name) VALUES(?, ?, ?)''', langs)
+
+# Commit changes to database table
+db.commit()
+
+# Close database connection
+db.close()
diff --git a/get_languages.pyc b/get_languages.pyc
new file mode 100644
index 000000000..5b23d3f87
--- /dev/null
+++ b/get_languages.pyc
Binary files differ
diff --git a/get_series.py b/get_series.py
new file mode 100644
index 000000000..41904e9d7
--- /dev/null
+++ b/get_series.py
@@ -0,0 +1,41 @@
+import os
+import sqlite3
+import requests
+
+from get_sonarr_settings import *
+
+# Open database connection
+db = sqlite3.connect('bazarr.db')
+c = db.cursor()
+
+# Get shows data from Sonarr
+url_sonarr_api_series = url_sonarr + "/api/series?apikey=" + apikey_sonarr
+
+r = requests.get(url_sonarr_api_series)
+shows_list = []
+for show in r.json():
+ try:
+ overview = unicode(show['overview'])
+ except:
+ overview = ""
+ try:
+ poster_big = show['images'][2]['url'].split('?')[0]
+ poster = os.path.splitext(poster_big)[0] + '-250' + os.path.splitext(poster_big)[1]
+ except:
+ poster = ""
+ try:
+ fanart = show['images'][0]['url'].split('?')[0]
+ except:
+ fanart = ""
+ # Update or insert shows list in database table
+ try:
+ c.execute('''UPDATE table_shows SET title = ?, path = ?, tvdbId = ?, sonarrSeriesId = ?, overview = ?, poster = ?, fanart = ? WHERE tvdbid = ?''', (show["title"],show["path"],show["tvdbId"],show["id"],overview,poster,fanart,show["tvdbId"]))
+ except:
+ print show["title"]
+ c.execute('''INSERT INTO table_shows(title, path, tvdbId, languages,`hearing_impaired`, sonarrSeriesId, overview, poster, fanart) VALUES (?,?,?,(SELECT languages FROM table_shows WHERE tvdbId = ?),(SELECT `hearing_impaired` FROM table_shows WHERE tvdbId = ?), ?, ?, ?, ?)''', (show["title"],show["path"],show["tvdbId"],show["tvdbId"],show["tvdbId"],show["id"],overview,poster,fanart))
+
+# Commit changes to database table
+db.commit()
+
+# Close database connection
+db.close()
diff --git a/get_sonarr_settings.py b/get_sonarr_settings.py
new file mode 100644
index 000000000..387795074
--- /dev/null
+++ b/get_sonarr_settings.py
@@ -0,0 +1,33 @@
+import sqlite3
+import os
+import ast
+
+# Open database connection
+db = sqlite3.connect('bazarr.db')
+c = db.cursor()
+
+# Get Sonarr API URL from database config table
+c.execute('''SELECT * FROM table_settings_sonarr''')
+config_sonarr = c.fetchone()
+
+# Close database connection
+db.close()
+
+# Build Sonarr URL
+ip_sonarr = config_sonarr[0]
+port_sonarr = str(config_sonarr[1])
+baseurl_sonarr = config_sonarr[2]
+ssl_sonarr = config_sonarr[3]
+apikey_sonarr = config_sonarr[4]
+
+if ssl_sonarr == 1:
+ protocol_sonarr = "https"
+else:
+ protocol_sonarr = "http"
+if baseurl_sonarr == "":
+ base_url_sonarr = ""
+else:
+ base_url_sonarr = "/" + baseurl_sonarr.strip("/")
+
+url_sonarr = protocol_sonarr + "://" + ip_sonarr + ":" + port_sonarr + base_url_sonarr
+url_sonarr_short = protocol_sonarr + "://" + ip_sonarr + ":" + port_sonarr
diff --git a/get_sonarr_settings.pyc b/get_sonarr_settings.pyc
new file mode 100644
index 000000000..e7fcef83a
--- /dev/null
+++ b/get_sonarr_settings.pyc
Binary files differ
diff --git a/init_db.py b/init_db.py
new file mode 100644
index 000000000..fab283f9a
--- /dev/null
+++ b/init_db.py
@@ -0,0 +1,23 @@
+import os.path
+import sqlite3
+
+# Check if database exist
+if os.path.exists('bazarr.db') == True:
+ pass
+else:
+ # Get SQL script from file
+ fd = open('create_db.sql', 'r')
+ script = fd.read()
+
+ # Open database connection
+ db = sqlite3.connect('bazarr.db')
+ c = db.cursor()
+
+ # Execute script and commit change to database
+ c.executescript(script)
+
+ # Close database connection
+ db.close()
+
+ # Close SQL script file
+ fd.close()
diff --git a/init_db.pyc b/init_db.pyc
new file mode 100644
index 000000000..cf1ce3fb4
--- /dev/null
+++ b/init_db.pyc
Binary files differ
diff --git a/list_subtitles.py b/list_subtitles.py
new file mode 100644
index 000000000..dda83d9a8
--- /dev/null
+++ b/list_subtitles.py
@@ -0,0 +1,73 @@
+import os
+import enzyme
+import babelfish
+import pycountry
+import sqlite3
+import ast
+
+from get_general_settings import *
+
+def store_subtitles(file):
+ languages = []
+ 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:
+ languages.append([str(pycountry.languages.lookup(subtitle_track.language).alpha_2),None])
+ except:
+ pass
+ except:
+ pass
+
+ conn_db = sqlite3.connect('bazarr.db')
+ c_db = conn_db.cursor()
+ enabled_languages = c_db.execute("SELECT code2 FROM table_settings_languages WHERE enabled = 1").fetchall()
+
+ for language in enabled_languages:
+ subtitle_path = os.path.splitext(file)[0] + "." + str(language[0]) + ".srt"
+ if os.path.isfile(subtitle_path):
+ languages.append([str(language[0]),str(path_replace_reverse(subtitle_path))])
+ try:
+ c_db.execute("UPDATE table_episodes SET subtitles = ? WHERE path = ?", (str(languages), path_replace_reverse(file)))
+ conn_db.commit()
+ except:
+ pass
+ c_db.close()
+
+ return languages
+
+def list_missing_subtitles(file):
+ conn_db = sqlite3.connect('bazarr.db')
+ c_db = conn_db.cursor()
+ actual_subtitles_long = c_db.execute("SELECT sonarrSeriesId, subtitles FROM table_episodes WHERE path = ?", (file,)).fetchone()
+ desired_subtitles = c_db.execute("SELECT languages FROM table_shows WHERE sonarrSeriesId = ?", (actual_subtitles_long[0],)).fetchone()
+ c_db.close()
+ missing_subtitles = []
+ actual_subtitles = []
+ if desired_subtitles[0] == "None":
+ pass
+ else:
+ actual_subtitles_long = ast.literal_eval(actual_subtitles_long[1])
+ for actual_subtitle in actual_subtitles_long:
+ actual_subtitles.append(actual_subtitle[0])
+
+ desired_subtitles = ast.literal_eval(desired_subtitles[0])
+
+ missing_subtitles = (list(set(desired_subtitles) - set(actual_subtitles)))
+ return str(missing_subtitles)
+
+def full_scan_subtitles():
+ conn_db = sqlite3.connect('bazarr.db')
+ c_db = conn_db.cursor()
+ all_path = c_db.execute("SELECT path FROM table_episodes").fetchall()
+ c_db.close()
+
+ for path in all_path:
+ print store_subtitles(path_replace(path[0]))
+
+#print list_missing_subtitles('/tv/Fear the Walking Dead/Season 3/Fear.The.Walking.Dead.S03E01.CONVERT.720p.WEB.h264-TBS[rarbg].mkv')
+#full_scan_subtitles()
diff --git a/list_subtitles.pyc b/list_subtitles.pyc
new file mode 100644
index 000000000..e536f6d24
--- /dev/null
+++ b/list_subtitles.pyc
Binary files differ
diff --git a/static/android-chrome-96x96.png b/static/android-chrome-96x96.png
new file mode 100644
index 000000000..28b3772de
--- /dev/null
+++ b/static/android-chrome-96x96.png
Binary files differ
diff --git a/static/apple-touch-icon.png b/static/apple-touch-icon.png
new file mode 100644
index 000000000..f770370b1
--- /dev/null
+++ b/static/apple-touch-icon.png
Binary files differ
diff --git a/static/browserconfig.xml b/static/browserconfig.xml
new file mode 100644
index 000000000..09f85098e
--- /dev/null
+++ b/static/browserconfig.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<browserconfig>
+ <msapplication>
+ <tile>
+ <square150x150logo src="/static/mstile-150x150.png"/>
+ <TileColor>#da532c</TileColor>
+ </tile>
+ </msapplication>
+</browserconfig>
diff --git a/static/favicon-16x16.png b/static/favicon-16x16.png
new file mode 100644
index 000000000..03618a704
--- /dev/null
+++ b/static/favicon-16x16.png
Binary files differ
diff --git a/static/favicon-32x32.png b/static/favicon-32x32.png
new file mode 100644
index 000000000..85dbb8462
--- /dev/null
+++ b/static/favicon-32x32.png
Binary files differ
diff --git a/static/favicon.ico b/static/favicon.ico
new file mode 100644
index 000000000..02c0a6412
--- /dev/null
+++ b/static/favicon.ico
Binary files differ
diff --git a/static/logo128.png b/static/logo128.png
new file mode 100644
index 000000000..dd44ff0fc
--- /dev/null
+++ b/static/logo128.png
Binary files differ
diff --git a/static/logo64.png b/static/logo64.png
new file mode 100644
index 000000000..d623162b3
--- /dev/null
+++ b/static/logo64.png
Binary files differ
diff --git a/static/manifest.json b/static/manifest.json
new file mode 100644
index 000000000..e552f41d7
--- /dev/null
+++ b/static/manifest.json
@@ -0,0 +1,13 @@
+{
+ "name": "",
+ "icons": [
+ {
+ "src": "/static/android-chrome-96x96.png",
+ "sizes": "96x96",
+ "type": "image/png"
+ }
+ ],
+ "theme_color": "#ffffff",
+ "background_color": "#ffffff",
+ "display": "standalone"
+} \ No newline at end of file
diff --git a/static/mstile-150x150.png b/static/mstile-150x150.png
new file mode 100644
index 000000000..b7e1bad77
--- /dev/null
+++ b/static/mstile-150x150.png
Binary files differ
diff --git a/static/safari-pinned-tab.svg b/static/safari-pinned-tab.svg
new file mode 100644
index 000000000..24fecc60f
--- /dev/null
+++ b/static/safari-pinned-tab.svg
@@ -0,0 +1,27 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
+ "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
+ width="128.000000pt" height="128.000000pt" viewBox="0 0 128.000000 128.000000"
+ preserveAspectRatio="xMidYMid meet">
+<metadata>
+Created by potrace 1.11, written by Peter Selinger 2001-2013
+</metadata>
+<g transform="translate(0.000000,128.000000) scale(0.100000,-0.100000)"
+fill="#000000" stroke="none">
+<path d="M495 1261 c-39 -9 -77 -21 -84 -25 -8 -5 -17 -3 -22 5 -7 11 -9 11
+-9 -2 0 -8 -22 -27 -50 -42 -51 -28 -67 -39 -130 -94 -20 -18 -41 -29 -45 -25
+-5 4 -5 2 -2 -4 9 -15 -22 -56 -36 -48 -7 4 -9 4 -5 -1 8 -8 -49 -117 -64
+-123 -4 -2 -6 -8 -3 -13 3 -5 0 -19 -6 -31 -7 -13 -14 -32 -15 -43 -1 -11 -7
+-40 -13 -65 -16 -69 -13 -177 8 -258 11 -40 19 -75 20 -77 0 -3 17 -38 38 -78
+25 -47 64 -98 112 -147 41 -41 74 -78 73 -82 -1 -4 4 -5 11 -2 7 2 23 -5 35
+-16 12 -11 22 -18 22 -14 0 4 8 0 18 -8 9 -8 46 -23 82 -33 36 -10 70 -22 75
+-25 6 -4 57 -8 115 -9 116 -2 245 26 317 70 53 32 137 92 141 101 2 5 9 5 14
+2 6 -4 8 -3 5 2 -3 6 14 31 38 57 24 27 44 50 45 52 1 3 16 37 34 77 51 111
+70 200 63 305 -7 92 -10 118 -20 143 -3 8 -7 24 -9 35 -2 11 -5 22 -8 25 -3 3
+-14 25 -25 50 -11 24 -37 67 -57 94 -20 27 -34 54 -31 59 3 6 1 7 -4 4 -13 -8
+-36 17 -25 28 4 5 2 5 -4 2 -6 -3 -27 9 -47 27 -20 18 -58 44 -84 58 -26 14
+-47 30 -46 35 1 5 -8 9 -20 8 -12 -1 -30 2 -40 7 -10 5 -28 11 -40 14 -140 30
+-218 31 -322 5z"/>
+</g>
+</svg>
diff --git a/utils.py b/utils.py
new file mode 100644
index 000000000..83971c341
--- /dev/null
+++ b/utils.py
@@ -0,0 +1,44 @@
+import datetime
+
+def pretty_date(time=False):
+ """
+ Get a datetime object or a int() Epoch timestamp and return a
+ pretty string like 'an hour ago', 'Yesterday', '3 months ago',
+ 'just now', etc
+ """
+ from datetime import datetime
+ now = datetime.now()
+ if type(time) is int:
+ diff = now - datetime.fromtimestamp(time)
+ elif isinstance(time,datetime):
+ diff = now - time
+ elif not time:
+ diff = now - now
+ second_diff = diff.seconds
+ day_diff = diff.days
+
+ if day_diff < 0:
+ return ''
+
+ if day_diff == 0:
+ if second_diff < 10:
+ return "just now"
+ if second_diff < 60:
+ return str(second_diff) + " seconds ago"
+ if second_diff < 120:
+ return "a minute ago"
+ if second_diff < 3600:
+ return str(second_diff / 60) + " minutes ago"
+ if second_diff < 7200:
+ return "an hour ago"
+ if second_diff < 86400:
+ return str(second_diff / 3600) + " hours ago"
+ if day_diff == 1:
+ return "Yesterday"
+ if day_diff < 7:
+ return str(day_diff) + " days ago"
+ if day_diff < 31:
+ return str(day_diff / 7) + " weeks ago"
+ if day_diff < 365:
+ return str(day_diff / 30) + " months ago"
+ return str(day_diff / 365) + " years ago"
diff --git a/utils.pyc b/utils.pyc
new file mode 100644
index 000000000..0b4f5995d
--- /dev/null
+++ b/utils.pyc
Binary files differ
diff --git a/views/episodes.tpl b/views/episodes.tpl
new file mode 100644
index 000000000..9244f4752
--- /dev/null
+++ b/views/episodes.tpl
@@ -0,0 +1,200 @@
+<html>
+ <head>
+ <!DOCTYPE html>
+ <script src="https://code.jquery.com/jquery-latest.min.js"></script>
+ <script src="https://cdn.jsdelivr.net/semantic-ui/latest/semantic.min.js"></script>
+ <script src="https://semantic-ui.com/javascript/library/tablesort.js"></script>
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/semantic-ui/latest/semantic.min.css">
+
+ <link rel="apple-touch-icon" sizes="120x120" href="/static/apple-touch-icon.png">
+ <link rel="icon" type="image/png" sizes="32x32" href="/static/favicon-32x32.png">
+ <link rel="icon" type="image/png" sizes="16x16" href="/static/favicon-16x16.png">
+ <link rel="manifest" href="/static/manifest.json">
+ <link rel="mask-icon" href="/static/safari-pinned-tab.svg" color="#5bbad5">
+ <link rel="shortcut icon" href="/static/favicon.ico">
+ <meta name="msapplication-config" content="/static/browserconfig.xml">
+ <meta name="theme-color" content="#ffffff">
+
+ <title>{{details[0]}} - Bazarr</title>
+ <style>
+ body {
+ background-color: #1b1c1d;
+ background-image: url("/image_proxy/{{details[3]}}");
+ background-repeat: no-repeat;
+ background-attachment: fixed;
+ background-size: cover;
+ background-position:center center;
+ }
+ #divmenu {
+ background-color: #1b1c1d;
+ opacity: 0.9;
+ border-radius: 5px;
+ padding-top: 2em;
+ padding-bottom: 1em;
+ padding-left: 1em;
+ padding-right: 128px;
+ }
+ #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;
+ }
+ </style>
+
+ <script>
+ $(document).ready(function(){
+ $('.ui.accordion').accordion();
+ var first_season_acc_title = document.getElementsByClassName("title")[0];
+ first_season_acc_title.className += " active";
+ var first_season_acc_content = document.getElementsByClassName("content")[0];
+ first_season_acc_content.className += " active";
+ });
+
+ $(window).on('beforeunload',function(){
+ $('#loader').addClass('active');
+ });
+ </script>
+ </head>
+ <body>
+ %import ast
+ <div style="display: none;"><img src="/image_proxy/{{details[3]}}"></div>
+ <div id='loader' class="ui page dimmer">
+ <div class="ui indeterminate text loader">Loading...</div>
+ </div>
+ <div id="divmenu" class="ui container">
+ <div id="menu" class="ui inverted borderless labeled icon huge menu four item">
+ <a href="/"><img class="logo" src="/static/logo128.png"></a>
+ <div style="height:80px;" class="ui container">
+ <a class="item" href="/">
+ <i class="play icon"></i>
+ Series
+ </a>
+ <a class="item" href="/history">
+ <i class="wait icon"></i>
+ History
+ </a>
+ <a class="item" href="/settings">
+ <i class="settings icon"></i>
+ Settings
+ </a>
+ <a class="item" href="/system">
+ <i class="laptop icon"></i>
+ System
+ </a>
+ </div>
+ </div>
+ </div>
+
+ <div style='padding-left: 2em; padding-right: 2em;' class='ui container'>
+ <br>
+ <div class="ui hidden negative message">
+ <i class="close icon"></i>
+ <div class="header">
+ An error occured while trying to delete subtitles file from disk.
+ </div>
+ <p>Please try again.</p>
+ </div>
+
+ <div id="divdetails" class="ui container">
+ <img class="left floated ui image" src="/image_proxy/{{details[2]}}">
+ <h2>{{details[0]}}</h2>
+ <p>{{details[1]}}</p>
+ </div>
+
+ %if len(seasons) == 0:
+ <div id="fondblanc" class="ui container">
+ <h2 class="ui header">No episode file available for this series.</h2>
+ </div>
+ %else:
+ %for season in seasons:
+ <div id="fondblanc" class="ui container">
+ <h1 class="ui header">Season {{season[0][2]}}</h1>
+ <div class="ui accordion">
+ <div class="title">
+ <div class="ui one column stackable center aligned page grid">
+ <div class="column twelve wide">
+ <h3 class="ui header"><i class="dropdown icon"></i>
+ Show/Hide Episodes</h3>
+ </div>
+ </div>
+ </div>
+ <div class="content">
+ <table class="ui very basic single line selectable table">
+ <thead>
+ <tr>
+ <th class="collapsing">Episode</th>
+ <th>Title</th>
+ <th class="collapsing">Existing subtitles</th>
+ <th class="collapsing">Missing subtitles</th>
+ <th class="no-sort"></th>
+ </tr>
+ </thead>
+ <tbody>
+ %for episode in season:
+ <tr>
+ <td>{{episode[3]}}</td>
+ <td>{{episode[0]}}</td>
+ <td>
+ %actual_languages = ast.literal_eval(episode[4])
+ %if actual_languages is not None:
+ %for language in actual_languages:
+
+ %if language[1] is not None:
+ <a href="/remove_subtitles?episodePath={{episode[1]}}&subtitlesPath={{language[1]}}&sonarrSeriesId={{episode[5]}}" class="ui tiny label">
+ {{language[0]}}
+ <i class="delete icon"></i>
+ </a>
+ %else:
+ <div class="ui tiny label">
+ {{language[0]}}
+ </div>
+ %end
+ %end
+ %end
+ </td>
+ <td>
+ %missing_languages = ast.literal_eval(episode[6])
+ %if missing_languages is not None:
+ %for language in missing_languages:
+ <div class="ui tiny label">
+ {{language}}
+ </div>
+ %end
+ %end
+ </td>
+ <td class="collapsing">
+ %if len(missing_languages) > 0:
+ <div class="ui inverted basic compact icon" data-tooltip="Download missing subtitles" data-inverted="" onclick="location.href='/edit_series/{{episode[3]}}';">
+ <i class="ui black download icon"></i>
+ </div>
+ %end
+ </td>
+ </tr>
+ %end
+ </tbody>
+ </table>
+ </div>
+ </div>
+ </div>
+ %end
+ </div>
+ </body>
+</html> \ No newline at end of file
diff --git a/views/history.tpl b/views/history.tpl
new file mode 100644
index 000000000..c446e6306
--- /dev/null
+++ b/views/history.tpl
@@ -0,0 +1,124 @@
+<html>
+ <head>
+ <!DOCTYPE html>
+ <script src="https://code.jquery.com/jquery-latest.min.js"></script>
+ <script src="https://cdn.jsdelivr.net/semantic-ui/latest/semantic.min.js"></script>
+ <script src="https://semantic-ui.com/javascript/library/tablesort.js"></script>
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/semantic-ui/latest/semantic.min.css">
+
+ <link rel="apple-touch-icon" sizes="120x120" href="/static/apple-touch-icon.png">
+ <link rel="icon" type="image/png" sizes="32x32" href="/static/favicon-32x32.png">
+ <link rel="icon" type="image/png" sizes="16x16" href="/static/favicon-16x16.png">
+ <link rel="manifest" href="/static/manifest.json">
+ <link rel="mask-icon" href="/static/safari-pinned-tab.svg" color="#5bbad5">
+ <link rel="shortcut icon" href="/static/favicon.ico">
+ <meta name="msapplication-config" content="/static/browserconfig.xml">
+ <meta name="theme-color" content="#ffffff">
+
+ <title>History - Bazarr</title>
+
+ <style>
+ body {
+ background-color: #272727;
+ }
+ #divmenu {
+ background-color: #272727;
+ opacity: 0.9;
+ padding-top: 2em;
+ padding-bottom: 1em;
+ padding-left: 1em;
+ padding-right: 128px;
+ }
+ #fondblanc {
+ background-color: #ffffff;
+ border-radius: 0px;
+ box-shadow: 0px 0px 5px 5px #ffffff;
+ margin-top: 32px;
+ }
+ #tablehistory {
+ padding: 3em;
+ }
+ </style>
+ </head>
+ <body>
+ <div id='loader' class="ui page dimmer">
+ <div class="ui indeterminate text loader">Loading...</div>
+ </div>
+ <div id="divmenu" class="ui container">
+ <div style="background-color:#272727;" class="ui inverted borderless labeled icon huge menu four item">
+ <a href="/"><img style="margin-right:32px;" class="logo" src="/static/logo128.png"></a>
+ <div style="height:80px;" class="ui container">
+ <a class="item" href="/">
+ <i class="play icon"></i>
+ Series
+ </a>
+ <a class="item" href="/history">
+ <i class="wait icon"></i>
+ History
+ </a>
+ <a class="item" href="/settings">
+ <i class="settings icon"></i>
+ Settings
+ </a>
+ <a class="item" href="/system">
+ <i class="laptop icon"></i>
+ System
+ </a>
+ </div>
+ </div>
+ </div>
+
+ <div id="fondblanc" class="ui container">
+ <table id="tablehistory" class="ui very basic selectable sortable 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
+ %from utils import *
+ %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="/episodes/{{row[6]}}">{{row[1]}}</a></td>
+ <td class="collapsing">
+ <%episode = row[2].split('x')%>
+ {{episode[0] + 'x' + episode[1].zfill(2)}}
+ </td>
+ <td>{{row[3]}}</td>
+ <td class="collapsing">
+ <div class="ui inverted" data-tooltip="{{time.strftime('%A, %B %d %Y %H:%M', time.localtime(row[4]))}}" data-inverted="">
+ {{pretty_date(row[4])}}
+ </div>
+ </td>
+ <td>{{row[5]}}</td>
+ </tr>
+ %end
+ </tbody>
+ </table>
+ </div>
+ </body>
+</html>
+
+
+<script>
+ $('a').click(function(){
+ $('#loader').addClass('active');
+ })
+</script> \ No newline at end of file
diff --git a/views/series.tpl b/views/series.tpl
new file mode 100644
index 000000000..52c2b0a80
--- /dev/null
+++ b/views/series.tpl
@@ -0,0 +1,225 @@
+<html>
+ <head>
+ <!DOCTYPE html>
+ <script src="https://code.jquery.com/jquery-latest.min.js"></script>
+ <script src="https://cdn.jsdelivr.net/semantic-ui/latest/semantic.min.js"></script>
+ <script src="https://semantic-ui.com/javascript/library/tablesort.js"></script>
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/semantic-ui/latest/semantic.min.css">
+
+ <link rel="apple-touch-icon" sizes="120x120" href="/static/apple-touch-icon.png">
+ <link rel="icon" type="image/png" sizes="32x32" href="/static/favicon-32x32.png">
+ <link rel="icon" type="image/png" sizes="16x16" href="/static/favicon-16x16.png">
+ <link rel="manifest" href="/static/manifest.json">
+ <link rel="mask-icon" href="/static/safari-pinned-tab.svg" color="#5bbad5">
+ <link rel="shortcut icon" href="/static/favicon.ico">
+ <meta name="msapplication-config" content="/static/browserconfig.xml">
+ <meta name="theme-color" content="#ffffff">
+
+ <title>Bazarr</title>
+
+ <style>
+ body {
+ background-color: #272727;
+ }
+ #divmenu {
+ background-color: #272727;
+ opacity: 0.9;
+ padding-top: 2em;
+ padding-bottom: 1em;
+ padding-left: 1em;
+ padding-right: 128px;
+ }
+ #fondblanc {
+ background-color: #ffffff;
+ border-radius: 0px;
+ box-shadow: 0px 0px 5px 5px #ffffff;
+ margin-top: 32px;
+ }
+ #tableseries {
+ padding: 3em;
+ }
+ #divdetails {
+ min-height: 250px;
+ }
+ </style>
+ </head>
+ <body>
+ <div id='loader' class="ui page dimmer">
+ <div class="ui indeterminate text loader">Loading...</div>
+ </div>
+ <div id="divmenu" class="ui container">
+ <div style="background-color:#272727;" class="ui inverted borderless labeled icon huge menu four item">
+ <a href="/"><img style="margin-right:32px;" class="logo" src="/static/logo128.png"></a>
+ <div style="height:80px;" class="ui container">
+ <a class="item" href="/">
+ <i class="play icon"></i>
+ Series
+ </a>
+ <a class="item" href="/history">
+ <i class="wait icon"></i>
+ History
+ </a>
+ <a class="item" href="/settings">
+ <i class="settings icon"></i>
+ Settings
+ </a>
+ <a class="item" href="/system">
+ <i class="laptop icon"></i>
+ System
+ </a>
+ </div>
+ </div>
+ </div>
+
+ <div id="fondblanc" class="ui container">
+ <table id="tableseries" class="ui very basic selectable sortable table">
+ <thead>
+ <tr>
+ <th class="sorted ascending">Name</th>
+ <th>Status</th>
+ <th>Path</th>
+ <th>Language</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="/episodes/{{row[5]}}">{{row[1]}}</a></td>
+ <td>
+ %if os.path.exists(row[2]):
+ <div class="ui inverted basic compact icon" data-tooltip="Path exist" data-inverted="">
+ <i class="ui checkmark icon"></i>
+ </div>
+ %else:
+ <div class="ui inverted basic compact icon" data-tooltip="Path not found. Is your path substitution settings correct?" data-inverted="">
+ <i class="ui warning sign icon"></i>
+ </div>
+ %end
+ </td>
+ <td>
+ {{row[2]}}
+ </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>{{row[4]}}</td>
+ <td>
+ <%
+ 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 series" data-inverted="" data-tvdbid="{{row[0]}}" data-title="{{row[1]}}" data-poster="{{row[6]}}" data-languages="{{!subs_languages_list}}" data-hearing-impaired="{{row[4]}}">
+ <i class="ui black configure icon"></i>
+ </div>
+ </td>
+ </tr>
+ %end
+ </tbody>
+ </table>
+ </div>
+
+ <div class="ui small modal">
+ <div class="header">
+ <div id="series_title"></div>
+ </div>
+ <div class="content">
+ <form name="series_form" id="series_form" action="" method="post" class="ui form">
+ <div id="divdetails" class="ui grid">
+ <div class="four wide column">
+ <img id="series_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>Languages</label>
+ </div>
+ <div class="nine wide column">
+ <select name="languages" id="series_languages" multiple="" 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="series_hearing-impaired_div" class="ui toggle checkbox">
+ <input name="hearing_impaired" id="series_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="series_form" class="ui blue approve button">Save</button>
+ </div>
+ </div>
+ </body>
+</html>
+
+
+<script>
+ if (sessionStorage.scrolly) {
+ $(window).scrollTop(sessionStorage.scrolly);
+ sessionStorage.clear();
+ }
+
+ $('table').tablesort();
+
+ $('a').click(function(){
+ $('#loader').addClass('active');
+ })
+
+ $('.modal')
+ .modal({
+ autofocus: false
+ })
+ ;
+
+ $('.config').click(function(){
+ sessionStorage.scrolly=$(window).scrollTop();
+
+ $('#series_form').attr('action', '/edit_series/' + $(this).data("tvdbid"));
+
+ $("#series_title").html($(this).data("title"));
+ $("#series_poster").attr("src", "/image_proxy" + $(this).data("poster"));
+
+ $('#series_languages').dropdown('clear');
+ var languages_array = eval($(this).data("languages"));
+ $('#series_languages').dropdown('set selected',languages_array);
+
+ if ($(this).data("hearing-impaired") == "True") {
+ $("#series_hearing-impaired_div").checkbox('check');
+ } else {
+ $("#series_hearing-impaired_div").checkbox('uncheck');
+ }
+
+ $('.small.modal').modal('show');
+ })
+
+ $('#series_languages').dropdown();
+
+</script> \ No newline at end of file
diff --git a/views/settings.tpl b/views/settings.tpl
new file mode 100644
index 000000000..1fc715b39
--- /dev/null
+++ b/views/settings.tpl
@@ -0,0 +1,105 @@
+<html>
+ <head>
+ <!DOCTYPE html>
+ <script src="https://code.jquery.com/jquery-latest.min.js"></script>
+ <script src="https://cdn.jsdelivr.net/semantic-ui/latest/semantic.min.js"></script>
+ <script src="https://semantic-ui.com/javascript/library/tablesort.js"></script>
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/semantic-ui/latest/semantic.min.css">
+
+ <link rel="apple-touch-icon" sizes="120x120" href="/static/apple-touch-icon.png">
+ <link rel="icon" type="image/png" sizes="32x32" href="/static/favicon-32x32.png">
+ <link rel="icon" type="image/png" sizes="16x16" href="/static/favicon-16x16.png">
+ <link rel="manifest" href="/static/manifest.json">
+ <link rel="mask-icon" href="/static/safari-pinned-tab.svg" color="#5bbad5">
+ <link rel="shortcut icon" href="/static/favicon.ico">
+ <meta name="msapplication-config" content="/static/browserconfig.xml">
+ <meta name="theme-color" content="#ffffff">
+
+ <title>Settings - Bazarr</title>
+
+ <style>
+ body {
+ background-color: #272727;
+ }
+ #divmenu {
+ background-color: #272727;
+ opacity: 0.9;
+ padding-top: 2em;
+ padding-bottom: 1em;
+ padding-left: 1em;
+ padding-right: 128px;
+ }
+ #fondblanc {
+ background-color: #ffffff;
+ border-radius: 0px;
+ box-shadow: 0px 0px 5px 5px #ffffff;
+ margin-top: 32px;
+ padding: 1em;
+ }
+ </style>
+ </head>
+ <body>
+ <div id='loader' class="ui page dimmer">
+ <div class="ui indeterminate text loader">Loading...</div>
+ </div>
+ <div id="divmenu" class="ui container">
+ <div style="background-color:#272727;" class="ui inverted borderless labeled icon huge menu four item">
+ <a href="/"><img style="margin-right:32px;" class="logo" src="/static/logo128.png"></a>
+ <div style="height:80px;" class="ui container">
+ <a class="menu item" href="/">
+ <i class="play icon"></i>
+ Series
+ </a>
+ <a class="menu item" href="/history">
+ <i class="wait icon"></i>
+ History
+ </a>
+ <a class="menu item" href="/settings">
+ <i class="settings icon"></i>
+ Settings
+ </a>
+ <a class="menu item" href="/system">
+ <i class="laptop icon"></i>
+ System
+ </a>
+ </div>
+ </div>
+ </div>
+
+ <div id="fondblanc" class="ui container">
+ <div class="ui top attached tabular menu">
+ <a class="item active" data-tab="general">General</a>
+ <a class="item" data-tab="sonarr">Sonarr</a>
+ <a class="item" data-tab="subliminal">Subliminal</a>
+ <a class="item" data-tab="providers">Providers</a>
+ <a class="item" data-tab="languages">Languages</a>
+ </div>
+ <div class="ui bottom attached tab segment active" data-tab="general">
+ General
+ </div>
+ <div class="ui bottom attached tab segment" data-tab="sonarr">
+ Sonarr
+ </div>
+ <div class="ui bottom attached tab segment" data-tab="subliminal">
+ Subliminal
+ </div>
+ <div class="ui bottom attached tab segment" data-tab="providers">
+ Providers
+ </div>
+ <div class="ui bottom attached tab segment" data-tab="languages">
+ Languages
+ </div>
+ </div>
+ </body>
+</html>
+
+
+<script>
+ $('.menu .item')
+ .tab()
+ ;
+
+ $('a.menu').click(function(){
+ $('#loader').addClass('active');
+ })
+</script> \ No newline at end of file
diff --git a/views/system.tpl b/views/system.tpl
new file mode 100644
index 000000000..3ca9197a1
--- /dev/null
+++ b/views/system.tpl
@@ -0,0 +1,97 @@
+<html>
+ <head>
+ <!DOCTYPE html>
+ <script src="https://code.jquery.com/jquery-latest.min.js"></script>
+ <script src="https://cdn.jsdelivr.net/semantic-ui/latest/semantic.min.js"></script>
+ <script src="https://semantic-ui.com/javascript/library/tablesort.js"></script>
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/semantic-ui/latest/semantic.min.css">
+
+ <link rel="apple-touch-icon" sizes="120x120" href="/static/apple-touch-icon.png">
+ <link rel="icon" type="image/png" sizes="32x32" href="/static/favicon-32x32.png">
+ <link rel="icon" type="image/png" sizes="16x16" href="/static/favicon-16x16.png">
+ <link rel="manifest" href="/static/manifest.json">
+ <link rel="mask-icon" href="/static/safari-pinned-tab.svg" color="#5bbad5">
+ <link rel="shortcut icon" href="/static/favicon.ico">
+ <meta name="msapplication-config" content="/static/browserconfig.xml">
+ <meta name="theme-color" content="#ffffff">
+
+ <title>System - Bazarr</title>
+
+ <style>
+ body {
+ background-color: #272727;
+ }
+ #divmenu {
+ background-color: #272727;
+ opacity: 0.9;
+ padding-top: 2em;
+ padding-bottom: 1em;
+ padding-left: 1em;
+ padding-right: 128px;
+ }
+ #fondblanc {
+ background-color: #ffffff;
+ border-radius: 0px;
+ box-shadow: 0px 0px 5px 5px #ffffff;
+ margin-top: 32px;
+ padding: 1em;
+ }
+ </style>
+ </head>
+ <body>
+ <div id='loader' class="ui page dimmer">
+ <div class="ui indeterminate text loader">Loading...</div>
+ </div>
+ <div id="divmenu" class="ui container">
+ <div style="background-color:#272727;" class="ui inverted borderless labeled icon huge menu four item">
+ <a href="/"><img style="margin-right:32px;" class="logo" src="/static/logo128.png"></a>
+ <div style="height:80px;" class="ui container">
+ <a class="menu item" href="/">
+ <i class="play icon"></i>
+ Series
+ </a>
+ <a class="menu item" href="/history">
+ <i class="wait icon"></i>
+ History
+ </a>
+ <a class="menu item" href="/settings">
+ <i class="settings icon"></i>
+ Settings
+ </a>
+ <a class="menu item" href="/system">
+ <i class="laptop icon"></i>
+ System
+ </a>
+ </div>
+ </div>
+ </div>
+
+ <div id="fondblanc" class="ui container">
+ <div class="ui top attached tabular menu">
+ <a class="item active" data-tab="tasks">Tasks</a>
+ <a class="item" data-tab="logs">Logs</a>
+ <a class="item" data-tab="about">About</a>
+ </div>
+ <div class="ui bottom attached tab segment active" data-tab="tasks">
+ Tasks
+ </div>
+ <div class="ui bottom attached tab segment" data-tab="logs">
+ Logs
+ </div>
+ <div class="ui bottom attached tab segment" data-tab="about">
+ About
+ </div>
+ </div>
+ </body>
+</html>
+
+
+<script>
+ $('.menu .item')
+ .tab()
+ ;
+
+ $('a.menu').click(function(){
+ $('#loader').addClass('active');
+ })
+</script> \ No newline at end of file