[Feature] Subtitles Upload (#512)

* Subtitle upload support.

* Changes requested for PR #512.

* Work on differentiating manual upload from the other types.

* Support for forced subtitles.

* Fixed message when forced.

* Fixed forced subtitle filename.

* Fixed missing subtitles for movies not updating.

* Changed subtitle upload dialog format.

* Fixed upload UI.

* Fixed toggle.

* Small fixes.

* Small fixes.
This commit is contained in:
Eduardo Almeida 2019-08-23 01:02:11 +01:00 committed by morpheus65535
parent 9a7372c880
commit fdf04df0ed
6 changed files with 333 additions and 18 deletions

View File

@ -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
@ -492,6 +492,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"'

View File

@ -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)
@ -1917,6 +1918,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():
@ -2009,6 +2055,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()

View File

@ -194,6 +194,7 @@
<th class="collapsing">Existing<br>subtitles</th>
<th class="collapsing">Missing<br>subtitles</th>
<th class="collapsing">Manual<br>search</th>
<th class="collapsing">Manual<br>upload</th>
</tr>
</thead>
<tbody>
@ -286,6 +287,11 @@
<a data-episodePath="{{episode[1]}}" data-scenename="{{episode[8]}}" data-language="{{subs_languages_list}}" data-hi="{{details[4]}}" data-forced="{{details[9]}}" data-series_title="{{details[0]}}" data-season="{{episode[2]}}" data-episode="{{episode[3]}}" data-episode_title="{{episode[0]}}" data-sonarrSeriesId="{{episode[5]}}" data-sonarrEpisodeId="{{episode[7]}}" class="manual_search ui tiny label"><i class="ui user icon" style="margin-right:0px" ></i></a>
%end
</td>
<td>
%if subs_languages is not None:
<a data-episodePath="{{episode[1]}}" data-scenename="{{episode[8]}}" data-language="{{subs_languages_list}}" data-hi="{{details[4]}}" data-series_title="{{details[0]}}" data-season="{{episode[2]}}" data-episode="{{episode[3]}}" data-episode_title="{{episode[0]}}" data-sonarrSeriesId="{{episode[5]}}" data-sonarrEpisodeId="{{episode[7]}}" class="manual_upload ui tiny label"><i class="ui cloud upload icon" style="margin-right:0px" ></i></a>
%end
</td>
</tr>
%end
</tbody>
@ -392,6 +398,59 @@
</div>
</div>
<div class="upload_dialog ui small modal">
<i class="close icon"></i>
<div class="header">
<span id="series_title_span_u"></span> - <span id="season_u"></span>x<span id="episode_u"></span> - <span id="episode_title_u"></span>
</div>
<div class="content">
<form class="ui form" name="upload_form" id="upload_form" action="{{base_url}}manual_upload_subtitle" method="post" enctype="multipart/form-data">
<div class="ui grid">
<div class="middle aligned row">
<div class="right aligned three wide column">
<label>Language</label>
</div>
<div class="thirteen wide column">
<select class="ui search dropdown" id="language" name="language">
%for language in subs_languages_list:
<option value="{{language}}">{{language_from_alpha2(language)}}</option>
%end
</select>
</div>
</div>
<div class="middle aligned row">
<div class="right aligned three wide column">
<label>Forced</label>
</div>
<div class="thirteen wide column">
<div class="ui toggle checkbox">
<input name="forced" type="checkbox" value="1">
<label></label>
</div>
</div>
</div>
<div class="middle aligned row">
<div class="right aligned three wide column">
<label>File</label>
</div>
<div class="thirteen wide column">
<input type="file" name="upload">
</div>
</div>
</div>
<input type="hidden" id="upload_episodePath" name="episodePath" value="" />
<input type="hidden" id="upload_sceneName" name="sceneName" value="" />
<input type="hidden" id="upload_sonarrSeriesId" name="sonarrSeriesId" value="" />
<input type="hidden" id="upload_sonarrEpisodeId" name="sonarrEpisodeId" value="" />
<input type="hidden" id="upload_title" name="title" value="" />
</form>
</div>
<div class="actions">
<button class="ui cancel button" >Cancel</button>
<button type="submit" name="save" value="save" form="upload_form" class="ui blue approve button">Save</button>
</div>
</div>
% include('footer.tpl')
</body>
</html>
@ -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');
});

