summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorLouis Vézina <[email protected]>2017-10-16 19:27:19 -0400
committerLouis Vézina <[email protected]>2017-10-16 19:27:19 -0400
commitedb46d9115be4e83282fce10e6505cd5ad0b26bb (patch)
tree511409b526b1552de6745c94fff7295479dcf7bc
parent546c3ac0662c9ab578dc6fe4c4885e85a6378f3f (diff)
downloadbazarr-edb46d9115be4e83282fce10e6505cd5ad0b26bb.tar.gz
bazarr-edb46d9115be4e83282fce10e6505cd5ad0b26bb.zip
Continuig development
-rw-r--r--.gitignore2
-rw-r--r--Dockerfile10
-rw-r--r--bazarr.dbbin1398784 -> 786432 bytes
-rw-r--r--bazarr.py130
-rw-r--r--create_db.sql64
-rw-r--r--create_db.sql.old49
-rw-r--r--get_episodes.py76
-rw-r--r--get_general_settings.py5
-rw-r--r--get_general_settings.pycbin1353 -> 1391 bytes
-rw-r--r--get_series.py9
-rw-r--r--get_sonarr_settings.pycbin871 -> 871 bytes
-rw-r--r--get_subtitle.py45
-rw-r--r--list_subtitles.py20
-rw-r--r--list_subtitles.pycbin4043 -> 4603 bytes
-rw-r--r--requirements.txt11
-rw-r--r--scheduler.py12
-rw-r--r--views/episodes.tpl54
-rw-r--r--views/history.tpl19
-rw-r--r--views/series.tpl12
-rw-r--r--views/settings.tpl31
-rw-r--r--views/system.tpl67
-rw-r--r--views/wanted.tpl37
22 files changed, 554 insertions, 99 deletions
diff --git a/.gitignore b/.gitignore
index 2a7da2d1c..e4514e4a7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
*.pyc
bazarr.db
cachefile.dbm
+*.log
+*.log.*
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 000000000..2645cb9ac
--- /dev/null
+++ b/Dockerfile
@@ -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"] \ No newline at end of file
diff --git a/bazarr.db b/bazarr.db
index 92b5a6f7f..fdf6f98d1 100644
--- a/bazarr.db
+++ b/bazarr.db
Binary files differ
diff --git a/bazarr.py b/bazarr.py
index 840610352..f1fab7704 100644
--- a/bazarr.py
+++ b/bazarr.py
@@ -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)
-
-@route('/get_subtitle', method='GET')
+ list_missing_subtitles(sonarrSeriesId)
+
+@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')
diff --git a/create_db.sql b/create_db.sql
new file mode 100644
index 000000000..68642c0dd
--- /dev/null
+++ b/create_db.sql
@@ -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;
diff --git a/create_db.sql.old b/create_db.sql.old
deleted file mode 100644
index 2c88fac02..000000000
--- a/create_db.sql.old
+++ /dev/null
@@ -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
-);
diff --git a/get_episodes.py b/get_episodes.py
index 6f9846cc9..24ffee633 100644
--- a/get_episodes.py
+++ b/get_episodes.py
@@ -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,20 +35,88 @@ 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()
+
+ # Close database connection
+ c.close()
+
# Store substitles from episodes we've just added
new_scan_subtitles()
list_missing_subtitles()
diff --git a/get_general_settings.py b/get_general_settings.py
index b7599083e..b1871d4dd 100644
--- a/get_general_settings.py
+++ b/get_general_settings.py
@@ -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:
diff --git a/get_general_settings.pyc b/get_general_settings.pyc
index da21bc29f..8593d559f 100644
--- a/get_general_settings.pyc
+++ b/get_general_settings.pyc
Binary files differ
diff --git a/get_series.py b/get_series.py
index e487a4f34..c8dba939f 100644
--- a/get_series.py
+++ b/get_series.py
@@ -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 = []
@@ -35,15 +34,13 @@ def update_series():
fanart = show['images'][0]['url'].split('?')[0]
except:
fanart = ""
-
+
# Add shows in Sonarr to current shows list
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
diff --git a/get_sonarr_settings.pyc b/get_sonarr_settings.pyc
index 3dc844f3d..8063bc4a8 100644
--- a/get_sonarr_settings.pyc
+++ b/get_sonarr_settings.pyc
Binary files differ
diff --git a/get_subtitle.py b/get_subtitle.py
index 0d525d4df..b51b49cdc 100644
--- a/get_subtitle.py
+++ b/get_subtitle.py
@@ -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)
+
diff --git a/list_subtitles.py b/list_subtitles.py
index c3bbe6803..743fa6171 100644
--- a/list_subtitles.py
+++ b/list_subtitles.py
@@ -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 = []
@@ -101,6 +106,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')
diff --git a/list_subtitles.pyc b/list_subtitles.pyc
index eb427fce1..bb5e3e330 100644
--- a/list_subtitles.pyc
+++ b/list_subtitles.pyc
Binary files differ
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 000000000..221f6b2bb
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,11 @@
+babelfish
+bottle
+bottle-fdsend
+dogpile.cache
+enzyme
+py-pretty
+pycountry
+requests
+subliminal
+urllib3
+waitress \ No newline at end of file
diff --git a/scheduler.py b/scheduler.py
new file mode 100644
index 000000000..87b16683a
--- /dev/null
+++ b/scheduler.py
@@ -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()
diff --git a/views/episodes.tpl b/views/episodes.tpl
index 335aa1c0a..9227bc74b 100644
--- a/views/episodes.tpl
+++ b/views/episodes.tpl
@@ -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>
@@ -189,4 +189,52 @@
%end
</div>
</body>
-</html> \ No newline at end of file
+</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> \ No newline at end of file
diff --git a/views/history.tpl b/views/history.tpl
index ce8528927..86c1854c9 100644
--- a/views/history.tpl
+++ b/views/history.tpl
@@ -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}}";
})
diff --git a/views/series.tpl b/views/series.tpl
index f8127109d..dfd86878a 100644
--- a/views/series.tpl
+++ b/views/series.tpl
@@ -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();
diff --git a/views/settings.tpl b/views/settings.tpl
index d7d3ee4cb..032b0e54d 100644
--- a/views/settings.tpl
+++ b/views/settings.tpl
@@ -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> \ No newline at end of file
diff --git a/views/system.tpl b/views/system.tpl
index 40732ff1a..215f565bd 100644
--- a/views/system.tpl
+++ b/views/system.tpl
@@ -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');
})
diff --git a/views/wanted.tpl b/views/wanted.tpl
index a9438a13c..c35ed9dec 100644
--- a/views/wanted.tpl
+++ b/views/wanted.tpl
@@ -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> \ No newline at end of file