Merge remote-tracking branch 'origin/development' into Flask

# Conflicts:
#	views/menu.tpl
pull/884/head
Moravčík, Marian 5 years ago
commit 3d8fe99c66

@ -5,6 +5,7 @@ import os
from simpleconfigparser import simpleconfigparser
from get_args import args
from six import PY3
defaults = {
'general': {
@ -139,7 +140,10 @@ defaults = {
}
}
settings = simpleconfigparser(defaults=defaults)
if PY3:
settings = simpleconfigparser(defaults=defaults, interpolation=None)
else:
settings = simpleconfigparser(defaults=defaults)
settings.read(os.path.join(args.config_dir, 'config', 'config.ini'))
base_url = settings.general.base_url

@ -190,6 +190,9 @@ def list_missing_subtitles(no=None, epno=None):
"FROM table_episodes LEFT JOIN table_shows "
"on table_episodes.sonarrSeriesId = table_shows.sonarrSeriesId" +
episodes_subtitles_clause)
if isinstance(episodes_subtitles, six.string_types):
logging.error("BAZARR list missing subtitles query to DB returned this instead of rows: " + episodes_subtitles)
return
missing_subtitles_global = []
use_embedded_subs = settings.general.getboolean('use_embedded_subs')
@ -244,6 +247,9 @@ def list_missing_subtitles_movies(no=None):
movies_subtitles = database.execute("SELECT radarrId, subtitles, languages, forced FROM table_movies" +
movies_subtitles_clause)
if isinstance(movies_subtitles, six.string_types):
logging.error("BAZARR list missing subtitles query to DB returned this instead of rows: " + movies_subtitles)
return
missing_subtitles_global = []
use_embedded_subs = settings.general.getboolean('use_embedded_subs')

