diff options
author | Louis Vézina <[email protected]> | 2017-10-16 19:27:19 -0400 |
---|---|---|
committer | Louis Vézina <[email protected]> | 2017-10-16 19:27:19 -0400 |
commit | edb46d9115be4e83282fce10e6505cd5ad0b26bb (patch) | |
tree | 511409b526b1552de6745c94fff7295479dcf7bc | |
parent | 546c3ac0662c9ab578dc6fe4c4885e85a6378f3f (diff) | |
download | bazarr-edb46d9115be4e83282fce10e6505cd5ad0b26bb.tar.gz bazarr-edb46d9115be4e83282fce10e6505cd5ad0b26bb.zip |
Continuig development
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | Dockerfile | 10 | ||||
-rw-r--r-- | bazarr.db | bin | 1398784 -> 786432 bytes | |||
-rw-r--r-- | bazarr.py | 130 | ||||
-rw-r--r-- | create_db.sql | 64 | ||||
-rw-r--r-- | create_db.sql.old | 49 | ||||
-rw-r--r-- | get_episodes.py | 76 | ||||
-rw-r--r-- | get_general_settings.py | 5 | ||||
-rw-r--r-- | get_general_settings.pyc | bin | 1353 -> 1391 bytes | |||
-rw-r--r-- | get_series.py | 9 | ||||
-rw-r--r-- | get_sonarr_settings.pyc | bin | 871 -> 871 bytes | |||
-rw-r--r-- | get_subtitle.py | 45 | ||||
-rw-r--r-- | list_subtitles.py | 20 | ||||
-rw-r--r-- | list_subtitles.pyc | bin | 4043 -> 4603 bytes | |||
-rw-r--r-- | requirements.txt | 11 | ||||
-rw-r--r-- | scheduler.py | 12 | ||||
-rw-r--r-- | views/episodes.tpl | 54 | ||||
-rw-r--r-- | views/history.tpl | 19 | ||||
-rw-r--r-- | views/series.tpl | 12 | ||||
-rw-r--r-- | views/settings.tpl | 31 | ||||
-rw-r--r-- | views/system.tpl | 67 | ||||
-rw-r--r-- | views/wanted.tpl | 37 |
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 Binary files differ@@ -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 Binary files differindex da21bc29f..8593d559f 100644 --- a/get_general_settings.pyc +++ b/get_general_settings.pyc 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 Binary files differindex 3dc844f3d..8063bc4a8 100644 --- a/get_sonarr_settings.pyc +++ b/get_sonarr_settings.pyc 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 Binary files differindex eb427fce1..bb5e3e330 100644 --- a/list_subtitles.pyc +++ b/list_subtitles.pyc 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 |