Continuig development

This commit is contained in:
Louis Vézina 2017-10-16 19:27:19 -04:00
parent 546c3ac066
commit edb46d9115
22 changed files with 554 additions and 99 deletions

2
.gitignore vendored
View file

@ -1,3 +1,5 @@
*.pyc
bazarr.db
cachefile.dbm
*.log
*.log.*

10
Dockerfile Normal file
View file

@ -0,0 +1,10 @@
FROM alpine:latest
# Update
RUN apk add --update python py-pip
# Install app dependencies
RUN pip install -r requirements.txt
EXPOSE 6767
CMD ["python", "/src/bazarr.py"]

BIN
bazarr.db

Binary file not shown.

128
bazarr.py
View file

@ -25,6 +25,44 @@ from list_subtitles import *
from get_subtitle import *
from utils import *
import logging
from logging.handlers import TimedRotatingFileHandler
logger = logging.getLogger('waitress')
db = sqlite3.connect('bazarr.db')
c = db.cursor()
c.execute("SELECT log_level FROM table_settings_general")
log_level = c.fetchone()
log_level = log_level[0]
if log_level is None:
log_level = "WARNING"
log_level = getattr(logging, log_level)
c.close()
class OneLineExceptionFormatter(logging.Formatter):
def formatException(self, exc_info):
"""
Format an exception so that it prints on a single line.
"""
result = super(OneLineExceptionFormatter, self).formatException(exc_info)
return repr(result) # or format into one line however you want to
def format(self, record):
s = super(OneLineExceptionFormatter, self).format(record)
if record.exc_text:
s = s.replace('\n', '') + '|'
return s
def configure_logging():
fh = TimedRotatingFileHandler('bazarr.log', when="midnight", interval=1, backupCount=7)
f = OneLineExceptionFormatter('%(asctime)s|%(levelname)s|%(message)s|',
'%d/%m/%Y %H:%M:%S')
fh.setFormatter(f)
root = logging.getLogger()
root.setLevel(log_level)
root.addHandler(fh)
configure_logging()
@route('/static/:path#.+#', name='static')
def static(path):
return static_file(path, root='static')
@ -48,7 +86,7 @@ def series():
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)
output = template('series', rows=data, languages=languages)
return output
@route('/edit_series/<no:int>', method='POST')
@ -74,6 +112,8 @@ def edit_series(no):
conn.commit()
c.close()
list_missing_subtitles(no)
redirect('/')
@route('/update_series')
@ -88,6 +128,12 @@ def update_all_episodes_list():
redirect('/')
@route('/add_new_episodes')
def add_new_episodes_list():
add_new_episodes()
redirect('/')
@route('/episodes/<no:int>', method='GET')
def episodes(no):
conn = sqlite3.connect('bazarr.db')
@ -97,14 +143,30 @@ def episodes(no):
series_details = []
series_details = c.execute("SELECT title, overview, poster, fanart, hearing_impaired FROM table_shows WHERE sonarrSeriesId LIKE ?", (str(no),)).fetchone()
episodes = c.execute("SELECT title, path_substitution(path), season, episode, subtitles, sonarrSeriesId, missing_subtitles, sonarrEpisodeId FROM table_episodes WHERE sonarrSeriesId LIKE ?", (str(no),)).fetchall()
episodes = c.execute("SELECT title, path_substitution(path), season, episode, subtitles, sonarrSeriesId, missing_subtitles, sonarrEpisodeId FROM table_episodes WHERE sonarrSeriesId LIKE ? ORDER BY episode ASC", (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)
return template('episodes', no=no, details=series_details, seasons=seasons_list, url_sonarr_short=url_sonarr_short)
@route('/scan_disk/<no:int>', method='GET')
def scan_disk(no):
ref = request.environ['HTTP_REFERER']
series_scan_subtitles(no)
redirect(ref)
@route('/search_missing_subtitles/<no:int>', method='GET')
def search_missing_subtitles(no):
ref = request.environ['HTTP_REFERER']
series_download_subtitles(no)
redirect(ref)
@route('/history')
def history():
@ -141,11 +203,28 @@ def wanted():
offset = (int(page) - 1) * 15
max_page = (missing_count / 15) + 1
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 FROM table_episodes INNER JOIN table_shows on table_shows.sonarrSeriesId = table_episodes.sonarrSeriesId WHERE table_episodes.missing_subtitles != '[]' LIMIT 15 OFFSET ?", (offset,))
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 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', rows=data, missing_count=missing_count, page=page, max_page=max_page)
@route('/wanted_search_missing_subtitles')
def wanted_search_missing_subtitles():
ref = request.environ['HTTP_REFERER']
db = sqlite3.connect('bazarr.db')
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.close()
for episode in data:
wanted_download_subtitles(episode[0])
redirect(ref)
@route('/settings')
def settings():
db = sqlite3.connect('bazarr.db')
@ -169,11 +248,12 @@ def save_settings():
settings_general_ip = request.forms.get('settings_general_ip')
settings_general_port = request.forms.get('settings_general_port')
settings_general_baseurl = request.forms.get('settings_general_baseurl')
settings_general_loglevel = request.forms.get('settings_general_loglevel')
settings_general_sourcepath = request.forms.getall('settings_general_sourcepath')
settings_general_destpath = request.forms.getall('settings_general_destpath')
settings_general_pathmapping = []
settings_general_pathmapping.extend([list(a) for a in zip(settings_general_sourcepath, settings_general_destpath)])
c.execute("UPDATE table_settings_general SET ip = ?, port = ?, base_url = ?, path_mapping = ?", (settings_general_ip, settings_general_port, settings_general_baseurl, str(settings_general_pathmapping)))
c.execute("UPDATE table_settings_general SET ip = ?, port = ?, base_url = ?, path_mapping = ?, log_level = ?", (settings_general_ip, settings_general_port, settings_general_baseurl, str(settings_general_pathmapping), settings_general_loglevel))
settings_sonarr_ip = request.forms.get('settings_sonarr_ip')
settings_sonarr_port = request.forms.get('settings_sonarr_port')
@ -205,17 +285,22 @@ def system():
db = sqlite3.connect('bazarr.db')
c = db.cursor()
c.execute("SELECT * FROM table_scheduler")
data = c.fetchall()
tasks = c.fetchall()
c.close()
return template('system', rows=data)
@route('/remove_subtitles', method='GET')
logs = []
for line in reversed(open('bazarr.log').readlines()):
logs.append(line.rstrip())
return template('system', tasks=tasks, logs=logs)
@route('/remove_subtitles', method='POST')
def remove_subtitles():
episodePath = request.GET.episodePath
language = request.GET.language
subtitlesPath = request.GET.subtitlesPath
sonarrSeriesId = request.GET.sonarrSeriesId
sonarrEpisodeId = request.GET.sonarrEpisodeId
episodePath = request.forms.get('episodePath')
language = request.forms.get('language')
subtitlesPath = request.forms.get('subtitlesPath')
sonarrSeriesId = request.forms.get('sonarrSeriesId')
sonarrEpisodeId = request.forms.get('sonarrEpisodeId')
try:
os.remove(subtitlesPath)
@ -224,18 +309,17 @@ def remove_subtitles():
except OSError:
pass
store_subtitles(episodePath)
list_missing_subtitles()
redirect('/episodes/' + sonarrSeriesId)
list_missing_subtitles(sonarrSeriesId)
@route('/get_subtitle', method='GET')
@route('/get_subtitle', method='POST')
def get_subtitle():
ref = request.environ['HTTP_REFERER']
episodePath = request.GET.episodePath
language = request.GET.language
hi = request.GET.hi
sonarrSeriesId = request.GET.sonarrSeriesId
sonarrEpisodeId = request.GET.sonarrEpisodeId
episodePath = request.forms.get('episodePath')
language = request.forms.get('language')
hi = request.forms.get('hi')
sonarrSeriesId = request.forms.get('sonarrSeriesId')
sonarrEpisodeId = request.forms.get('sonarrEpisodeId')
db = sqlite3.connect('bazarr.db')
c = db.cursor()
@ -252,7 +336,7 @@ def get_subtitle():
if result is not None:
history_log(1, sonarrSeriesId, sonarrEpisodeId, result)
store_subtitles(episodePath)
list_missing_subtitles()
list_missing_subtitles(sonarrSeriesId)
redirect(ref)
except OSError:
redirect(ref + '?error=2')

64
create_db.sql Normal file
View file

@ -0,0 +1,64 @@
BEGIN TRANSACTION;
CREATE TABLE "table_shows" (
`tvdbId` INTEGER NOT NULL UNIQUE,
`title` TEXT NOT NULL,
`path` TEXT NOT NULL UNIQUE,
`languages` TEXT,
`hearing_impaired` TEXT,
`sonarrSeriesId` INTEGER NOT NULL UNIQUE,
`overview` TEXT,
`poster` TEXT,
`fanart` TEXT,
PRIMARY KEY(`tvdbId`)
);
CREATE TABLE "table_settings_sonarr" (
`ip` TEXT NOT NULL,
`port` INTEGER NOT NULL,
`base_url` TEXT,
`ssl` INTEGER,
`apikey` TEXT
);
INSERT INTO `table_settings_sonarr` (ip,port,base_url,ssl,apikey) VALUES ('127.0.0.1',8989,'/','False',Null);
CREATE TABLE "table_settings_providers" (
`name` TEXT NOT NULL UNIQUE,
`enabled` INTEGER,
PRIMARY KEY(`name`)
);
CREATE TABLE "table_settings_languages" (
`code3` TEXT NOT NULL UNIQUE,
`code2` TEXT,
`name` TEXT NOT NULL,
`enabled` INTEGER,
PRIMARY KEY(`code3`)
);
CREATE TABLE "table_settings_general" (
`ip` TEXT NOT NULL,
`port` INTEGER NOT NULL,
`base_url` TEXT,
`path_mapping` TEXT
);
INSERT INTO `table_settings_general` (ip,port,base_url,path_mapping) VALUES ('0.0.0.0',6767,'/',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,
`action` INTEGER NOT NULL,
`sonarrSeriesId` INTEGER NOT NULL,
`sonarrEpisodeId` INTEGER NOT NULL,
`timestamp` INTEGER NOT NULL,
`description` TEXT NOT NULL
);
CREATE TABLE "table_episodes" (
`sonarrSeriesId` INTEGER NOT NULL,
`sonarrEpisodeId` INTEGER NOT NULL UNIQUE,
`title` TEXT NOT NULL,
`path` TEXT NOT NULL UNIQUE,
`season` INTEGER NOT NULL,
`episode` INTEGER NOT NULL,
`subtitles` TEXT,
`missing_subtitles` TEXT
);
COMMIT;

View file

@ -1,49 +0,0 @@
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
);

