Merge branch 'development'

pull/26/head
Louis Vézina 7 years ago
commit feaa375327

@ -1,11 +1,12 @@
FROM lsiobase/alpine.python FROM debian:buster
EXPOSE 6767 EXPOSE 6767
VOLUME /tv VOLUME /tv
# Update # Update
RUN apk add --update build-base python-dev py2-pip py-setuptools jpeg-dev zlib-dev git libgit2-dev RUN apt-get update
RUN apt-get install -y build-essential python-dev python-pip python-setuptools libjpeg-dev zlib1g-dev git libgit2-dev libffi-dev
# Get application source from Github # Get application source from Github
RUN git clone -b master --single-branch https://github.com/morpheus65535/bazarr.git /bazarr RUN git clone -b master --single-branch https://github.com/morpheus65535/bazarr.git /bazarr

@ -64,7 +64,9 @@ Linux:
* Open your browser and go to `http://localhost:6767/` * Open your browser and go to `http://localhost:6767/`
## Docker: ## Docker:
* You can use [this image](https://hub.docker.com/r/morpheus65535/bazarr) to quickly build your own isolated app container. Thanks to [Linux Server](https://github.com/linuxserver) for the base image. It's based on the Linux instructions above. For more info about Docker check out the [official website](https://www.docker.com). * You can use [this image](https://hub.docker.com/r/morpheus65535/bazarr) to quickly build your own isolated app container. It's based on the Linux instructions above. For more info about Docker check out the [official website](https://www.docker.com).
docker create --name=bazarr -v /etc/localtime:/etc/localtime:ro -v /etc/timezone:/etc/timezone:ro -v /path/to/series/directory:/tv -v /path/to/config/directory/on/host:/bazarr/data -p 6767:6767 --restart=always morpheus65535/bazarr:latest
## First run (important to read!!!): ## First run (important to read!!!):
@ -77,12 +79,16 @@ Linux:
* Configure Sonarr ip, port, base url, SSL and API key. * Configure Sonarr ip, port, base url, SSL and API key.
### 4 - In "Subliminal" tab: ### 4 - In "Subliminal" tab:
* Configure enabled providers and enabled languages. Enabled languages are those that you are going to be able to assign to a series later. * Configure enabled providers and enabled languages. Enabled languages are those that you are going to be able to assign to a series later.
### 5 - Save those settings and restart Bazarr. ### 5 - Save those settings and restart (important!!!) Bazarr.
### 6 - Wait 2 minutes
### 7 - On the "Series" page, you should now see all your series listed with a wrench icon on yellow background. Those are the series that need to be configured. Click on those one you want to get subtitles for and select desired languages. You don't have to do this for every series but it will looks cleaner without all this yellow ;-). If you don't want any substitles for a series, just click on the wrench icon and then on "Save" without selecting anything.
### 8 - On each series page, you should see episode files available on disk, existing subtitles and missing subtitles (in case you requested some and they aren't already existing).
* If you don't see your episodes right now, wait some more time. It take time to do the initial synchronization between Sonarr and Bazarr.
### 6 - On the "Series" page, click on "Update Series" ### 9 - On "Wanted" page, you should see all the episodes who have missing subtitles.
* You should now see all your series listed with a wrench icon on yellow background. Those are the series that need to be configured. Click on each one and select desired languages. You have to do this even if you don't want subtitles for a series. Just click on the wrench icon and then on "Save".
* When you've finished going trough all those series to configure desired languages, you have to "Update All Episodes" from the "Series" page. Don't be impatient, it will take about 1 minute by 1000 episodes Bazarr need to scan for existing internal and external subtitles. If Bazarr is accessing those episodes trough a network share, it's going to take much more longer than that. Keep in mind that Bazarr have to open each and every episode files to analyze the content.
* Once the scan is finished, you should be able to access episodes list for each series and see those missing subtitles on the wanted page.
### 10 - Have fun and keep in mind that providers may temporary refuse connection due to connection limit exceeded or problem on the provider web service. ### 10 - Have fun and keep in mind that providers may temporary refuse connection due to connection limit exceeded or problem on the provider web service.

@ -1,7 +1,9 @@
bazarr_version = '0.1.1'
from bottle import route, run, template, static_file, request, redirect from bottle import route, run, template, static_file, request, redirect
import bottle import bottle
bottle.debug(True) #bottle.debug(True)
bottle.TEMPLATES.clear() #bottle.TEMPLATES.clear()
import os import os
bottle.TEMPLATE_PATH.insert(0,os.path.join(os.path.dirname(__file__), 'views/')) bottle.TEMPLATE_PATH.insert(0,os.path.join(os.path.dirname(__file__), 'views/'))
@ -34,6 +36,7 @@ from scheduler import *
import logging import logging
from logging.handlers import TimedRotatingFileHandler from logging.handlers import TimedRotatingFileHandler
logger = logging.getLogger('waitress') logger = logging.getLogger('waitress')
db = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db')) db = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db'))
c = db.cursor() c = db.cursor()
@ -41,7 +44,7 @@ c.execute("SELECT log_level FROM table_settings_general")
log_level = c.fetchone() log_level = c.fetchone()
log_level = log_level[0] log_level = log_level[0]
if log_level is None: if log_level is None:
log_level = "WARNING" log_level = "INFO"
log_level = getattr(logging, log_level) log_level = getattr(logging, log_level)
c.close() c.close()
@ -64,6 +67,9 @@ def configure_logging():
f = OneLineExceptionFormatter('%(asctime)s|%(levelname)s|%(message)s|', f = OneLineExceptionFormatter('%(asctime)s|%(levelname)s|%(message)s|',
'%d/%m/%Y %H:%M:%S') '%d/%m/%Y %H:%M:%S')
fh.setFormatter(f) fh.setFormatter(f)
logging.getLogger("enzyme").setLevel(logging.ERROR)
logging.getLogger("apscheduler").setLevel(logging.WARNING)
logging.getLogger("subliminal").setLevel(logging.ERROR)
root = logging.getLogger() root = logging.getLogger()
root.setLevel(log_level) root.setLevel(log_level)
root.addHandler(fh) root.addHandler(fh)
@ -129,30 +135,6 @@ def edit_series(no):
redirect(ref) redirect(ref)
@route(base_url + 'update_series')
def update_series_list():
ref = request.environ['HTTP_REFERER']
update_series()
redirect(ref)
@route(base_url + 'update_all_episodes')
def update_all_episodes_list():
ref = request.environ['HTTP_REFERER']
update_all_episodes()
redirect(ref)
@route(base_url + 'add_new_episodes')
def add_new_episodes_list():
ref = request.environ['HTTP_REFERER']
add_new_episodes()
redirect(ref)
@route(base_url + 'episodes/<no:int>', method='GET') @route(base_url + 'episodes/<no:int>', method='GET')
def episodes(no): def episodes(no):
conn = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db')) conn = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db'))
@ -160,7 +142,8 @@ def episodes(no):
c = conn.cursor() c = conn.cursor()
series_details = [] series_details = []
series_details = c.execute("SELECT title, overview, poster, fanart, hearing_impaired FROM table_shows WHERE sonarrSeriesId LIKE ?", (str(no),)).fetchone() series_details = c.execute("SELECT title, overview, poster, fanart, hearing_impaired, tvdbid FROM table_shows WHERE sonarrSeriesId LIKE ?", (str(no),)).fetchone()
tvdbid = series_details[5]
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 = 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))) episodes = reversed(sorted(episodes, key=operator.itemgetter(2)))
@ -169,7 +152,7 @@ def episodes(no):
seasons_list.append(list(season)) seasons_list.append(list(season))
c.close() c.close()
return template('episodes', no=no, details=series_details, seasons=seasons_list, url_sonarr_short=url_sonarr_short, base_url=base_url) return template('episodes', no=no, details=series_details, seasons=seasons_list, url_sonarr_short=url_sonarr_short, base_url=base_url, tvdbid=tvdbid)
@route(base_url + 'scan_disk/<no:int>', method='GET') @route(base_url + 'scan_disk/<no:int>', method='GET')
def scan_disk(no): def scan_disk(no):
@ -302,7 +285,8 @@ def save_settings():
def check_update(): def check_update():
ref = request.environ['HTTP_REFERER'] ref = request.environ['HTTP_REFERER']
check_and_apply_update() result = check_and_apply_update()
logging.info(result)
redirect(ref) redirect(ref)
@ -318,11 +302,101 @@ def system():
for line in reversed(open(os.path.join(os.path.dirname(__file__), 'data/log/bazarr.log')).readlines()): for line in reversed(open(os.path.join(os.path.dirname(__file__), 'data/log/bazarr.log')).readlines()):
logs.append(line.rstrip()) logs.append(line.rstrip())
def get_time_from_interval(interval):
interval_clean = interval.split('[')
interval_clean = interval_clean[1][:-1]
interval_split = interval_clean.split(':')
hour = interval_split[0]
minute = interval_split[1].lstrip("0")
second = interval_split[2].lstrip("0")
text = "every "
if hour != "0":
text = text + hour
if hour == "1":
text = text + " hour"
else:
text = text + " hours"
if minute != "" and second != "":
text = text + ", "
elif minute == "" and second != "":
text = text + " and "
elif minute != "" and second == "":
text = text + " and "
if minute != "":
text = text + minute
if minute == "1":
text = text + " minute"
else:
text = text + " minutes"
if second != "":
text = text + " and "
if second != "":
text = text + second
if second == "1":
text = text + " second"
else:
text = text + " seconds"
return text
def get_time_from_cron(cron):
text = "at "
hour = str(cron[5])
minute = str(cron[6])
second = str(cron[7])
if hour != "0" and hour != "*":
text = text + hour
if hour == "0" or hour == "1":
text = text + " hour"
else:
text = text + " hours"
if minute != "*" and second != "0":
text = text + ", "
elif minute == "*" and second != "0":
text = text + " and "
elif minute != "0" and minute != "*" and second == "0":
text = text + " and "
if minute != "0" and minute != "*":
text = text + minute
if minute == "0" or minute == "1":
text = text + " minute"
else:
text = text + " minutes"
if second != "0" and second != "*":
text = text + " and "
if second != "0" and second != "*":
text = text + second
if second == "0" or second == "1":
text = text + " second"
else:
text = text + " seconds"
return text
task_list = [] task_list = []
for job in scheduler.get_jobs(): for job in scheduler.get_jobs():
task_list.append([job.name, job.trigger.interval.__str__(), pretty.date(job.next_run_time.replace(tzinfo=None))]) if job.trigger.__str__().startswith('interval'):
task_list.append([job.name, get_time_from_interval(str(job.trigger)), pretty.date(job.next_run_time.replace(tzinfo=None)), job.id])
elif job.trigger.__str__().startswith('cron'):
task_list.append([job.name, get_time_from_cron(job.trigger.fields), pretty.date(job.next_run_time.replace(tzinfo=None)), job.id])
return template('system', tasks=tasks, logs=logs, base_url=base_url, task_list=task_list) return template('system', tasks=tasks, logs=logs, base_url=base_url, task_list=task_list, bazarr_version=bazarr_version)
@route(base_url + 'execute/<taskid>')
def execute_task(taskid):
ref = request.environ['HTTP_REFERER']
execute_now(taskid)
redirect(ref)
@route(base_url + 'remove_subtitles', method='POST') @route(base_url + 'remove_subtitles', method='POST')
def remove_subtitles(): def remove_subtitles():
@ -331,6 +405,7 @@ def remove_subtitles():
subtitlesPath = request.forms.get('subtitlesPath') subtitlesPath = request.forms.get('subtitlesPath')
sonarrSeriesId = request.forms.get('sonarrSeriesId') sonarrSeriesId = request.forms.get('sonarrSeriesId')
sonarrEpisodeId = request.forms.get('sonarrEpisodeId') sonarrEpisodeId = request.forms.get('sonarrEpisodeId')
tvdbid = request.forms.get('tvdbid')
try: try:
os.remove(subtitlesPath) os.remove(subtitlesPath)
@ -339,7 +414,7 @@ def remove_subtitles():
except OSError: except OSError:
pass pass
store_subtitles(episodePath) store_subtitles(episodePath)
list_missing_subtitles(sonarrSeriesId) list_missing_subtitles(tvdbid)
@route(base_url + 'get_subtitle', method='POST') @route(base_url + 'get_subtitle', method='POST')
def get_subtitle(): def get_subtitle():
@ -350,6 +425,7 @@ def get_subtitle():
hi = request.forms.get('hi') hi = request.forms.get('hi')
sonarrSeriesId = request.forms.get('sonarrSeriesId') sonarrSeriesId = request.forms.get('sonarrSeriesId')
sonarrEpisodeId = request.forms.get('sonarrEpisodeId') sonarrEpisodeId = request.forms.get('sonarrEpisodeId')
tvdbid = request.forms.get('tvdbid')
db = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db')) db = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db'))
c = db.cursor() c = db.cursor()
@ -366,9 +442,9 @@ def get_subtitle():
if result is not None: if result is not None:
history_log(1, sonarrSeriesId, sonarrEpisodeId, result) history_log(1, sonarrSeriesId, sonarrEpisodeId, result)
store_subtitles(episodePath) store_subtitles(episodePath)
list_missing_subtitles(sonarrSeriesId) list_missing_subtitles(tvdbid)
redirect(ref) redirect(ref)
except OSError: except OSError:
redirect(ref + '?error=2') pass
run(host=ip, port=port, server='waitress') run(host=ip, port=port, server='waitress')