View File

@ -74,6 +74,10 @@
<div class="ui inverted basic compact icon" data-tooltip="Subtitles file has been upgraded." data-inverted="" data-position="top left">
<i class="ui recycle icon"></i>
</div>
%elif row[0] == 4:
<div class="ui inverted basic compact icon" data-tooltip="Subtitles file has been manually uploaded." data-inverted="" data-position="top left">
<i class="ui cloud upload icon"></i>
</div>
%end
</td>
<td>

View File

@ -76,6 +76,10 @@
<div class="ui inverted basic compact icon" data-tooltip="Subtitles file has been upgraded." data-inverted="" data-position="top left">
<i class="ui recycle icon"></i>
</div>
%elif row[0] == 4:
<div class="ui inverted basic compact icon" data-tooltip="Subtitles file has been manually uploaded." data-inverted="" data-position="top left">
<i class="ui cloud upload icon"></i>
</div>
%end
</td>
<td>

View File

@ -125,6 +125,7 @@
%>
%if subs_languages is not None:
<button class="manual_search ui button" data-tooltip="Manually search for subtitles" data-inverted="" data-moviePath="{{details[8]}}" data-scenename="{{details[12]}}" data-language="{{subs_languages_list}}" data-hi="{{details[4]}}" data-forced="{{details[15]}}" data-movie_title="{{details[0]}}" data-radarrId="{{details[10]}}"><i class="ui inverted large compact user icon"></i></button>
<button class="manual_upload ui button" data-tooltip="Manually upload subtitles" data-inverted="" data-moviePath="{{details[8]}}" data-scenename="{{details[12]}}" data-language="{{subs_languages_list}}" data-hi="{{details[4]}}" data-movie_title="{{details[0]}}" data-radarrId="{{details[10]}}"><i class="ui inverted large compact cloud upload icon"></i></button>
%end
<button id="config" class="ui button" data-tooltip="Edit movie" data-inverted="" data-tmdbid="{{details[5]}}" data-title="{{details[0]}}" data-poster="{{details[2]}}" data-audio="{{details[6]}}" data-languages="{{!subs_languages_list}}" data-hearing-impaired="{{details[4]}}" data-forced="{{details[15]}}"><i class="ui inverted large compact configure icon"></i></button>
</div>
@ -354,6 +355,58 @@
</div>
</div>
<div class="upload_dialog ui small modal">
<i class="close icon"></i>
<div class="header">
<span id="movie_title_upload_span"></span>
</div>
<div class="scrolling content">
<form class="ui form" name="upload_form" id="upload_form" action="{{base_url}}manual_upload_subtitle_movie" method="post" enctype="multipart/form-data">
<div class="ui grid">
<div class="middle aligned row">
<div class="right aligned three wide column">
<label>Language</label>
</div>
<div class="thirteen wide column">
<select class="ui search dropdown" id="language" name="language">
%for language in subs_languages_list:
<option value="{{language}}">{{language_from_alpha2(language)}}</option>
%end
</select>
</div>
</div>
<div class="middle aligned row">
<div class="right aligned three wide column">
<label>Forced</label>
</div>
<div class="thirteen wide column">
<div class="ui toggle checkbox">
<input name="forced" type="checkbox" value="1">
<label></label>
</div>
</div>
</div>
<div class="middle aligned row">
<div class="right aligned three wide column">
<label>File</label>
</div>
<div class="thirteen wide column">
<input type="file" name="upload">
</div>
</div>
</div>
<input type="hidden" id="upload_moviePath" name="moviePath" value="" />
<input type="hidden" id="upload_sceneName" name="sceneName" value="" />
<input type="hidden" id="upload_radarrId" name="radarrId" value="" />
<input type="hidden" id="upload_title" name="title" value="" />
</form>
</div>
<div class="actions">
<button class="ui cancel button" >Cancel</button>
<button type="submit" name="save" value="save" form="upload_form" class="ui blue approve button">Save</button>
</div>
</div>
% include('footer.tpl')
</body>
</html>
@ -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')
;