View file

@ -1,6 +1,7 @@
import sqlite3
import requests
from get_general_settings import *
from list_subtitles import *
def update_all_episodes():
@ -20,7 +21,12 @@ def update_all_episodes():
else:
base_url_sonarr = "/" + config_sonarr[2].strip("/")
# Get current episodes id in DB
current_episodes_db = c.execute('SELECT sonarrEpisodeId FROM table_episodes').fetchall()
current_episodes_db_list = [x[0] for x in current_episodes_db]
# Get sonarrId for each series from database
current_episodes_sonarr = []
c.execute("SELECT sonarrSeriesId FROM table_shows")
seriesIdList = c.fetchall()
for seriesId in seriesIdList:
@ -29,14 +35,82 @@ def update_all_episodes():
r = requests.get(url_sonarr_api_episode)
for episode in r.json():
if episode['hasFile']:
# Add shows in Sonarr to current shows list
current_episodes_sonarr.append(episode['id'])
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 sqlite3.Error:
test = 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']))
c.execute('''UPDATE table_episodes SET sonarrSeriesId = ?, sonarrEpisodeId = ?, title = ?, path = ?, season = ?, episode = ? WHERE sonarrEpisodeId = ?''', (episode['seriesId'], episode['id'], episode['title'], episode['episodeFile']['path'], episode['seasonNumber'], episode['episodeNumber'], episode['id']))
else:
continue
continue
# Delete episodes not in Sonarr anymore
deleted_items = []
for item in current_episodes_db_list:
if item not in current_episodes_sonarr:
deleted_items.append(tuple([item]))
c.executemany('DELETE FROM table_episodes WHERE sonarrEpisodeId = ?',deleted_items)
# Commit changes to database table
db.commit()
# Close database connection
c.close()
# Store substitles for all episodes
full_scan_subtitles()
list_missing_subtitles()
def add_new_episodes():
# 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 current episodes in DB
current_episodes_db = c.execute('SELECT sonarrEpisodeId FROM table_episodes').fetchall()
current_episodes_db_list = [x[0] for x in current_episodes_db]
current_episodes_sonarr = []
# 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']:
# Add shows in Sonarr to current shows list
current_episodes_sonarr.append(episode['id'])
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:
pass
db.commit()
# Delete episodes not in Sonarr anymore
deleted_items = []
for item in current_episodes_db_list:
if item not in current_episodes_sonarr:
deleted_items.append(tuple([item]))
c.executemany('DELETE FROM table_episodes WHERE sonarrEpisodeId = ?',deleted_items)
# Commit changes to database table
db.commit()

