bazarr/bazarr/api.py

1798 lines
73 KiB
Python
Raw Normal View History

2020-03-24 12:17:03 +00:00
# coding=utf-8
2019-12-11 03:20:42 +00:00
import ast
from datetime import timedelta
2020-06-19 13:18:48 +00:00
from dateutil import rrule
import pretty
2020-01-23 04:10:33 +00:00
import time
2020-02-07 17:40:43 +00:00
from operator import itemgetter
2020-02-17 00:38:10 +00:00
import platform
2020-03-29 13:58:32 +00:00
import re
2020-05-06 00:09:25 +00:00
import json
2021-03-25 14:22:43 +00:00
import hashlib
import apprise
import gc
2019-12-11 03:20:42 +00:00
2020-02-17 00:38:10 +00:00
from get_args import args
2021-03-25 14:22:43 +00:00
from config import settings, base_url, save_settings, get_settings
from logger import empty_log
2019-12-11 03:20:42 +00:00
from init import *
import logging
from database import database, get_exclusion_clause, get_profiles_list, get_desired_languages, get_profile_id_name, \
get_audio_profile_languages, update_profile_id_list
2020-05-19 13:27:13 +00:00
from helper import path_mappings
from get_languages import language_from_alpha2, language_from_alpha3, alpha2_from_alpha3, alpha3_from_alpha2
from get_subtitle import download_subtitle, series_download_subtitles, manual_search, manual_download_subtitle, \
manual_upload_subtitle, wanted_search_missing_subtitles_series, wanted_search_missing_subtitles_movies, \
episode_download_subtitles, movies_download_subtitles
2020-01-07 03:26:28 +00:00
from notifier import send_notifications, send_notifications_movie
from list_subtitles import store_subtitles, store_subtitles_movie, series_scan_subtitles, movies_scan_subtitles, \
list_missing_subtitles, list_missing_subtitles_movies
2020-07-19 20:02:38 +00:00
from utils import history_log, history_log_movie, blacklist_log, blacklist_delete, blacklist_delete_all, \
blacklist_log_movie, blacklist_delete_movie, blacklist_delete_all_movie, get_sonarr_version, get_radarr_version, \
delete_subtitles, subtitles_apply_mods, translate_subtitles_file
from get_providers import get_providers, get_providers_auth, list_throttled_providers, reset_throttled_providers, \
get_throttled_providers, set_throttled_providers
2020-05-12 12:25:03 +00:00
from event_handler import event_stream
2020-05-18 12:21:16 +00:00
from scheduler import scheduler
from subsyncer import subsync
from filesystem import browse_bazarr_filesystem, browse_sonarr_filesystem, browse_radarr_filesystem
2020-01-07 03:26:28 +00:00
2020-09-26 12:58:56 +00:00
from subliminal_patch.core import SUBTITLE_EXTENSIONS, guessit
2021-03-25 14:22:43 +00:00
from flask import Flask, jsonify, request, Response, Blueprint, url_for, make_response, session
from flask_restful import Resource, Api, abort
from functools import wraps
2021-03-25 14:22:43 +00:00
api_bp = Blueprint('api', __name__, url_prefix=base_url.rstrip('/') + '/api')
2019-12-16 13:58:10 +00:00
api = Api(api_bp)
2019-12-15 04:58:51 +00:00
2021-03-25 14:22:43 +00:00
None_Keys = ['null', 'undefined', '']
def check_credentials(user, pw):
username = settings.auth.username
password = settings.auth.password
if hashlib.md5(pw.encode('utf-8')).hexdigest() == password and user == username:
return True
return False
2019-12-15 04:58:51 +00:00
def authenticate(actual_method):
@wraps(actual_method)
def wrapper(*args, **kwargs):
2021-03-25 14:22:43 +00:00
if settings.auth.type == 'basic':
auth = request.authorization
if not (auth and check_credentials(request.authorization.username, request.authorization.password)):
return ('Unauthorized', 401, {
'WWW-Authenticate': 'Basic realm="Login Required"'
})
elif settings.auth.type == 'form':
if 'logged_in' not in session:
return abort(401, message="Unauthorized")
apikey_settings = settings.auth.apikey
apikey_get = request.args.get('apikey')
apikey_post = request.form.get('apikey')
apikey_header = None
2021-03-25 14:22:43 +00:00
if 'X-API-KEY' in request.headers:
apikey_header = request.headers['X-API-KEY']
if apikey_settings in [apikey_get, apikey_post, apikey_header]:
return actual_method(*args, **kwargs)
2021-03-25 14:22:43 +00:00
return abort(401)
return wrapper
2021-03-25 14:22:43 +00:00
def postprocess(item: dict):
# Parse tags
if 'tags' in item:
if item['tags'] is None:
item['tags'] = []
else:
item['tags'] = ast.literal_eval(item['tags'])
if 'monitored' in item:
if item['monitored'] is None:
item['monitored'] = False
else:
item['monitored'] = item['monitored'] == 'True'
if 'hearing_impaired' in item and item['hearing_impaired'] is not None:
if item['hearing_impaired'] is None:
item['hearing_impaired'] = False
else:
item['hearing_impaired'] = item['hearing_impaired'] == 'True'
if 'language' in item:
if item['language'] == 'None':
item['language'] = None
elif item['language'] is not None:
splitted_language = item['language'].split(':')
item['language'] = {"name": language_from_alpha2(splitted_language[0]),
"code2": splitted_language[0],
"code3": alpha3_from_alpha2(splitted_language[0]),
"forced": True if item['language'].endswith(':forced') else False,
"hi": True if item['language'].endswith(':hi') else False}
def postprocessSeries(item):
postprocess(item)
# Parse audio language
if 'audio_language' in item and item['audio_language'] is not None:
item['audio_language'] = get_audio_profile_languages(series_id=item['sonarrSeriesId'])
if 'alternateTitles' in item:
if item['alternateTitles'] is None:
item['alternativeTitles'] = []
else:
item['alternativeTitles'] = ast.literal_eval(item['alternateTitles'])
del item["alternateTitles"]
# Parse seriesType
if 'seriesType' in item and item['seriesType'] is not None:
item['seriesType'] = item['seriesType'].capitalize()
if 'path' in item:
item['path'] = path_mappings.path_replace(item['path'])
# Confirm if path exist
item['exist'] = os.path.isdir(item['path'])
# map poster and fanart to server proxy
if 'poster' in item:
poster = item['poster']
item['poster'] = f"{base_url}/images/series{poster}"
if 'fanart' in item:
fanart = item['fanart']
item['fanart'] = f"{base_url}/images/series{fanart}"
def postprocessEpisode(item, desired=None):
if desired is None:
desired = []
postprocess(item)
if 'audio_language' in item and item['audio_language'] is not None:
item['audio_language'] = get_audio_profile_languages(episode_id=item['sonarrEpisodeId'])
if 'subtitles' in item:
if item['subtitles'] is None:
raw_subtitles = []
else:
raw_subtitles = ast.literal_eval(item['subtitles'])
subtitles = []
for subs in raw_subtitles:
subtitle = subs[0].split(':')
sub = {"name": language_from_alpha2(subtitle[0]),
"code2": subtitle[0],
"code3": alpha3_from_alpha2(subtitle[0]),
"path": subs[1],
"forced": False,
"hi": False}
if len(subtitle) > 1:
sub["forced"] = True if subtitle[1] == 'forced' else False
sub["hi"] = True if subtitle[1] == 'hi' else False
subtitles.append(sub)
item.update({"subtitles": subtitles})
if settings.general.getboolean('embedded_subs_show_desired'):
item['subtitles'] = [x for x in item['subtitles'] if
x['code2'] in desired or x['path']]
# Parse missing subtitles
if 'missing_subtitles' in item:
if item['missing_subtitles'] is None:
item['missing_subtitles'] = []
else:
item['missing_subtitles'] = ast.literal_eval(item['missing_subtitles'])
for i, subs in enumerate(item['missing_subtitles']):
subtitle = subs.split(':')
item['missing_subtitles'][i] = {"name": language_from_alpha2(subtitle[0]),
"code2": subtitle[0],
"code3": alpha3_from_alpha2(subtitle[0]),
"forced": False,
"hi": False}
if len(subtitle) > 1:
item['missing_subtitles'][i].update({
"forced": True if subtitle[1] == 'forced' else False,
"hi": True if subtitle[1] == 'hi' else False
})
if 'scene_name' in item:
item["sceneName"] = item["scene_name"]
del item["scene_name"]
if 'path' in item:
if item['path']:
# Provide mapped path
item['path'] = path_mappings.path_replace(item['path'])
item['exist'] = os.path.isfile(item['path'])
2020-04-16 11:52:35 +00:00
2021-03-25 14:22:43 +00:00
# TODO: Move
def postprocessMovie(item):
postprocess(item)
# Parse audio language
if 'audio_language' in item and item['audio_language'] is not None:
item['audio_language'] = get_audio_profile_languages(movie_id=item['radarrId'])
2020-04-16 11:52:35 +00:00
2021-03-25 14:22:43 +00:00
# Parse alternate titles
if 'alternativeTitles' in item:
if item['alternativeTitles'] is None:
item['alternativeTitles'] = []
else:
item['alternativeTitles'] = ast.literal_eval(item['alternativeTitles'])
# Parse failed attempts
if 'failedAttempts' in item:
if item['failedAttempts']:
item['failedAttempts'] = ast.literal_eval(item['failedAttempts'])
# Parse subtitles
if 'subtitles' in item:
if item['subtitles'] is None:
item['subtitles'] = []
else:
item['subtitles'] = ast.literal_eval(item['subtitles'])
for i, subs in enumerate(item['subtitles']):
language = subs[0].split(':')
item['subtitles'][i] = {"path": subs[1],
"name": language_from_alpha2(language[0]),
"code2": language[0],
"code3": alpha3_from_alpha2(language[0]),
"forced": False,
"hi": False}
if len(language) > 1:
item['subtitles'][i].update({
"forced": True if language[1] == 'forced' else False,
"hi": True if language[1] == 'hi' else False
})
if settings.general.getboolean('embedded_subs_show_desired'):
desired_lang_list = get_desired_languages(item['profileId'])
item['subtitles'] = [x for x in item['subtitles'] if x['code2'] in desired_lang_list or x['path']]
item['subtitles'] = sorted(item['subtitles'], key=itemgetter('name', 'forced'))
# Parse missing subtitles
if 'missing_subtitles' in item:
if item['missing_subtitles'] is None:
item['missing_subtitles'] = []
else:
item['missing_subtitles'] = ast.literal_eval(item['missing_subtitles'])
for i, subs in enumerate(item['missing_subtitles']):
language = subs.split(':')
item['missing_subtitles'][i] = {"name": language_from_alpha2(language[0]),
"code2": language[0],
"code3": alpha3_from_alpha2(language[0]),
"forced": False,
"hi": False}
if len(language) > 1:
item['missing_subtitles'][i].update({
"forced": True if language[1] == 'forced' else False,
"hi": True if language[1] == 'hi' else False
})
# Provide mapped path
if 'path' in item:
if item['path']:
item['path'] = path_mappings.path_replace_movie(item['path'])
# Confirm if path exist
item['exist'] = os.path.isfile(item['path'])
if 'subtitles_path' in item:
# Provide mapped subtitles path
item['subtitles_path'] = path_mappings.path_replace_movie(item['subtitles_path'])
# map poster and fanart to server proxy
if 'poster' in item:
poster = item['poster']
item['poster'] = f"{base_url}/images/movies{poster}"
if 'fanart' in item:
fanart = item['fanart']
item['fanart'] = f"{base_url}/images/movies{fanart}"
class SystemAccount(Resource):
def post(self):
if settings.auth.type != 'form':
return '', 405
action = request.args.get('action')
if action == 'login':
username = request.form.get('username')
password = request.form.get('password')
if check_credentials(username, password):
session['logged_in'] = True
return '', 204
elif action == 'logout':
if settings.auth.type == 'basic':
return abort(401)
elif settings.auth.type == 'form':
session.clear()
gc.collect()
return '', 204
return '', 401
class System(Resource):
2020-04-16 11:52:35 +00:00
@authenticate
2021-03-25 14:22:43 +00:00
def post(self):
2020-05-18 02:22:36 +00:00
from server import webserver
2021-03-25 14:22:43 +00:00
action = request.args.get('action')
if action == "shutdown":
webserver.shutdown()
elif action == "restart":
webserver.restart()
return '', 204
2020-04-16 11:52:35 +00:00
class BadgesSeries(Resource):
@authenticate
2019-12-15 04:58:51 +00:00
def get(self):
missing_episodes = database.execute("SELECT table_shows.tags, table_episodes.monitored, table_shows.seriesType "
"FROM table_episodes INNER JOIN table_shows on table_shows.sonarrSeriesId ="
" table_episodes.sonarrSeriesId WHERE missing_subtitles is not null AND "
"missing_subtitles != '[]'" + get_exclusion_clause('series'))
missing_episodes = len(missing_episodes)
missing_movies = database.execute("SELECT tags, monitored FROM table_movies WHERE missing_subtitles is not "
"null AND missing_subtitles != '[]'" + get_exclusion_clause('movie'))
missing_movies = len(missing_movies)
2020-05-20 03:20:35 +00:00
2021-03-25 14:22:43 +00:00
throttled_providers = len(eval(str(get_throttled_providers())))
result = {
2021-03-25 14:22:43 +00:00
"episodes": missing_episodes,
"movies": missing_movies,
"providers": throttled_providers
2019-12-15 04:58:51 +00:00
}
return jsonify(result)
2020-01-22 04:54:32 +00:00
class Languages(Resource):
@authenticate
2020-01-22 04:54:32 +00:00
def get(self):
2021-03-25 14:22:43 +00:00
result = database.execute("SELECT name, code2, enabled FROM table_settings_languages ORDER BY name")
for item in result:
item['enabled'] = item['enabled'] == 1
2020-01-22 04:54:32 +00:00
return jsonify(result)
class LanguagesProfiles(Resource):
@authenticate
def get(self):
2021-03-25 14:22:43 +00:00
return jsonify(get_profiles_list())
2020-05-06 00:09:25 +00:00
class Notifications(Resource):
@authenticate
2021-03-25 14:22:43 +00:00
def patch(self):
protocol = request.form.get("protocol")
path = request.form.get("path")
2020-05-06 00:09:25 +00:00
2021-03-25 14:22:43 +00:00
asset = apprise.AppriseAsset(async_mode=False)
apobj = apprise.Apprise(asset=asset)
apobj.add(f"{protocol}://{path}")
apobj.notify(
title='Bazarr test notification',
body='Test notification'
)
2020-05-06 00:09:25 +00:00
return '', 204
2021-03-25 14:22:43 +00:00
class Searches(Resource):
@authenticate
2020-03-29 13:58:32 +00:00
def get(self):
query = request.args.get('query')
search_list = []
if query:
if settings.general.getboolean('use_sonarr'):
# Get matching series
series = database.execute("SELECT title, sonarrSeriesId, year FROM table_shows WHERE title LIKE ? "
2021-03-25 14:22:43 +00:00
"ORDER BY title ASC", ("%" + query + "%",))
search_list += series
2020-03-29 13:58:32 +00:00
if settings.general.getboolean('use_radarr'):
# Get matching movies
movies = database.execute("SELECT title, radarrId, year FROM table_movies WHERE title LIKE ? ORDER BY "
2021-03-25 14:22:43 +00:00
"title ASC", ("%" + query + "%",))
search_list += movies
2020-03-29 13:58:32 +00:00
return jsonify(search_list)
2021-03-25 14:22:43 +00:00
class SystemSettings(Resource):
2020-04-30 12:38:05 +00:00
@authenticate
def get(self):
2021-03-25 14:22:43 +00:00
data = get_settings()
2020-04-30 12:38:05 +00:00
2021-03-25 14:22:43 +00:00
notifications = database.execute("SELECT * FROM table_settings_notifier ORDER BY name")
for i, item in enumerate(notifications):
item["enabled"] = item["enabled"] == 1
notifications[i] = item
data['notifications'] = dict()
data['notifications']['providers'] = notifications
2020-04-30 12:38:05 +00:00
2021-03-25 14:22:43 +00:00
return jsonify(data)
2020-04-30 12:38:05 +00:00
@authenticate
2020-03-11 10:58:45 +00:00
def post(self):
2021-03-25 14:22:43 +00:00
enabled_languages = request.form.getlist('languages-enabled')
if len(enabled_languages) != 0:
database.execute("UPDATE table_settings_languages SET enabled=0")
for code in enabled_languages:
database.execute("UPDATE table_settings_languages SET enabled=1 WHERE code2=?", (code,))
languages_profiles = request.form.get('languages-profiles')
if languages_profiles:
existing_ids = database.execute('SELECT profileId FROM table_languages_profiles')
existing = [x['profileId'] for x in existing_ids]
for item in json.loads(languages_profiles):
if item['profileId'] in existing:
# Update existing profiles
database.execute('UPDATE table_languages_profiles SET name = ?, cutoff = ?, items = ? '
'WHERE profileId = ?', (item['name'],
2021-03-25 14:22:43 +00:00
item['cutoff'] if item['cutoff'] != 'null' else None,
json.dumps(item['items']),
item['profileId']))
existing.remove(item['profileId'])
else:
# Add new profiles
database.execute('INSERT INTO table_languages_profiles (profileId, name, cutoff, items) '
'VALUES (?, ?, ?, ?)', (item['profileId'],
item['name'],
2021-03-25 14:22:43 +00:00
item['cutoff'] if item['cutoff'] != 'null' else None,
json.dumps(item['items'])))
for profileId in existing:
# Unassign this profileId from series and movies
database.execute('UPDATE table_shows SET profileId = null WHERE profileId = ?', (profileId,))
database.execute('UPDATE table_movies SET profileId = null WHERE profileId = ?', (profileId,))
# Remove deleted profiles
database.execute('DELETE FROM table_languages_profiles WHERE profileId = ?', (profileId,))
update_profile_id_list()
if settings.general.getboolean('use_sonarr'):
scheduler.add_job(list_missing_subtitles, kwargs={'send_event': False})
if settings.general.getboolean('use_radarr'):
scheduler.add_job(list_missing_subtitles_movies, kwargs={'send_event': False})
2021-03-25 14:22:43 +00:00
# Update Notification
notifications = request.form.getlist('notifications-providers')
for item in notifications:
item = json.loads(item)
database.execute("UPDATE table_settings_notifier SET enabled = ?, url = ? WHERE name = ?",
(item['enabled'], item['url'], item['name']))
2020-03-11 10:58:45 +00:00
2021-03-25 14:22:43 +00:00
save_settings(zip(request.form.keys(), request.form.listvalues()))
return '', 204
2020-03-11 10:58:45 +00:00
2020-02-20 11:41:05 +00:00
class SystemTasks(Resource):
@authenticate
2020-02-20 11:41:05 +00:00
def get(self):
2020-02-23 14:25:24 +00:00
taskid = request.args.get('taskid')
2020-02-20 11:41:05 +00:00
task_list = scheduler.get_task_list()
2020-02-23 14:25:24 +00:00
if taskid:
for item in task_list:
if item['job_id'] == taskid:
task_list = [item]
continue
2020-02-20 11:41:05 +00:00
return jsonify(data=task_list)
@authenticate
2020-02-22 14:03:58 +00:00
def post(self):
2020-04-15 04:02:44 +00:00
taskid = request.form.get('taskid')
2020-02-22 14:03:58 +00:00
scheduler.execute_job_now(taskid)
2021-03-25 14:22:43 +00:00
return '', 204
2020-02-22 14:03:58 +00:00
2020-02-17 17:54:20 +00:00
class SystemLogs(Resource):
@authenticate
2020-02-17 17:54:20 +00:00
def get(self):
logs = []
with io.open(os.path.join(args.config_dir, 'log', 'bazarr.log'), encoding='UTF-8') as file:
for line in file.readlines():
lin = line.split('|')
if len(lin) > 4:
2021-03-27 12:59:12 +00:00
log = dict()
log["timestamp"] = lin[0]
log["type"] = lin[1].rstrip()
log["message"] = lin[3]
if lin[4] != '\n':
log['exception'] = lin[4].strip('\'').replace(' ', '\u2003\u2003')
2021-03-27 12:59:12 +00:00
2021-03-25 14:22:43 +00:00
logs.append(log)
2020-02-17 17:54:20 +00:00
logs.reverse()
return jsonify(data=logs)
@authenticate
2021-03-25 14:22:43 +00:00
def delete(self):
empty_log()
return '', 204
2020-02-23 15:17:49 +00:00
2020-02-17 00:38:10 +00:00
class SystemStatus(Resource):
def get(self):
system_status = {}
system_status.update({'bazarr_version': os.environ["BAZARR_VERSION"]})
system_status.update({'sonarr_version': get_sonarr_version()})
system_status.update({'radarr_version': get_radarr_version()})
system_status.update({'operating_system': platform.platform()})
system_status.update({'python_version': platform.python_version()})
system_status.update({'bazarr_directory': os.path.dirname(os.path.dirname(__file__))})
system_status.update({'bazarr_config_directory': args.config_dir})
return jsonify(data=system_status)
class SystemReleases(Resource):
@authenticate
2020-02-17 00:38:10 +00:00
def get(self):
releases = []
try:
with io.open(os.path.join(args.config_dir, 'config', 'releases.txt'), 'r', encoding='UTF-8') as f:
2021-01-27 16:29:08 +00:00
releases = json.loads(f.read())
filtered_releases = []
for release in releases:
if settings.general.branch == 'master' and not release['prerelease']:
filtered_releases.append(release)
elif settings.general.branch != 'master' and any(not x['prerelease'] for x in filtered_releases):
continue
elif settings.general.branch != 'master':
filtered_releases.append(release)
if settings.general.branch == 'master':
filtered_releases = filtered_releases[:5]
for i, release in enumerate(filtered_releases):
body = release['body'].replace('- ', '').split('\n')[1:]
filtered_releases[i] = {"body": body,
"name": release['name'],
"date": release['date'][:10],
"prerelease": release['prerelease'],
"current": True if release['name'].lstrip('v') == os.environ["BAZARR_VERSION"]
else False}
2020-02-17 00:38:10 +00:00
except Exception as e:
logging.exception(
'BAZARR cannot parse releases caching file: ' + os.path.join(args.config_dir, 'config', 'releases.txt'))
return jsonify(data=filtered_releases)
2020-02-17 00:38:10 +00:00
2019-12-11 03:20:42 +00:00
class Series(Resource):
@authenticate
2021-03-25 14:22:43 +00:00
def get(self):
start = request.args.get('start') or 0
length = request.args.get('length') or -1
2021-03-25 14:22:43 +00:00
seriesId = request.args.getlist('seriesid[]')
2019-12-11 03:20:42 +00:00
2021-03-25 14:22:43 +00:00
count = database.execute("SELECT COUNT(*) as count FROM table_shows", only_one=True)['count']
2019-12-11 03:20:42 +00:00
2021-03-25 14:22:43 +00:00
if len(seriesId) != 0:
seriesIdList = ','.join(seriesId)
result = database.execute(
f"SELECT * FROM table_shows WHERE sonarrSeriesId in ({seriesIdList}) ORDER BY sortTitle ASC")
else:
result = database.execute("SELECT * FROM table_shows ORDER BY sortTitle ASC LIMIT ? OFFSET ?"
, (length, start))
2019-12-11 03:20:42 +00:00
2021-03-25 14:22:43 +00:00
for item in result:
postprocessSeries(item)
2019-12-15 04:58:51 +00:00
# Add missing subtitles episode count
episodeMissingCount = database.execute("SELECT table_shows.tags, table_episodes.monitored, "
"table_shows.seriesType FROM table_episodes INNER JOIN table_shows "
"on table_shows.sonarrSeriesId = table_episodes.sonarrSeriesId "
"WHERE table_episodes.sonarrSeriesId=? AND missing_subtitles is not "
2021-03-25 14:22:43 +00:00
"null AND missing_subtitles != '[]'" +
get_exclusion_clause('series'), (item['sonarrSeriesId'],))
episodeMissingCount = len(episodeMissingCount)
item.update({"episodeMissingCount": episodeMissingCount})
2019-12-15 04:58:51 +00:00
# Add episode count
episodeFileCount = database.execute("SELECT table_shows.tags, table_episodes.monitored, "
"table_shows.seriesType FROM table_episodes INNER JOIN table_shows on "
"table_shows.sonarrSeriesId = table_episodes.sonarrSeriesId WHERE "
2021-03-25 14:22:43 +00:00
"table_episodes.sonarrSeriesId=?" + get_exclusion_clause('series'),
(item['sonarrSeriesId'],))
episodeFileCount = len(episodeFileCount)
item.update({"episodeFileCount": episodeFileCount})
2020-02-08 13:38:59 +00:00
2021-03-25 14:22:43 +00:00
return jsonify(data=result, total=count)
2019-12-11 03:20:42 +00:00
@authenticate
2020-01-22 04:54:32 +00:00
def post(self):
2021-03-25 14:22:43 +00:00
seriesIdList = request.form.getlist('seriesid')
profileIdList = request.form.getlist('profileid')
2020-01-22 04:54:32 +00:00
2021-03-25 14:22:43 +00:00
for idx in range(len(seriesIdList)):
seriesId = seriesIdList[idx]
profileId = profileIdList[idx]
2020-01-22 04:54:32 +00:00
2021-03-25 14:22:43 +00:00
if profileId in None_Keys:
profileId = None
else:
try:
profileId = int(profileId)
except Exception:
return '', 400
2020-01-22 04:54:32 +00:00
2021-03-25 14:22:43 +00:00
database.execute("UPDATE table_shows SET profileId=? WHERE sonarrSeriesId=?", (profileId, seriesId))
2020-01-22 04:54:32 +00:00
2021-03-25 14:22:43 +00:00
list_missing_subtitles(no=seriesId)
2020-01-22 04:54:32 +00:00
2021-03-25 14:22:43 +00:00
# event_stream(type='series', action='update', series=seriesId)
2020-08-13 13:33:12 +00:00
2020-01-22 04:54:32 +00:00
return '', 204
@authenticate
2021-03-25 14:22:43 +00:00
def patch(self):
seriesid = request.form.get('seriesid')
action = request.form.get('action')
if action == "scan-disk":
series_scan_subtitles(seriesid)
return '', 204
elif action == "search-missing":
series_download_subtitles(seriesid)
return '', 204
elif action == "search-wanted":
wanted_search_missing_subtitles_series()
return '', 204
2020-01-29 01:46:35 +00:00
2021-03-25 14:22:43 +00:00
return '', 400
2020-01-29 01:46:35 +00:00
2019-12-14 17:34:14 +00:00
class Episodes(Resource):
@authenticate
2019-12-14 17:34:14 +00:00
def get(self):
2020-01-12 17:50:27 +00:00
seriesId = request.args.get('seriesid')
episodeId = request.args.get('episodeid')
if episodeId:
result = database.execute("SELECT * FROM table_episodes WHERE sonarrEpisodeId=?", (episodeId,))
elif seriesId:
2020-01-04 05:55:44 +00:00
result = database.execute("SELECT * FROM table_episodes WHERE sonarrSeriesId=? ORDER BY season DESC, "
"episode DESC", (seriesId,))
2019-12-14 17:34:14 +00:00
else:
return "Series ID not provided", 400
profileId = database.execute("SELECT profileId FROM table_shows WHERE sonarrSeriesId = ?", (seriesId,),
only_one=True)['profileId']
desired_languages = str(get_desired_languages(profileId))
2021-03-25 14:22:43 +00:00
desired = ast.literal_eval(desired_languages)
2019-12-14 17:34:14 +00:00
for item in result:
2021-03-25 14:22:43 +00:00
postprocessEpisode(item, desired)
2019-12-14 17:34:14 +00:00
2021-03-25 14:22:43 +00:00
return jsonify(data=result)
2020-01-10 00:54:00 +00:00
2019-12-14 17:34:14 +00:00
2021-03-25 14:22:43 +00:00
# PATCH: Download Subtitles
# POST: Upload Subtitles
# DELETE: Delete Subtitles
class EpisodesSubtitles(Resource):
2020-09-26 12:58:56 +00:00
@authenticate
2021-03-25 14:22:43 +00:00
def patch(self):
sonarrSeriesId = request.args.get('seriesid')
sonarrEpisodeId = request.args.get('episodeid')
episodeInfo = database.execute(
"SELECT title, path, scene_name, audio_language FROM table_episodes WHERE sonarrEpisodeId=?",
(sonarrEpisodeId,), only_one=True)
2021-03-25 14:22:43 +00:00
title = episodeInfo['title']
episodePath = path_mappings.path_replace(episodeInfo['path'])
sceneName = episodeInfo['scene_name']
audio_language = episodeInfo['audio_language']
if sceneName is None: sceneName = "None"
2020-01-07 03:26:28 +00:00
language = request.form.get('language')
hi = request.form.get('hi').capitalize()
forced = request.form.get('forced').capitalize()
2021-03-25 14:22:43 +00:00
2020-01-07 03:26:28 +00:00
providers_list = get_providers()
providers_auth = get_providers_auth()
audio_language_list = get_audio_profile_languages(episode_id=sonarrEpisodeId)
if len(audio_language_list) > 0:
audio_language = audio_language_list[0]['name']
else:
2021-03-25 14:22:43 +00:00
audio_language = None
2020-01-07 03:26:28 +00:00
try:
2021-03-25 14:22:43 +00:00
result = download_subtitle(episodePath, language, audio_language, hi, forced, providers_list,
providers_auth, sceneName, title, 'series')
2020-01-07 03:26:28 +00:00
if result is not None:
message = result[0]
path = result[1]
forced = result[5]
2020-09-10 18:26:37 +00:00
if result[8]:
language_code = result[2] + ":hi"
elif forced:
language_code = result[2] + ":forced"
else:
language_code = result[2]
2020-01-07 03:26:28 +00:00
provider = result[3]
score = result[4]
subs_id = result[6]
2020-07-19 20:02:38 +00:00
subs_path = result[7]
2021-03-25 14:22:43 +00:00
history_log(1, sonarrSeriesId, sonarrEpisodeId, message, path, language_code, provider, score, subs_id,
subs_path)
2020-01-07 03:26:28 +00:00
send_notifications(sonarrSeriesId, sonarrEpisodeId, message)
store_subtitles(path, episodePath)
2020-02-12 17:41:40 +00:00
else:
2020-05-12 12:25:03 +00:00
event_stream(type='episode', action='update', series=int(sonarrSeriesId), episode=int(sonarrEpisodeId))
2020-01-07 03:26:28 +00:00
except OSError:
pass
return '', 204
@authenticate
2020-01-07 03:26:28 +00:00
def post(self):
2021-03-25 14:22:43 +00:00
sonarrSeriesId = request.args.get('seriesid')
sonarrEpisodeId = request.args.get('episodeid')
episodeInfo = database.execute(
"SELECT title, path, scene_name, audio_language FROM table_episodes WHERE sonarrEpisodeId=?",
(sonarrEpisodeId,), only_one=True)
title = episodeInfo['title']
episodePath = path_mappings.path_replace(episodeInfo['path'])
sceneName = episodeInfo['scene_name']
audio_language = episodeInfo['audio_language']
if sceneName is None: sceneName = "None"
2020-01-07 03:26:28 +00:00
language = request.form.get('language')
2020-01-11 04:40:38 +00:00
forced = True if request.form.get('forced') == 'on' else False
2021-03-25 14:22:43 +00:00
subFile = request.files.get('file')
2020-01-07 03:26:28 +00:00
2021-03-25 14:22:43 +00:00
_, ext = os.path.splitext(subFile.filename)
2020-01-07 03:26:28 +00:00
if ext not in SUBTITLE_EXTENSIONS:
raise ValueError('A subtitle of an invalid format was uploaded.')
try:
result = manual_upload_subtitle(path=episodePath,
language=language,
forced=forced,
title=title,
scene_name=sceneName,
media_type='series',
2021-03-25 14:22:43 +00:00
subtitle=subFile,
audio_language=audio_language)
2020-01-07 03:26:28 +00:00
if result is not None:
message = result[0]
path = result[1]
2020-07-19 20:02:38 +00:00
subs_path = result[2]
2020-09-10 18:26:37 +00:00
if forced:
language_code = language + ":forced"
2020-09-10 18:26:37 +00:00
else:
language_code = language
2020-01-07 03:26:28 +00:00
provider = "manual"
score = 360
2021-03-25 14:22:43 +00:00
history_log(4, sonarrSeriesId, sonarrEpisodeId, message, path, language_code, provider, score,
subtitles_path=subs_path)
if not settings.general.getboolean('dont_notify_manual_actions'):
send_notifications(sonarrSeriesId, sonarrEpisodeId, message)
2020-01-07 03:26:28 +00:00
store_subtitles(path, episodePath)
except OSError:
pass
return '', 204
@authenticate
2021-03-25 14:22:43 +00:00
def delete(self):
sonarrSeriesId = request.args.get('seriesid')
sonarrEpisodeId = request.args.get('episodeid')
episodeInfo = database.execute(
"SELECT title, path, scene_name, audio_language FROM table_episodes WHERE sonarrEpisodeId=?",
(sonarrEpisodeId,), only_one=True)
2020-07-19 20:02:38 +00:00
2021-03-25 14:22:43 +00:00
episodePath = path_mappings.path_replace(episodeInfo['path'])
2020-01-23 04:10:33 +00:00
2021-03-25 14:22:43 +00:00
language = request.form.get('language')
forced = request.form.get('forced')
hi = request.form.get('hi')
subtitlesPath = request.form.get('path')
2020-01-23 04:10:33 +00:00
2021-03-25 14:22:43 +00:00
result = delete_subtitles(media_type='series',
language=language,
forced=forced,
hi=hi,
media_path=episodePath,
subtitles_path=subtitlesPath,
sonarr_series_id=sonarrSeriesId,
sonarr_episode_id=sonarrEpisodeId)
2021-03-25 14:22:43 +00:00
return '', 204
2019-12-15 04:58:51 +00:00
class Movies(Resource):
@authenticate
2019-12-15 04:58:51 +00:00
def get(self):
start = request.args.get('start') or 0
length = request.args.get('length') or -1
2021-03-25 14:22:43 +00:00
id = request.args.getlist('radarrid[]')
count = database.execute("SELECT COUNT(*) as count FROM table_movies", only_one=True)['count']
2021-03-25 14:22:43 +00:00
if len(id) != 0:
movieIdList = ','.join(id)
result = database.execute(
f"SELECT * FROM table_movies WHERE radarrId in ({movieIdList}) ORDER BY sortTitle ASC")
2019-12-15 04:58:51 +00:00
else:
2019-12-28 16:43:48 +00:00
result = database.execute("SELECT * FROM table_movies ORDER BY sortTitle ASC LIMIT ? OFFSET ?",
(length, start))
2019-12-15 04:58:51 +00:00
for item in result:
2021-03-25 14:22:43 +00:00
postprocessMovie(item)
2019-12-15 04:58:51 +00:00
2021-03-25 14:22:43 +00:00
return jsonify(data=result, total=count)
2021-03-25 14:22:43 +00:00
@authenticate
def post(self):
radarrIdList = request.form.getlist('radarrid')
profileIdList = request.form.getlist('profileid')
2019-12-15 04:58:51 +00:00
2021-03-25 14:22:43 +00:00
for idx in range(len(radarrIdList)):
radarrId = radarrIdList[idx]
profileId = profileIdList[idx]
2020-02-08 13:38:59 +00:00
2021-03-25 14:22:43 +00:00
if profileId in None_Keys:
profileId = None
else:
try:
profileId = int(profileId)
except Exception:
return '', 400
2021-03-25 14:22:43 +00:00
database.execute("UPDATE table_movies SET profileId=? WHERE radarrId=?", (profileId, radarrId))
2019-12-15 04:58:51 +00:00
2021-03-25 14:22:43 +00:00
list_missing_subtitles_movies(no=radarrId)
2020-02-04 17:57:37 +00:00
2021-03-25 14:22:43 +00:00
# event_stream(type='movies', action='update', movie=radarrId)
2020-02-04 17:57:37 +00:00
2021-03-25 14:22:43 +00:00
return '', 204
2020-02-04 17:57:37 +00:00
2021-03-25 14:22:43 +00:00
@authenticate
def patch(self):
radarrid = request.form.get('radarrid')
action = request.form.get('action')
if action == "scan-disk":
movies_scan_subtitles(radarrid)
return '', 204
elif action == "search-missing":
movies_download_subtitles(radarrid)
return '', 204
elif action == "search-wanted":
wanted_search_missing_subtitles_movies()
return '', 204
2020-02-04 17:57:37 +00:00
2021-03-25 14:22:43 +00:00
return '', 400
2020-02-04 17:57:37 +00:00
2020-08-13 13:33:12 +00:00
2021-03-25 14:22:43 +00:00
"""
:param language: Alpha2 language code
"""
2020-02-04 17:57:37 +00:00
2021-03-25 14:22:43 +00:00
class MoviesSubtitles(Resource):
@authenticate
2021-03-25 14:22:43 +00:00
def patch(self):
# Download
radarrId = request.args.get('radarrid')
2021-03-25 14:22:43 +00:00
movieInfo = database.execute("SELECT title, path, sceneName, audio_language FROM table_movies WHERE radarrId=?",
(radarrId,), only_one=True)
2021-03-25 14:22:43 +00:00
moviePath = path_mappings.path_replace_movie(movieInfo['path'])
sceneName = movieInfo['sceneName']
if sceneName is None: sceneName = 'None'
2021-03-25 14:22:43 +00:00
title = movieInfo['title']
audio_language = movieInfo['audio_language']
2021-03-25 14:22:43 +00:00
language = request.form.get('language')
hi = request.form.get('hi').capitalize()
forced = request.form.get('forced').capitalize()
2021-03-25 14:22:43 +00:00
providers_list = get_providers()
providers_auth = get_providers_auth()
2021-03-25 14:22:43 +00:00
audio_language_list = get_audio_profile_languages(movie_id=radarrId)
if len(audio_language_list) > 0:
audio_language = audio_language_list[0]['name']
else:
audio_language = None
2021-03-25 14:22:43 +00:00
try:
result = download_subtitle(moviePath, language, audio_language, hi, forced, providers_list,
providers_auth, sceneName, title, 'movie')
if result is not None:
message = result[0]
path = result[1]
forced = result[5]
if result[8]:
language_code = result[2] + ":hi"
elif forced:
language_code = result[2] + ":forced"
else:
language_code = result[2]
provider = result[3]
score = result[4]
subs_id = result[6]
subs_path = result[7]
history_log_movie(1, radarrId, message, path, language_code, provider, score, subs_id, subs_path)
send_notifications_movie(radarrId, message)
store_subtitles_movie(path, moviePath)
else:
event_stream(type='movie', action='update', movie=int(radarrId))
except OSError:
pass
2021-03-25 14:22:43 +00:00
return '', 204
@authenticate
2020-02-05 03:50:35 +00:00
def post(self):
2021-03-25 14:22:43 +00:00
# Upload
# TODO: Support Multiply Upload
radarrId = request.args.get('radarrid')
movieInfo = database.execute("SELECT title, path, sceneName, audio_language FROM table_movies WHERE radarrId=?",
(radarrId,), only_one=True)
2020-02-05 03:50:35 +00:00
2021-03-25 14:22:43 +00:00
moviePath = path_mappings.path_replace_movie(movieInfo['path'])
sceneName = movieInfo['sceneName']
if sceneName is None: sceneName = 'None'
title = movieInfo['title']
audioLanguage = movieInfo['audio_language']
language = request.form.get('language')
forced = True if request.form.get('forced') == 'true' else False
subFile = request.files.get('file')
_, ext = os.path.splitext(subFile.filename)
if ext not in SUBTITLE_EXTENSIONS:
raise ValueError('A subtitle of an invalid format was uploaded.')
2020-02-05 03:50:35 +00:00
try:
2021-03-25 14:22:43 +00:00
result = manual_upload_subtitle(path=moviePath,
language=language,
forced=forced,
title=title,
scene_name=sceneName,
media_type='movie',
subtitle=subFile,
audio_language=audioLanguage)
2021-03-25 14:22:43 +00:00
if result is not None:
message = result[0]
path = result[1]
subs_path = result[2]
if forced:
language_code = language + ":forced"
else:
language_code = language
provider = "manual"
score = 120
history_log_movie(4, radarrId, message, path, language_code, provider, score, subtitles_path=subs_path)
if not settings.general.getboolean('dont_notify_manual_actions'):
send_notifications_movie(radarrId, message)
store_subtitles_movie(path, moviePath)
except OSError:
pass
2020-02-05 03:50:35 +00:00
return '', 204
@authenticate
2020-02-07 17:40:43 +00:00
def delete(self):
2021-03-25 14:22:43 +00:00
# Delete
radarrId = request.args.get('radarrid')
movieInfo = database.execute("SELECT path FROM table_movies WHERE radarrId=?", (radarrId,), only_one=True)
moviePath = path_mappings.path_replace_movie(movieInfo['path'])
2020-02-07 17:40:43 +00:00
language = request.form.get('language')
2020-07-19 20:02:38 +00:00
forced = request.form.get('forced')
2020-09-10 18:26:37 +00:00
hi = request.form.get('hi')
2021-03-25 14:22:43 +00:00
subtitlesPath = request.form.get('path')
2020-02-07 17:40:43 +00:00
2020-07-19 20:02:38 +00:00
result = delete_subtitles(media_type='movie',
language=language,
forced=forced,
2020-09-10 18:26:37 +00:00
hi=hi,
2020-07-19 20:02:38 +00:00
media_path=moviePath,
subtitles_path=subtitlesPath,
radarr_id=radarrId)
if result:
return '', 202
else:
return '', 204
2020-02-07 17:40:43 +00:00
2021-03-25 14:22:43 +00:00
class Providers(Resource):
@authenticate
def get(self):
throttled_providers = list_throttled_providers()
providers = list()
for provider in throttled_providers:
providers.append({
"name": provider[0],
"status": provider[1] if provider[1] is not None else "Good",
"retry": provider[2] if provider[2] != "now" else "-"
})
return jsonify(data=providers)
@authenticate
def post(self):
action = request.form.get('action')
if action == 'reset':
reset_throttled_providers()
return '', 204
return '', 400
class ProviderMovies(Resource):
@authenticate
def get(self):
# Manual Search
radarrId = request.args.get('radarrid')
movieInfo = database.execute("SELECT title, path, sceneName, profileId FROM table_movies WHERE radarrId=?",
(radarrId,), only_one=True)
title = movieInfo['title']
moviePath = path_mappings.path_replace_movie(movieInfo['path'])
sceneName = movieInfo['sceneName']
profileId = movieInfo['profileId']
if sceneName is None: sceneName = "None"
providers_list = get_providers()
providers_auth = get_providers_auth()
data = manual_search(moviePath, profileId, providers_list, providers_auth, sceneName, title,
'movie')
if not data:
data = []
return jsonify(data=data)
@authenticate
2020-02-07 17:40:43 +00:00
def post(self):
2021-03-25 14:22:43 +00:00
# Manual Download
radarrId = request.args.get('radarrid')
movieInfo = database.execute("SELECT title, path, sceneName, audio_language FROM table_movies WHERE radarrId=?",
(radarrId,), only_one=True)
title = movieInfo['title']
moviePath = path_mappings.path_replace_movie(movieInfo['path'])
sceneName = movieInfo['sceneName']
if sceneName is None: sceneName = "None"
audio_language = movieInfo['audio_language']
2020-02-07 17:40:43 +00:00
language = request.form.get('language')
hi = request.form.get('hi').capitalize()
forced = request.form.get('forced').capitalize()
2021-03-25 14:22:43 +00:00
selected_provider = request.form.get('provider')
subtitle = request.form.get('subtitle')
2020-02-07 17:40:43 +00:00
providers_auth = get_providers_auth()
audio_language_list = get_audio_profile_languages(movie_id=radarrId)
if len(audio_language_list) > 0:
audio_language = audio_language_list[0]['name']
else:
audio_language = 'None'
2020-02-07 17:40:43 +00:00
try:
2021-03-25 14:22:43 +00:00
result = manual_download_subtitle(moviePath, language, audio_language, hi, forced, subtitle,
selected_provider, providers_auth, sceneName, title, 'movie')
2020-02-07 17:40:43 +00:00
if result is not None:
message = result[0]
path = result[1]
forced = result[5]
2020-09-10 18:26:37 +00:00
if result[8]:
language_code = result[2] + ":hi"
elif forced:
language_code = result[2] + ":forced"
else:
language_code = result[2]
2020-02-07 17:40:43 +00:00
provider = result[3]
score = result[4]
subs_id = result[6]
2020-07-19 20:02:38 +00:00
subs_path = result[7]
2021-03-25 14:22:43 +00:00
history_log_movie(2, radarrId, message, path, language_code, provider, score, subs_id, subs_path)
if not settings.general.getboolean('dont_notify_manual_actions'):
send_notifications_movie(radarrId, message)
2020-02-07 17:40:43 +00:00
store_subtitles_movie(path, moviePath)
except OSError:
pass
return '', 204
2021-03-25 14:22:43 +00:00
class ProviderEpisodes(Resource):
@authenticate
2021-03-25 14:22:43 +00:00
def get(self):
# Manual Search
sonarrEpisodeId = request.args.get('episodeid')
episodeInfo = database.execute(
"SELECT title, path, scene_name, audio_language, sonarrSeriesId FROM table_episodes WHERE sonarrEpisodeId=?",
(sonarrEpisodeId,), only_one=True)
title = episodeInfo['title']
episodePath = path_mappings.path_replace(episodeInfo['path'])
sceneName = episodeInfo['scene_name']
seriesId = episodeInfo['sonarrSeriesId']
seriesInfo = database.execute("SELECT profileId FROM table_shows WHERE sonarrSeriesId=?", (seriesId,),
only_one=True)
profileId = seriesInfo['profileId']
if sceneName is None: sceneName = "None"
2020-02-07 17:40:43 +00:00
providers_list = get_providers()
providers_auth = get_providers_auth()
2021-03-25 14:22:43 +00:00
data = manual_search(episodePath, profileId, providers_list, providers_auth, sceneName, title,
'series')
if not data:
data = []
2021-03-25 14:22:43 +00:00
return jsonify(data=data)
2020-02-07 17:40:43 +00:00
@authenticate
2020-02-07 17:40:43 +00:00
def post(self):
2021-03-25 14:22:43 +00:00
# Manual Download
sonarrSeriesId = request.args.get('seriesid')
sonarrEpisodeId = request.args.get('episodeid')
episodeInfo = database.execute("SELECT title, path, scene_name FROM table_episodes WHERE sonarrEpisodeId=?",
(sonarrEpisodeId,), only_one=True)
title = episodeInfo['title']
episodePath = path_mappings.path_replace(episodeInfo['path'])
sceneName = episodeInfo['scene_name']
if sceneName is None: sceneName = "None"
2020-02-07 17:40:43 +00:00
language = request.form.get('language')
hi = request.form.get('hi').capitalize()
forced = request.form.get('forced').capitalize()
selected_provider = request.form.get('provider')
subtitle = request.form.get('subtitle')
providers_auth = get_providers_auth()
2021-03-25 14:22:43 +00:00
audio_language_list = get_audio_profile_languages(episode_id=sonarrEpisodeId)
if len(audio_language_list) > 0:
audio_language = audio_language_list[0]['name']
else:
audio_language = 'None'
2020-02-07 17:40:43 +00:00
try:
2021-03-25 14:22:43 +00:00
result = manual_download_subtitle(episodePath, language, audio_language, hi, forced, subtitle,
selected_provider, providers_auth, sceneName, title, 'series')
2020-02-07 17:40:43 +00:00
if result is not None:
message = result[0]
path = result[1]
forced = result[5]
2020-09-10 18:26:37 +00:00
if result[8]:
language_code = result[2] + ":hi"
elif forced:
language_code = result[2] + ":forced"
else:
language_code = result[2]
2020-02-07 17:40:43 +00:00
provider = result[3]
score = result[4]
subs_id = result[6]
2020-07-19 20:02:38 +00:00
subs_path = result[7]
2021-03-25 14:22:43 +00:00
history_log(2, sonarrSeriesId, sonarrEpisodeId, message, path, language_code, provider, score, subs_id,
subs_path)
if not settings.general.getboolean('dont_notify_manual_actions'):
2021-03-25 14:22:43 +00:00
send_notifications(sonarrSeriesId, sonarrEpisodeId, message)
store_subtitles(path, episodePath)
2020-02-07 17:40:43 +00:00
return result, 201
except OSError:
pass
return '', 204
2021-03-25 14:22:43 +00:00
class EpisodesHistory(Resource):
@authenticate
def get(self):
start = request.args.get('start') or 0
length = request.args.get('length') or -1
2021-03-25 14:22:43 +00:00
episodeid = request.args.get('episodeid')
upgradable_episodes_not_perfect = []
if settings.general.getboolean('upgrade_subs'):
days_to_upgrade_subs = settings.general.days_to_upgrade_subs
minimum_timestamp = ((datetime.datetime.now() - timedelta(days=int(days_to_upgrade_subs))) -
datetime.datetime(1970, 1, 1)).total_seconds()
if settings.general.getboolean('upgrade_manual'):
query_actions = [1, 2, 3, 6]
else:
query_actions = [1, 3]
upgradable_episodes = database.execute(
"SELECT video_path, MAX(timestamp) as timestamp, score, table_shows.tags, table_episodes.monitored, "
2020-07-19 20:02:38 +00:00
"table_shows.seriesType FROM table_history INNER JOIN table_episodes on "
"table_episodes.sonarrEpisodeId = table_history.sonarrEpisodeId INNER JOIN table_shows on "
"table_shows.sonarrSeriesId = table_episodes.sonarrSeriesId WHERE action IN (" +
2021-03-25 14:22:43 +00:00
','.join(map(str, query_actions)) + ") AND timestamp > ? AND score is not null" +
get_exclusion_clause('series') + " GROUP BY table_history.video_path", (minimum_timestamp,))
for upgradable_episode in upgradable_episodes:
if upgradable_episode['timestamp'] > minimum_timestamp:
try:
int(upgradable_episode['score'])
except ValueError:
pass
else:
if int(upgradable_episode['score']) < 360:
upgradable_episodes_not_perfect.append(upgradable_episode)
2021-03-25 14:22:43 +00:00
# TODO: Find a better solution
query_limit = ""
if episodeid:
query_limit = f"AND table_episodes.sonarrEpisodeId={episodeid}"
episode_history = database.execute("SELECT table_shows.title as seriesTitle, table_episodes.monitored, "
"table_episodes.season || 'x' || table_episodes.episode as episode_number, "
"table_episodes.title as episodeTitle, table_history.timestamp, table_history.subs_id, "
"table_history.description, table_history.sonarrSeriesId, table_episodes.path, "
"table_history.language, table_history.score, table_shows.tags, table_history.action, "
"table_history.subtitles_path, table_history.sonarrEpisodeId, table_history.provider, "
"table_shows.seriesType FROM table_history LEFT JOIN table_shows on "
"table_shows.sonarrSeriesId = table_history.sonarrSeriesId LEFT JOIN table_episodes on "
"table_episodes.sonarrEpisodeId = table_history.sonarrEpisodeId WHERE "
"table_episodes.title is not NULL " + query_limit + " ORDER BY timestamp DESC LIMIT ? OFFSET ?",
(length, start))
blacklist_db = database.execute("SELECT provider, subs_id FROM table_blacklist ")
2021-03-25 14:22:43 +00:00
for item in episode_history:
# Mark episode as upgradable or not
item.update({"upgradable": False})
2021-03-25 14:22:43 +00:00
if {"video_path": str(item['path']), "timestamp": float(item['timestamp']), "score": str(item['score']),
"tags": str(item['tags']), "monitored": str(item['monitored']),
"seriesType": str(item['seriesType'])} in upgradable_episodes_not_perfect:
if os.path.isfile(path_mappings.path_replace(item['subtitles_path'])):
item.update({"upgradable": True})
2021-03-25 14:22:43 +00:00
del item['path']
2021-03-25 14:22:43 +00:00
postprocessEpisode(item)
2021-03-25 14:22:43 +00:00
if item['score']:
item['score'] = str(round((int(item['score']) * 100 / 360), 2)) + "%"
2020-07-19 20:02:38 +00:00
2021-03-25 14:22:43 +00:00
# Make timestamp pretty
if item['timestamp']:
item["raw_timestamp"] = int(item['timestamp']);
item['timestamp'] = pretty.date(item["raw_timestamp"])
2020-07-19 20:02:38 +00:00
# Check if subtitles is blacklisted
2021-03-25 14:22:43 +00:00
item.update({"blacklisted": False})
2020-07-19 20:02:38 +00:00
if item['action'] not in [0, 4, 5]:
2021-03-25 14:22:43 +00:00
for blacklisted_item in blacklist_db:
if blacklisted_item['provider'] == item['provider'] and blacklisted_item['subs_id'] == item[
'subs_id']:
item.update({"blacklisted": True})
break
2020-07-19 20:02:38 +00:00
2021-03-25 14:22:43 +00:00
count = database.execute("SELECT COUNT(*) as count FROM table_history LEFT JOIN table_episodes "
"on table_episodes.sonarrEpisodeId = table_history.sonarrEpisodeId WHERE "
"table_episodes.title is not NULL", only_one=True)['count']
2021-03-25 14:22:43 +00:00
return jsonify(data=episode_history, total=count)
2021-03-25 14:22:43 +00:00
class MoviesHistory(Resource):
@authenticate
def get(self):
start = request.args.get('start') or 0
length = request.args.get('length') or -1
2021-03-25 14:22:43 +00:00
radarrid = request.args.get('radarrid')
2019-12-16 17:46:03 +00:00
upgradable_movies = []
upgradable_movies_not_perfect = []
if settings.general.getboolean('upgrade_subs'):
days_to_upgrade_subs = settings.general.days_to_upgrade_subs
minimum_timestamp = ((datetime.datetime.now() - timedelta(days=int(days_to_upgrade_subs))) -
datetime.datetime(1970, 1, 1)).total_seconds()
if settings.general.getboolean('upgrade_manual'):
query_actions = [1, 2, 3, 6]
else:
query_actions = [1, 3]
upgradable_movies = database.execute(
"SELECT video_path, MAX(timestamp) as timestamp, score, tags, monitored FROM table_history_movie "
"INNER JOIN table_movies on table_movies.radarrId=table_history_movie.radarrId WHERE action IN (" +
','.join(map(str, query_actions)) + ") AND timestamp > ? AND score is not NULL" +
get_exclusion_clause('movie') + " GROUP BY video_path", (minimum_timestamp,))
for upgradable_movie in upgradable_movies:
if upgradable_movie['timestamp'] > minimum_timestamp:
try:
int(upgradable_movie['score'])
except ValueError:
pass
else:
if int(upgradable_movie['score']) < 120:
upgradable_movies_not_perfect.append(upgradable_movie)
2021-03-25 14:22:43 +00:00
# TODO: Find a better solution
query_limit = ""
if radarrid:
query_limit = f"AND table_movies.radarrid={radarrid}"
2021-03-25 14:22:43 +00:00
movie_history = database.execute(
"SELECT table_history_movie.action, table_movies.title, table_history_movie.timestamp, "
"table_history_movie.description, table_history_movie.radarrId, table_movies.monitored,"
"table_history_movie.video_path as path, table_history_movie.language, table_movies.tags, "
"table_history_movie.score, table_history_movie.subs_id, table_history_movie.provider, "
"table_history_movie.subtitles_path, table_history_movie.subtitles_path FROM "
"table_history_movie LEFT JOIN table_movies on table_movies.radarrId = "
"table_history_movie.radarrId WHERE table_movies.title is not NULL " + query_limit + " ORDER BY timestamp DESC LIMIT ? OFFSET ?",
(length, start))
blacklist_db = database.execute("SELECT provider, subs_id FROM table_blacklist_movie")
for item in movie_history:
# Mark movies as upgradable or not
item.update({"upgradable": False})
2021-03-25 14:22:43 +00:00
if {"video_path": str(item['path']), "timestamp": float(item['timestamp']), "score": str(item['score']),
"tags": str(item['tags']), "monitored": str(item['monitored'])} in upgradable_movies_not_perfect:
if os.path.isfile(path_mappings.path_replace_movie(item['subtitles_path'])):
item.update({"upgradable": True})
2021-03-25 14:22:43 +00:00
del item['path']
2021-03-25 14:22:43 +00:00
postprocessMovie(item)
2021-03-25 14:22:43 +00:00
if item['score']:
item['score'] = str(round((int(item['score']) * 100 / 120), 2)) + "%"
2021-03-25 14:22:43 +00:00
# Make timestamp pretty
if item['timestamp']:
item["raw_timestamp"] = int(item['timestamp']);
item['timestamp'] = pretty.date(item["raw_timestamp"])
2020-07-19 20:02:38 +00:00
# Check if subtitles is blacklisted
2021-03-25 14:22:43 +00:00
item.update({"blacklisted": False})
2020-07-19 20:02:38 +00:00
if item['action'] not in [0, 4, 5]:
2021-03-25 14:22:43 +00:00
for blacklisted_item in blacklist_db:
if blacklisted_item['provider'] == item['provider'] and blacklisted_item['subs_id'] == item[
'subs_id']:
item.update({"blacklisted": True})
break
2020-07-19 20:02:38 +00:00
2021-03-25 14:22:43 +00:00
count = database.execute("SELECT COUNT(*) as count FROM table_history_movie LEFT JOIN table_movies on "
"table_movies.radarrId = table_history_movie.radarrId WHERE table_movies.title "
"is not NULL", only_one=True)['count']
2020-07-19 20:02:38 +00:00
2021-03-25 14:22:43 +00:00
return jsonify(data=movie_history, total=count)
2020-06-19 13:18:48 +00:00
class HistoryStats(Resource):
@authenticate
def get(self):
timeframe = request.args.get('timeframe') or 'month'
action = request.args.get('action') or 'All'
provider = request.args.get('provider') or 'All'
language = request.args.get('language') or 'All'
history_where_clause = " WHERE id"
# timeframe must be in ['week', 'month', 'trimester', 'year']
if timeframe == 'year':
days = 364
elif timeframe == 'trimester':
days = 90
elif timeframe == 'month':
days = 30
elif timeframe == 'week':
days = 6
history_where_clause += " AND datetime(timestamp, 'unixepoch') BETWEEN datetime('now', '-" + str(days) + \
" days') AND datetime('now', 'localtime')"
if action != 'All':
history_where_clause += " AND action = " + action
else:
history_where_clause += " AND action IN (1,2,3)"
if provider != 'All':
history_where_clause += " AND provider = '" + provider + "'"
if language != 'All':
history_where_clause += " AND language = '" + language + "'"
data_series = database.execute("SELECT strftime ('%Y-%m-%d',datetime(timestamp, 'unixepoch')) as date, "
"COUNT(id) as count FROM table_history" + history_where_clause +
" GROUP BY strftime ('%Y-%m-%d',datetime(timestamp, 'unixepoch'))")
data_movies = database.execute("SELECT strftime ('%Y-%m-%d',datetime(timestamp, 'unixepoch')) as date, "
"COUNT(id) as count FROM table_history_movie" + history_where_clause +
" GROUP BY strftime ('%Y-%m-%d',datetime(timestamp, 'unixepoch'))")
for dt in rrule.rrule(rrule.DAILY,
dtstart=datetime.datetime.now() - datetime.timedelta(days=days),
until=datetime.datetime.now()):
if not any(d['date'] == dt.strftime('%Y-%m-%d') for d in data_series):
data_series.append({'date': dt.strftime('%Y-%m-%d'), 'count': 0})
if not any(d['date'] == dt.strftime('%Y-%m-%d') for d in data_movies):
data_movies.append({'date': dt.strftime('%Y-%m-%d'), 'count': 0})
sorted_data_series = sorted(data_series, key=lambda i: i['date'])
sorted_data_movies = sorted(data_movies, key=lambda i: i['date'])
2021-03-25 14:22:43 +00:00
return jsonify(series=sorted_data_series, movies=sorted_data_movies)
2020-06-19 13:18:48 +00:00
2021-03-25 14:22:43 +00:00
# GET: Get Wanted Episodes
class EpisodesWanted(Resource):
@authenticate
def get(self):
start = request.args.get('start') or 0
length = request.args.get('length') or -1
data = database.execute("SELECT table_shows.title as seriesTitle, table_episodes.monitored, "
"table_episodes.season || 'x' || table_episodes.episode as episode_number, "
"table_episodes.title as episodeTitle, table_episodes.missing_subtitles, "
2021-03-25 14:22:43 +00:00
"table_episodes.sonarrSeriesId, "
"table_episodes.sonarrEpisodeId, table_episodes.scene_name as sceneName, table_shows.tags, "
"table_episodes.failedAttempts, table_shows.seriesType FROM table_episodes INNER JOIN "
"table_shows on table_shows.sonarrSeriesId = table_episodes.sonarrSeriesId WHERE "
"table_episodes.missing_subtitles != '[]'" + get_exclusion_clause('series') +
2021-01-22 15:36:23 +00:00
" ORDER BY table_episodes._rowid_ DESC LIMIT ? OFFSET ?", (length, start))
for item in data:
2021-03-25 14:22:43 +00:00
postprocessEpisode(item)
count = database.execute("SELECT COUNT(*) as count, table_shows.tags, table_shows.seriesType FROM "
"table_episodes INNER JOIN table_shows on table_shows.sonarrSeriesId = "
"table_episodes.sonarrSeriesId WHERE missing_subtitles != '[]'" +
get_exclusion_clause('series'), only_one=True)['count']
2021-03-25 14:22:43 +00:00
return jsonify(data=data, total=count)
2021-03-25 14:22:43 +00:00
# GET: Get Wanted Movies
class MoviesWanted(Resource):
@authenticate
def get(self):
start = request.args.get('start') or 0
length = request.args.get('length') or -1
2021-03-25 14:22:43 +00:00
data = database.execute("SELECT title, missing_subtitles, radarrId, sceneName, "
"failedAttempts, tags, monitored FROM table_movies WHERE missing_subtitles != '[]'" +
2021-03-25 14:22:43 +00:00
get_exclusion_clause('movie') +
" ORDER BY _rowid_ DESC LIMIT ? OFFSET ?", (length, start))
for item in data:
2021-03-25 14:22:43 +00:00
postprocessMovie(item)
2021-03-25 14:22:43 +00:00
count = database.execute("SELECT COUNT(*) as count FROM table_movies WHERE missing_subtitles != '[]'" +
get_exclusion_clause('movie'), only_one=True)['count']
2021-03-25 14:22:43 +00:00
return jsonify(data=data, total=count)
2020-02-13 04:16:22 +00:00
2021-03-25 14:22:43 +00:00
# GET: get blacklist
# POST: add blacklist
# DELETE: remove blacklist
class EpisodesBlacklist(Resource):
2020-07-19 20:02:38 +00:00
@authenticate
def get(self):
start = request.args.get('start') or 0
length = request.args.get('length') or -1
data = database.execute("SELECT table_shows.title as seriesTitle, table_episodes.season || 'x' || "
"table_episodes.episode as episode_number, table_episodes.title as episodeTitle, "
"table_episodes.sonarrSeriesId, table_blacklist.provider, table_blacklist.subs_id, "
"table_blacklist.language, table_blacklist.timestamp FROM table_blacklist INNER JOIN "
"table_episodes on table_episodes.sonarrEpisodeId = table_blacklist.sonarr_episode_id "
"INNER JOIN table_shows on table_shows.sonarrSeriesId = "
"table_blacklist.sonarr_series_id ORDER BY table_blacklist.timestamp DESC LIMIT ? "
"OFFSET ?", (length, start))
for item in data:
# Make timestamp pretty
item.update({'timestamp': pretty.date(datetime.datetime.fromtimestamp(item['timestamp']))})
2021-03-25 14:22:43 +00:00
postprocessEpisode(item)
2020-07-19 20:02:38 +00:00
2021-03-25 14:22:43 +00:00
return jsonify(data=data)
2020-07-19 20:02:38 +00:00
@authenticate
def post(self):
2021-03-25 14:22:43 +00:00
sonarr_series_id = int(request.args.get('seriesid'))
sonarr_episode_id = int(request.args.get('episodeid'))
2020-07-19 20:02:38 +00:00
provider = request.form.get('provider')
subs_id = request.form.get('subs_id')
language = request.form.get('language')
2021-03-25 14:22:43 +00:00
episodeInfo = database.execute("SELECT path FROM table_episodes WHERE sonarrEpisodeId=?",
(sonarr_episode_id,), only_one=True)
media_path = episodeInfo['path']
2020-07-19 20:02:38 +00:00
subtitles_path = request.form.get('subtitles_path')
blacklist_log(sonarr_series_id=sonarr_series_id,
sonarr_episode_id=sonarr_episode_id,
provider=provider,
subs_id=subs_id,
2021-03-25 14:22:43 +00:00
language=language)
2020-07-19 20:02:38 +00:00
delete_subtitles(media_type='series',
language=alpha3_from_alpha2(language),
2021-03-25 14:22:43 +00:00
forced=False,
hi=False,
media_path=media_path,
subtitles_path=subtitles_path,
2020-07-19 20:02:38 +00:00
sonarr_series_id=sonarr_series_id,
sonarr_episode_id=sonarr_episode_id)
episode_download_subtitles(sonarr_episode_id)
event_stream(type='episodeHistory')
return '', 200
@authenticate
def delete(self):
2021-03-25 14:22:43 +00:00
if request.args.get("all") == "true":
blacklist_delete_all()
else:
provider = request.form.get('provider')
subs_id = request.form.get('subs_id')
blacklist_delete(provider=provider, subs_id=subs_id)
return '', 204
2020-07-19 20:02:38 +00:00
2021-03-25 14:22:43 +00:00
# GET: get blacklist
# POST: add blacklist
# DELETE: remove blacklist
class MoviesBlacklist(Resource):
2020-07-19 20:02:38 +00:00
@authenticate
def get(self):
start = request.args.get('start') or 0
length = request.args.get('length') or -1
data = database.execute("SELECT table_movies.title, table_movies.radarrId, table_blacklist_movie.provider, "
"table_blacklist_movie.subs_id, table_blacklist_movie.language, "
"table_blacklist_movie.timestamp FROM table_blacklist_movie INNER JOIN "
"table_movies on table_movies.radarrId = table_blacklist_movie.radarr_id "
"ORDER BY table_blacklist_movie.timestamp DESC LIMIT ? "
"OFFSET ?", (length, start))
for item in data:
2021-03-25 14:22:43 +00:00
postprocessMovie(item)
2020-07-19 20:02:38 +00:00
# Make timestamp pretty
item.update({'timestamp': pretty.date(datetime.datetime.fromtimestamp(item['timestamp']))})
2021-03-25 14:22:43 +00:00
return jsonify(data=data)
2020-07-19 20:02:38 +00:00
@authenticate
def post(self):
2021-03-25 14:22:43 +00:00
radarr_id = int(request.args.get('radarrid'))
2020-07-19 20:02:38 +00:00
provider = request.form.get('provider')
subs_id = request.form.get('subs_id')
language = request.form.get('language')
2021-03-25 14:22:43 +00:00
data = database.execute("SELECT title, radarrId, provider, subs_id, path"
"timestamp FROM table_movies WHERE radarrId=?", (radarr_id), only_one=True)
media_path = data['path']
2020-07-19 20:02:38 +00:00
subtitles_path = request.form.get('subtitles_path')
blacklist_log_movie(radarr_id=radarr_id,
provider=provider,
subs_id=subs_id,
2021-03-25 14:22:43 +00:00
language=language)
2020-07-19 20:02:38 +00:00
delete_subtitles(media_type='movie',
language=alpha3_from_alpha2(language),
forced=forced,
2020-09-10 18:26:37 +00:00
hi=hi,
2021-03-25 14:22:43 +00:00
media_path=media_path,
subtitles_path=subtitles_path,
2020-07-19 20:02:38 +00:00
radarr_id=radarr_id)
movies_download_subtitles(radarr_id)
event_stream(type='movieHistory')
return '', 200
@authenticate
def delete(self):
2021-03-25 14:22:43 +00:00
if request.args.get("all") == "true":
blacklist_delete_all_movie()
else:
provider = request.form.get('provider')
subs_id = request.form.get('subs_id')
blacklist_delete_movie(provider=provider, subs_id=subs_id)
2020-07-19 20:02:38 +00:00
return '', 200
2021-03-25 14:22:43 +00:00
class Subtitles(Resource):
2020-07-19 20:02:38 +00:00
@authenticate
2021-03-25 14:22:43 +00:00
def patch(self):
action = request.args.get('action')
2020-07-19 20:02:38 +00:00
language = request.form.get('language')
2021-03-25 14:22:43 +00:00
subtitles_path = request.form.get('path')
media_type = request.form.get('type')
id = request.form.get('id')
if media_type == 'episode':
subtitles_path = path_mappings.path_replace(subtitles_path)
metadata = database.execute("SELECT path, sonarrSeriesId FROM table_episodes"
" WHERE sonarrEpisodeId = ?", (id,), only_one=True)
video_path = path_mappings.path_replace(metadata['path'])
else:
2021-03-25 14:22:43 +00:00
subtitles_path = path_mappings.path_replace_movie(subtitles_path)
metadata = database.execute("SELECT path FROM table_movies WHERE radarrId = ?",
(id,), only_one=True)
video_path = path_mappings.path_replace_movie(metadata['path'])
if action == 'sync':
if media_type == 'episode':
subsync.sync(video_path=video_path, srt_path=subtitles_path,
srt_lang=language, media_type='series', sonarr_series_id=metadata['sonarrSeriesId'],
sonarr_episode_id=int(id))
else:
2021-03-25 14:22:43 +00:00
subsync.sync(video_path=video_path, srt_path=subtitles_path,
srt_lang=language, media_type='movies', radarr_id=id)
elif action == 'translate':
dest_language = language
forced = True if request.form.get('forced') == 'true' else False
hi = True if request.form.get('hi') == 'true' else False
result = translate_subtitles_file(video_path=video_path, source_srt_file=subtitles_path,
to_lang=dest_language,
forced=forced, hi=hi)
if result:
if media_type == 'episode':
store_subtitles(path_mappings.path_replace_reverse(video_path), video_path)
else:
store_subtitles_movie(path_mappings.path_replace_reverse_movie(video_path), video_path)
return '', 200
else:
return '', 404
else:
subtitles_apply_mods(language, subtitles_path, [action])
2020-08-05 12:43:53 +00:00
2021-03-25 14:22:43 +00:00
return '', 204
2020-08-05 12:43:53 +00:00
2021-03-25 14:22:43 +00:00
class SubtitleNameInfo(Resource):
@authenticate
2021-03-25 14:22:43 +00:00
def get(self):
names = request.args.getlist('filenames[]')
results = []
for name in names:
opts = dict()
opts['type'] = 'episode'
result = guessit(name, options=opts)
result['filename'] = name
if 'subtitle_language' in result:
result['subtitle_language'] = str(result['subtitle_language'])
2021-03-25 14:22:43 +00:00
if 'episode' in result:
result['episode'] = result['episode']
else:
result['episode'] = 0
2021-03-25 14:22:43 +00:00
if 'season' in result:
result['season'] = result['season']
else:
2021-03-25 14:22:43 +00:00
result['season'] = 0
results.append(result)
return jsonify(data=results)
class BrowseBazarrFS(Resource):
@authenticate
def get(self):
2020-06-26 15:28:49 +00:00
path = request.args.get('path') or ''
2020-06-25 10:33:48 +00:00
data = []
2021-03-25 14:22:43 +00:00
try:
result = browse_bazarr_filesystem(path)
if result is None:
raise ValueError
except Exception:
return jsonify([])
2020-06-25 10:33:48 +00:00
for item in result['directories']:
2021-03-25 14:22:43 +00:00
data.append({'name': item['name'], 'children': True, 'path': item['path']})
2020-06-26 15:28:49 +00:00
return jsonify(data)
class BrowseSonarrFS(Resource):
@authenticate
def get(self):
2020-06-26 15:28:49 +00:00
path = request.args.get('path') or ''
2020-06-25 10:33:48 +00:00
data = []
2021-03-25 14:22:43 +00:00
try:
result = browse_sonarr_filesystem(path)
if result is None:
raise ValueError
except Exception:
return jsonify([])
2020-06-25 10:33:48 +00:00
for item in result['directories']:
2021-03-25 14:22:43 +00:00
data.append({'name': item['name'], 'children': True, 'path': item['path']})
2020-06-26 15:28:49 +00:00
return jsonify(data)
class BrowseRadarrFS(Resource):
@authenticate
def get(self):
2020-06-26 15:28:49 +00:00
path = request.args.get('path') or ''
2020-06-25 10:33:48 +00:00
data = []
2021-03-25 14:22:43 +00:00
try:
result = browse_radarr_filesystem(path)
if result is None:
raise ValueError
except Exception:
return jsonify([])
2020-06-25 10:33:48 +00:00
for item in result['directories']:
2021-03-25 14:22:43 +00:00
data.append({'name': item['name'], 'children': True, 'path': item['path']})
2020-06-26 15:28:49 +00:00
return jsonify(data)
2021-03-25 14:22:43 +00:00
api.add_resource(BadgesSeries, '/badges')
2020-02-04 17:57:37 +00:00
2021-03-25 14:22:43 +00:00
api.add_resource(Providers, '/providers')
api.add_resource(ProviderMovies, '/providers/movies')
api.add_resource(ProviderEpisodes, '/providers/episodes')
2020-03-29 13:58:32 +00:00
2021-03-25 14:22:43 +00:00
api.add_resource(System, '/system')
api.add_resource(Searches, "/system/searches")
api.add_resource(SystemAccount, '/system/account')
api.add_resource(SystemTasks, '/system/tasks')
api.add_resource(SystemLogs, '/system/logs')
api.add_resource(SystemStatus, '/system/status')
api.add_resource(SystemReleases, '/system/releases')
api.add_resource(SystemSettings, '/system/settings')
api.add_resource(Languages, '/system/languages')
api.add_resource(LanguagesProfiles, '/system/languages/profiles')
api.add_resource(Notifications, '/system/notifications')
2020-04-30 12:38:05 +00:00
2021-03-25 14:22:43 +00:00
api.add_resource(Subtitles, '/subtitles')
api.add_resource(SubtitleNameInfo, '/subtitles/info')
2020-09-26 12:58:56 +00:00
2019-12-28 05:52:00 +00:00
api.add_resource(Series, '/series')
2021-03-25 14:22:43 +00:00
2019-12-28 05:52:00 +00:00
api.add_resource(Episodes, '/episodes')
2021-03-25 14:22:43 +00:00
api.add_resource(EpisodesWanted, '/episodes/wanted')
api.add_resource(EpisodesSubtitles, '/episodes/subtitles')
api.add_resource(EpisodesHistory, '/episodes/history')
api.add_resource(EpisodesBlacklist, '/episodes/blacklist')
2020-02-04 17:57:37 +00:00
2019-12-28 05:52:00 +00:00
api.add_resource(Movies, '/movies')
2021-03-25 14:22:43 +00:00
api.add_resource(MoviesWanted, '/movies/wanted')
api.add_resource(MoviesSubtitles, '/movies/subtitles')
api.add_resource(MoviesHistory, '/movies/history')
api.add_resource(MoviesBlacklist, '/movies/blacklist')
api.add_resource(HistoryStats, '/history/stats')
2021-03-25 14:22:43 +00:00
api.add_resource(BrowseBazarrFS, '/files')
api.add_resource(BrowseSonarrFS, '/files/sonarr')
api.add_resource(BrowseRadarrFS, '/files/radarr')