diff --git a/bazarr/get_subtitle.py b/bazarr/get_subtitle.py index d1779ffef..f4c1db359 100644 --- a/bazarr/get_subtitle.py +++ b/bazarr/get_subtitle.py @@ -19,7 +19,7 @@ from subzero.video import parse_video from subliminal import region, score as subliminal_scores, \ list_subtitles, Episode, Movie from subliminal_patch.core import SZAsyncProviderPool, download_best_subtitles, save_subtitles, download_subtitles, \ - list_all_subtitles + list_all_subtitles, get_subtitle_path from subliminal_patch.score import compute_score from subliminal.refiners.tvdb import series_re from get_languages import language_from_alpha3, alpha2_from_alpha3, alpha3_from_alpha2, language_from_alpha2 @@ -491,6 +491,51 @@ def manual_download_subtitle(path, language, hi, forced, subtitle, provider, pro logging.debug('BAZARR Ended manually downloading subtitles for file: ' + path) +def manual_upload_subtitle(path, language, forced, title, scene_name, media_type, subtitle): + logging.debug('BAZARR Manually uploading subtitles for this file: ' + path) + + single = settings.general.getboolean('single_language') + + chmod = int(settings.general.chmod, 8) if not sys.platform.startswith( + 'win') and settings.general.getboolean('chmod_enabled') else None + + _, ext = os.path.splitext(subtitle.filename) + + language = alpha3_from_alpha2(language) + + if language == 'pob': + lang_obj = Language('por', 'BR') + else: + lang_obj = Language(language) + + if forced: + lang_obj = Language.rebuild(lang_obj, forced=True) + + subtitle_path = get_subtitle_path(video_path=force_unicode(path), + language=None if single else lang_obj, + extension=ext, + forced_tag=forced) + + subtitle_path = force_unicode(subtitle_path) + + if os.path.exists(subtitle_path): + os.remove(subtitle_path) + + subtitle.save(subtitle_path) + + if chmod: + os.chmod(subtitle_path, chmod) + + message = language_from_alpha3(language) + (" forced" if forced else "") + " subtitles manually uploaded." + + if media_type == 'series': + reversed_path = path_replace_reverse(path) + else: + reversed_path = path_replace_reverse_movie(path) + + return message, reversed_path + + def series_download_subtitles(no): if settings.sonarr.getboolean('only_monitored'): monitored_only_query_string = ' AND monitored = "True"' diff --git a/bazarr/main.py b/bazarr/main.py index acccb2135..999482bc7 100644 --- a/bazarr/main.py +++ b/bazarr/main.py @@ -61,12 +61,13 @@ from get_episodes import * from list_subtitles import store_subtitles, store_subtitles_movie, series_scan_subtitles, movies_scan_subtitles, \ list_missing_subtitles, list_missing_subtitles_movies from get_subtitle import download_subtitle, series_download_subtitles, movies_download_subtitles, \ - manual_search, manual_download_subtitle + manual_search, manual_download_subtitle, manual_upload_subtitle from utils import history_log, history_log_movie from scheduler import * from notifier import send_notifications, send_notifications_movie from config import settings, url_sonarr, url_radarr, url_radarr_short, url_sonarr_short, base_url from helper import path_replace_movie +from subliminal_patch.core import SUBTITLE_EXTENSIONS from subliminal_patch.extensions import provider_registry as provider_manager reload(sys) @@ -1911,6 +1912,51 @@ def manual_get_subtitle(): pass +@route(base_url + 'manual_upload_subtitle', method='POST') +@custom_auth_basic(check_credentials) +def perform_manual_upload_subtitle(): + authorize() + ref = request.environ['HTTP_REFERER'] + + episodePath = request.forms.get('episodePath') + sceneName = request.forms.get('sceneName') + language = request.forms.get('language') + forced = True if request.forms.get('forced') == '1' else False + upload = request.files.get('upload') + sonarrSeriesId = request.forms.get('sonarrSeriesId') + sonarrEpisodeId = request.forms.get('sonarrEpisodeId') + title = request.forms.get('title') + + _, 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', + subtitle=upload) + + if result is not None: + message = result[0] + path = result[1] + language_code = language + ":forced" if forced else language + provider = "manual" + score = 360 + history_log(4, sonarrSeriesId, sonarrEpisodeId, message, path, language_code, provider, score) + send_notifications(sonarrSeriesId, sonarrEpisodeId, message) + store_subtitles(unicode(episodePath)) + list_missing_subtitles(sonarrSeriesId) + + redirect(ref) + except OSError: + pass + + @route(base_url + 'get_subtitle_movie', method='POST') @custom_auth_basic(check_credentials) def get_subtitle_movie(): @@ -2003,6 +2049,50 @@ def manual_get_subtitle_movie(): pass +@route(base_url + 'manual_upload_subtitle_movie', method='POST') +@custom_auth_basic(check_credentials) +def perform_manual_upload_subtitle_movie(): + authorize() + ref = request.environ['HTTP_REFERER'] + + moviePath = request.forms.get('moviePath') + sceneName = request.forms.get('sceneName') + language = request.forms.get('language') + forced = True if request.forms.get('forced') == '1' else False + upload = request.files.get('upload') + radarrId = request.forms.get('radarrId') + title = request.forms.get('title') + + _, 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='series', + subtitle=upload) + + if result is not None: + message = result[0] + path = result[1] + language_code = language + ":forced" if forced else language + provider = "manual" + score = 120 + history_log_movie(4, radarrId, message, path, language_code, provider, score) + send_notifications_movie(radarrId, message) + store_subtitles_movie(unicode(moviePath)) + list_missing_subtitles_movies(radarrId) + + redirect(ref) + except OSError: + pass + + def configured(): conn = sqlite3.connect(os.path.join(args.config_dir, 'db', 'bazarr.db'), timeout=30) c = conn.cursor() diff --git a/views/episodes.tpl b/views/episodes.tpl index 71fca45c6..4f0838906 100644 --- a/views/episodes.tpl +++ b/views/episodes.tpl @@ -194,6 +194,7 @@ Existing
subtitles Missing
subtitles Manual
search + Manual
upload @@ -286,6 +287,11 @@ %end + + %if subs_languages is not None: + + %end + %end @@ -392,6 +398,59 @@ + + % include('footer.tpl') @@ -464,15 +523,10 @@ }); }); - $('a:not(.manual_search), .menu .item, button:not(#config, .cancel, #search_missing_subtitles)').on('click', function(){ + $('a:not(.manual_search, .manual_upload), .menu .item, button:not(#config, .cancel, #search_missing_subtitles)').on('click', function(){ $('#loader').addClass('active'); }); - $('.modal') - .modal({ - autofocus: false - }); - $('#config').on('click', function(){ $('#series_form').attr('action', '{{base_url}}edit_series/{{no}}'); @@ -494,7 +548,12 @@ $("#series_hearing-impaired_div").checkbox('uncheck'); } - $('.config_dialog').modal('show'); + $('.config_dialog') + .modal({ + centered: false, + autofocus: false + }) + .modal('show'); }); $('.manual_search').on('click', function(){ @@ -599,7 +658,41 @@ $('.search_dialog') .modal({ - centered: false + centered: false, + autofocus: false + }) + .modal('show'); + }); + + $('.manual_upload').on('click', function(){ + $("#series_title_span_u").html($(this).data("series_title")); + $("#season_u").html($(this).data("season")); + $("#episode_u").html($(this).data("episode")); + $("#episode_title_u").html($(this).data("episode_title")); + + episodePath = $(this).attr("data-episodePath"); + sceneName = $(this).attr("data-sceneName"); + language = $(this).attr("data-language"); + hi = $(this).attr("data-hi"); + sonarrSeriesId = $(this).attr("data-sonarrSeriesId"); + sonarrEpisodeId = $(this).attr("data-sonarrEpisodeId"); + var languages = Array.from({{!subs_languages_list}}); + var is_pb = languages.includes('pb'); + var is_pt = languages.includes('pt'); + var title = "{{!details[0].replace("'", "\'")}}"; + + $('#language').dropdown(); + + $('#upload_episodePath').val(episodePath); + $('#upload_sceneName').val(sceneName); + $('#upload_sonarrSeriesId').val(sonarrSeriesId); + $('#upload_sonarrEpisodeId').val(sonarrEpisodeId); + $('#upload_title').val(title); + + $('.upload_dialog') + .modal({ + centered: false, + autofocus: false }) .modal('show'); }); diff --git a/views/historymovies.tpl b/views/historymovies.tpl index ae4ea11cd..5c183d307 100644 --- a/views/historymovies.tpl +++ b/views/historymovies.tpl @@ -74,6 +74,10 @@
+ %elif row[0] == 4: +
+ +
%end diff --git a/views/historyseries.tpl b/views/historyseries.tpl index 8c706ce45..02d676bd8 100644 --- a/views/historyseries.tpl +++ b/views/historyseries.tpl @@ -76,6 +76,10 @@
+ %elif row[0] == 4: +
+ +
%end diff --git a/views/movie.tpl b/views/movie.tpl index c1a1a06ef..5fb5ef966 100644 --- a/views/movie.tpl +++ b/views/movie.tpl @@ -125,6 +125,7 @@ %> %if subs_languages is not None: + %end @@ -354,6 +355,58 @@ + + % include('footer.tpl') @@ -425,15 +478,10 @@ }); }); - $('a, .menu .item, button:not(#config, .cancel, .manual_search, #search_missing_subtitles_movie)').on('click', function(){ + $('a, .menu .item, button:not(#config, .cancel, .manual_search, .manual_upload, #search_missing_subtitles_movie)').on('click', function(){ $('#loader').addClass('active'); }); - $('.modal') - .modal({ - autofocus: false - }); - $('#config').on('click', function(){ $('#movie_form').attr('action', '{{base_url}}edit_movie/{{no}}'); @@ -455,7 +503,12 @@ $("#movie_hearing-impaired_div").checkbox('uncheck'); } - $('.config_dialog').modal('show'); + $('.config_dialog') + .modal({ + centered: false, + autofocus: false + }) + .modal('show'); }); $('.manual_search').on('click', function(){ @@ -557,7 +610,33 @@ $('.search_dialog') .modal({ - centered: false + centered: false, + autofocus: false + }) + .modal('show') + ; + }); + + $('.manual_upload').on('click', function() { + $("#movie_title_upload_span").html($(this).data("movie_title")); + + moviePath = $(this).attr("data-moviePath"); + sceneName = $(this).attr("data-sceneName"); + language = $(this).attr("data-language"); + radarrId = $(this).attr("data-radarrId"); + var title = "{{!details[0].replace("'", "\'")}}"; + + $('#language').dropdown(); + + $('#upload_moviePath').val(moviePath); + $('#upload_sceneName').val(sceneName); + $('#upload_radarrId').val(radarrId); + $('#upload_title').val(title); + + $('.upload_dialog') + .modal({ + centered: false, + autofocus: false }) .modal('show') ;