View file

@ -16,7 +16,10 @@ db.close()
ip = general_settings[0]
port = general_settings[1]
base_url = general_settings[2]
path_mappings = ast.literal_eval(general_settings[3])
if general_settings[3] is None:
path_mappings = []
else:
path_mappings = ast.literal_eval(general_settings[3])
def path_replace(path):
for path_mapping in path_mappings:

Binary file not shown.

View file

@ -11,7 +11,6 @@ def update_series():
# 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 = []
@ -40,10 +39,8 @@ def update_series():
current_shows_sonarr.append(show['tvdbId'])
# 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"]
result = 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"]))
if result.rowcount == 0:
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))
# Delete shows not in Sonarr anymore

Binary file not shown.

View file

@ -1,7 +1,12 @@
import os
import sqlite3
import ast
from babelfish import *
from subliminal import *
from pycountry import *
from get_general_settings import *
from list_subtitles import *
from utils import *
# configure the cache
region.configure('dogpile.cache.dbm', arguments={'filename': 'cachefile.dbm'})
@ -20,3 +25,43 @@ def download_subtitle(path, language, hi, providers):
return message
except:
return None
def series_download_subtitles(no):
conn_db = sqlite3.connect('bazarr.db')
c_db = conn_db.cursor()
episodes_details = c_db.execute("SELECT path, missing_subtitles, sonarrEpisodeId FROM table_episodes WHERE path = ?", (no,)).fetchall()
series_details = c_db.execute("SELECT hearing_impaired FROM table_shows WHERE sonarrSeriesId = ?", (no,)).fetchone()
enabled_providers = c_db.execute("SELECT name FROM table_settings_providers WHERE enabled = 1").fetchall()
c_db.close()
providers_list = []
for provider in enabled_providers:
providers_list.append(provider[0])
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)
if message is not None:
store_subtitles(path_replace(episode[0]))
history_log(1, no, episode[2], message)
list_missing_subtitles(no)
def wanted_download_subtitles(path):
conn_db = sqlite3.connect('bazarr.db')
c_db = conn_db.cursor()
episodes_details = c_db.execute("SELECT table_episodes.path, table_episodes.missing_subtitles, table_episodes.sonarrEpisodeId, table_episodes.sonarrSeriesId, table_shows.hearing_impaired FROM table_episodes INNER JOIN table_shows on table_shows.sonarrSeriesId = table_episodes.sonarrSeriesId WHERE table_episodes.path = ? AND missing_subtitles != '[]'", (path_replace_reverse(path),)).fetchall()
enabled_providers = c_db.execute("SELECT name FROM table_settings_providers WHERE enabled = 1").fetchall()
c_db.close()
providers_list = []
for provider in enabled_providers:
providers_list.append(provider[0])
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)
if message is not None:
store_subtitles(path_replace(episode[0]))
list_missing_subtitles(episode[3])
history_log(1, episode[3], episode[2], message)