@ -179,7 +179,7 @@ class VideoFileParser:
if PY3:
command = [parser] + commandArgs + [inputFile]
completedProcess = subprocess.run(
command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, encoding="utf-8"
)
if completedProcess.returncode:
raise IOError(

@ -462,6 +462,15 @@ elif sys.platform.startswith(('linux', 'darwin', 'sunos5')) or 'bsd' in sys.plat
@property
def path(self):
if self._path is None:
# Make sure both attributes are string
try:
self._scandir_path = self._scandir_path.decode('unicode_escape')
except (UnicodeDecodeError, AttributeError):
pass
try:
self.name = self.name.decode('unicode_escape')
except (UnicodeDecodeError, AttributeError):
pass
self._path = join(self._scandir_path, self.name)
return self._path

@ -26,7 +26,7 @@ THE SOFTWARE.
try:
from configparser import ConfigParser as configparser, NoOptionError, NoSectionError
except ImportError:
from ConfigParser import SafeConfigParser as configparser, NoOptionError, NoSectionError
from ConfigParser import RawConfigParser as configparser, NoOptionError, NoSectionError
class simpleconfigparser(configparser):
@ -126,6 +126,6 @@ class simpleconfigparser(configparser):
def get(self, section, option, raw=False, vars=None, fallback=None):
try:
# Strip out quotes from the edges
return configparser.get(self, section, option, raw=raw, vars=vars).strip('"\'')
return configparser.get(self, section, option).strip('"\'')
except NoOptionError:
return None

@ -31,7 +31,7 @@ from subliminal.core import guessit, ProviderPool, io, is_windows_special_path,
ThreadPoolExecutor, check_video
from subliminal_patch.exceptions import TooManyRequests, APIThrottled
from subzero.language import Language, ENDSWITH_LANGUAGECODE_RE
from subzero.language import Language, ENDSWITH_LANGUAGECODE_RE, FULL_LANGUAGE_LIST
from scandir import scandir, scandir_generic as _scandir_generic
import six
@ -610,7 +610,8 @@ def _search_external_subtitles(path, languages=None, only_one=False, scandir_gen
forced = "forced" in adv_tag
# remove possible language code for matching
p_root_bare = ENDSWITH_LANGUAGECODE_RE.sub("", p_root)
p_root_bare = ENDSWITH_LANGUAGECODE_RE.sub(
lambda m: "" if str(m.group(1)).lower() in FULL_LANGUAGE_LIST else m.group(0), p_root)
p_root_lower = p_root_bare.lower()

@ -75,6 +75,7 @@ class BetaSeriesProvider(Provider):
def query(self, languages, video):
# query the server
result = None
self.video = video
matches = set()
if video.tvdb_id:
params = {'key': self.token,
@ -137,8 +138,13 @@ class BetaSeriesProvider(Provider):
r.raise_for_status()
archive = _get_archive(r.content)
subtitle_content = _get_subtitle_from_archive(
archive) if archive else r.content
if archive:
subtitle_names = _get_subtitle_names_from_archive(archive)
subtitle_to_download = _choose_subtitle_with_release_group(subtitle_names, self.video.release_group)
logger.debug('Subtitle to download: ' + subtitle_to_download)
subtitle_content = archive.read(subtitle_to_download)
else:
subtitle_content = r.content
if subtitle_content:
subtitle.content = fix_line_ending(subtitle_content)
@ -160,7 +166,8 @@ def _get_archive(content):
return archive
def _get_subtitle_from_archive(archive):
def _get_subtitle_names_from_archive(archive):
subtitlesToConsider = []
for name in archive.namelist():
# discard hidden files
if os.path.split(name)[-1].startswith('.'):
@ -170,9 +177,13 @@ def _get_subtitle_from_archive(archive):
if not name.lower().endswith(SUBTITLE_EXTENSIONS):
continue
return archive.read(name)
subtitlesToConsider.append(name)
return None
if len(subtitlesToConsider)>0:
logger.debug('Subtitles in archive: ' + ' '.join(subtitlesToConsider))
return subtitlesToConsider
else:
return None
def _translateLanguageCodeToLanguage(languageCode):
@ -180,3 +191,11 @@ def _translateLanguageCodeToLanguage(languageCode):
return Language.fromalpha2('en')
elif languageCode.lower() == 'vf':
return Language.fromalpha2('fr')
def _choose_subtitle_with_release_group(subtitle_names, release_group):
if release_group:
for subtitle in subtitle_names:
if release_group in subtitle:
return subtitle
return subtitle_names[0]

@ -20,6 +20,15 @@ from subliminal import Subtitle as Subtitle_
from subliminal.subtitle import Episode, Movie, sanitize_release_group, get_equivalent_release_groups
from subliminal_patch.utils import sanitize
from ftfy import fix_text
from codecs import BOM_UTF8, BOM_UTF16_BE, BOM_UTF16_LE, BOM_UTF32_BE, BOM_UTF32_LE
BOMS = (
(BOM_UTF8, "UTF-8"),
(BOM_UTF32_BE, "UTF-32-BE"),
(BOM_UTF32_LE, "UTF-32-LE"),
(BOM_UTF16_BE, "UTF-16-BE"),
(BOM_UTF16_LE, "UTF-16-LE"),
)
logger = logging.getLogger(__name__)
@ -106,6 +115,9 @@ class Subtitle(Subtitle_):
# normalize line endings
self.content = self.content.replace(b"\r\n", b"\n").replace(b'\r', b'\n')
def _check_bom(self, data):
return [encoding for bom, encoding in BOMS if data.startswith(bom)]
def guess_encoding(self):
"""Guess encoding using the language, falling back on chardet.
@ -120,6 +132,11 @@ class Subtitle(Subtitle_):
encodings = ['utf-8']
# check UTF BOMs
bom_encodings = self._check_bom(self.content)
if bom_encodings:
encodings = list(set(enc.lower() for enc in bom_encodings + encodings))
# add language-specific encodings
# http://scratchpad.wikia.com/wiki/Character_Encoding_Recommendation_for_Languages

@ -4,7 +4,7 @@ import types
import re
from babelfish.exceptions import LanguageError
from babelfish import Language as Language_, basestr
from babelfish import Language as Language_, basestr, LANGUAGE_MATRIX
from six.moves import zip
repl_map = {
@ -32,6 +32,11 @@ repl_map = {
"tib": "bo",
}
ALPHA2_LIST = list(set(filter(lambda x: x, map(lambda x: x.alpha2, LANGUAGE_MATRIX)))) + list(repl_map.values())
ALPHA3b_LIST = list(set(filter(lambda x: x, map(lambda x: x.alpha3, LANGUAGE_MATRIX)))) + \
list(set(filter(lambda x: len(x) == 3, list(repl_map.keys()))))
FULL_LANGUAGE_LIST = ALPHA2_LIST + ALPHA3b_LIST
def language_from_stream(l):
if not l:

@ -0,0 +1,508 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta name="mobile-web-app-capable" content="yes"/>
<meta name="apple-mobile-web-app-capable" content="yes"/>
<link href="{{base_url}}static/noty/noty.css" rel="stylesheet">
<script src="{{base_url}}static/noty/noty.min.js" type="text/javascript"></script>
<style>
#divmenu {
padding-top: 1em;
}
.menu_segment {
padding: 0em !important;
border: none !important;
margin: 0em !important;
background: none !important;
}
#icon_menu {
background: transparent !important;
border: solid;
border-width: 1px;
border-color: white;
}
.prompt {
background-color: #333333 !important;
color: white !important;
border-radius: 3px !important;
}
.searchicon {
color: white !important;
}
.ui.progress .bar>.progress {
right: auto;
left: .5em;
color: black !important;
}
.ui.disabled.progress.notification_progress {
opacity: unset !important;
}
div.disabled { pointer-events: none; }
button.disabled { pointer-events: none; }
@media only screen and (max-width: 1024px) {
.logo {
width: 36px;
height: auto;
}
}
</style>
</head>
<body>
% from get_args import args
% from get_providers import update_throttled_provider
% update_throttled_provider()
% import ast
% import datetime
% import os
% from database import database
% import operator
% from config import settings
% from functools import reduce
%if settings.sonarr.getboolean('only_monitored'):
% monitored_only_query_string_sonarr = ' AND monitored = "True"'
%else:
% monitored_only_query_string_sonarr = ""
%end
%if settings.radarr.getboolean('only_monitored'):
% monitored_only_query_string_radarr = ' AND monitored = "True"'
%else:
% monitored_only_query_string_radarr = ""
%end
% wanted_series = database.execute("SELECT COUNT(*) as count FROM table_episodes WHERE missing_subtitles != '[]'" + monitored_only_query_string_sonarr, only_one=True)['count']
% wanted_movies = database.execute("SELECT COUNT(*) as count FROM table_movies WHERE missing_subtitles != '[]'" + monitored_only_query_string_radarr, only_one=True)['count']
% from get_providers import list_throttled_providers
% throttled_providers_count = len(eval(str(settings.general.throtteled_providers)))
<div id="divmenu" class="ui container">
<div class="ui grid">
<div class="three wide column">
<div class="ui compact segment menu_segment">
<a href="{{base_url}}"><img class="logo" src="{{base_url}}static/logo128.png"></a>
</div>
</div>
<div class="ten wide right aligned compact column">
<div id="button_menu" class="ui center aligned segment menu_segment" hidden="hidden">
<div class="ui inverted compact borderless labeled icon massive menu menu_segment">
% if settings.general.getboolean('use_sonarr'):
<a class="item" href="{{base_url}}series">
<i class="play icon"></i>
Series
</a>
% end
% if settings.general.getboolean('use_radarr'):
<a class="item" href="{{base_url}}movies">
<i class="film icon"></i>
Movies
</a>
% end
<a class="item" href="{{base_url}}history">
<i class="wait icon"></i>
History
</a>
<a class="item" href="{{base_url}}wanted">
<i class="warning sign icon">
% if settings.general.getboolean('use_sonarr') and wanted_series > 0:
<div class="floating ui tiny yellow label" style="left:90% !important;top:0.5em !important;">
{{wanted_series}}
</div>
% end
% if settings.general.getboolean('use_radarr') and wanted_movies > 0:
<div class="floating ui tiny green label" style="left:90% !important;top:3em !important;">
{{wanted_movies}}
</div>
% end
</i>
Wanted
</a>
<a class="item" href="{{base_url}}settings">
<i class="settings icon"></i>
Settings
</a>
<a class="item" href="{{base_url}}system">
<i class="laptop icon">
% if throttled_providers_count:
<div class="floating ui tiny yellow label" style="left:90% !important;top:0.5em !important;">
{{throttled_providers_count}}
</div>
% end
</i>
System
</a>
<a id="donate" class="item" href="https://beerpay.io/morpheus65535/bazarr">
<i class="red heart icon"></i>
Donate
</a>
</div>
</div>
<div class="ui center aligned segment menu_segment">
<div class="ui grid">
<div id="search_column" class="centered column">
<div class="ui fluid search">
<div class="ui left icon fluid input">
<input class="prompt" type="text" placeholder="Search in your library">
<i class="searchicon search icon"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="three wide right aligned column">
<div id="dropdown_menu" class="ui segment menu_segment" hidden="hidden">
<div id="icon_menu" class="ui compact tiny icon menu">
<div id="dropdown_menu_button" class="ui floating right dropdown item">
<i class="inverted bars icon"></i>
<div class="ui menu">
% if settings.general.getboolean('use_sonarr'):
<a class="item" href="{{base_url}}series">
<i class="play icon"></i>
Series
</a>
% end
% if settings.general.getboolean('use_radarr'):
<a class="item" href="{{base_url}}movies">
<i class="film icon"></i>
Movies
</a>
% end
<a class="item" href="{{base_url}}history">
<i class="wait icon"></i>
History
</a>
<a class="item" href="{{base_url}}wanted">
<i class="warning sign icon"></i>
<span style="margin-right: 1em;">Wanted</span>
% if settings.general.getboolean('use_sonarr') and wanted_series > 0:
<div class="ui tiny yellow label">
{{wanted_series}}
</div>
% end
% if settings.general.getboolean('use_radarr') and wanted_movies > 0:
<div class="ui tiny green label">
{{wanted_movies}}
</div>
% end
</a>
<a class="item" href="{{base_url}}settings">
<i class="settings icon"></i>
Settings
</a>
<a class="item" href="{{base_url}}system">
<i class="laptop icon">
% if throttled_providers_count:
<div class="floating ui tiny yellow label" style="left:90% !important;top:0.5em !important;">
{{throttled_providers_count}}
</div>
% end
</i>
System
</a>
<a id="donate" class="item" href="https://beerpay.io/morpheus65535/bazarr">
<i class="red heart icon"></i>
Donate
</a>
</div>
</div>
</div>
</div>
</div>
</div>
% restart_required = database.execute("SELECT configured, updated FROM system")
% for item in restart_required:
% restart_required = item
% break
% end
% if restart_required['updated'] == '1' and restart_required['configured'] == '1':
<div class='ui center aligned grid'><div class='fifteen wide column'><div class="ui red message">Bazarr Needs To Be Restarted To Apply The Last Update & Changes To General Settings. Click <a href=# id="restart_link">Here</a> To Restart.</div></div></div>
% elif restart_required['updated'] == '1':
<div class='ui center aligned grid'><div class='fifteen wide column'><div class="ui red message">Bazarr Needs To Be Restarted To Apply Changes To The Last Update. Click <a href=# id="restart_link">Here</a> To Restart.</div></div></div>
% elif restart_required['configured'] == '1':
<div class='ui center aligned grid'><div class='fifteen wide column'><div class="ui red message">Bazarr Needs To Be Restarted To Apply Changes To General Settings. Click <a href=# id="restart_link">Here</a> To Restart.</div></div></div>
% end
% from six import PY2
% import datetime
% if PY2:
<div class='ui left aligned grid'>
<div class='fluid column'>
<div class="ui yellow icon message">
<i class="python icon"></i>
<div class="content">
<div class="header">Python deprecation warning</div>
Bazarr is now compatible with Python 3.6 and newer. You should upgrade Python as we'll drop support for Python 2.7.x by the end of January 2020.
<div class="ui bulleted list">
% if os.name == 'posix':
<div class="item">If you are running under Docker, don't worry, we'll take care of this for you. Just pull the new image that should be available within a couple of days.</div>
% end
% if os.name == 'nt':
<div class="item">If you have installed using the Windows Installer, just download the new installer that will upgrade your current installation (make sure to not change installation directory).</div>
% end
<div class="item">If you are running from source, it's up to you to install Python 3 (don't forget requirements.txt) and use it to run Bazarr.</div>
</div>
</div>
</div>
</div>
</div>
% end
</div>
</body>
</html>
<script>
$('.ui.dropdown').dropdown();
$('.ui.search')
.search({
apiSettings: {
url: '{{base_url}}search_json/{query}',
onResponse: function(results) {
const response = {
results : []
};
$.each(results.items, function(index, item) {
response.results.push({
title : item.name,
url : item.url
});
});
return response;
}
},
minCharacters : 2
})
;
if (window.location.href.indexOf("episodes") > -1) {
$('.menu_segment').css('background', 'rgba(0, 0, 0, 0.8)');
$('.menu_segment').css('border-color', '#000000');
$('#divmenu').css('background', 'rgba(0, 0, 0, 0.8)');
$('#divmenu').css('box-shadow', '0 0 5px 5px #000000');
}
else if (window.location.href.indexOf("movie/") > -1) {
$('.menu_segment').css('background', 'rgba(0, 0, 0, 0.9)');
$('.menu_segment').css('border-color', '#000000');
$('#divmenu').css('background', 'rgba(0, 0, 0, 0.8)');
$('#divmenu').css('box-shadow', '0 0 5px 5px #000000');
}
else {
$('.menu_segment').css('background', '#272727');
$('.menu_segment').css('border-color', '#272727');
$('#divmenu').css('background', '#272727');
}
$('#restart_link').on('click', function(){
$('#loader_text').text("Bazarr is restarting. Please Wait...");
$.ajax({
url: "{{base_url}}restart",
async: true,
error: (function(){
setTimeout(function(){ setInterval(ping, 2000); },8000);
})
})
});
% from config import settings
% from get_args import args
% ip = settings.general.ip
% port = args.port if args.port else settings.general.port
% base_url = settings.general.base_url
if ("{{ip}}" === "0.0.0.0") {
public_ip = window.location.hostname;
} else {
public_ip = "{{ip}}";
}
protocol = window.location.protocol;
if (window.location.port === '{{current_port}}') {
public_port = '{{port}}';
} else {
public_port = window.location.port;
}
function ping() {
$.ajax({
url: protocol + '//' + public_ip + ':' + public_port + '{{base_url}}',
success: function(result) {
window.location.href= protocol + '//' + public_ip + ':' + public_port + '{{base_url}}';
}
});
}
$( window ).on('resize', function () {
if($(window).width() < 1024){
$('#button_menu').hide();
$('#dropdown_menu').show();
$('#search_column').removeClass('ten wide');
} else {
$('#dropdown_menu').hide();
$('#button_menu').show();
$('#search_column').addClass('ten wide');
}
}).resize();
</script>
<script type="text/javascript">
var url_notifications = location.protocol +"//" + window.location.host + "{{base_url}}notifications";
var notificationTimeout;
var timeout;
var killer;
var item = {};
var length = {};
function doNotificationsAjax() {
$.ajax({
url: url_notifications,
success: function (data) {
if (data !== "") {
data = JSON.parse(data);
var msg = data[0];
var type = data[1];
var duration = data[2];
var button = data[3];
var queue = data[4];
var item = data[5];
var length = data[6];
if (length === 0) {
var message = msg;
} else {
var message = msg + '<p><div class="ui disabled progress notification_progress" data-value=' + item + ' data-total=' + length + ' style="margin-bottom: -0.25em"><div class="bar"><div class="progress"></div></div></div>'
}
if (duration === 'temporary') {
timeout = 3000;
killer = queue;
} else if (duration === 'long') {
timeout = 15000;
killer = queue;
} else {
timeout = false;
killer = false;
}
if (button === 'refresh') {
button = [ Noty.button('Refresh', 'ui tiny primary button', function () { window.location.reload() }) ];
} else if (button === 'restart') {
// to be completed
button = [ Noty.button('Restart', 'ui tiny primary button', function () { alert('Restart Not Implemented Yet!') }) ];
} else {
button = [];
}
new Noty({
text: message,
progressBar: false,
animation: {
open: null,
close: null
},
type: type,
layout: 'bottomRight',
theme: 'semanticui',
queue: queue,
timeout: timeout,
killer: killer,
buttons: button,
force: false
}).show();
$('.notification_progress').progress({
duration : 0,
autoSuccess: false,
label: 'ratio',
text: {
ratio: '{value} / {total}'
}
});
}
},
complete: function (data) {
// Schedule the next
if (data.responseText !== "") {
notificationTimeout = setTimeout(doNotificationsAjax, 100);
} else {
notificationTimeout = setTimeout(doNotificationsAjax, 5000);
}
},
error: function () {
notificationTimeout = setTimeout(doNotificationsAjax, 5000);
}
});
}
notificationTimeout = setTimeout(doNotificationsAjax, 1000);
$(window).bind('beforeunload', function(){
clearTimeout(notificationTimeout);
});
</script>
<script type="text/javascript">
var url_tasks = location.protocol +"//" + window.location.host + "{{base_url}}running_tasks";
var tasksTimeout;
function doTasksAjax() {
$.ajax({
url: url_tasks,
dataType: 'json',
success: function (data) {
$('#tasks > tbody > tr').each(function() {
if ($.inArray($(this).attr('id'), data['tasks']) > -1) {
$(this).find('td:last').find('div:first').addClass('disabled');
$(this).find('td:last').find('div:first').find('i:first').addClass('loading');
} else {
$(this).find('td:last').find('div:first').removeClass('disabled');
$(this).find('td:last').find('div:first').find('i:first').removeClass('loading');
}
});
if ($.inArray('wanted_search_missing_subtitles', data['tasks']) > -1) {
$('#wanted_search_missing_subtitles').addClass('disabled');
$('#wanted_search_missing_subtitles_movies').addClass('disabled');
$('#wanted_search_missing_subtitles').find('i:first').addClass('loading');
$('#wanted_search_missing_subtitles_movies').find('i:first').addClass('loading');
} else {
$('#wanted_search_missing_subtitles').removeClass('disabled');
$('#wanted_search_missing_subtitles_movies').removeClass('disabled');
$('#wanted_search_missing_subtitles').find('i:first').removeClass('loading');
$('#wanted_search_missing_subtitles_movies').find('i:first').removeClass('loading');
}
%if 'no' in locals():
if ($.inArray('search_missing_subtitles_{{no}}', data['tasks']) > -1) {
$('#search_missing_subtitles').addClass('disabled');
$('#search_missing_subtitles').find('i:first').addClass('loading');
} else {
$('#search_missing_subtitles').removeClass('disabled');
$('#search_missing_subtitles').find('i:first').removeClass('loading');
}
if ($.inArray('search_missing_subtitles_movie_{{no}}', data['tasks']) > -1) {
$('#search_missing_subtitles_movie').addClass('disabled');
$('#search_missing_subtitles_movie').find('i:first').addClass('loading');
} else {
$('#search_missing_subtitles_movie').removeClass('disabled');
$('#search_missing_subtitles_movie').find('i:first').removeClass('loading');
}
%end
},
complete: function (data) {
// Schedule the next
tasksTimeout = setTimeout(doTasksAjax, 5000);
}
});
}
tasksTimeout = setTimeout(doTasksAjax, 500);
$(window).bind('beforeunload', function(){
clearTimeout(tasksTimeout);
});
</script>
Loading…
Cancel
Save