Initial commit

pull/26/head
Louis Vézina 7 years ago committed by GitHub
parent b1db26b377
commit a9187e033e

Binary file not shown.

@ -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)

@ -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
);

@ -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()

@ -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')

Binary file not shown.

@ -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()

Binary file not shown.

@ -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()

@ -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

Binary file not shown.

@ -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()

Binary file not shown.

@ -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()

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

@ -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"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

@ -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>

After

Width:  |  Height:  |  Size: 1.5 KiB

@ -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"

Binary file not shown.

@ -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>

@ -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>

@ -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>

@ -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>

@ -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>
Loading…
Cancel
Save