@ -3,7 +3,7 @@ from get_general_settings import *
import os import os
import pygit2 import pygit2
current_working_directory = os.getcwd() current_working_directory = os.path.dirname(__file__)
repository_path = pygit2.discover_repository(current_working_directory) repository_path = pygit2.discover_repository(current_working_directory)
local_repo = pygit2.Repository(repository_path) local_repo = pygit2.Repository(repository_path)
@ -15,13 +15,17 @@ def check_and_apply_update(repo=local_repo, remote_name='origin'):
merge_result, _ = repo.merge_analysis(remote_id) merge_result, _ = repo.merge_analysis(remote_id)
# Up to date, do nothing # Up to date, do nothing
if merge_result & pygit2.GIT_MERGE_ANALYSIS_UP_TO_DATE: if merge_result & pygit2.GIT_MERGE_ANALYSIS_UP_TO_DATE:
print 'Up to date' result = 'No new version of Bazarr available.'
return pass
# We can just fastforward # We can just fastforward
elif merge_result & pygit2.GIT_MERGE_ANALYSIS_FASTFORWARD: elif merge_result & pygit2.GIT_MERGE_ANALYSIS_FASTFORWARD:
repo.checkout_tree(repo.get(remote_id)) repo.checkout_tree(repo.get(remote_id))
master_ref = repo.lookup_reference('refs/heads/master') master_ref = repo.lookup_reference('refs/heads/' + branch)
master_ref.set_target(remote_id) master_ref.set_target(remote_id)
repo.head.set_target(remote_id) repo.head.set_target(remote_id)
result = 'Bazarr updated to latest version and restarting.'
os.execlp('python', 'python', os.path.join(os.path.dirname(__file__), 'bazarr.py'))
else: else:
raise AssertionError('Unknown merge analysis result') raise AssertionError('Unknown merge analysis result')
return result