View file

@ -65,10 +65,15 @@ def store_subtitles(file):
return actual_subtitles
def list_missing_subtitles():
def list_missing_subtitles(*no):
query_string = ''
try:
query_string = " WHERE table_episodes.sonarrSeriesId = " + str(no[0])
except:
pass
conn_db = sqlite3.connect('bazarr.db')
c_db = conn_db.cursor()
episodes_subtitles = c_db.execute("SELECT table_episodes.sonarrEpisodeId, table_episodes.subtitles, table_shows.languages FROM table_episodes INNER JOIN table_shows on table_episodes.sonarrSeriesId = table_shows.sonarrSeriesId").fetchall()
episodes_subtitles = c_db.execute("SELECT table_episodes.sonarrEpisodeId, table_episodes.subtitles, table_shows.languages FROM table_episodes INNER JOIN table_shows on table_episodes.sonarrSeriesId = table_shows.sonarrSeriesId" + query_string).fetchall()
missing_subtitles_global = []
@ -102,6 +107,17 @@ def full_scan_subtitles():
for episode in episodes:
store_subtitles(path_replace(episode[0]))
def series_scan_subtitles(no):
conn_db = sqlite3.connect('bazarr.db')
c_db = conn_db.cursor()
episodes = c_db.execute("SELECT path FROM table_episodes WHERE sonarrSeriesId = ?", (no,)).fetchall()
c_db.close()
for episode in episodes:
store_subtitles(path_replace(episode[0]))
list_missing_subtitles(no)
def new_scan_subtitles():
conn_db = sqlite3.connect('bazarr.db')
c_db = conn_db.cursor()

