bazarr/bazarr/api.py

1851 lines
81 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 os
import ast
from datetime import timedelta
import datetime
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
import io
2020-03-29 13:58:32 +00:00
import re
2020-05-06 00:09:25 +00:00
import json
2019-12-11 03:20:42 +00:00
2020-02-17 00:38:10 +00:00
from get_args import args
2020-03-11 10:58:45 +00:00
from config import settings, base_url, save_settings
2019-12-11 03:20:42 +00:00
from init import *
import logging
from database import database, filter_exclusions
2020-05-19 13:27:13 +00:00
from helper import path_mappings
2020-01-07 03:26:28 +00:00
from get_languages import language_from_alpha3, language_from_alpha2, alpha2_from_alpha3, alpha2_from_language, \
alpha3_from_language, alpha3_from_alpha2
from get_subtitle import download_subtitle, series_download_subtitles, movies_download_subtitles, \
2020-02-13 04:16:22 +00:00
manual_search, manual_download_subtitle, manual_upload_subtitle, wanted_search_missing_subtitles_series, \
2020-07-19 20:02:38 +00:00
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
2020-04-30 12:38:05 +00:00
from get_providers import get_providers, get_providers_auth, list_throttled_providers, reset_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
from subliminal_patch.core import SUBTITLE_EXTENSIONS
2020-06-25 10:33:48 +00:00
from flask import Flask, jsonify, request, Response, Blueprint, url_for, make_response
from flask_restful import Resource, Api, abort
from functools import wraps
2019-12-31 19:02:49 +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
def authenticate(actual_method):
@wraps(actual_method)
def wrapper(*args, **kwargs):
apikey_settings = settings.auth.apikey
apikey_get = request.args.get('apikey')
apikey_post = request.form.get('apikey')
if apikey_settings in [apikey_get, apikey_post]:
return actual_method(*args, **kwargs)
return abort(401, message="Unauthorized")
return wrapper
2020-04-16 11:52:35 +00:00
class Shutdown(Resource):
@authenticate
def get(self):
2020-05-18 02:22:36 +00:00
from server import webserver
2020-05-18 01:51:16 +00:00
webserver.shutdown()
2020-04-16 11:52:35 +00:00
class Restart(Resource):
@authenticate
def get(self):
2020-05-18 02:22:36 +00:00
from server import webserver
2020-05-18 01:51:16 +00:00
webserver.restart()
2020-04-16 11:52:35 +00:00
2019-12-15 04:58:51 +00:00
class Badges(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 != '[]'")
missing_episodes = filter_exclusions(missing_episodes, '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 != '[]'")
missing_movies = filter_exclusions(missing_movies, 'movie')
missing_movies = len(missing_movies)
2020-05-20 03:20:35 +00:00
2019-12-15 04:58:51 +00:00
result = {
"missing_episodes": missing_episodes,
"missing_movies": missing_movies,
2019-12-15 04:58:51 +00:00
"throttled_providers": len(eval(str(settings.general.throtteled_providers)))
}
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):
enabled = request.args.get('enabled')
if enabled.lower() in ['true', '1']:
2020-04-24 10:56:03 +00:00
result = database.execute("SELECT * FROM table_settings_languages WHERE enabled=1 ORDER BY name")
2020-01-22 04:54:32 +00:00
else:
2020-04-24 10:56:03 +00:00
result = database.execute("SELECT * FROM table_settings_languages ORDER BY name")
2020-01-22 04:54:32 +00:00
return jsonify(result)
2020-05-06 00:09:25 +00:00
class Notifications(Resource):
@authenticate
def get(self):
result = database.execute("SELECT * FROM table_settings_notifier ORDER BY name")
return jsonify(data=result)
@authenticate
def post(self):
notification_providers = json.loads(request.form.get('notification_providers'))
for item in notification_providers:
database.execute("UPDATE table_settings_notifier SET enabled = ?, url = ? WHERE name = ?",
(item['enabled'], item['url'], item['name']))
save_settings(zip(request.form.keys(), request.form.listvalues()))
2020-05-06 00:09:25 +00:00
return '', 204
2020-03-29 13:58:32 +00:00
class Search(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 ? "
"ORDER BY title ASC", ("%"+query+"%",))
for serie in series:
search_list.append({'name': re.sub(r'\ \(\d{4}\)', '', serie['title']) + ' (' + serie['year'] + ')',
'url': url_for('episodes', no=serie['sonarrSeriesId'])})
if settings.general.getboolean('use_radarr'):
# Get matching movies
movies = database.execute("SELECT title, radarrId, year FROM table_movies WHERE title LIKE ? ORDER BY "
"title ASC", ("%"+query+"%",))
for movie in movies:
search_list.append({'name': re.sub(r'\ \(\d{4}\)', '', movie['title']) + ' (' + movie['year'] + ')',
'url': url_for('movie', no=movie['radarrId'])})
return jsonify(search_list)
2020-04-30 12:38:05 +00:00
class ResetProviders(Resource):
@authenticate
def get(self):
reset_throttled_providers()
return '', 200
2020-03-11 10:58:45 +00:00
class SaveSettings(Resource):
@authenticate
2020-03-11 10:58:45 +00:00
def post(self):
2020-04-24 15:19:18 +00:00
save_settings(zip(request.form.keys(), request.form.listvalues()))
2020-03-11 10:58:45 +00:00
return '', 200
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
for item in task_list:
# Add Datatables rowId
item.update({"DT_RowId": item['job_id']})
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)
return '', 200
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 = []
lin = line.split('|')
logs.append(lin)
logs.reverse()
return jsonify(data=logs)
2020-02-23 15:17:49 +00:00
class SystemProviders(Resource):
@authenticate
2020-02-23 15:17:49 +00:00
def get(self):
throttled_providers = list_throttled_providers()
for i in range(len(throttled_providers)):
throttled_providers[i][1] = throttled_providers[i][1] if throttled_providers[i][1] is not None else "Good"
throttled_providers[i][2] = throttled_providers[i][2] if throttled_providers[i][2] != "now" else "-"
return jsonify(data=throttled_providers)
2020-02-17 00:38:10 +00:00
class SystemStatus(Resource):
@authenticate
2020-02-17 00:38:10 +00:00
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:
releases = ast.literal_eval(f.read())
for release in releases:
release[1] = release[1].replace('- ', '')
release[1] = release[1].split('\r\n')
release[1].pop(0)
release.append(True if release[0].lstrip('v') == os.environ["BAZARR_VERSION"] else False)
except Exception as e:
logging.exception(
'BAZARR cannot parse releases caching file: ' + os.path.join(args.config_dir, 'config', 'releases.txt'))
return jsonify(data=releases)
2019-12-11 03:20:42 +00:00
class Series(Resource):
@authenticate
def get(self, **kwargs):
start = request.args.get('start') or 0
length = request.args.get('length') or -1
draw = request.args.get('draw')
2020-01-25 17:08:20 +00:00
seriesId = request.args.get('seriesid')
row_count = database.execute("SELECT COUNT(*) as count FROM table_shows", only_one=True)['count']
2019-12-11 03:20:42 +00:00
if seriesId:
2019-12-28 16:43:48 +00:00
result = database.execute("SELECT * FROM table_shows WHERE sonarrSeriesId=? ORDER BY sortTitle ASC LIMIT ? "
2020-01-02 06:16:00 +00:00
"OFFSET ?", (seriesId, length, start))
2020-02-08 13:38:59 +00:00
desired_languages = database.execute("SELECT languages FROM table_shows WHERE sonarrSeriesId=?",
(seriesId,), only_one=True)['languages']
if desired_languages == "None":
desired_languages = '[]'
2019-12-11 03:20:42 +00:00
else:
2019-12-28 16:43:48 +00:00
result = database.execute("SELECT * FROM table_shows ORDER BY sortTitle ASC LIMIT ? OFFSET ?", (length, start))
2019-12-11 03:20:42 +00:00
for item in result:
2020-01-25 17:08:20 +00:00
# Add Datatables rowId
item.update({"DT_RowId": 'row_' + str(item['sonarrSeriesId'])})
2019-12-11 03:20:42 +00:00
# Parse audio language
2020-02-25 00:22:18 +00:00
item.update({"audio_language": {"name": item['audio_language'],
"code2": alpha2_from_language(item['audio_language']) or None,
"code3": alpha3_from_language(item['audio_language']) or None}})
2019-12-11 03:20:42 +00:00
# Parse desired languages
2019-12-15 04:58:51 +00:00
if item['languages'] and item['languages'] != 'None':
item.update({"languages": ast.literal_eval(item['languages'])})
for i, subs in enumerate(item['languages']):
item['languages'][i] = {"name": language_from_alpha2(subs),
"code2": subs,
"code3": alpha3_from_alpha2(subs)}
2019-12-11 03:20:42 +00:00
# Parse alternate titles
2019-12-15 04:58:51 +00:00
if item['alternateTitles']:
item.update({"alternateTitles": ast.literal_eval(item['alternateTitles'])})
2019-12-11 03:20:42 +00:00
# Parse tags
item.update({"tags": ast.literal_eval(item['tags'])})
# Parse seriesType
item.update({"seriesType": item['seriesType'].capitalize()})
2019-12-11 03:20:42 +00:00
# Provide mapped path
2020-05-19 13:27:13 +00:00
mapped_path = path_mappings.path_replace(item['path'])
2019-12-11 03:20:42 +00:00
item.update({"mapped_path": mapped_path})
# Confirm if path exist
item.update({"exist": os.path.isdir(mapped_path)})
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 "
"null AND missing_subtitles != '[]'", (item['sonarrSeriesId'],))
episodeMissingCount = filter_exclusions(episodeMissingCount, 'series')
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 "
"table_episodes.sonarrSeriesId=?", (item['sonarrSeriesId'],))
episodeFileCount = filter_exclusions(episodeFileCount, 'series')
episodeFileCount = len(episodeFileCount)
item.update({"episodeFileCount": episodeFileCount})
2020-02-08 13:38:59 +00:00
# Add the series desired subtitles language code2
try:
item.update({"desired_languages": desired_languages})
except NameError:
pass
return jsonify(draw=draw, recordsTotal=row_count, recordsFiltered=row_count, data=result)
2019-12-11 03:20:42 +00:00
@authenticate
2020-01-22 04:54:32 +00:00
def post(self):
seriesId = request.args.get('seriesid')
lang = request.form.getlist('languages')
if len(lang) > 0:
pass
else:
lang = 'None'
single_language = settings.general.getboolean('single_language')
if single_language:
if str(lang) == "['None']":
lang = 'None'
else:
lang = str(lang)
else:
if str(lang) == "['']":
lang = '[]'
2020-01-25 17:08:20 +00:00
hi = request.form.get('hi')
2020-01-22 04:54:32 +00:00
forced = request.form.get('forced')
if hi == "on":
hi = "True"
else:
hi = "False"
result = database.execute("UPDATE table_shows SET languages=?, hearing_impaired=?, forced=? WHERE "
"sonarrSeriesId=?", (str(lang), hi, forced, seriesId))
list_missing_subtitles(no=seriesId)
return '', 204
2020-01-29 01:46:35 +00:00
class SeriesEditSave(Resource):
@authenticate
2020-01-29 01:46:35 +00:00
def post(self):
2020-04-15 04:02:44 +00:00
lang = request.form.getlist('languages[]')
hi = request.form.getlist('hi[]')
forced = request.form.getlist('forced[]')
2020-01-29 04:18:39 +00:00
if lang == ['None']:
lang = 'None'
seriesIdList = []
seriesidLangList = []
seriesidHiList = []
seriesidForcedList = []
2020-04-15 04:02:44 +00:00
for item in request.form.getlist('seriesid[]'):
2020-01-29 01:46:35 +00:00
seriesid = item.lstrip('row_')
seriesIdList.append(seriesid)
if len(lang):
seriesidLangList.append([str(lang), seriesid])
if len(hi):
seriesidHiList.append([hi[0], seriesid])
if len(forced):
seriesidForcedList.append([forced[0], seriesid])
try:
if len(lang):
database.execute("UPDATE table_shows SET languages=? WHERE sonarrSeriesId=?", seriesidLangList,
execute_many=True)
if len(hi):
database.execute("UPDATE table_shows SET hearing_impaired=? WHERE sonarrSeriesId=?", seriesidHiList,
execute_many=True)
if len(forced):
database.execute("UPDATE table_shows SET forced=? WHERE sonarrSeriesId=?", seriesidForcedList,
execute_many=True)
except:
pass
else:
for seriesId in seriesIdList:
list_missing_subtitles(no=seriesId, send_event=False)
event_stream(type='series_editor', action='update')
event_stream(type='badges')
2020-01-29 01:46:35 +00:00
return '', 204
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-02 14:41:30 +00:00
start = request.args.get('start') or 0
length = request.args.get('length') or -1
draw = request.args.get('draw')
2020-01-12 17:50:27 +00:00
seriesId = request.args.get('seriesid')
episodeId = request.args.get('episodeid')
2020-01-02 14:41:30 +00:00
row_count = database.execute("SELECT COUNT(*) as count FROM table_episodes WHERE sonarrSeriesId=?",
(seriesId,), only_one=True)['count']
2020-01-12 17:50:27 +00:00
if episodeId:
result = database.execute("SELECT * FROM table_episodes WHERE sonarrEpisodeId=?", (episodeId,))
desired_languages = database.execute("SELECT languages FROM table_shows WHERE sonarrSeriesId=?",
(seriesId,), only_one=True)['languages']
if desired_languages == "None":
desired_languages = '[]'
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,))
2020-01-10 00:54:00 +00:00
desired_languages = database.execute("SELECT languages FROM table_shows WHERE sonarrSeriesId=?",
(seriesId,), only_one=True)['languages']
if desired_languages == "None":
desired_languages = '[]'
2019-12-14 17:34:14 +00:00
else:
return "Series ID not provided", 400
2019-12-14 17:34:14 +00:00
for item in result:
2020-01-12 17:50:27 +00:00
# Add Datatables rowId
item.update({"DT_RowId": 'row_' + str(item['sonarrEpisodeId'])})
2019-12-15 04:58:51 +00:00
# Parse subtitles
if item['subtitles']:
item.update({"subtitles": ast.literal_eval(item['subtitles'])})
for subs in item['subtitles']:
2020-01-04 21:25:25 +00:00
subtitle = subs[0].split(':')
subs[0] = {"name": language_from_alpha2(subtitle[0]),
"code2": subtitle[0],
"code3": alpha3_from_alpha2(subtitle[0]),
"forced": True if len(subtitle) > 1 else False}
if settings.general.getboolean('embedded_subs_show_desired'):
item['subtitles'] = [x for x in item['subtitles'] if
x[0]['code2'] in ast.literal_eval(desired_languages) or x[1]]
2020-05-14 15:59:32 +00:00
else:
item.update({"subtitles": []})
2019-12-15 04:58:51 +00:00
# Parse missing subtitles
if item['missing_subtitles']:
item.update({"missing_subtitles": ast.literal_eval(item['missing_subtitles'])})
for i, subs in enumerate(item['missing_subtitles']):
2020-01-04 21:25:25 +00:00
subtitle = subs.split(':')
item['missing_subtitles'][i] = {"name": language_from_alpha2(subtitle[0]),
"code2": subtitle[0],
"code3": alpha3_from_alpha2(subtitle[0]),
"forced": True if len(subtitle) > 1 else False}
2020-05-14 15:59:32 +00:00
else:
item.update({"missing_subtitles": []})
2019-12-15 04:58:51 +00:00
2019-12-14 17:34:14 +00:00
# Provide mapped path
2020-05-19 13:27:13 +00:00
mapped_path = path_mappings.path_replace(item['path'])
2019-12-14 17:34:14 +00:00
item.update({"mapped_path": mapped_path})
# Confirm if path exist
item.update({"exist": os.path.isfile(mapped_path)})
2020-01-10 00:54:00 +00:00
# Add the series desired subtitles language code2
item.update({"desired_languages": desired_languages})
2020-01-02 14:41:30 +00:00
return jsonify(draw=draw, recordsTotal=row_count, recordsFiltered=row_count, data=result)
2019-12-14 17:34:14 +00:00
2020-01-07 03:26:28 +00:00
class EpisodesSubtitlesDelete(Resource):
@authenticate
2020-01-07 03:26:28 +00:00
def delete(self):
episodePath = request.form.get('episodePath')
language = request.form.get('language')
2020-07-19 20:02:38 +00:00
forced = request.form.get('forced')
2020-01-07 03:26:28 +00:00
subtitlesPath = request.form.get('subtitlesPath')
sonarrSeriesId = request.form.get('sonarrSeriesId')
sonarrEpisodeId = request.form.get('sonarrEpisodeId')
2020-07-19 20:02:38 +00:00
result = delete_subtitles(media_type='series',
language=language,
forced=forced,
media_path=episodePath,
subtitles_path=subtitlesPath,
sonarr_series_id=sonarrSeriesId,
sonarr_episode_id=sonarrEpisodeId)
if result:
return '', 202
else:
return '', 204
2020-01-07 03:26:28 +00:00
class EpisodesSubtitlesDownload(Resource):
@authenticate
2020-01-07 03:26:28 +00:00
def post(self):
episodePath = request.form.get('episodePath')
sceneName = request.form.get('sceneName')
if sceneName == "null":
sceneName = "None"
language = request.form.get('language')
hi = request.form.get('hi').capitalize()
forced = request.form.get('forced').capitalize()
sonarrSeriesId = request.form.get('sonarrSeriesId')
sonarrEpisodeId = request.form.get('sonarrEpisodeId')
title = request.form.get('title')
providers_list = get_providers()
providers_auth = get_providers_auth()
2020-03-25 00:59:57 +00:00
audio_language = database.execute("SELECT audio_language FROM table_shows WHERE sonarrSeriesId=?",
(sonarrSeriesId,), only_one=True)['audio_language']
2020-01-07 03:26:28 +00:00
try:
2020-03-25 00:59:57 +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]
language_code = result[2] + ":forced" if forced else result[2]
provider = result[3]
score = result[4]
subs_id = result[6]
2020-07-19 20:02:38 +00:00
subs_path = result[7]
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
return result, 201
except OSError:
pass
return '', 204
class EpisodesSubtitlesManualSearch(Resource):
@authenticate
2020-01-07 03:26:28 +00:00
def post(self):
start = request.args.get('start') or 0
length = request.args.get('length') or -1
draw = request.args.get('draw')
episodePath = request.form.get('episodePath')
sceneName = request.form.get('sceneName')
if sceneName == "null":
sceneName = "None"
language = request.form.get('language')
hi = request.form.get('hi').capitalize()
forced = request.form.get('forced').capitalize()
title = request.form.get('title')
providers_list = get_providers()
providers_auth = get_providers_auth()
data = manual_search(episodePath, language, hi, forced, providers_list, providers_auth, sceneName, title,
'series')
if not data:
data = []
2020-01-07 03:26:28 +00:00
row_count = len(data)
return jsonify(draw=draw, recordsTotal=row_count, recordsFiltered=row_count, data=data)
class EpisodesSubtitlesManualDownload(Resource):
@authenticate
2020-01-07 03:26:28 +00:00
def post(self):
episodePath = request.form.get('episodePath')
sceneName = request.form.get('sceneName')
if sceneName == "null":
sceneName = "None"
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')
sonarrSeriesId = request.form.get('sonarrSeriesId')
sonarrEpisodeId = request.form.get('sonarrEpisodeId')
title = request.form.get('title')
providers_auth = get_providers_auth()
2020-03-25 00:59:57 +00:00
audio_language = database.execute("SELECT audio_language FROM table_shows WHERE sonarrSeriesId=?",
(sonarrSeriesId,), only_one=True)['audio_language']
2020-01-07 03:26:28 +00:00
try:
2020-03-25 00:59:57 +00:00
result = manual_download_subtitle(episodePath, language, audio_language, hi, forced, subtitle,
selected_provider, 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]
language_code = result[2] + ":forced" if forced else result[2]
provider = result[3]
score = result[4]
subs_id = result[6]
2020-07-19 20:02:38 +00:00
subs_path = result[7]
history_log(2, sonarrSeriesId, sonarrEpisodeId, message, path, language_code, provider, score, subs_id, 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)
return result, 201
except OSError:
pass
return '', 204
class EpisodesSubtitlesUpload(Resource):
@authenticate
2020-01-07 03:26:28 +00:00
def post(self):
episodePath = request.form.get('episodePath')
sceneName = request.form.get('sceneName')
if sceneName == "null":
sceneName = "None"
language = request.form.get('language')
2020-01-11 04:40:38 +00:00
forced = True if request.form.get('forced') == 'on' else False
2020-01-07 03:26:28 +00:00
upload = request.files.get('upload')
sonarrSeriesId = request.form.get('sonarrSeriesId')
sonarrEpisodeId = request.form.get('sonarrEpisodeId')
title = request.form.get('title')
2020-06-01 01:59:33 +00:00
audioLanguage = request.form.get('audioLanguage')
2020-01-07 03:26:28 +00:00
_, ext = os.path.splitext(upload.filename)
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',
2020-06-01 01:59:33 +00:00
subtitle=upload,
audio_language=audioLanguage)
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-01-07 03:26:28 +00:00
language_code = language + ":forced" if forced else language
provider = "manual"
score = 360
2020-07-19 20:02:38 +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)
return result, 201
except OSError:
pass
return '', 204
2019-12-14 17:34:14 +00:00
2020-01-22 04:54:32 +00:00
class EpisodesScanDisk(Resource):
@authenticate
2020-01-22 04:54:32 +00:00
def get(self):
seriesid = request.args.get('seriesid')
series_scan_subtitles(seriesid)
return '', 200
class EpisodesSearchMissing(Resource):
@authenticate
2020-01-22 04:54:32 +00:00
def get(self):
seriesid = request.args.get('seriesid')
series_download_subtitles(seriesid)
return '', 200
2020-01-23 04:10:33 +00:00
class EpisodesHistory(Resource):
@authenticate
2020-01-23 04:10:33 +00:00
def get(self):
episodeid = request.args.get('episodeid')
2020-07-19 20:02:38 +00:00
episode_history = database.execute("SELECT action, timestamp, language, provider, score, sonarrSeriesId, "
"sonarrEpisodeId, subs_id, video_path, subtitles_path FROM table_history "
2020-01-23 04:10:33 +00:00
"WHERE sonarrEpisodeId=? ORDER BY timestamp DESC", (episodeid,))
for item in episode_history:
item['timestamp'] = "<div title='" + \
time.strftime('%d/%m/%Y %H:%M:%S', time.localtime(item['timestamp'])) + \
"' data-toggle='tooltip' data-placement='left'>" + \
pretty.date(datetime.datetime.fromtimestamp(item['timestamp'])) + "</div>"
if item['language']:
2020-07-19 20:02:38 +00:00
language = item['language'].split(':')
item['language'] = {"name": language_from_alpha2(language[0]),
"code2": language[0],
"code3": alpha3_from_alpha2(language[0]),
"forced": True if len(language) > 1 else False}
2020-01-23 04:10:33 +00:00
if item['score']:
item['score'] = str(round((int(item['score']) * 100 / 360), 2)) + "%"
2020-07-19 20:02:38 +00:00
if item['video_path']:
# Provide mapped path
mapped_path = path_mappings.path_replace(item['video_path'])
item.update({"mapped_path": mapped_path})
# Confirm if path exist
item.update({"exist": os.path.isfile(mapped_path)})
else:
item.update({"mapped_path": None})
item.update({"exist": False})
if item['subtitles_path']:
# Provide mapped subtitles path
mapped_subtitles_path = path_mappings.path_replace_movie(item['subtitles_path'])
item.update({"mapped_subtitles_path": mapped_subtitles_path})
else:
item.update({"mapped_subtitles_path": None})
# Check if subtitles is blacklisted
if item['action'] not in [0, 4, 5]:
blacklist_db = database.execute(
"SELECT provider, subs_id FROM table_blacklist WHERE provider=? AND "
"subs_id=?", (item['provider'], item['subs_id']))
else:
blacklist_db = []
if len(blacklist_db):
item.update({"blacklisted": True})
else:
item.update({"blacklisted": False})
2020-01-23 04:10:33 +00:00
return jsonify(data=episode_history)
class EpisodesTools(Resource):
@authenticate
def get(self):
episodeid = request.args.get('episodeid')
episode_ext_subs = database.execute("SELECT path, subtitles FROM table_episodes WHERE sonarrEpisodeId=?",
(episodeid,), only_one=True)
try:
all_subs = ast.literal_eval(episode_ext_subs['subtitles'])
except:
episode_external_subtitles = None
else:
episode_external_subtitles = []
for subs in all_subs:
if subs[1]:
subtitle = subs[0].split(':')
subs[0] = {"name": language_from_alpha2(subtitle[0]),
"code2": subtitle[0],
"code3": alpha3_from_alpha2(subtitle[0]),
"forced": True if len(subtitle) > 1 else False}
episode_external_subtitles.append({'language': subs[0],
'path': path_mappings.path_replace(subs[1]),
'filename': os.path.basename(subs[1]),
'videopath': path_mappings.path_replace(episode_ext_subs['path'])})
return jsonify(data=episode_external_subtitles)
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
draw = request.args.get('draw')
2020-02-04 17:57:37 +00:00
moviesId = request.args.get('radarrid')
row_count = database.execute("SELECT COUNT(*) as count FROM table_movies", only_one=True)['count']
2019-12-15 04:58:51 +00:00
if moviesId:
2019-12-28 16:43:48 +00:00
result = database.execute("SELECT * FROM table_movies WHERE radarrId=? ORDER BY sortTitle ASC LIMIT ? "
2020-02-04 17:57:37 +00:00
"OFFSET ?", (moviesId, length, start))
2020-02-08 13:38:59 +00:00
desired_languages = database.execute("SELECT languages FROM table_movies WHERE radarrId=?",
(moviesId,), only_one=True)['languages']
if desired_languages == "None":
desired_languages = '[]'
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:
2020-02-04 17:57:37 +00:00
# Add Datatables rowId
item.update({"DT_RowId": 'row_' + str(item['radarrId'])})
2019-12-15 04:58:51 +00:00
# Parse audio language
2020-02-25 00:22:18 +00:00
item.update({"audio_language": {"name": item['audio_language'],
"code2": alpha2_from_language(item['audio_language']) or None,
"code3": alpha3_from_language(item['audio_language']) or None}})
2019-12-15 04:58:51 +00:00
# Parse desired languages
if item['languages'] and item['languages'] != 'None':
item.update({"languages": ast.literal_eval(item['languages'])})
for i, subs in enumerate(item['languages']):
item['languages'][i] = {"name": language_from_alpha2(subs),
"code2": subs,
"code3": alpha3_from_alpha2(subs)}
# Parse alternate titles
if item['alternativeTitles']:
item.update({"alternativeTitles": ast.literal_eval(item['alternativeTitles'])})
# Parse failed attempts
if item['failedAttempts']:
item.update({"failedAttempts": ast.literal_eval(item['failedAttempts'])})
# Parse subtitles
if item['subtitles']:
item.update({"subtitles": ast.literal_eval(item['subtitles'])})
2020-02-07 17:40:43 +00:00
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": True if len(language) > 1 else False}
if settings.general.getboolean('embedded_subs_show_desired'):
desired_lang_list = []
if isinstance(item['languages'], list):
desired_lang_list = [x['code2'] for x in item['languages']]
item['subtitles'] = [x for x in item['subtitles'] if x['code2'] in desired_lang_list or x['path']]
2020-02-07 17:40:43 +00:00
item['subtitles'] = sorted(item['subtitles'], key=itemgetter('name', 'forced'))
2020-05-14 15:59:32 +00:00
else:
item.update({"subtitles": []})
2019-12-15 04:58:51 +00:00
# Parse missing subtitles
if item['missing_subtitles']:
item.update({"missing_subtitles": ast.literal_eval(item['missing_subtitles'])})
for i, subs in enumerate(item['missing_subtitles']):
2020-02-08 13:38:59 +00:00
language = subs.split(':')
2020-02-07 17:40:43 +00:00
item['missing_subtitles'][i] = {"name": language_from_alpha2(language[0]),
"code2": language[0],
"code3": alpha3_from_alpha2(language[0]),
"forced": True if len(language) > 1 else False}
2020-05-14 15:59:32 +00:00
else:
item.update({"missing_subtitles": []})
2019-12-15 04:58:51 +00:00
# Parse tags
item.update({"tags": ast.literal_eval(item['tags'])})
2019-12-15 04:58:51 +00:00
# Provide mapped path
2020-05-19 13:27:13 +00:00
mapped_path = path_mappings.path_replace_movie(item['path'])
2019-12-15 04:58:51 +00:00
item.update({"mapped_path": mapped_path})
# Confirm if path exist
item.update({"exist": os.path.isfile(mapped_path)})
2020-02-08 13:38:59 +00:00
# Add the movie desired subtitles language code2
try:
item.update({"desired_languages": desired_languages})
except NameError:
pass
return jsonify(draw=draw, recordsTotal=row_count, recordsFiltered=row_count, data=result)
2019-12-15 04:58:51 +00:00
@authenticate
2020-02-04 17:57:37 +00:00
def post(self):
radarrId = request.args.get('radarrid')
lang = request.form.getlist('languages')
if len(lang) > 0:
pass
else:
lang = 'None'
single_language = settings.general.getboolean('single_language')
if single_language:
if str(lang) == "['None']":
lang = 'None'
else:
lang = str(lang)
else:
if str(lang) == "['']":
lang = '[]'
hi = request.form.get('hi')
forced = request.form.get('forced')
if hi == "on":
hi = "True"
else:
hi = "False"
result = database.execute("UPDATE table_movies SET languages=?, hearing_impaired=?, forced=? WHERE "
"radarrId=?", (str(lang), hi, forced, radarrId))
list_missing_subtitles_movies(no=radarrId)
return '', 204
2020-02-05 03:50:35 +00:00
class MoviesEditSave(Resource):
@authenticate
2020-02-05 03:50:35 +00:00
def post(self):
2020-04-15 04:02:44 +00:00
lang = request.form.getlist('languages[]')
hi = request.form.getlist('hi[]')
forced = request.form.getlist('forced[]')
2020-02-05 03:50:35 +00:00
if lang == ['None']:
lang = 'None'
radarrIdList = []
radarrIdLangList = []
radarrIdHiList = []
radarrIdForcedList = []
2020-04-15 04:02:44 +00:00
for item in request.form.getlist('radarrid[]'):
2020-02-05 03:50:35 +00:00
radarrid = item.lstrip('row_')
radarrIdList.append(radarrid)
if len(lang):
radarrIdLangList.append([str(lang), radarrid])
if len(hi):
radarrIdHiList.append([hi[0], radarrid])
if len(forced):
radarrIdForcedList.append([forced[0], radarrid])
try:
if len(lang):
database.execute("UPDATE table_movies SET languages=? WHERE radarrId=?", radarrIdLangList,
execute_many=True)
if len(hi):
database.execute("UPDATE table_movies SET hearing_impaired=? WHERE radarrId=?", radarrIdHiList,
execute_many=True)
if len(forced):
database.execute("UPDATE table_movies SET forced=? WHERE radarrId=?", radarrIdForcedList,
execute_many=True)
except:
pass
else:
for radarrId in radarrIdList:
list_missing_subtitles_movies(no=radarrId, send_event=False)
event_stream(type='movies_editor', action='update')
event_stream(type='badges')
2020-02-05 03:50:35 +00:00
return '', 204
2020-02-07 17:40:43 +00:00
class MovieSubtitlesDelete(Resource):
@authenticate
2020-02-07 17:40:43 +00:00
def delete(self):
moviePath = request.form.get('moviePath')
language = request.form.get('language')
2020-07-19 20:02:38 +00:00
forced = request.form.get('forced')
2020-02-07 17:40:43 +00:00
subtitlesPath = request.form.get('subtitlesPath')
radarrId = request.form.get('radarrId')
2020-07-19 20:02:38 +00:00
result = delete_subtitles(media_type='movie',
language=language,
forced=forced,
media_path=moviePath,
subtitles_path=subtitlesPath,
radarr_id=radarrId)
if result:
return '', 202
else:
return '', 204
2020-02-07 17:40:43 +00:00
class MovieSubtitlesDownload(Resource):
@authenticate
2020-02-07 17:40:43 +00:00
def post(self):
moviePath = request.form.get('moviePath')
sceneName = request.form.get('sceneName')
if sceneName == "null":
sceneName = "None"
language = request.form.get('language')
hi = request.form.get('hi').capitalize()
forced = request.form.get('forced').capitalize()
radarrId = request.form.get('radarrId')
title = request.form.get('title')
providers_list = get_providers()
providers_auth = get_providers_auth()
2020-03-25 00:59:57 +00:00
audio_language = database.execute("SELECT audio_language FROM table_movies WHERE radarrId=?", (radarrId,),
only_one=True)['audio_language']
2020-02-07 17:40:43 +00:00
try:
2020-03-25 00:59:57 +00:00
result = download_subtitle(moviePath, language, audio_language, hi, forced, providers_list,
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]
language_code = result[2] + ":forced" if forced else result[2]
provider = result[3]
score = result[4]
subs_id = result[6]
2020-07-19 20:02:38 +00:00
subs_path = result[7]
history_log_movie(1, radarrId, message, path, language_code, provider, score, subs_id, subs_path)
2020-02-07 17:40:43 +00:00
send_notifications_movie(radarrId, message)
store_subtitles_movie(path, moviePath)
2020-02-12 17:41:40 +00:00
else:
2020-05-12 12:25:03 +00:00
event_stream(type='movie', action='update', movie=int(radarrId))
2020-02-07 17:40:43 +00:00
return result, 201
except OSError:
pass
return '', 204
class MovieSubtitlesManualSearch(Resource):
@authenticate
2020-02-07 17:40:43 +00:00
def post(self):
start = request.args.get('start') or 0
length = request.args.get('length') or -1
draw = request.args.get('draw')
moviePath = request.form.get('moviePath')
sceneName = request.form.get('sceneName')
if sceneName == "null":
sceneName = "None"
language = request.form.get('language')
hi = request.form.get('hi').capitalize()
forced = request.form.get('forced').capitalize()
title = request.form.get('title')
providers_list = get_providers()
providers_auth = get_providers_auth()
data = manual_search(moviePath, language, hi, forced, providers_list, providers_auth, sceneName, title,
'movie')
if not data:
data = []
2020-02-07 17:40:43 +00:00
row_count = len(data)
return jsonify(draw=draw, recordsTotal=row_count, recordsFiltered=row_count, data=data)
class MovieSubtitlesManualDownload(Resource):
@authenticate
2020-02-07 17:40:43 +00:00
def post(self):
moviePath = request.form.get('moviePath')
sceneName = request.form.get('sceneName')
if sceneName == "null":
sceneName = "None"
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')
radarrId = request.form.get('radarrId')
title = request.form.get('title')
providers_auth = get_providers_auth()
2020-03-25 00:59:57 +00:00
audio_language = database.execute("SELECT audio_language FROM table_movies WHERE radarrId=?", (radarrId,),
only_one=True)['audio_language']
2020-02-07 17:40:43 +00:00
try:
2020-03-25 00:59:57 +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]
language_code = result[2] + ":forced" if forced else result[2]
provider = result[3]
score = result[4]
subs_id = result[6]
2020-07-19 20:02:38 +00:00
subs_path = result[7]
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)
return result, 201
except OSError:
pass
return '', 204
class MovieSubtitlesUpload(Resource):
@authenticate
2020-02-07 17:40:43 +00:00
def post(self):
moviePath = request.form.get('moviePath')
sceneName = request.form.get('sceneName')
if sceneName == "null":
sceneName = "None"
language = request.form.get('language')
forced = True if request.form.get('forced') == 'on' else False
upload = request.files.get('upload')
radarrId = request.form.get('radarrId')
title = request.form.get('title')
2020-06-01 01:59:33 +00:00
audioLanguage = request.form.get('audioLanguage')
2020-02-07 17:40:43 +00:00
_, ext = os.path.splitext(upload.filename)
if ext not in SUBTITLE_EXTENSIONS:
raise ValueError('A subtitle of an invalid format was uploaded.')
try:
result = manual_upload_subtitle(path=moviePath,
language=language,
forced=forced,
title=title,
scene_name=sceneName,
media_type='movie',
2020-06-01 01:59:33 +00:00
subtitle=upload,
audio_language=audioLanguage)
2020-02-07 17:40:43 +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-02-07 17:40:43 +00:00
language_code = language + ":forced" if forced else language
provider = "manual"
score = 120
2020-07-19 20:02:38 +00:00
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)
2020-02-07 17:40:43 +00:00
store_subtitles_movie(path, moviePath)
return result, 201
except OSError:
pass
return '', 204
class MovieScanDisk(Resource):
@authenticate
2020-02-07 17:40:43 +00:00
def get(self):
radarrid = request.args.get('radarrid')
movies_scan_subtitles(radarrid)
return '', 200
class MovieSearchMissing(Resource):
@authenticate
2020-02-07 17:40:43 +00:00
def get(self):
radarrid = request.args.get('radarrid')
movies_download_subtitles(radarrid)
return '', 200
class MovieHistory(Resource):
@authenticate
2020-02-07 17:40:43 +00:00
def get(self):
radarrid = request.args.get('radarrid')
2020-07-19 20:02:38 +00:00
movie_history = database.execute("SELECT action, timestamp, language, provider, score, radarrId, subs_id, "
"video_path, subtitles_path FROM table_history_movie WHERE radarrId=? ORDER "
"BY timestamp DESC", (radarrid,))
2020-02-07 17:40:43 +00:00
for item in movie_history:
item['timestamp'] = "<div title='" + \
time.strftime('%d/%m/%Y %H:%M:%S', time.localtime(item['timestamp'])) + \
"' data-toggle='tooltip' data-placement='left'>" + \
pretty.date(datetime.datetime.fromtimestamp(item['timestamp'])) + "</div>"
if item['language']:
2020-07-19 20:02:38 +00:00
language = item['language'].split(':')
item['language'] = {"name": language_from_alpha2(language[0]),
"code2": language[0],
"code3": alpha3_from_alpha2(language[0]),
"forced": True if len(language) > 1 else False}
2020-02-07 17:40:43 +00:00
if item['score']:
item['score'] = str(round((int(item['score']) * 100 / 120), 2)) + "%"
2020-07-19 20:02:38 +00:00
if item['video_path']:
# Provide mapped path
mapped_path = path_mappings.path_replace(item['video_path'])
item.update({"mapped_path": mapped_path})
# Confirm if path exist
item.update({"exist": os.path.isfile(mapped_path)})
else:
item.update({"mapped_path": None})
item.update({"exist": False})
if item['subtitles_path']:
# Provide mapped subtitles path
mapped_subtitles_path = path_mappings.path_replace_movie(item['subtitles_path'])
item.update({"mapped_subtitles_path": mapped_subtitles_path})
else:
item.update({"mapped_subtitles_path": None})
# Check if subtitles is blacklisted
if item['action'] not in [0, 4, 5]:
blacklist_db = database.execute(
"SELECT provider, subs_id FROM table_blacklist_movie WHERE provider=? AND "
"subs_id=?", (item['provider'], item['subs_id']))
else:
blacklist_db = []
if len(blacklist_db):
item.update({"blacklisted": True})
else:
item.update({"blacklisted": False})
2020-02-07 17:40:43 +00:00
return jsonify(data=movie_history)
2020-02-05 03:50:35 +00:00
class MovieTools(Resource):
@authenticate
def get(self):
movieid = request.args.get('movieid')
movie_ext_subs = database.execute("SELECT path, subtitles FROM table_movies WHERE radarrId=?",
(movieid,), only_one=True)
try:
all_subs = ast.literal_eval(movie_ext_subs['subtitles'])
except:
movie_external_subtitles = None
else:
movie_external_subtitles = []
for subs in all_subs:
if subs[1]:
subtitle = subs[0].split(':')
subs[0] = {"name": language_from_alpha2(subtitle[0]),
"code2": subtitle[0],
"code3": alpha3_from_alpha2(subtitle[0]),
"forced": True if len(subtitle) > 1 else False}
movie_external_subtitles.append({'language': subs[0],
'path': path_mappings.path_replace_movie(subs[1]),
'filename': os.path.basename(subs[1]),
'videopath': path_mappings.path_replace_movie(movie_ext_subs['path'])})
return jsonify(data=movie_external_subtitles)
class HistorySeries(Resource):
@authenticate
def get(self):
start = request.args.get('start') or 0
length = request.args.get('length') or -1
draw = request.args.get('draw')
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]
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 (" +
','.join(map(str, query_actions)) + ") AND timestamp > ? AND score is not null GROUP BY "
"table_history.video_path, table_history.language", (minimum_timestamp,))
upgradable_episodes = filter_exclusions(upgradable_episodes, 'series')
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)
2020-05-08 17:01:09 +00:00
row_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']
2020-07-19 20:02:38 +00:00
data = database.execute("SELECT table_shows.title as seriesTitle, table_episodes.monitored, "
"table_episodes.season || 'x' || table_episodes.episode as episode_number, "
2020-07-19 20:02:38 +00:00
"table_episodes.title as episodeTitle, table_history.timestamp, table_history.subs_id, "
"table_history.description, table_history.sonarrSeriesId, table_episodes.path, "
2020-07-19 20:02:38 +00:00
"table_history.language, table_history.score, table_shows.tags, table_history.action, "
"table_history.subtitles_path, table_history.sonarrEpisodeId, table_history.provider "
"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 ORDER BY timestamp DESC LIMIT ? OFFSET ?",
(length, start))
for item in data:
# Mark episode as upgradable or not
if {"video_path": str(item['path']), "timestamp": float(item['timestamp']), "score": str(item['score']), "tags": str(item['tags']), "monitored": str(item['monitored'])} in upgradable_episodes_not_perfect:
item.update({"upgradable": True})
else:
item.update({"upgradable": False})
# Parse language
if item['language'] and item['language'] != '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 len(splitted_language) > 1 else False}
# Make timestamp pretty
if item['timestamp']:
item['timestamp'] = pretty.date(int(item['timestamp']))
2020-07-19 20:02:38 +00:00
if item['path']:
# Provide mapped path
mapped_path = path_mappings.path_replace(item['path'])
item.update({"mapped_path": mapped_path})
2020-07-19 20:02:38 +00:00
# Confirm if path exist
item.update({"exist": os.path.isfile(mapped_path)})
else:
item.update({"mapped_path": None})
item.update({"exist": False})
if item['subtitles_path']:
# Provide mapped subtitles path
mapped_subtitles_path = path_mappings.path_replace_movie(item['subtitles_path'])
item.update({"mapped_subtitles_path": mapped_subtitles_path})
else:
item.update({"mapped_subtitles_path": None})
# Check if subtitles is blacklisted
if item['action'] not in [0, 4, 5]:
blacklist_db = database.execute("SELECT provider, subs_id FROM table_blacklist WHERE provider=? AND "
"subs_id=?", (item['provider'], item['subs_id']))
else:
blacklist_db = []
if len(blacklist_db):
item.update({"blacklisted": True})
else:
item.update({"blacklisted": False})
return jsonify(draw=draw, recordsTotal=row_count, recordsFiltered=row_count, data=data)
class HistoryMovies(Resource):
@authenticate
def get(self):
start = request.args.get('start') or 0
length = request.args.get('length') or -1
draw = request.args.get('draw')
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]
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 GROUP BY video_path, "
"language", (minimum_timestamp,))
upgradable_movies = filter_exclusions(upgradable_movies, 'movie')
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)
2020-05-08 17:01:09 +00:00
row_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']
data = database.execute("SELECT table_history_movie.action, table_movies.title, table_history_movie.timestamp, "
2020-07-19 20:02:38 +00:00
"table_history_movie.description, table_history_movie.radarrId, table_movies.monitored,"
" table_history_movie.video_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 ORDER BY timestamp "
"DESC LIMIT ? OFFSET ?", (length, start))
for item in data:
# Mark movies as upgradable or not
if {"video_path": str(item['video_path']), "timestamp": float(item['timestamp']), "score": str(item['score']), "tags": str(item['tags']), "monitored": str(item['monitored'])} in upgradable_movies_not_perfect:
item.update({"upgradable": True})
else:
item.update({"upgradable": False})
# Parse language
if item['language'] and item['language'] != '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 len(splitted_language) > 1 else False}
# Make timestamp pretty
if item['timestamp']:
item['timestamp'] = pretty.date(int(item['timestamp']))
if item['video_path']:
# Provide mapped path
2020-05-19 13:27:13 +00:00
mapped_path = path_mappings.path_replace_movie(item['video_path'])
item.update({"mapped_path": mapped_path})
# Confirm if path exist
item.update({"exist": os.path.isfile(mapped_path)})
else:
item.update({"mapped_path": None})
item.update({"exist": False})
2020-07-19 20:02:38 +00:00
if item['subtitles_path']:
# Provide mapped subtitles path
mapped_subtitles_path = path_mappings.path_replace_movie(item['subtitles_path'])
item.update({"mapped_subtitles_path": mapped_subtitles_path})
else:
item.update({"mapped_subtitles_path": None})
# Check if subtitles is blacklisted
if item['action'] not in [0, 4, 5]:
blacklist_db = database.execute("SELECT provider, subs_id FROM table_blacklist_movie WHERE provider=? "
"AND subs_id=?", (item['provider'], item['subs_id']))
else:
blacklist_db = []
if len(blacklist_db):
item.update({"blacklisted": True})
else:
item.update({"blacklisted": False})
2019-12-16 17:46:03 +00:00
return jsonify(draw=draw, recordsTotal=row_count, recordsFiltered=row_count, data=data)
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'])
return jsonify(data_series=sorted_data_series, data_movies=sorted_data_movies)
class WantedSeries(Resource):
@authenticate
def get(self):
start = request.args.get('start') or 0
length = request.args.get('length') or -1
draw = request.args.get('draw')
2020-07-13 17:59:11 +00:00
data_count = database.execute("SELECT table_episodes.monitored, table_shows.tags, table_shows.seriesType FROM "
"table_episodes INNER JOIN table_shows on table_shows.sonarrSeriesId = "
"table_episodes.sonarrSeriesId WHERE table_episodes.missing_subtitles != '[]'")
data_count = filter_exclusions(data_count, 'series')
row_count = len(data_count)
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, "
"table_episodes.sonarrSeriesId, table_episodes.path, table_shows.hearing_impaired, "
"table_episodes.sonarrEpisodeId, table_episodes.scene_name, table_shows.tags, "
"table_episodes.failedAttempts, table_shows.seriesType FROM table_episodes INNER JOIN "
"table_shows on table_shows.sonarrSeriesId = table_episodes.sonarrSeriesId WHERE "
2020-07-28 16:23:20 +00:00
"table_episodes.missing_subtitles != '[]' ORDER BY table_episodes._rowid_ DESC")
data = filter_exclusions(data, 'series')[int(start):int(start)+int(length)]
for item in data:
# Parse missing subtitles
if item['missing_subtitles']:
item.update({"missing_subtitles": ast.literal_eval(item['missing_subtitles'])})
for i, subs in enumerate(item['missing_subtitles']):
splitted_subs = subs.split(':')
item['missing_subtitles'][i] = {"name": language_from_alpha2(splitted_subs[0]),
"code2": splitted_subs[0],
"code3": alpha3_from_alpha2(splitted_subs[0]),
"forced": True if len(splitted_subs) > 1 else False}
2020-05-14 15:59:32 +00:00
else:
item.update({"missing_subtitles": []})
# Provide mapped path
2020-05-19 13:27:13 +00:00
mapped_path = path_mappings.path_replace(item['path'])
item.update({"mapped_path": mapped_path})
# Confirm if path exist
item.update({"exist": os.path.isfile(mapped_path)})
return jsonify(draw=draw, recordsTotal=row_count, recordsFiltered=row_count, data=data)
class WantedMovies(Resource):
@authenticate
def get(self):
start = request.args.get('start') or 0
length = request.args.get('length') or -1
draw = request.args.get('draw')
2020-07-13 17:59:11 +00:00
data_count = database.execute("SELECT tags, monitored FROM table_movies WHERE missing_subtitles != '[]'")
data_count = filter_exclusions(data_count, 'movie')
row_count = len(data_count)
data = database.execute("SELECT title, missing_subtitles, radarrId, path, hearing_impaired, sceneName, "
"failedAttempts, tags, monitored FROM table_movies WHERE missing_subtitles != '[]' "
2020-07-28 16:23:20 +00:00
"ORDER BY _rowid_ DESC")
data = filter_exclusions(data, 'movie')[int(start):int(start)+int(length)]
for item in data:
# Parse missing subtitles
if item['missing_subtitles']:
item.update({"missing_subtitles": ast.literal_eval(item['missing_subtitles'])})
for i, subs in enumerate(item['missing_subtitles']):
splitted_subs = subs.split(':')
item['missing_subtitles'][i] = {"name": language_from_alpha2(splitted_subs[0]),
"code2": splitted_subs[0],
"code3": alpha3_from_alpha2(splitted_subs[0]),
"forced": True if len(splitted_subs) > 1 else False}
2020-05-14 15:59:32 +00:00
else:
item.update({"missing_subtitles": []})
# Provide mapped path
2020-05-19 13:27:13 +00:00
mapped_path = path_mappings.path_replace_movie(item['path'])
item.update({"mapped_path": mapped_path})
# Confirm if path exist
item.update({"exist": os.path.isfile(mapped_path)})
return jsonify(draw=draw, recordsTotal=row_count, recordsFiltered=row_count, data=data)
2020-02-13 04:16:22 +00:00
class SearchWantedSeries(Resource):
@authenticate
2020-02-13 04:16:22 +00:00
def get(self):
wanted_search_missing_subtitles_series()
return '', 200
class SearchWantedMovies(Resource):
@authenticate
2020-02-13 04:16:22 +00:00
def get(self):
wanted_search_missing_subtitles_movies()
return '', 200
2020-07-19 20:02:38 +00:00
class BlacklistSeries(Resource):
@authenticate
def get(self):
start = request.args.get('start') or 0
length = request.args.get('length') or -1
draw = request.args.get('draw')
row_count = database.execute("SELECT COUNT(*) as count FROM table_blacklist", only_one=True)['count']
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']))})
# Convert language code2 to name
if item['language']:
language = item['language'].split(':')
item['language'] = {"name": language_from_alpha2(language[0]),
"code2": language[0],
"code3": alpha3_from_alpha2(language[0]),
"forced": True if len(language) > 1 else False}
return jsonify(draw=draw, recordsTotal=row_count, recordsFiltered=row_count, data=data)
class BlacklistEpisodeSubtitlesAdd(Resource):
@authenticate
def post(self):
sonarr_series_id = int(request.form.get('sonarr_series_id'))
sonarr_episode_id = int(request.form.get('sonarr_episode_id'))
provider = request.form.get('provider')
subs_id = request.form.get('subs_id')
language = request.form.get('language')
forced = request.form.get('forced')
language_str = language + ':forced' if forced == 'true' else language
media_path = request.form.get('video_path')
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,
language=language_str)
delete_subtitles(media_type='series',
language=alpha3_from_alpha2(language),
forced=forced,
media_path=path_mappings.path_replace(media_path),
subtitles_path=path_mappings.path_replace(subtitles_path),
sonarr_series_id=sonarr_series_id,
sonarr_episode_id=sonarr_episode_id)
episode_download_subtitles(sonarr_episode_id)
event_stream(type='episodeHistory')
return '', 200
class BlacklistEpisodeSubtitlesRemove(Resource):
@authenticate
def delete(self):
provider = request.form.get('provider')
subs_id = request.form.get('subs_id')
blacklist_delete(provider=provider, subs_id=subs_id)
return '', 200
class BlacklistEpisodeSubtitlesRemoveAll(Resource):
@authenticate
def delete(self):
blacklist_delete_all()
return '', 200
class BlacklistMovies(Resource):
@authenticate
def get(self):
start = request.args.get('start') or 0
length = request.args.get('length') or -1
draw = request.args.get('draw')
row_count = database.execute("SELECT COUNT(*) as count FROM table_blacklist_movie", only_one=True)['count']
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:
# Make timestamp pretty
item.update({'timestamp': pretty.date(datetime.datetime.fromtimestamp(item['timestamp']))})
# Convert language code2 to name
if item['language']:
language = item['language'].split(':')
item['language'] = {"name": language_from_alpha2(language[0]),
"code2": language[0],
"code3": alpha3_from_alpha2(language[0]),
"forced": True if len(language) > 1 else False}
return jsonify(draw=draw, recordsTotal=row_count, recordsFiltered=row_count, data=data)
class BlacklistMovieSubtitlesAdd(Resource):
@authenticate
def post(self):
radarr_id = int(request.form.get('radarr_id'))
provider = request.form.get('provider')
subs_id = request.form.get('subs_id')
language = request.form.get('language')
forced = request.form.get('forced')
language_str = language + ':forced' if forced == 'true' else language
media_path = request.form.get('video_path')
subtitles_path = request.form.get('subtitles_path')
blacklist_log_movie(radarr_id=radarr_id,
provider=provider,
subs_id=subs_id,
language=language_str)
delete_subtitles(media_type='movie',
language=alpha3_from_alpha2(language),
forced=forced,
media_path=path_mappings.path_replace_movie(media_path),
subtitles_path=path_mappings.path_replace_movie(subtitles_path),
radarr_id=radarr_id)
movies_download_subtitles(radarr_id)
event_stream(type='movieHistory')
return '', 200
class BlacklistMovieSubtitlesRemove(Resource):
@authenticate
def delete(self):
provider = request.form.get('provider')
subs_id = request.form.get('subs_id')
blacklist_delete_movie(provider=provider, subs_id=subs_id)
return '', 200
class BlacklistMovieSubtitlesRemoveAll(Resource):
@authenticate
def delete(self):
blacklist_delete_all_movie()
return '', 200
class SyncSubtitles(Resource):
@authenticate
def post(self):
language = request.form.get('language')
subtitles_path = request.form.get('subtitlesPath')
video_path = request.form.get('videoPath')
media_type = request.form.get('mediaType')
if media_type == 'series':
episode_metadata = database.execute("SELECT sonarrSeriesId, sonarrEpisodeId FROM table_episodes"
" WHERE path = ?", (path_mappings.path_replace_reverse(video_path),),
only_one=True)
subsync.sync(video_path=video_path, srt_path=subtitles_path,
srt_lang=language, media_type=media_type, sonarr_series_id=episode_metadata['sonarrSeriesId'],
sonarr_episode_id=episode_metadata['sonarrEpisodeId'])
else:
movie_metadata = database.execute("SELECT radarrId FROM table_movies WHERE path = ?",
(path_mappings.path_replace_reverse_movie(video_path),), only_one=True)
subsync.sync(video_path=video_path, srt_path=subtitles_path,
srt_lang=language, media_type=media_type, radarr_id=movie_metadata['radarrId'])
return '', 200
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 = []
result = browse_bazarr_filesystem(path)
2020-06-25 10:33:48 +00:00
for item in result['directories']:
2020-06-26 15:28:49 +00:00
data.append({'text': item['name'], 'children': True, 'path': item['path']})
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 = []
result = browse_sonarr_filesystem(path)
for item in result['directories']:
2020-06-26 15:28:49 +00:00
data.append({'text': item['name'], 'children': True, 'path': item['path']})
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 = []
result = browse_radarr_filesystem(path)
for item in result['directories']:
2020-06-26 15:28:49 +00:00
data.append({'text': item['name'], 'children': True, 'path': item['path']})
return jsonify(data)
2020-04-16 11:52:35 +00:00
api.add_resource(Shutdown, '/shutdown')
api.add_resource(Restart, '/restart')
2019-12-28 05:52:00 +00:00
api.add_resource(Badges, '/badges')
2020-01-22 04:54:32 +00:00
api.add_resource(Languages, '/languages')
2020-05-06 00:09:25 +00:00
api.add_resource(Notifications, '/notifications')
2020-02-04 17:57:37 +00:00
2020-03-29 13:58:32 +00:00
api.add_resource(Search, '/search_json')
2020-04-30 12:38:05 +00:00
api.add_resource(ResetProviders, '/resetproviders')
2020-03-11 10:58:45 +00:00
api.add_resource(SaveSettings, '/savesettings')
2020-02-20 11:41:05 +00:00
api.add_resource(SystemTasks, '/systemtasks')
2020-02-17 17:54:20 +00:00
api.add_resource(SystemLogs, '/systemlogs')
2020-02-23 15:17:49 +00:00
api.add_resource(SystemProviders, '/systemproviders')
2020-02-17 00:38:10 +00:00
api.add_resource(SystemStatus, '/systemstatus')
api.add_resource(SystemReleases, '/systemreleases')
2019-12-28 05:52:00 +00:00
api.add_resource(Series, '/series')
2020-01-29 01:46:35 +00:00
api.add_resource(SeriesEditSave, '/series_edit_save')
2019-12-28 05:52:00 +00:00
api.add_resource(Episodes, '/episodes')
2020-01-07 03:26:28 +00:00
api.add_resource(EpisodesSubtitlesDelete, '/episodes_subtitles_delete')
api.add_resource(EpisodesSubtitlesDownload, '/episodes_subtitles_download')
api.add_resource(EpisodesSubtitlesManualSearch, '/episodes_subtitles_manual_search')
api.add_resource(EpisodesSubtitlesManualDownload, '/episodes_subtitles_manual_download')
api.add_resource(EpisodesSubtitlesUpload, '/episodes_subtitles_upload')
2020-01-22 04:54:32 +00:00
api.add_resource(EpisodesScanDisk, '/episodes_scan_disk')
api.add_resource(EpisodesSearchMissing, '/episodes_search_missing')
2020-01-23 04:10:33 +00:00
api.add_resource(EpisodesHistory, '/episodes_history')
api.add_resource(EpisodesTools, '/episodes_tools')
2020-02-04 17:57:37 +00:00
2019-12-28 05:52:00 +00:00
api.add_resource(Movies, '/movies')
2020-02-05 03:50:35 +00:00
api.add_resource(MoviesEditSave, '/movies_edit_save')
2020-02-07 17:40:43 +00:00
api.add_resource(MovieSubtitlesDelete, '/movie_subtitles_delete')
api.add_resource(MovieSubtitlesDownload, '/movie_subtitles_download')
api.add_resource(MovieSubtitlesManualSearch, '/movie_subtitles_manual_search')
api.add_resource(MovieSubtitlesManualDownload, '/movie_subtitles_manual_download')
api.add_resource(MovieSubtitlesUpload, '/movie_subtitles_upload')
api.add_resource(MovieScanDisk, '/movie_scan_disk')
api.add_resource(MovieSearchMissing, '/movie_search_missing')
api.add_resource(MovieHistory, '/movie_history')
api.add_resource(MovieTools, '/movie_tools')
2020-02-04 17:57:37 +00:00
2019-12-28 05:52:00 +00:00
api.add_resource(HistorySeries, '/history_series')
api.add_resource(HistoryMovies, '/history_movies')
2020-06-19 13:18:48 +00:00
api.add_resource(HistoryStats, '/history_stats')
2020-02-04 17:57:37 +00:00
2019-12-28 05:52:00 +00:00
api.add_resource(WantedSeries, '/wanted_series')
api.add_resource(WantedMovies, '/wanted_movies')
2020-02-13 04:16:22 +00:00
api.add_resource(SearchWantedSeries, '/search_wanted_series')
api.add_resource(SearchWantedMovies, '/search_wanted_movies')
2020-07-19 20:02:38 +00:00
api.add_resource(BlacklistSeries, '/blacklist_series')
api.add_resource(BlacklistEpisodeSubtitlesAdd, '/blacklist_episode_subtitles_add')
api.add_resource(BlacklistEpisodeSubtitlesRemove, '/blacklist_episode_subtitles_remove')
api.add_resource(BlacklistEpisodeSubtitlesRemoveAll, '/blacklist_episode_subtitles_remove_all')
api.add_resource(BlacklistMovies, '/blacklist_movies')
api.add_resource(BlacklistMovieSubtitlesAdd, '/blacklist_movie_subtitles_add')
api.add_resource(BlacklistMovieSubtitlesRemove, '/blacklist_movie_subtitles_remove')
api.add_resource(BlacklistMovieSubtitlesRemoveAll, '/blacklist_movie_subtitles_remove_all')
api.add_resource(SyncSubtitles, '/sync_subtitles')
api.add_resource(BrowseBazarrFS, '/browse_bazarr_filesystem')
api.add_resource(BrowseSonarrFS, '/browse_sonarr_filesystem')
api.add_resource(BrowseRadarrFS, '/browse_radarr_filesystem')