@ -40,7 +40,7 @@ CREATE TABLE "table_settings_general" (
`branch` TEXT, `branch` TEXT,
`auto_update` INTEGER `auto_update` INTEGER
); );
INSERT INTO `table_settings_general` (ip,port,base_url,path_mapping,log_level, branch, auto_update) VALUES ('0.0.0.0',6767,'/',Null,'WARNING','master','True'); INSERT INTO `table_settings_general` (ip,port,base_url,path_mapping,log_level, branch, auto_update) VALUES ('0.0.0.0',6767,'/',Null,'INFO','master','True');
CREATE TABLE `table_scheduler` ( CREATE TABLE `table_scheduler` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, `id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
`name` TEXT NOT NULL, `name` TEXT NOT NULL,

@ -21,6 +21,7 @@ def update_all_episodes():
base_url_sonarr = "" base_url_sonarr = ""
else: else:
base_url_sonarr = "/" + config_sonarr[2].strip("/") base_url_sonarr = "/" + config_sonarr[2].strip("/")
apikey_sonarr = config_sonarr[4]
# Get current episodes id in DB # Get current episodes id in DB
current_episodes_db = c.execute('SELECT sonarrEpisodeId FROM table_episodes').fetchall() current_episodes_db = c.execute('SELECT sonarrEpisodeId FROM table_episodes').fetchall()
@ -32,7 +33,7 @@ def update_all_episodes():
seriesIdList = c.fetchall() seriesIdList = c.fetchall()
for seriesId in seriesIdList: for seriesId in seriesIdList:
# Get episodes data for a series from Sonarr # 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] url_sonarr_api_episode = protocol_sonarr + "://" + config_sonarr[0] + ":" + str(config_sonarr[1]) + base_url_sonarr + "/api/episode?seriesId=" + str(seriesId[0]) + "&apikey=" + apikey_sonarr
r = requests.get(url_sonarr_api_episode) r = requests.get(url_sonarr_api_episode)
for episode in r.json(): for episode in r.json():
if episode['hasFile']: if episode['hasFile']:
@ -59,7 +60,16 @@ def update_all_episodes():
# Close database connection # Close database connection
c.close() c.close()
#Cleanup variables to free memory
del current_episodes_db
del current_episodes_db_list
del seriesIdList
del r
del current_episodes_sonarr
del deleted_items
del c
# Store substitles for all episodes # Store substitles for all episodes
full_scan_subtitles() full_scan_subtitles()
list_missing_subtitles() list_missing_subtitles()
@ -80,44 +90,61 @@ def add_new_episodes():
base_url_sonarr = "" base_url_sonarr = ""
else: else:
base_url_sonarr = "/" + config_sonarr[2].strip("/") base_url_sonarr = "/" + config_sonarr[2].strip("/")
apikey_sonarr = config_sonarr[4]
# Get current episodes in DB if apikey_sonarr == None:
current_episodes_db = c.execute('SELECT sonarrEpisodeId FROM table_episodes').fetchall() # Close database connection
current_episodes_db_list = [x[0] for x in current_episodes_db] c.close()
current_episodes_sonarr = [] pass
else:
# Get sonarrId for each series from database # Get current episodes in DB
c.execute("SELECT sonarrSeriesId FROM table_shows") current_episodes_db = c.execute('SELECT sonarrEpisodeId FROM table_episodes').fetchall()
seriesIdList = c.fetchall() current_episodes_db_list = [x[0] for x in current_episodes_db]
for seriesId in seriesIdList: current_episodes_sonarr = []
# 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] # Get sonarrId for each series from database
r = requests.get(url_sonarr_api_episode) c.execute("SELECT sonarrSeriesId FROM table_shows")
seriesIdList = c.fetchall()
for episode in r.json(): for seriesId in seriesIdList:
if episode['hasFile']: # Get episodes data for a series from Sonarr
# Add shows in Sonarr to current shows list url_sonarr_api_episode = protocol_sonarr + "://" + config_sonarr[0] + ":" + str(config_sonarr[1]) + base_url_sonarr + "/api/episode?seriesId=" + str(seriesId[0]) + "&apikey=" + apikey_sonarr
current_episodes_sonarr.append(episode['id']) r = requests.get(url_sonarr_api_episode)
for episode in r.json():
try: if episode['hasFile']:
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'])) # Add shows in Sonarr to current shows list
except: current_episodes_sonarr.append(episode['id'])
pass
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() db.commit()
# Delete episodes not in Sonarr anymore # Close database connection
deleted_items = [] c.close()
for item in current_episodes_db_list:
if item not in current_episodes_sonarr: #Cleanup variables to free memory
deleted_items.append(tuple([item])) del current_episodes_db
c.executemany('DELETE FROM table_episodes WHERE sonarrEpisodeId = ?',deleted_items) del current_episodes_db_list
del seriesIdList
del r
del current_episodes_sonarr
del deleted_items
del c
# Commit changes to database table # Store substitles from episodes we've just added
db.commit() new_scan_subtitles()
try:
# Close database connection list_missing_subtitles()
c.close() except:
pass
# Store substitles from episodes we've just added
new_scan_subtitles()
list_missing_subtitles()

@ -9,49 +9,52 @@ def update_series():
db = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db')) db = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db'))
c = db.cursor() c = db.cursor()
# Get shows data from Sonarr if apikey_sonarr == None:
url_sonarr_api_series = url_sonarr + "/api/series?apikey=" + apikey_sonarr pass
r = requests.get(url_sonarr_api_series) else:
shows_list = [] # Get shows data from Sonarr
url_sonarr_api_series = url_sonarr + "/api/series?apikey=" + apikey_sonarr
# Get current shows in DB r = requests.get(url_sonarr_api_series)
current_shows_db = c.execute('SELECT tvdbId FROM table_shows').fetchall() shows_list = []
current_shows_db_list = [x[0] for x in current_shows_db]
current_shows_sonarr = [] # Get current shows in DB
current_shows_db = c.execute('SELECT tvdbId FROM table_shows').fetchall()
# Parsing data returned from Sonarr current_shows_db_list = [x[0] for x in current_shows_db]
for show in r.json(): current_shows_sonarr = []
try:
overview = unicode(show['overview']) # Parsing data returned from Sonarr
except: for show in r.json():
overview = "" try:
try: overview = unicode(show['overview'])
poster_big = show['images'][2]['url'].split('?')[0] except:
poster = os.path.splitext(poster_big)[0] + '-250' + os.path.splitext(poster_big)[1] overview = ""
except: try:
poster = "" poster_big = show['images'][2]['url'].split('?')[0]
try: poster = os.path.splitext(poster_big)[0] + '-250' + os.path.splitext(poster_big)[1]
fanart = show['images'][0]['url'].split('?')[0] except:
except: poster = ""
fanart = "" try:
fanart = show['images'][0]['url'].split('?')[0]
# Add shows in Sonarr to current shows list except:
current_shows_sonarr.append(show['tvdbId']) fanart = ""
# Update or insert shows list in database table # Add shows in Sonarr to current shows list
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"])) current_shows_sonarr.append(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)) # Update or insert shows list in database table
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"]))
# Delete shows not in Sonarr anymore if result.rowcount == 0:
deleted_items = [] 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))
for item in current_shows_db_list:
if item not in current_shows_sonarr: # Delete shows not in Sonarr anymore
deleted_items.append(tuple([item])) deleted_items = []
c.executemany('DELETE FROM table_shows WHERE tvdbId = ?',deleted_items) for item in current_shows_db_list:
if item not in current_shows_sonarr:
# Commit changes to database table deleted_items.append(tuple([item]))
db.commit() c.executemany('DELETE FROM table_shows WHERE tvdbId = ?',deleted_items)
# Commit changes to database table
db.commit()
# Close database connection # Close database connection
db.close() db.close()

@ -21,11 +21,21 @@ def download_subtitle(path, language, hi, providers):
downloaded_provider = str(result[0]).strip('<>').split(' ')[0][:-8] downloaded_provider = str(result[0]).strip('<>').split(' ')[0][:-8]
downloaded_language = pycountry.languages.lookup(str(str(result[0]).strip('<>').split(' ')[2].strip('[]'))).name downloaded_language = pycountry.languages.lookup(str(str(result[0]).strip('<>').split(' ')[2].strip('[]'))).name
message = downloaded_language + " subtitles downloaded from " + downloaded_provider + "." message = downloaded_language + " subtitles downloaded from " + downloaded_provider + "."
return message return message
except: except:
return None return None
del video
del best_subtitles
try:
del result
del downloaded_provider
del downloaded_language
del message
except:
pass
def series_download_subtitles(no): def series_download_subtitles(no):
conn_db = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db')) conn_db = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db'))
c_db = conn_db.cursor() c_db = conn_db.cursor()
@ -65,6 +75,16 @@ def wanted_download_subtitles(path):
list_missing_subtitles(episode[3]) list_missing_subtitles(episode[3])
history_log(1, episode[3], episode[2], message) history_log(1, episode[3], episode[2], message)
del conn_db
del c_db
del episodes_details
del enabled_providers
del providers_list
try:
del message
except:
pass
def wanted_search_missing_subtitles(): def wanted_search_missing_subtitles():
db = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db')) db = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db'))
db.create_function("path_substitution", 1, path_replace) db.create_function("path_substitution", 1, path_replace)
@ -76,3 +96,7 @@ def wanted_search_missing_subtitles():
for episode in data: for episode in data:
wanted_download_subtitles(episode[0]) wanted_download_subtitles(episode[0])
del db
del c
del data

@ -68,21 +68,22 @@ def store_subtitles(file):
def list_missing_subtitles(*no): def list_missing_subtitles(*no):
query_string = '' query_string = ''
try: try:
query_string = " WHERE table_episodes.sonarrSeriesId = " + str(no[0]) query_string = " WHERE table_shows.tvdbId = " + str(no[0])
except: except:
pass pass
conn_db = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db')) conn_db = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db'))
c_db = conn_db.cursor() 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" + query_string).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 = []
missing_subtitles_global = []
for episode_subtitles in episodes_subtitles: for episode_subtitles in episodes_subtitles:
actual_subtitles = [] actual_subtitles = []
desired_subtitles = [] desired_subtitles = []
missing_subtitles = [] missing_subtitles = []
if episode_subtitles[1] != None: if episode_subtitles[1] != None:
actual_subtitles = ast.literal_eval(episode_subtitles[1]) actual_subtitles = ast.literal_eval(episode_subtitles[1])
if episode_subtitles[2] != None:
desired_subtitles = ast.literal_eval(episode_subtitles[2]) desired_subtitles = ast.literal_eval(episode_subtitles[2])
actual_subtitles_list = [] actual_subtitles_list = []
if desired_subtitles == None: if desired_subtitles == None:
@ -91,9 +92,10 @@ def list_missing_subtitles(*no):
for item in actual_subtitles: for item in actual_subtitles:
actual_subtitles_list.append(item[0]) actual_subtitles_list.append(item[0])
missing_subtitles = list(set(desired_subtitles) - set(actual_subtitles_list)) missing_subtitles = list(set(desired_subtitles) - set(actual_subtitles_list))
missing_subtitles_global.append(tuple([str(missing_subtitles), episode_subtitles[0]])) missing_subtitles_global.append(tuple([str(missing_subtitles), episode_subtitles[0]]))
c_db.executemany("UPDATE table_episodes SET missing_subtitles = ? WHERE sonarrEpisodeId = ?", (missing_subtitles_global)) c_db.executemany("UPDATE table_episodes SET missing_subtitles = ? WHERE sonarrEpisodeId = ?", (missing_subtitles_global))
conn_db.commit() conn_db.commit()
c_db.close() c_db.close()
@ -105,7 +107,7 @@ def full_scan_subtitles():
c_db.close() c_db.close()
for episode in episodes: for episode in episodes:
store_subtitles(path_replace(episode[0])) store_subtitles(path_replace(episode[0].encode('utf-8')))
def series_scan_subtitles(no): def series_scan_subtitles(no):
conn_db = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db')) conn_db = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db'))
@ -114,10 +116,10 @@ def series_scan_subtitles(no):
c_db.close() c_db.close()
for episode in episodes: for episode in episodes:
store_subtitles(path_replace(episode[0])) store_subtitles(path_replace(episode[0].encode('utf-8')))
list_missing_subtitles(no) list_missing_subtitles(no)
def new_scan_subtitles(): def new_scan_subtitles():
conn_db = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db')) conn_db = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db'))
c_db = conn_db.cursor() c_db = conn_db.cursor()
@ -125,8 +127,4 @@ def new_scan_subtitles():
c_db.close() c_db.close()
for episode in episodes: for episode in episodes:
store_subtitles(path_replace(episode[0])) store_subtitles(path_replace(episode[0].encode('utf-8')))
#full_scan_subtitles()
#new_scan_subtitles()
#list_missing_subtitles()

@ -1,13 +1,22 @@
from get_general_settings import *
from get_series import * from get_series import *
from get_episodes import * from get_episodes import *
from list_subtitles import *
from get_subtitle import * from get_subtitle import *
from check_update import * from check_update import *
from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.schedulers.background import BackgroundScheduler
from datetime import datetime
scheduler = BackgroundScheduler() scheduler = BackgroundScheduler()
scheduler.add_job(check_and_apply_update, 'interval', hours=6, max_instances=1, coalesce=True, id='update_bazarr', name='Update bazarr from source on Github') if automatic == 'True':
scheduler.add_job(check_and_apply_update, 'interval', hours=6, max_instances=1, coalesce=True, id='update_bazarr', name='Update bazarr from source on Github')
scheduler.add_job(update_series, 'interval', minutes=1, max_instances=1, coalesce=True, id='update_series', name='Update series list from Sonarr') scheduler.add_job(update_series, 'interval', minutes=1, max_instances=1, coalesce=True, id='update_series', name='Update series list from Sonarr')
scheduler.add_job(add_new_episodes, 'interval', minutes=1, max_instances=1, coalesce=True, id='add_new_episodes', name='Add new episodes from Sonarr') scheduler.add_job(add_new_episodes, 'interval', minutes=1, max_instances=1, coalesce=True, id='add_new_episodes', name='Add new episodes from Sonarr')
scheduler.add_job(update_all_episodes, 'cron', hour=4, max_instances=1, coalesce=True, id='update_all_episodes', name='Update all episodes from Sonarr')
scheduler.add_job(list_missing_subtitles, 'interval', minutes=5, max_instances=1, coalesce=True, id='list_missing_subtitles', name='Process missing subtitles for all series')
scheduler.add_job(wanted_search_missing_subtitles, 'interval', minutes=15, max_instances=1, coalesce=True, id='wanted_search_missing_subtitles', name='Search for wanted subtitles') scheduler.add_job(wanted_search_missing_subtitles, 'interval', minutes=15, max_instances=1, coalesce=True, id='wanted_search_missing_subtitles', name='Search for wanted subtitles')
scheduler.start() scheduler.start()
def execute_now(taskid):
scheduler.modify_job(taskid, jobstore=None, next_run_time=datetime.now())

@ -121,7 +121,7 @@
%if len(seasons) == 0: %if len(seasons) == 0:
<div id="fondblanc" class="ui container"> <div id="fondblanc" class="ui container">
<h2 class="ui header">No episode file available for this series.</h2> <h3 class="ui header">No episode file available for this series or Bazarr is still synchronizing with Sonarr. Please come back later.</h3>
</div> </div>
%else: %else:
%for season in seasons: %for season in seasons:
@ -155,36 +155,43 @@
%if episode[4] is not None: %if episode[4] is not None:
% actual_languages = ast.literal_eval(episode[4]) % actual_languages = ast.literal_eval(episode[4])
%else: %else:
actual_languages = '[]' % actual_languages = '[]'
%end %end
%if actual_languages is not None:
%try:
%for language in actual_languages: %for language in actual_languages:
%if language[1] is not None: %if language[1] is not None:
<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"> <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]}} {{language[0]}}
<i class="delete icon"></i> <i class="delete icon"></i>
</a> </a>
%else: %else:
<div class="ui tiny label"> <div class="ui tiny label">
{{language[0]}} {{language[0]}}
</div> </div>
%end %end
%end %end
%except:
%pass
%end %end
</td> </td>
<td> <td>
%if episode[6] is not None: %try:
% missing_languages = ast.literal_eval(episode[6]) %if episode[6] is not None:
%else: % missing_languages = ast.literal_eval(episode[6])
% missing_languages = None %else:
%end % missing_languages = None
%if missing_languages is not None: %end
%for language in missing_languages: %if missing_languages is not None:
<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"> %for language in missing_languages:
{{language}} <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">
<i style="margin-left:3px; margin-right:0px" class="search icon"></i> {{language}}
</a> <i style="margin-left:3px; margin-right:0px" class="search icon"></i>
</a>
%end
%end %end
%except:
%pass
%end %end
</td> </td>
</tr> </tr>
@ -214,7 +221,8 @@
language: $(this).attr("data-language"), language: $(this).attr("data-language"),
subtitlesPath: $(this).attr("data-subtitlesPath"), subtitlesPath: $(this).attr("data-subtitlesPath"),
sonarrSeriesId: $(this).attr("data-sonarrSeriesId"), sonarrSeriesId: $(this).attr("data-sonarrSeriesId"),
sonarrEpisodeId: $(this).attr("data-sonarrEpisodeId") sonarrEpisodeId: $(this).attr("data-sonarrEpisodeId"),
tvdbid: {{tvdbid}}
}; };
$.ajax({ $.ajax({
url: "{{base_url}}remove_subtitles", url: "{{base_url}}remove_subtitles",
@ -231,7 +239,8 @@
language: $(this).attr("data-language"), language: $(this).attr("data-language"),
hi: $(this).attr("data-hi"), hi: $(this).attr("data-hi"),
sonarrSeriesId: $(this).attr("data-sonarrSeriesId"), sonarrSeriesId: $(this).attr("data-sonarrSeriesId"),
sonarrEpisodeId: $(this).attr("data-sonarrEpisodeId") sonarrEpisodeId: $(this).attr("data-sonarrEpisodeId"),
tvdbid: {{tvdbid}}
}; };
$.ajax({ $.ajax({
url: "{{base_url}}get_subtitle", url: "{{base_url}}get_subtitle",

@ -78,12 +78,6 @@
</div> </div>
<div id="fondblanc" class="ui container"> <div id="fondblanc" class="ui container">
<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"> <table id="tableseries" class="ui very basic selectable sortable table">
<thead> <thead>
<tr> <tr>
@ -111,7 +105,7 @@
%end %end
%end %end
</td> </td>
<td>{{row[4]}}</td> <td>{{!"" if row[4] == None else row[4]}}</td>
<td {{!"style='background-color: yellow;'" if row[4] == None else ""}}> <td {{!"style='background-color: yellow;'" if row[4] == None else ""}}>
<% <%
subs_languages_list = [] subs_languages_list = []
@ -200,18 +194,6 @@
}) })
; ;
$('#update_series').click(function(){
window.location = '{{base_url}}update_series';
})
$('#update_all_episodes').click(function(){
window.location = '{{base_url}}update_all_episodes';
})
$('#add_new_episodes').click(function(){
window.location = '{{base_url}}add_new_episodes';
})
$('.config').click(function(){ $('.config').click(function(){
sessionStorage.scrolly=$(window).scrollTop(); sessionStorage.scrolly=$(window).scrollTop();

@ -236,7 +236,7 @@
<label>Manual update</label> <label>Manual update</label>
</div> </div>
<div class="eleven wide column"> <div class="eleven wide column">
<button id="settings_general_check_update" class="ui blue button">Check now and update</button> <a id="settings_general_check_update" class="ui blue button">Check now and update</a>
</div> </div>
</div> </div>
</div> </div>
@ -372,7 +372,7 @@
; ;
$('#settings_general_check_update').click(function(){ $('#settings_general_check_update').click(function(){
window.location.href = '{{base_url}}check_update'; window.location = '{{base_url}}check_update';
}) })
$('a:not(.tabs), button:not(.cancel)').click(function(){ $('a:not(.tabs), button:not(.cancel)').click(function(){

@ -83,8 +83,9 @@
<thead> <thead>
<tr> <tr>
<th>Name</th> <th>Name</th>
<th>Interval</th> <th>Execution Frequency</th>
<th>Next Execution</th> <th>Next Execution</th>
<th class="collapsing"></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -93,6 +94,11 @@
<td>{{task[0]}}</td> <td>{{task[0]}}</td>
<td>{{task[1]}}</td> <td>{{task[1]}}</td>
<td>{{task[2]}}</td> <td>{{task[2]}}</td>
<td class="collapsing">
<div class="execute ui inverted basic compact icon" data-tooltip="Execute {{task[0]}}" data-inverted="" data-taskid='{{task[3]}}'>
<i class="ui black refresh icon"></i>
</div>
</td>
</tr> </tr>
%end %end
</tbody> </tbody>
@ -135,7 +141,7 @@ icon"></i></td>
</div> </div>
</div> </div>
<div class="ui bottom attached tab segment" data-tab="about"> <div class="ui bottom attached tab segment" data-tab="about">
About Bazarr version: {{bazarr_version}}
</div> </div>
</div> </div>
@ -173,6 +179,10 @@ icon"></i></td>
.tab() .tab()
; ;
$('.execute').click(function(){
window.location = '{{base_url}}execute/' + $(this).data("taskid");
})
$('.log').click(function(){ $('.log').click(function(){
$("#message").html($(this).data("message")); $("#message").html($(this).data("message"));
$("#exception").html($(this).data("exception")); $("#exception").html($(this).data("exception"));

Loading…
Cancel
Save