Binary file not shown.

11
requirements.txt Normal file
View file

@ -0,0 +1,11 @@
babelfish
bottle
bottle-fdsend
dogpile.cache
enzyme
py-pretty
pycountry
requests
subliminal
urllib3
waitress

12
scheduler.py Normal file
View file

@ -0,0 +1,12 @@
import datetime, threading, time
def foo():
next_call = time.time()
while True:
print datetime.datetime.now()
next_call = next_call+1;
time.sleep(next_call - time.time())
timerThread = threading.Thread(target=foo)
timerThread.daemon = True
timerThread.start()

View file

@ -156,7 +156,7 @@
%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={{path_replace(language[1])}}&language={{pycountry.languages.lookup(str(language[0])).alpha_3}}&sonarrSeriesId={{episode[5]}}&sonarrEpisodeId={{episode[7]}}" class="ui tiny label">
<a data-episodePath="{{episode[1]}}" data-subtitlesPath="{{path_replace(language[1])}}" data-language="{{pycountry.languages.lookup(str(language[0])).alpha_3}}" data-sonarrSeriesId={{episode[5]}} data-sonarrEpisodeId={{episode[7]}} class="remove_subtitles ui tiny label">
{{language[0]}}
<i class="delete icon"></i>
</a>
@ -172,7 +172,7 @@
%missing_languages = ast.literal_eval(episode[6])
%if missing_languages is not None:
%for language in missing_languages:
<a href="/get_subtitle?episodePath={{episode[1]}}&language={{pycountry.languages.lookup(str(language)).alpha_3}}&hi={{details[4]}}&sonarrSeriesId={{episode[5]}}&sonarrEpisodeId={{episode[7]}}" class="ui tiny label">
<a data-episodePath="{{episode[1]}}" data-language="{{pycountry.languages.lookup(str(language)).alpha_3}}" data-hi="{{details[4]}}" data-sonarrSeriesId={{episode[5]}} data-sonarrEpisodeId={{episode[7]}} class="get_subtitle ui tiny label">
{{language}}
<i style="margin-left:3px; margin-right:0px" class="search icon"></i>
</a>
@ -190,3 +190,51 @@
</div>
</body>
</html>
<script>
$('#scan_disk').click(function(){
window.location = '/scan_disk/{{no}}';
})
$('#search_missing_subtitles').click(function(){
window.location = '/search_missing_subtitles/{{no}}';
})
$('.remove_subtitles').click(function(){
var values = {
episodePath: $(this).attr("data-episodePath"),
language: $(this).attr("data-language"),
subtitlesPath: $(this).attr("data-subtitlesPath"),
sonarrSeriesId: $(this).attr("data-sonarrSeriesId"),
sonarrEpisodeId: $(this).attr("data-sonarrEpisodeId")
};
$.ajax({
url: "/remove_subtitles",
type: "POST",
dataType: "json",
data: values
});
$('#loader').addClass('active');
})
$('.get_subtitle').click(function(){
var values = {
episodePath: $(this).attr("data-episodePath"),
language: $(this).attr("data-language"),
hi: $(this).attr("data-hi"),
sonarrSeriesId: $(this).attr("data-sonarrSeriesId"),
sonarrEpisodeId: $(this).attr("data-sonarrEpisodeId")
};
$.ajax({
url: "/get_subtitle",
type: "POST",
dataType: "json",
data: values
});
$('#loader').addClass('active');
})
$(document).ajaxStop(function(){
window.location.reload();
});
</script>

View file

@ -123,12 +123,12 @@
<div class="column"></div>
<div class="center aligned column">
<i class="\\
%if page == "1":
%if page == '1':
disabled\\
%end
fast backward icon"></i>
<i class="\\
%if page == "1":
%if page == '1':
disabled\\
%end
backward icon"></i>
@ -153,16 +153,23 @@
<script>
$('a').click(function(){
if (sessionStorage.scrolly) {
$(window).scrollTop(sessionStorage.scrolly);
sessionStorage.clear();
}
$('a, i').click(function(){
sessionStorage.scrolly=$(window).scrollTop();
$('#loader').addClass('active');
})
$('.fast.backward').click(function(){
location.href="?page=1";
})
$('.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}}";
})

View file

@ -81,6 +81,7 @@
<div class="ui basic buttons">
<button id="update_series" class="ui button"><i class="refresh icon"></i>Update Series</button>
<button id="update_all_episodes" class="ui button"><i class="refresh icon"></i>Update All Episodes</button>
<button id="add_new_episodes" class="ui button"><i class="wait icon"></i>Add New Episodes</button>
</div>
<table id="tableseries" class="ui very basic selectable sortable table">
@ -97,7 +98,7 @@
%import ast
%import os
%for row in rows:
<tr class="selectable">
<tr class="selectable" {{!"style='background-color: yellow;'" if row[4] == None else ""}}>
<td><a href="/episodes/{{row[5]}}">{{row[1]}}</a></td>
<td>
{{row[2]}}
@ -120,7 +121,7 @@
end
end
%>
<div class="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]}}">
<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>
@ -131,6 +132,7 @@
</div>
<div class="ui small modal">
<i class="close icon"></i>
<div class="header">
<div id="series_title"></div>
</div>
@ -188,7 +190,7 @@
$('table').tablesort();
$('a, button').click(function(){
$('a, button:not(.cancel)').click(function(){
$('#loader').addClass('active');
})
@ -206,6 +208,10 @@
window.location = '/update_all_episodes';
})
$('#add_new_episodes').click(function(){
window.location = '/add_new_episodes';
})
$('.config').click(function(){
sessionStorage.scrolly=$(window).scrollTop();

View file

@ -124,6 +124,22 @@
</div>
</div>
</div>
<div class="middle aligned row">
<div class="right aligned four wide column">
<label>Log Level</label>
</div>
<div class="eleven wide column">
<select name="settings_general_loglevel" id="settings_loglevel" class="ui fluid selection dropdown">
<option value="">Log Level</option>
<option value="DEBUG">Debug</option>
<option value="INFO">Info</option>
<option value="WARNING">Warning</option>
<option value="ERROR">Error</option>
<option value="CRITICAL">Critical</option>
</select>
</div>
</div>
</div>
</div>
@ -131,7 +147,11 @@
<div class="twelve wide column">
<div class="ui grid">
%import ast
%path_substitutions = ast.literal_eval(settings_general[3])
%if settings_general[3] is not None:
% path_substitutions = ast.literal_eval(settings_general[3])
%else:
% path_substitutions = []
%end
%for x in range(0, 5):
% path = []
% try:
@ -165,6 +185,9 @@
<div class="ui container"><button class="ui blue right floated button">Save</button></div>
<br>
<div class="ui dividing header">Sonarr settings</div>
<div class="ui negative message">
<p>These changes require that you restart Bazarr.</p>
</div>
<div class="twelve wide column">
<div class="ui grid">
<div class="middle aligned row">
@ -230,6 +253,9 @@
<br>
<div class="ui dividing header">Subtitles providers</div>
<div class="twelve wide column">
<div class="ui negative message">
<p>Be aware that the more providers you enable, the longer it will take everytime you search for a subtitles.</p>
</div>
<div class="ui grid">
<div class="middle aligned row">
<div class="right aligned four wide column">
@ -294,11 +320,14 @@
$("#sonarr_ssl_div").checkbox('uncheck');
}
$('#settings_loglevel').dropdown('clear');
$('#settings_loglevel').dropdown('set selected','{{!settings_general[4]}}');
$('#settings_providers').dropdown('clear');
$('#settings_providers').dropdown('set selected',{{!enabled_providers}});
$('#settings_languages').dropdown('clear');
$('#settings_languages').dropdown('set selected',{{!enabled_languages}});
$('#settings_loglevel').dropdown();
$('#settings_providers').dropdown();
$('#settings_languages').dropdown();
</script>

View file

@ -34,6 +34,7 @@
border-radius: 0px;
box-shadow: 0px 0px 5px 5px #ffffff;
margin-top: 32px;
margin-bottom: 3em;
padding: 1em;
}
</style>
@ -80,21 +81,85 @@
Tasks
</div>
<div class="ui bottom attached tab segment" data-tab="logs">
Logs
<div class="content">
<table class="ui very basic selectable table">
<thead>
<tr>
<th class="collapsing"></th>
<th>Message</th>
<th class="collapsing">Time</th>
</tr>
</thead>
<tbody>
%import time
%import datetime
%import pretty
%for log in logs:
%line = []
%line = log.split('|')
<tr class='log' data-message='{{line[2]}}' data-exception='{{line[3].replace("\\n", "<br />")}}'>
<td class="collapsing"><i class="\\
%if line[1] == 'INFO':
blue info circle \\
%elif line[1] == 'WARNING':
yellow warning circle \\
%elif line[1] == 'ERROR':
red bug \\
%end
icon"></i></td>
<td>{{line[2]}}</td>
<td title='{{line[0]}}' class="collapsing">{{pretty.date(int(time.mktime(datetime.datetime.strptime(line[0], "%d/%m/%Y %H:%M:%S").timetuple())))}}</td>
</tr>
%end
</tbody>
</table>
</div>
</div>
<div class="ui bottom attached tab segment" data-tab="about">
About
</div>
</div>
<div class="ui small modal">
<i class="close icon"></i>
<div class="header">
<div>Details</div>
</div>
<div class="content">
Message
<div id='message' class="ui segment">
<p></p>
</div>
Exception
<div id='exception' class="ui segment">
<p></p>
</div>
</div>
<div class="actions">
<button class="ui cancel button" >Close</button>
</div>
</div>
</body>
</html>
<script>
$('.modal')
.modal({
autofocus: false
})
;
$('.menu .item')
.tab()
;
$('.log').click(function(){
$("#message").html($(this).data("message"));
$("#exception").html($(this).data("exception"));
$('.small.modal').modal('show');
})
$('a.menu').click(function(){
$('#loader').addClass('active');
})

View file

@ -35,7 +35,10 @@
box-shadow: 0px 0px 5px 5px #ffffff;
margin-top: 32px;
margin-bottom: 3em;
padding: 3em;
padding: 2em 3em 2em 3em;
}
#tablehistory {
padding-top: 2em;
}
.fast.backward, .backward, .forward, .fast.forward {
cursor: pointer;
@ -77,6 +80,9 @@
</div>
<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>
<table id="tablehistory" class="ui very basic selectable table">
<thead>
<tr>
@ -101,7 +107,7 @@
%missing_languages = ast.literal_eval(row[3])
%if missing_languages is not None:
%for language in missing_languages:
<a href="/get_subtitle?episodePath={{row[5]}}&language={{pycountry.languages.lookup(str(language)).alpha_3}}&hi={{row[6]}}&sonarrSeriesId={{row[4]}}&sonarrEpisodeId={{row[7]}}" class="ui tiny label">
<a data-episodePath="{{row[5]}}" 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>
@ -147,7 +153,7 @@
<script>
$('a').click(function(){
$('a, button').click(function(){
$('#loader').addClass('active');
})
@ -163,4 +169,29 @@
$('.fast.forward').click(function(){
location.href="?page={{int(max_page)}}";
})
$('#wanted_search_missing_subtitles').click(function(){
window.location = '/wanted_search_missing_subtitles';
})
$('.get_subtitle').click(function(){
var values = {
episodePath: $(this).attr("data-episodePath"),
language: $(this).attr("data-language"),
hi: $(this).attr("data-hi"),
sonarrSeriesId: $(this).attr("data-sonarrSeriesId"),
sonarrEpisodeId: $(this).attr("data-sonarrEpisodeId")
};
$.ajax({
url: "/get_subtitle",
type: "POST",
dataType: "json",
data: values
});
$('#loader').addClass('active');
})
$(document).ajaxStop(function(){
window.location.reload();
});
</script>