mirror of https://github.com/morpheus65535/bazarr
Merge branch 'development'
This commit is contained in:
commit
20c3a3c55d
|
@ -8,6 +8,7 @@ cachefile.dbm
|
|||
bazarr.pid
|
||||
/venv
|
||||
/data
|
||||
/.vscode
|
||||
|
||||
# Allow
|
||||
!*.dll
|
|
@ -70,6 +70,7 @@ If you need something that is not already part of Bazarr, feel free to create a
|
|||
* TVSubtitles
|
||||
* Wizdom
|
||||
* XSubs
|
||||
* Yavka.net
|
||||
* Zimuku
|
||||
|
||||
## Screenshot
|
||||
|
|
189
bazarr.py
189
bazarr.py
|
@ -2,29 +2,26 @@
|
|||
|
||||
import os
|
||||
import platform
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import atexit
|
||||
|
||||
from bazarr.get_args import args
|
||||
from libs.six import PY3
|
||||
|
||||
|
||||
def check_python_version():
|
||||
python_version = platform.python_version_tuple()
|
||||
minimum_py2_tuple = (2, 7, 13)
|
||||
minimum_py3_tuple = (3, 7, 0)
|
||||
minimum_py2_str = ".".join(str(i) for i in minimum_py2_tuple)
|
||||
minimum_py3_str = ".".join(str(i) for i in minimum_py3_tuple)
|
||||
|
||||
if (int(python_version[0]) == minimum_py3_tuple[0] and int(python_version[1]) < minimum_py3_tuple[1]) or \
|
||||
(int(python_version[0]) != minimum_py3_tuple[0] and int(python_version[0]) != minimum_py2_tuple[0]):
|
||||
if int(python_version[0]) < minimum_py3_tuple[0]:
|
||||
print("Python " + minimum_py3_str + " or greater required. "
|
||||
"Current version is " + platform.python_version() + ". Please upgrade Python.")
|
||||
sys.exit(1)
|
||||
elif int(python_version[0]) == minimum_py2_tuple[0] and int(python_version[1]) < minimum_py2_tuple[1]:
|
||||
print("Python " + minimum_py2_str + " or greater required. "
|
||||
elif (int(python_version[0]) == minimum_py3_tuple[0] and int(python_version[1]) < minimum_py3_tuple[1]) or \
|
||||
(int(python_version[0]) != minimum_py3_tuple[0]):
|
||||
print("Python " + minimum_py3_str + " or greater required. "
|
||||
"Current version is " + platform.python_version() + ". Please upgrade Python.")
|
||||
sys.exit(1)
|
||||
|
||||
|
@ -34,156 +31,58 @@ check_python_version()
|
|||
dir_name = os.path.dirname(__file__)
|
||||
|
||||
|
||||
class ProcessRegistry:
|
||||
|
||||
def register(self, process):
|
||||
pass
|
||||
|
||||
def unregister(self, process):
|
||||
pass
|
||||
|
||||
|
||||
class DaemonStatus(ProcessRegistry):
|
||||
|
||||
def __init__(self):
|
||||
self.__should_stop = False
|
||||
self.__processes = set()
|
||||
|
||||
def register(self, process):
|
||||
self.__processes.add(process)
|
||||
|
||||
def unregister(self, process):
|
||||
self.__processes.remove(process)
|
||||
|
||||
@staticmethod
|
||||
def __wait_for_processes(processes, timeout):
|
||||
"""
|
||||
Waits all the provided processes for the specified amount of time in seconds.
|
||||
"""
|
||||
reference_ts = time.time()
|
||||
elapsed = 0
|
||||
remaining_processes = list(processes)
|
||||
while elapsed < timeout and len(remaining_processes) > 0:
|
||||
remaining_time = timeout - elapsed
|
||||
for ep in list(remaining_processes):
|
||||
if ep.poll() is not None:
|
||||
remaining_processes.remove(ep)
|
||||
else:
|
||||
if remaining_time > 0:
|
||||
if PY3:
|
||||
try:
|
||||
ep.wait(remaining_time)
|
||||
remaining_processes.remove(ep)
|
||||
except subprocess.TimeoutExpired:
|
||||
pass
|
||||
else:
|
||||
# In python 2 there is no such thing as some mechanism to wait with a timeout
|
||||
time.sleep(1)
|
||||
elapsed = time.time() - reference_ts
|
||||
remaining_time = timeout - elapsed
|
||||
return remaining_processes
|
||||
|
||||
@staticmethod
|
||||
def __send_signal(processes, signal_no, live_processes=None):
|
||||
"""
|
||||
Sends to every single of the specified processes the given signal and (if live_processes is not None) append to
|
||||
it processes which are still alive.
|
||||
"""
|
||||
for ep in processes:
|
||||
if ep.poll() is None:
|
||||
if live_processes is not None:
|
||||
live_processes.append(ep)
|
||||
try:
|
||||
ep.send_signal(signal_no)
|
||||
except Exception as e:
|
||||
print('Failed sending signal %s to process %s because of an unexpected error: %s' % (
|
||||
signal_no, ep.pid, e))
|
||||
return live_processes
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
Flags this instance as should stop and terminates as smoothly as possible children processes.
|
||||
"""
|
||||
self.__should_stop = True
|
||||
live_processes = DaemonStatus.__send_signal(self.__processes, signal.SIGINT, list())
|
||||
live_processes = DaemonStatus.__wait_for_processes(live_processes, 120)
|
||||
DaemonStatus.__send_signal(live_processes, signal.SIGTERM)
|
||||
|
||||
def should_stop(self):
|
||||
return self.__should_stop
|
||||
|
||||
|
||||
def start_bazarr(process_registry=ProcessRegistry()):
|
||||
def start_bazarr():
|
||||
script = [sys.executable, "-u", os.path.normcase(os.path.join(dir_name, 'bazarr', 'main.py'))] + sys.argv[1:]
|
||||
ep = subprocess.Popen(script, stdout=None, stderr=None, stdin=subprocess.DEVNULL)
|
||||
atexit.register(lambda: ep.kill())
|
||||
|
||||
print("Bazarr starting...")
|
||||
if PY3:
|
||||
ep = subprocess.Popen(script, stdout=None, stderr=None, stdin=subprocess.DEVNULL)
|
||||
else:
|
||||
ep = subprocess.Popen(script, stdout=None, stderr=None, stdin=None)
|
||||
process_registry.register(ep)
|
||||
try:
|
||||
ep.wait()
|
||||
process_registry.unregister(ep)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
def check_status():
|
||||
if os.path.exists(stopfile):
|
||||
try:
|
||||
os.remove(stopfile)
|
||||
except Exception:
|
||||
print('Unable to delete stop file.')
|
||||
finally:
|
||||
print('Bazarr exited.')
|
||||
sys.exit(0)
|
||||
|
||||
if os.path.exists(restartfile):
|
||||
try:
|
||||
os.remove(restartfile)
|
||||
except Exception:
|
||||
print('Unable to delete restart file.')
|
||||
else:
|
||||
print("Bazarr is restarting...")
|
||||
start_bazarr()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
restartfile = os.path.normcase(os.path.join(args.config_dir, 'bazarr.restart'))
|
||||
stopfile = os.path.normcase(os.path.join(args.config_dir, 'bazarr.stop'))
|
||||
restartfile = os.path.join(args.config_dir, 'bazarr.restart')
|
||||
stopfile = os.path.join(args.config_dir, 'bazarr.stop')
|
||||
|
||||
# Cleanup leftover files
|
||||
try:
|
||||
os.remove(restartfile)
|
||||
except Exception:
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
try:
|
||||
os.remove(stopfile)
|
||||
except Exception:
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
|
||||
def daemon(bazarr_runner=lambda: start_bazarr()):
|
||||
if os.path.exists(stopfile):
|
||||
try:
|
||||
os.remove(stopfile)
|
||||
except Exception:
|
||||
print('Unable to delete stop file.')
|
||||
else:
|
||||
print('Bazarr exited.')
|
||||
sys.exit(0)
|
||||
|
||||
if os.path.exists(restartfile):
|
||||
try:
|
||||
os.remove(restartfile)
|
||||
except Exception:
|
||||
print('Unable to delete restart file.')
|
||||
else:
|
||||
bazarr_runner()
|
||||
|
||||
|
||||
bazarr_runner = lambda: start_bazarr()
|
||||
|
||||
should_stop = lambda: False
|
||||
|
||||
if PY3:
|
||||
daemonStatus = DaemonStatus()
|
||||
|
||||
def shutdown():
|
||||
# indicates that everything should stop
|
||||
daemonStatus.stop()
|
||||
# emulate a Ctrl C command on itself (bypasses the signal thing but, then, emulates the "Ctrl+C break")
|
||||
os.kill(os.getpid(), signal.SIGINT)
|
||||
|
||||
signal.signal(signal.SIGTERM, lambda signal_no, frame: shutdown())
|
||||
|
||||
should_stop = lambda: daemonStatus.should_stop()
|
||||
bazarr_runner = lambda: start_bazarr(daemonStatus)
|
||||
|
||||
bazarr_runner()
|
||||
# Initial start of main bazarr process
|
||||
print("Bazarr starting...")
|
||||
start_bazarr()
|
||||
|
||||
# Keep the script running forever until stop is requested through term or keyboard interrupt
|
||||
while not should_stop():
|
||||
daemon(bazarr_runner)
|
||||
time.sleep(1)
|
||||
while True:
|
||||
check_status()
|
||||
try:
|
||||
if sys.platform.startswith('win'):
|
||||
time.sleep(5)
|
||||
else:
|
||||
os.wait()
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
pass
|
||||
|
|
|
@ -34,6 +34,8 @@ def gitconfig():
|
|||
logging.debug('BAZARR Settings git email')
|
||||
config_write.set_value("user", "email", "bazarr@fake.email")
|
||||
|
||||
config_write.release()
|
||||
|
||||
|
||||
def check_and_apply_update():
|
||||
check_releases()
|
||||
|
|
|
@ -104,7 +104,8 @@ defaults = {
|
|||
},
|
||||
'legendasdivx': {
|
||||
'username': '',
|
||||
'password': ''
|
||||
'password': '',
|
||||
'skip_wrong_fps': 'False'
|
||||
},
|
||||
'legendastv': {
|
||||
'username': '',
|
||||
|
@ -150,6 +151,7 @@ else:
|
|||
settings = simpleconfigparser(defaults=defaults)
|
||||
settings.read(os.path.join(args.config_dir, 'config', 'config.ini'))
|
||||
|
||||
settings.general.base_url = settings.general.base_url if settings.general.base_url else '/'
|
||||
base_url = settings.general.base_url
|
||||
|
||||
|
||||
|
|
|
@ -8,12 +8,24 @@ import time
|
|||
|
||||
from get_args import args
|
||||
from config import settings
|
||||
from subliminal_patch.exceptions import TooManyRequests, APIThrottled, ParseResponseError
|
||||
from subliminal_patch.exceptions import TooManyRequests, APIThrottled, ParseResponseError, IPAddressBlocked
|
||||
from subliminal.exceptions import DownloadLimitExceeded, ServiceUnavailable
|
||||
from subliminal import region as subliminal_cache_region
|
||||
|
||||
def time_until_end_of_day(dt=None):
|
||||
# type: (datetime.datetime) -> datetime.timedelta
|
||||
"""
|
||||
Get timedelta until end of day on the datetime passed, or current time.
|
||||
"""
|
||||
if dt is None:
|
||||
dt = datetime.datetime.now()
|
||||
tomorrow = dt + datetime.timedelta(days=1)
|
||||
return datetime.datetime.combine(tomorrow, datetime.time.min) - dt
|
||||
|
||||
hours_until_end_of_day = time_until_end_of_day().seconds // 3600 + 1
|
||||
|
||||
VALID_THROTTLE_EXCEPTIONS = (TooManyRequests, DownloadLimitExceeded, ServiceUnavailable, APIThrottled,
|
||||
ParseResponseError)
|
||||
ParseResponseError, IPAddressBlocked)
|
||||
VALID_COUNT_EXCEPTIONS = ('TooManyRequests', 'ServiceUnavailable', 'APIThrottled')
|
||||
|
||||
PROVIDER_THROTTLE_MAP = {
|
||||
|
@ -32,9 +44,16 @@ PROVIDER_THROTTLE_MAP = {
|
|||
"addic7ed": {
|
||||
DownloadLimitExceeded: (datetime.timedelta(hours=3), "3 hours"),
|
||||
TooManyRequests: (datetime.timedelta(minutes=5), "5 minutes"),
|
||||
IPAddressBlocked: (datetime.timedelta(hours=1), "1 hours"),
|
||||
|
||||
},
|
||||
"titulky": {
|
||||
DownloadLimitExceeded: (datetime.timedelta(hours=3), "3 hours")
|
||||
},
|
||||
"legendasdivx": {
|
||||
TooManyRequests: (datetime.timedelta(hours=3), "3 hours"),
|
||||
DownloadLimitExceeded: (datetime.timedelta(hours=hours_until_end_of_day), "{} hours".format(str(hours_until_end_of_day))),
|
||||
IPAddressBlocked: (datetime.timedelta(hours=hours_until_end_of_day), "{} hours".format(str(hours_until_end_of_day))),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -116,6 +135,7 @@ def get_providers_auth():
|
|||
},
|
||||
'legendasdivx': {'username': settings.legendasdivx.username,
|
||||
'password': settings.legendasdivx.password,
|
||||
'skip_wrong_fps': settings.legendasdivx.getboolean('skip_wrong_fps'),
|
||||
},
|
||||
'legendastv': {'username': settings.legendastv.username,
|
||||
'password': settings.legendastv.password,
|
||||
|
|
|
@ -207,7 +207,7 @@ def download_subtitle(path, language, audio_language, hi, forced, providers, pro
|
|||
path_decoder=force_unicode
|
||||
)
|
||||
except Exception as e:
|
||||
logging.exception('BAZARR Error saving Subtitles file to disk for this file:' + path)
|
||||
logging.exception('BAZARR Error saving Subtitles file to disk for this file:' + path + ': ' + repr(e))
|
||||
pass
|
||||
else:
|
||||
saved_any = True
|
||||
|
@ -473,11 +473,14 @@ def manual_download_subtitle(path, language, audio_language, hi, forced, subtitl
|
|||
logging.debug('BAZARR Ended manually downloading Subtitles for file: ' + path)
|
||||
|
||||
|
||||
def manual_upload_subtitle(path, language, forced, title, scene_name, media_type, subtitle):
|
||||
def manual_upload_subtitle(path, language, forced, title, scene_name, media_type, subtitle, audio_language):
|
||||
logging.debug('BAZARR Manually uploading subtitles for this file: ' + path)
|
||||
|
||||
single = settings.general.getboolean('single_language')
|
||||
|
||||
use_postprocessing = settings.general.getboolean('use_postprocessing')
|
||||
postprocessing_cmd = settings.general.postprocessing_cmd
|
||||
|
||||
chmod = int(settings.general.chmod, 8) if not sys.platform.startswith(
|
||||
'win') and settings.general.getboolean('chmod_enabled') else None
|
||||
|
||||
|
@ -540,6 +543,20 @@ def manual_upload_subtitle(path, language, forced, title, scene_name, media_type
|
|||
os.chmod(subtitle_path, chmod)
|
||||
|
||||
message = language_from_alpha3(language) + (" forced" if forced else "") + " Subtitles manually uploaded."
|
||||
|
||||
uploaded_language_code3 = language
|
||||
uploaded_language = language_from_alpha3(uploaded_language_code3)
|
||||
uploaded_language_code2 = alpha2_from_alpha3(uploaded_language_code3)
|
||||
audio_language_code2 = alpha2_from_language(audio_language)
|
||||
audio_language_code3 = alpha3_from_language(audio_language)
|
||||
|
||||
|
||||
if use_postprocessing is True:
|
||||
command = pp_replace(postprocessing_cmd, path, subtitle_path, uploaded_language,
|
||||
uploaded_language_code2, uploaded_language_code3, audio_language,
|
||||
audio_language_code2, audio_language_code3, forced)
|
||||
postprocessing(command, path)
|
||||
|
||||
|
||||
if media_type == 'series':
|
||||
reversed_path = path_replace_reverse(path)
|
||||
|
@ -973,7 +990,10 @@ def refine_from_ffprobe(path, video):
|
|||
video.video_codec = data['video'][0]['codec']
|
||||
if 'frame_rate' in data['video'][0]:
|
||||
if not video.fps:
|
||||
video.fps = data['video'][0]['frame_rate']
|
||||
if isinstance(data['video'][0]['frame_rate'], float):
|
||||
video.fps = data['video'][0]['frame_rate']
|
||||
else:
|
||||
video.fps = data['video'][0]['frame_rate'].magnitude
|
||||
|
||||
if 'audio' not in data:
|
||||
logging.debug('BAZARR FFprobe was unable to find audio tracks in the file!')
|
||||
|
|
|
@ -40,7 +40,7 @@ def store_subtitles(original_path, reversed_path):
|
|||
subtitle_languages = embedded_subs_reader.list_languages(reversed_path)
|
||||
for subtitle_language, subtitle_forced, subtitle_codec in subtitle_languages:
|
||||
try:
|
||||
if settings.general.getboolean("ignore_pgs_subs") and subtitle_codec == "hdmv_pgs_subtitle":
|
||||
if settings.general.getboolean("ignore_pgs_subs") and subtitle_codec == "PGS":
|
||||
logging.debug("BAZARR skipping pgs sub for language: " + str(alpha2_from_alpha3(subtitle_language)))
|
||||
continue
|
||||
|
||||
|
@ -116,7 +116,7 @@ def store_subtitles_movie(original_path, reversed_path):
|
|||
subtitle_languages = embedded_subs_reader.list_languages(reversed_path)
|
||||
for subtitle_language, subtitle_forced, subtitle_codec in subtitle_languages:
|
||||
try:
|
||||
if settings.general.getboolean("ignore_pgs_subs") and subtitle_codec == "hdmv_pgs_subtitle":
|
||||
if settings.general.getboolean("ignore_pgs_subs") and subtitle_codec == "PGS":
|
||||
logging.debug("BAZARR skipping pgs sub for language: " + str(alpha2_from_alpha3(subtitle_language)))
|
||||
continue
|
||||
|
||||
|
@ -383,7 +383,7 @@ def guess_external_subtitles(dest_folder, subtitles):
|
|||
logging.debug('BAZARR detected encoding %r', guess)
|
||||
if guess["confidence"] < 0.6:
|
||||
raise UnicodeError
|
||||
if guess["confidence"] < 0.8 or guess["encoding"] == "ascii":
|
||||
if guess["encoding"] == "ascii":
|
||||
guess["encoding"] = "utf-8"
|
||||
text = text.decode(guess["encoding"])
|
||||
detected_language = guess_language(text)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# coding=utf-8
|
||||
|
||||
bazarr_version = '0.8.4.3'
|
||||
bazarr_version = '0.8.4.4'
|
||||
|
||||
import os
|
||||
os.environ["SZ_USER_AGENT"] = "Bazarr/1"
|
||||
|
@ -65,7 +65,7 @@ from notifier import send_notifications, send_notifications_movie
|
|||
from check_update import check_and_apply_update
|
||||
from subliminal_patch.extensions import provider_registry as provider_manager
|
||||
from subliminal_patch.core import SUBTITLE_EXTENSIONS
|
||||
|
||||
from subliminal.cache import region
|
||||
|
||||
scheduler = Scheduler()
|
||||
|
||||
|
@ -222,7 +222,7 @@ def doShutdown():
|
|||
else:
|
||||
stop_file.write(six.text_type(''))
|
||||
stop_file.close()
|
||||
sys.exit(0)
|
||||
os._exit(0)
|
||||
|
||||
|
||||
@route(base_url + 'restart')
|
||||
|
@ -243,7 +243,7 @@ def restart():
|
|||
logging.info('Bazarr is being restarted...')
|
||||
restart_file.write(six.text_type(''))
|
||||
restart_file.close()
|
||||
sys.exit(0)
|
||||
os._exit(0)
|
||||
|
||||
|
||||
@route(base_url + 'wizard')
|
||||
|
@ -401,12 +401,19 @@ def save_wizard():
|
|||
else:
|
||||
settings_opensubtitles_skip_wrong_fps = 'True'
|
||||
|
||||
settings_legendasdivx_skip_wrong_fps = request.forms.get('settings_legendasdivx_skip_wrong_fps')
|
||||
if settings_legendasdivx_skip_wrong_fps is None:
|
||||
settings_legendasdivx_skip_wrong_fps = 'False'
|
||||
else:
|
||||
settings_legendasdivx_skip_wrong_fps = 'True'
|
||||
|
||||
settings.addic7ed.username = request.forms.get('settings_addic7ed_username')
|
||||
settings.addic7ed.password = request.forms.get('settings_addic7ed_password')
|
||||
settings.addic7ed.random_agents = text_type(settings_addic7ed_random_agents)
|
||||
settings.assrt.token = request.forms.get('settings_assrt_token')
|
||||
settings.legendasdivx.username = request.forms.get('settings_legendasdivx_username')
|
||||
settings.legendasdivx.password = request.forms.get('settings_legendasdivx_password')
|
||||
settings.legendasdivx.skip_wrong_fps = text_type(settings_legendasdivx_skip_wrong_fps)
|
||||
settings.legendastv.username = request.forms.get('settings_legendastv_username')
|
||||
settings.legendastv.password = request.forms.get('settings_legendastv_password')
|
||||
settings.opensubtitles.username = request.forms.get('settings_opensubtitles_username')
|
||||
|
@ -1536,13 +1543,26 @@ def save_settings():
|
|||
settings_opensubtitles_skip_wrong_fps = 'False'
|
||||
else:
|
||||
settings_opensubtitles_skip_wrong_fps = 'True'
|
||||
|
||||
|
||||
if (settings.opensubtitles.username != request.forms.get('settings_opensubtitles_username') or
|
||||
settings.opensubtitles.password != request.forms.get('settings_opensubtitles_password') or
|
||||
settings.opensubtitles.vip != text_type(settings_opensubtitles_vip)):
|
||||
region.delete("os_token")
|
||||
region.delete("os_server_url")
|
||||
|
||||
settings_legendasdivx_skip_wrong_fps = request.forms.get('settings_legendasdivx_skip_wrong_fps')
|
||||
if settings_legendasdivx_skip_wrong_fps is None:
|
||||
settings_legendasdivx_skip_wrong_fps = 'False'
|
||||
else:
|
||||
settings_legendasdivx_skip_wrong_fps = 'True'
|
||||
|
||||
settings.addic7ed.username = request.forms.get('settings_addic7ed_username')
|
||||
settings.addic7ed.password = request.forms.get('settings_addic7ed_password')
|
||||
settings.addic7ed.random_agents = text_type(settings_addic7ed_random_agents)
|
||||
settings.assrt.token = request.forms.get('settings_assrt_token')
|
||||
settings.legendasdivx.username = request.forms.get('settings_legendasdivx_username')
|
||||
settings.legendasdivx.password = request.forms.get('settings_legendasdivx_password')
|
||||
settings.legendasdivx.skip_wrong_fps = text_type(settings_legendasdivx_skip_wrong_fps)
|
||||
settings.legendastv.username = request.forms.get('settings_legendastv_username')
|
||||
settings.legendastv.password = request.forms.get('settings_legendastv_password')
|
||||
settings.opensubtitles.username = request.forms.get('settings_opensubtitles_username')
|
||||
|
@ -1848,14 +1868,17 @@ def perform_manual_upload_subtitle():
|
|||
authorize()
|
||||
ref = request.environ['HTTP_REFERER']
|
||||
|
||||
episodePath = request.forms.episodePath
|
||||
sceneName = request.forms.sceneName
|
||||
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.title
|
||||
title = request.forms.get('title')
|
||||
|
||||
data = database.execute("SELECT audio_language FROM table_shows WHERE sonarrSeriesId=?", (sonarrSeriesId,), only_one=True)
|
||||
audio_language = data['audio_language']
|
||||
|
||||
_, ext = os.path.splitext(upload.filename)
|
||||
|
||||
|
@ -1869,7 +1892,8 @@ def perform_manual_upload_subtitle():
|
|||
title=title,
|
||||
scene_name=sceneName,
|
||||
media_type='series',
|
||||
subtitle=upload)
|
||||
subtitle=upload,
|
||||
audio_language=audio_language)
|
||||
|
||||
if result is not None:
|
||||
message = result[0]
|
||||
|
@ -1988,13 +2012,16 @@ def perform_manual_upload_subtitle_movie():
|
|||
authorize()
|
||||
ref = request.environ['HTTP_REFERER']
|
||||
|
||||
moviePath = request.forms.moviePath
|
||||
sceneName = request.forms.sceneName
|
||||
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.title
|
||||
title = request.forms.get('title')
|
||||
|
||||
data = database.execute("SELECT audio_language FROM table_movies WHERE radarrId=?", (radarrId,), only_one=True)
|
||||
audio_language = data['audio_language']
|
||||
|
||||
_, ext = os.path.splitext(upload.filename)
|
||||
|
||||
|
@ -2008,7 +2035,8 @@ def perform_manual_upload_subtitle_movie():
|
|||
title=title,
|
||||
scene_name=sceneName,
|
||||
media_type='movie',
|
||||
subtitle=upload)
|
||||
subtitle=upload,
|
||||
audio_language=audio_language)
|
||||
|
||||
if result is not None:
|
||||
message = result[0]
|
||||
|
@ -2093,10 +2121,11 @@ def api_history():
|
|||
@custom_auth_basic(check_credentials)
|
||||
def test_url(protocol, url):
|
||||
authorize()
|
||||
url = six.moves.urllib.parse.unquote(url)
|
||||
url = protocol + "://" + six.moves.urllib.parse.unquote(url)
|
||||
try:
|
||||
result = requests.get(protocol + "://" + url, allow_redirects=False, verify=False).json()['version']
|
||||
except:
|
||||
result = requests.get(url, allow_redirects=False, verify=False).json()['version']
|
||||
except Exception as e:
|
||||
logging.exception('BAZARR cannot successfully contact this URL: ' + url)
|
||||
return dict(status=False)
|
||||
else:
|
||||
return dict(status=True, version=result)
|
||||
|
|
|
@ -18,8 +18,6 @@ from apscheduler.triggers.cron import CronTrigger
|
|||
from apscheduler.triggers.date import DateTrigger
|
||||
from apscheduler.events import EVENT_JOB_SUBMITTED, EVENT_JOB_EXECUTED, EVENT_JOB_ERROR
|
||||
from datetime import datetime
|
||||
import pytz
|
||||
from tzlocal import get_localzone
|
||||
from calendar import day_name
|
||||
import pretty
|
||||
from six import PY2
|
||||
|
@ -30,10 +28,7 @@ class Scheduler:
|
|||
def __init__(self):
|
||||
self.__running_tasks = []
|
||||
|
||||
if str(get_localzone()) == "local":
|
||||
self.aps_scheduler = BackgroundScheduler(timezone=pytz.timezone('UTC'))
|
||||
else:
|
||||
self.aps_scheduler = BackgroundScheduler()
|
||||
self.aps_scheduler = BackgroundScheduler()
|
||||
|
||||
# task listener
|
||||
def task_listener_add(event):
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -209,7 +209,7 @@ class TVsubtitlesProvider(Provider):
|
|||
if subtitles:
|
||||
return subtitles
|
||||
else:
|
||||
logger.error('No show id found for %r (%r)', video.series, {'year': video.year})
|
||||
logger.debug('No show id found for %r (%r)', video.series, {'year': video.year})
|
||||
|
||||
return []
|
||||
|
||||
|
|
|
@ -7,11 +7,13 @@ class TooManyRequests(ProviderError):
|
|||
"""Exception raised by providers when too many requests are made."""
|
||||
pass
|
||||
|
||||
|
||||
class APIThrottled(ProviderError):
|
||||
pass
|
||||
|
||||
|
||||
class ParseResponseError(ProviderError):
|
||||
"""Exception raised by providers when they are not able to parse the response."""
|
||||
pass
|
||||
|
||||
class IPAddressBlocked(ProviderError):
|
||||
"""Exception raised when providers block requests from IP Address."""
|
||||
pass
|
|
@ -9,13 +9,14 @@ from random import randint
|
|||
|
||||
from dogpile.cache.api import NO_VALUE
|
||||
from requests import Session
|
||||
from requests.exceptions import ConnectionError
|
||||
from subliminal.cache import region
|
||||
from subliminal.exceptions import DownloadLimitExceeded, AuthenticationError, ConfigurationError
|
||||
from subliminal.providers.addic7ed import Addic7edProvider as _Addic7edProvider, \
|
||||
Addic7edSubtitle as _Addic7edSubtitle, ParserBeautifulSoup
|
||||
from subliminal.subtitle import fix_line_ending
|
||||
from subliminal_patch.utils import sanitize
|
||||
from subliminal_patch.exceptions import TooManyRequests
|
||||
from subliminal_patch.exceptions import TooManyRequests, IPAddressBlocked
|
||||
from subliminal_patch.pitcher import pitchers, load_verification, store_verification
|
||||
from subzero.language import Language
|
||||
|
||||
|
@ -91,15 +92,19 @@ class Addic7edProvider(_Addic7edProvider):
|
|||
# login
|
||||
if self.username and self.password:
|
||||
def check_verification(cache_region):
|
||||
rr = self.session.get(self.server_url + 'panel.php', allow_redirects=False, timeout=10,
|
||||
headers={"Referer": self.server_url})
|
||||
if rr.status_code == 302:
|
||||
logger.info('Addic7ed: Login expired')
|
||||
cache_region.delete("addic7ed_data")
|
||||
else:
|
||||
logger.info('Addic7ed: Re-using old login')
|
||||
self.logged_in = True
|
||||
return True
|
||||
try:
|
||||
rr = self.session.get(self.server_url + 'panel.php', allow_redirects=False, timeout=10,
|
||||
headers={"Referer": self.server_url})
|
||||
if rr.status_code == 302:
|
||||
logger.info('Addic7ed: Login expired')
|
||||
cache_region.delete("addic7ed_data")
|
||||
else:
|
||||
logger.info('Addic7ed: Re-using old login')
|
||||
self.logged_in = True
|
||||
return True
|
||||
except ConnectionError as e:
|
||||
logger.debug("Addic7ed: There was a problem reaching the server: %s." % e)
|
||||
raise IPAddressBlocked("Addic7ed: Your IP is temporarily blocked.")
|
||||
|
||||
if load_verification("addic7ed", self.session, callback=check_verification):
|
||||
return
|
||||
|
|
|
@ -55,7 +55,7 @@ class ArgenteamSubtitle(Subtitle):
|
|||
return self._release_info
|
||||
|
||||
combine = []
|
||||
for attr in ("format", "version", "video_codec"):
|
||||
for attr in ("format", "version"):
|
||||
value = getattr(self, attr)
|
||||
if value:
|
||||
combine.append(value)
|
||||
|
@ -76,9 +76,11 @@ class ArgenteamSubtitle(Subtitle):
|
|||
if video.series and (sanitize(self.title) in (
|
||||
sanitize(name) for name in [video.series] + video.alternative_series)):
|
||||
matches.add('series')
|
||||
|
||||
# season
|
||||
if video.season and self.season == video.season:
|
||||
matches.add('season')
|
||||
|
||||
# episode
|
||||
if video.episode and self.episode == video.episode:
|
||||
matches.add('episode')
|
||||
|
@ -87,6 +89,9 @@ class ArgenteamSubtitle(Subtitle):
|
|||
if video.tvdb_id and str(self.tvdb_id) == str(video.tvdb_id):
|
||||
matches.add('tvdb_id')
|
||||
|
||||
# year (year is not available for series, but we assume it matches)
|
||||
matches.add('year')
|
||||
|
||||
elif isinstance(video, Movie) and self.movie_kind == 'movie':
|
||||
# title
|
||||
if video.title and (sanitize(self.title) in (
|
||||
|
@ -230,29 +235,29 @@ class ArgenteamProvider(Provider, ProviderSubtitleArchiveMixin):
|
|||
has_multiple_ids = len(argenteam_ids) > 1
|
||||
for aid in argenteam_ids:
|
||||
response = self.session.get(url, params={'id': aid}, timeout=10)
|
||||
|
||||
response.raise_for_status()
|
||||
content = response.json()
|
||||
|
||||
imdb_id = year = None
|
||||
returned_title = title
|
||||
if not is_episode and "info" in content:
|
||||
imdb_id = content["info"].get("imdb")
|
||||
year = content["info"].get("year")
|
||||
returned_title = content["info"].get("title", title)
|
||||
if content is not None: # eg https://argenteam.net/api/v1/episode?id=11534
|
||||
imdb_id = year = None
|
||||
returned_title = title
|
||||
if not is_episode and "info" in content:
|
||||
imdb_id = content["info"].get("imdb")
|
||||
year = content["info"].get("year")
|
||||
returned_title = content["info"].get("title", title)
|
||||
|
||||
for r in content['releases']:
|
||||
for s in r['subtitles']:
|
||||
movie_kind = "episode" if is_episode else "movie"
|
||||
page_link = self.BASE_URL + movie_kind + "/" + str(aid)
|
||||
# use https and new domain
|
||||
download_link = s['uri'].replace('http://www.argenteam.net/', self.BASE_URL)
|
||||
sub = ArgenteamSubtitle(language, page_link, download_link, movie_kind, returned_title,
|
||||
season, episode, year, r.get('team'), r.get('tags'),
|
||||
r.get('source'), r.get('codec'), content.get("tvdb"), imdb_id,
|
||||
asked_for_release_group=video.release_group,
|
||||
asked_for_episode=episode)
|
||||
subtitles.append(sub)
|
||||
for r in content['releases']:
|
||||
for s in r['subtitles']:
|
||||
movie_kind = "episode" if is_episode else "movie"
|
||||
page_link = self.BASE_URL + movie_kind + "/" + str(aid)
|
||||
# use https and new domain
|
||||
download_link = s['uri'].replace('http://www.argenteam.net/', self.BASE_URL)
|
||||
sub = ArgenteamSubtitle(language, page_link, download_link, movie_kind, returned_title,
|
||||
season, episode, year, r.get('team'), r.get('tags'),
|
||||
r.get('source'), r.get('codec'), content.get("tvdb"), imdb_id,
|
||||
asked_for_release_group=video.release_group,
|
||||
asked_for_episode=episode)
|
||||
subtitles.append(sub)
|
||||
|
||||
if has_multiple_ids:
|
||||
time.sleep(self.multi_result_throttle)
|
||||
|
|
|
@ -3,19 +3,27 @@ from __future__ import absolute_import
|
|||
import logging
|
||||
import io
|
||||
import os
|
||||
import rarfile
|
||||
import re
|
||||
import zipfile
|
||||
from time import sleep
|
||||
from urllib.parse import quote
|
||||
from requests.exceptions import HTTPError
|
||||
import rarfile
|
||||
|
||||
from requests import Session
|
||||
from guessit import guessit
|
||||
from subliminal_patch.exceptions import ParseResponseError
|
||||
from subliminal_patch.providers import Provider
|
||||
from subliminal.cache import region
|
||||
from subliminal.exceptions import ConfigurationError, AuthenticationError, ServiceUnavailable, DownloadLimitExceeded
|
||||
from subliminal.providers import ParserBeautifulSoup
|
||||
from subliminal_patch.subtitle import Subtitle
|
||||
from subliminal.subtitle import SUBTITLE_EXTENSIONS, fix_line_ending, guess_matches
|
||||
from subliminal.utils import sanitize, sanitize_release_group
|
||||
from subliminal.video import Episode, Movie
|
||||
from subliminal.subtitle import SUBTITLE_EXTENSIONS, fix_line_ending,guess_matches
|
||||
from subliminal_patch.exceptions import TooManyRequests, IPAddressBlocked
|
||||
from subliminal_patch.http import RetryingCFSession
|
||||
from subliminal_patch.providers import Provider
|
||||
from subliminal_patch.score import get_scores, framerate_equal
|
||||
from subliminal_patch.subtitle import Subtitle
|
||||
from subzero.language import Language
|
||||
from subliminal_patch.score import get_scores
|
||||
from dogpile.cache.api import NO_VALUE
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -23,15 +31,18 @@ class LegendasdivxSubtitle(Subtitle):
|
|||
"""Legendasdivx Subtitle."""
|
||||
provider_name = 'legendasdivx'
|
||||
|
||||
def __init__(self, language, video, data):
|
||||
def __init__(self, language, video, data, skip_wrong_fps=True):
|
||||
super(LegendasdivxSubtitle, self).__init__(language)
|
||||
self.language = language
|
||||
self.page_link = data['link']
|
||||
self.hits=data['hits']
|
||||
self.exact_match=data['exact_match']
|
||||
self.description=data['description'].lower()
|
||||
self.hits = data['hits']
|
||||
self.exact_match = data['exact_match']
|
||||
self.description = data['description']
|
||||
self.video = video
|
||||
self.videoname =data['videoname']
|
||||
self.sub_frame_rate = data['frame_rate']
|
||||
self.uploader = data['uploader']
|
||||
self.wrong_fps = False
|
||||
self.skip_wrong_fps = skip_wrong_fps
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
|
@ -44,40 +55,67 @@ class LegendasdivxSubtitle(Subtitle):
|
|||
def get_matches(self, video):
|
||||
matches = set()
|
||||
|
||||
if self.videoname.lower() in self.description:
|
||||
matches.update(['title'])
|
||||
matches.update(['season'])
|
||||
matches.update(['episode'])
|
||||
# if skip_wrong_fps = True no point to continue if they don't match
|
||||
subtitle_fps = None
|
||||
try:
|
||||
subtitle_fps = float(self.sub_frame_rate)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# episode
|
||||
if video.title and video.title.lower() in self.description:
|
||||
# check fps match and skip based on configuration
|
||||
if video.fps and subtitle_fps and not framerate_equal(video.fps, subtitle_fps):
|
||||
self.wrong_fps = True
|
||||
|
||||
if self.skip_wrong_fps:
|
||||
logger.debug("Legendasdivx :: Skipping subtitle due to FPS mismatch (expected: %s, got: %s)", video.fps, self.sub_frame_rate)
|
||||
# not a single match :)
|
||||
return set()
|
||||
logger.debug("Legendasdivx :: Frame rate mismatch (expected: %s, got: %s, but continuing...)", video.fps, self.sub_frame_rate)
|
||||
|
||||
description = sanitize(self.description)
|
||||
|
||||
video_filename = video.name
|
||||
video_filename = os.path.basename(video_filename)
|
||||
video_filename, _ = os.path.splitext(video_filename)
|
||||
video_filename = sanitize_release_group(video_filename)
|
||||
|
||||
if sanitize(video_filename) in description:
|
||||
matches.update(['title'])
|
||||
if video.year and '{:04d}'.format(video.year) in self.description:
|
||||
# relying people won' use just S01E01 for the file name
|
||||
if isinstance(video, Episode):
|
||||
matches.update(['series'])
|
||||
matches.update(['season'])
|
||||
matches.update(['episode'])
|
||||
|
||||
# can match both movies and series
|
||||
if video.year and '{:04d}'.format(video.year) in description:
|
||||
matches.update(['year'])
|
||||
|
||||
# match movie title (include alternative movie names)
|
||||
if isinstance(video, Movie):
|
||||
if video.title:
|
||||
for movie_name in [video.title] + video.alternative_titles:
|
||||
if sanitize(movie_name) in description:
|
||||
matches.update(['title'])
|
||||
|
||||
if isinstance(video, Episode):
|
||||
# already matched in search query
|
||||
if video.season and 's{:02d}'.format(video.season) in self.description:
|
||||
matches.update(['season'])
|
||||
if video.episode and 'e{:02d}'.format(video.episode) in self.description:
|
||||
matches.update(['episode'])
|
||||
if video.episode and video.season and video.series:
|
||||
if '{}.s{:02d}e{:02d}'.format(video.series.lower(),video.season,video.episode) in self.description:
|
||||
if video.title and sanitize(video.title) in description:
|
||||
matches.update(['title'])
|
||||
if video.series:
|
||||
for series_name in [video.series] + video.alternative_series:
|
||||
if sanitize(series_name) in description:
|
||||
matches.update(['series'])
|
||||
matches.update(['season'])
|
||||
matches.update(['episode'])
|
||||
if '{} s{:02d}e{:02d}'.format(video.series.lower(),video.season,video.episode) in self.description:
|
||||
matches.update(['series'])
|
||||
matches.update(['season'])
|
||||
matches.update(['episode'])
|
||||
if video.season and 's{:02d}'.format(video.season) in description:
|
||||
matches.update(['season'])
|
||||
if video.episode and 'e{:02d}'.format(video.episode) in description:
|
||||
matches.update(['episode'])
|
||||
|
||||
# release_group
|
||||
if video.release_group and video.release_group.lower() in self.description:
|
||||
if video.release_group and sanitize_release_group(video.release_group) in sanitize_release_group(description):
|
||||
matches.update(['release_group'])
|
||||
|
||||
# resolution
|
||||
|
||||
if video.resolution and video.resolution.lower() in self.description:
|
||||
if video.resolution and video.resolution.lower() in description:
|
||||
matches.update(['resolution'])
|
||||
|
||||
# format
|
||||
|
@ -87,9 +125,9 @@ class LegendasdivxSubtitle(Subtitle):
|
|||
if formats[0] == "web-dl":
|
||||
formats.append("webdl")
|
||||
formats.append("webrip")
|
||||
formats.append("web ")
|
||||
formats.append("web")
|
||||
for frmt in formats:
|
||||
if frmt.lower() in self.description:
|
||||
if frmt in description:
|
||||
matches.update(['format'])
|
||||
break
|
||||
|
||||
|
@ -97,21 +135,16 @@ class LegendasdivxSubtitle(Subtitle):
|
|||
if video.video_codec:
|
||||
video_codecs = [video.video_codec.lower()]
|
||||
if video_codecs[0] == "h264":
|
||||
formats.append("x264")
|
||||
video_codecs.append("x264")
|
||||
elif video_codecs[0] == "h265":
|
||||
formats.append("x265")
|
||||
for vc in formats:
|
||||
if vc.lower() in self.description:
|
||||
video_codecs.append("x265")
|
||||
for vc in video_codecs:
|
||||
if vc in description:
|
||||
matches.update(['video_codec'])
|
||||
break
|
||||
|
||||
# running guessit on a huge description may break guessit
|
||||
# matches |= guess_matches(video, guessit(self.description))
|
||||
return matches
|
||||
|
||||
|
||||
|
||||
|
||||
class LegendasdivxProvider(Provider):
|
||||
"""Legendasdivx Provider."""
|
||||
languages = {Language('por', 'BR')} | {Language('por')}
|
||||
|
@ -121,147 +154,223 @@ class LegendasdivxProvider(Provider):
|
|||
'User-Agent': os.environ.get("SZ_USER_AGENT", "Sub-Zero/2"),
|
||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
|
||||
'Origin': 'https://www.legendasdivx.pt',
|
||||
'Referer': 'https://www.legendasdivx.pt',
|
||||
'Pragma': 'no-cache',
|
||||
'Cache-Control': 'no-cache'
|
||||
'Referer': 'https://www.legendasdivx.pt'
|
||||
}
|
||||
loginpage = site + '/forum/ucp.php?mode=login'
|
||||
searchurl = site + '/modules.php?name=Downloads&file=jz&d_op=search&op=_jz00&query={query}'
|
||||
language_list = list(languages)
|
||||
download_link = site + '/modules.php{link}'
|
||||
|
||||
def __init__(self, username, password):
|
||||
def __init__(self, username, password, skip_wrong_fps=True):
|
||||
# make sure login credentials are configured.
|
||||
if any((username, password)) and not all((username, password)):
|
||||
raise ConfigurationError('Legendasdivx.pt :: Username and password must be specified')
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.skip_wrong_fps = skip_wrong_fps
|
||||
|
||||
def initialize(self):
|
||||
self.session = Session()
|
||||
self.login()
|
||||
logger.debug("Legendasdivx.pt :: Creating session for requests")
|
||||
self.session = RetryingCFSession()
|
||||
# re-use PHP Session if present
|
||||
prev_cookies = region.get("legendasdivx_cookies2")
|
||||
if prev_cookies != NO_VALUE:
|
||||
logger.debug("Legendasdivx.pt :: Re-using previous legendasdivx cookies: %s", prev_cookies)
|
||||
self.session.cookies.update(prev_cookies)
|
||||
# login if session has expired
|
||||
else:
|
||||
logger.debug("Legendasdivx.pt :: Session cookies not found!")
|
||||
self.session.headers.update(self.headers)
|
||||
self.login()
|
||||
|
||||
def terminate(self):
|
||||
self.logout()
|
||||
# session close
|
||||
self.session.close()
|
||||
|
||||
def login(self):
|
||||
logger.info('Logging in')
|
||||
self.headers['Referer'] = self.site + '/index.php'
|
||||
self.session.headers.update(self.headers.items())
|
||||
res = self.session.get(self.loginpage)
|
||||
bsoup = ParserBeautifulSoup(res.content, ['lxml'])
|
||||
|
||||
_allinputs = bsoup.findAll('input')
|
||||
fields = {}
|
||||
for field in _allinputs:
|
||||
fields[field.get('name')] = field.get('value')
|
||||
|
||||
fields['username'] = self.username
|
||||
fields['password'] = self.password
|
||||
fields['autologin'] = 'on'
|
||||
fields['viewonline'] = 'on'
|
||||
|
||||
self.headers['Referer'] = self.loginpage
|
||||
self.session.headers.update(self.headers.items())
|
||||
res = self.session.post(self.loginpage, fields)
|
||||
logger.debug('Legendasdivx.pt :: Logging in')
|
||||
try:
|
||||
logger.debug('Got session id %s' %
|
||||
self.session.cookies.get_dict()['PHPSESSID'])
|
||||
except KeyError as e:
|
||||
logger.error(repr(e))
|
||||
logger.error("Didn't get session id, check your credentials")
|
||||
return False
|
||||
res = self.session.get(self.loginpage)
|
||||
res.raise_for_status()
|
||||
bsoup = ParserBeautifulSoup(res.content, ['lxml'])
|
||||
|
||||
_allinputs = bsoup.findAll('input')
|
||||
data = {}
|
||||
# necessary to set 'sid' for POST request
|
||||
for field in _allinputs:
|
||||
data[field.get('name')] = field.get('value')
|
||||
|
||||
data['username'] = self.username
|
||||
data['password'] = self.password
|
||||
|
||||
res = self.session.post(self.loginpage, data)
|
||||
res.raise_for_status()
|
||||
# make sure we're logged in
|
||||
logger.debug('Legendasdivx.pt :: Logged in successfully: PHPSESSID: %s', self.session.cookies.get_dict()['PHPSESSID'])
|
||||
cj = self.session.cookies.copy()
|
||||
store_cks = ("PHPSESSID", "phpbb3_2z8zs_sid", "phpbb3_2z8zs_k", "phpbb3_2z8zs_u", "lang")
|
||||
for cn in iter(self.session.cookies.keys()):
|
||||
if cn not in store_cks:
|
||||
del cj[cn]
|
||||
# store session cookies on cache
|
||||
logger.debug("Legendasdivx.pt :: Storing legendasdivx session cookies: %r", cj)
|
||||
region.set("legendasdivx_cookies2", cj)
|
||||
|
||||
except KeyError:
|
||||
logger.error("Legendasdivx.pt :: Couldn't get session ID, check your credentials")
|
||||
raise AuthenticationError("Legendasdivx.pt :: Couldn't get session ID, check your credentials")
|
||||
except HTTPError as e:
|
||||
if "bloqueado" in res.text.lower():
|
||||
logger.error("LegendasDivx.pt :: Your IP is blocked on this server.")
|
||||
raise IPAddressBlocked("LegendasDivx.pt :: Your IP is blocked on this server.")
|
||||
logger.error("Legendasdivx.pt :: HTTP Error %s", e)
|
||||
raise TooManyRequests("Legendasdivx.pt :: HTTP Error %s", e)
|
||||
except Exception as e:
|
||||
logger.error(repr(e))
|
||||
logger.error('uncached error #legendasdivx #AA')
|
||||
return False
|
||||
logger.error("LegendasDivx.pt :: Uncaught error: %r", e)
|
||||
raise ServiceUnavailable("LegendasDivx.pt :: Uncaught error: %r", e)
|
||||
|
||||
return True
|
||||
def _process_page(self, video, bsoup):
|
||||
|
||||
def logout(self):
|
||||
# need to figure this out
|
||||
return True
|
||||
|
||||
def _process_page(self, video, bsoup, querytext, videoname):
|
||||
subtitles = []
|
||||
_allsubs = bsoup.findAll("div", {"class": "sub_box"})
|
||||
lang = Language.fromopensubtitles("pob")
|
||||
for _subbox in _allsubs:
|
||||
hits=0
|
||||
for th in _subbox.findAll("th", {"class": "color2"}):
|
||||
if th.string == 'Hits:':
|
||||
hits = int(th.parent.find("td").string)
|
||||
if th.string == 'Idioma:':
|
||||
lang = th.parent.find("td").find ("img").get ('src')
|
||||
if 'brazil' in lang:
|
||||
lang = Language.fromopensubtitles('pob')
|
||||
else:
|
||||
lang = Language.fromopensubtitles('por')
|
||||
|
||||
description = _subbox.find("td", {"class": "td_desc brd_up"})
|
||||
download = _subbox.find("a", {"class": "sub_download"})
|
||||
_allsubs = bsoup.findAll("div", {"class": "sub_box"})
|
||||
|
||||
for _subbox in _allsubs:
|
||||
|
||||
hits = 0
|
||||
for th in _subbox.findAll("th"):
|
||||
if th.text == 'Hits:':
|
||||
hits = int(th.find_next("td").text)
|
||||
if th.text == 'Idioma:':
|
||||
lang = th.find_next("td").find("img").get('src')
|
||||
if 'brazil' in lang.lower():
|
||||
lang = Language.fromopensubtitles('pob')
|
||||
elif 'portugal' in lang.lower():
|
||||
lang = Language.fromopensubtitles('por')
|
||||
else:
|
||||
continue
|
||||
if th.text == "Frame Rate:":
|
||||
frame_rate = th.find_next("td").text.strip()
|
||||
|
||||
# get description for matches
|
||||
description = _subbox.find("td", {"class": "td_desc brd_up"}).get_text()
|
||||
|
||||
# get subtitle link from footer
|
||||
sub_footer = _subbox.find("div", {"class": "sub_footer"})
|
||||
download = sub_footer.find("a", {"class": "sub_download"}) if sub_footer else None
|
||||
|
||||
# sometimes 'a' tag is not found and returns None. Most likely HTML format error!
|
||||
try:
|
||||
# sometimes BSoup just doesn't get the link
|
||||
logger.debug(download.get('href'))
|
||||
except Exception as e:
|
||||
logger.warning('skipping subbox on %s' % self.searchurl.format(query=querytext))
|
||||
download_link = self.download_link.format(link=download.get('href'))
|
||||
logger.debug("Legendasdivx.pt :: Found subtitle link on: %s ", download_link)
|
||||
except:
|
||||
logger.debug("Legendasdivx.pt :: Couldn't find download link. Trying next...")
|
||||
continue
|
||||
|
||||
# get subtitle uploader
|
||||
sub_header = _subbox.find("div", {"class" :"sub_header"})
|
||||
uploader = sub_header.find("a").text if sub_header else 'anonymous'
|
||||
|
||||
exact_match = False
|
||||
if video.name.lower() in description.get_text().lower():
|
||||
if video.name.lower() in description.lower():
|
||||
exact_match = True
|
||||
data = {'link': self.site + '/modules.php' + download.get('href'),
|
||||
|
||||
data = {'link': download_link,
|
||||
'exact_match': exact_match,
|
||||
'hits': hits,
|
||||
'videoname': videoname,
|
||||
'description': description.get_text() }
|
||||
'uploader': uploader,
|
||||
'frame_rate': frame_rate,
|
||||
'description': description
|
||||
}
|
||||
subtitles.append(
|
||||
LegendasdivxSubtitle(lang, video, data)
|
||||
LegendasdivxSubtitle(lang, video, data, skip_wrong_fps=self.skip_wrong_fps)
|
||||
)
|
||||
return subtitles
|
||||
|
||||
def query(self, video, language):
|
||||
try:
|
||||
logger.debug('Got session id %s' %
|
||||
self.session.cookies.get_dict()['PHPSESSID'])
|
||||
except Exception as e:
|
||||
self.login()
|
||||
def query(self, video, languages):
|
||||
|
||||
language_ids = '0'
|
||||
if isinstance(language, (tuple, list, set)):
|
||||
if len(language) == 1:
|
||||
language_ids = ','.join(sorted(l.opensubtitles for l in language))
|
||||
if language_ids == 'por':
|
||||
language_ids = '&form_cat=28'
|
||||
else:
|
||||
language_ids = '&form_cat=29'
|
||||
|
||||
videoname = video.name
|
||||
videoname = os.path.basename(videoname)
|
||||
videoname, _ = os.path.splitext(videoname)
|
||||
# querytext = videoname.lower()
|
||||
_searchurl = self.searchurl
|
||||
if video.imdb_id is None:
|
||||
if isinstance(video, Episode):
|
||||
querytext = "{} S{:02d}E{:02d}".format(video.series, video.season, video.episode)
|
||||
elif isinstance(video, Movie):
|
||||
querytext = video.title
|
||||
else:
|
||||
querytext = video.imdb_id
|
||||
|
||||
if isinstance(video, Movie):
|
||||
querytext = video.imdb_id if video.imdb_id else video.title
|
||||
|
||||
# querytext = querytext.replace(
|
||||
# ".", "+").replace("[", "").replace("]", "")
|
||||
if language_ids != '0':
|
||||
querytext = querytext + language_ids
|
||||
self.headers['Referer'] = self.site + '/index.php'
|
||||
self.session.headers.update(self.headers.items())
|
||||
res = self.session.get(_searchurl.format(query=querytext))
|
||||
# form_cat=28 = br
|
||||
# form_cat=29 = pt
|
||||
if "A legenda não foi encontrada" in res.text:
|
||||
logger.warning('%s not found', querytext)
|
||||
return []
|
||||
if isinstance(video, Episode):
|
||||
querytext = '"{} S{:02d}E{:02d}"'.format(video.series, video.season, video.episode)
|
||||
querytext = quote(quote(querytext))
|
||||
|
||||
# language query filter
|
||||
if isinstance(languages, (tuple, list, set)):
|
||||
language_ids = ','.join(sorted(l.opensubtitles for l in languages))
|
||||
if 'por' in language_ids: # prioritize portuguese subtitles
|
||||
lang_filter = '&form_cat=28'
|
||||
elif 'pob' in language_ids:
|
||||
lang_filter = '&form_cat=29'
|
||||
else:
|
||||
lang_filter = ''
|
||||
|
||||
querytext = querytext + lang_filter if lang_filter else querytext
|
||||
|
||||
try:
|
||||
# sleep for a 1 second before another request
|
||||
sleep(1)
|
||||
self.headers['Referer'] = self.site + '/index.php'
|
||||
self.session.headers.update(self.headers)
|
||||
res = self.session.get(_searchurl.format(query=querytext), allow_redirects=False)
|
||||
res.raise_for_status()
|
||||
if (res.status_code == 200 and "A legenda não foi encontrada" in res.text):
|
||||
logger.warning('Legendasdivx.pt :: query %s return no results!', querytext)
|
||||
# for series, if no results found, try again just with series and season (subtitle packs)
|
||||
if isinstance(video, Episode):
|
||||
logger.debug("Legendasdivx.pt :: trying again with just series and season on query.")
|
||||
querytext = re.sub("(e|E)(\d{2})", "", querytext)
|
||||
res = self.session.get(_searchurl.format(query=querytext), allow_redirects=False)
|
||||
res.raise_for_status()
|
||||
if (res.status_code == 200 and "A legenda não foi encontrada" in res.text):
|
||||
logger.warning('Legendasdivx.pt :: query %s return no results (for series and season only).', querytext)
|
||||
return []
|
||||
if res.status_code == 302: # got redirected to login page.
|
||||
# seems that our session cookies are no longer valid... clean them from cache
|
||||
region.delete("legendasdivx_cookies2")
|
||||
logger.debug("Legendasdivx.pt :: Logging in again. Cookies have expired!")
|
||||
# login and try again
|
||||
self.login()
|
||||
res = self.session.get(_searchurl.format(query=querytext))
|
||||
res.raise_for_status()
|
||||
except HTTPError as e:
|
||||
if "bloqueado" in res.text.lower():
|
||||
logger.error("LegendasDivx.pt :: Your IP is blocked on this server.")
|
||||
raise IPAddressBlocked("LegendasDivx.pt :: Your IP is blocked on this server.")
|
||||
logger.error("Legendasdivx.pt :: HTTP Error %s", e)
|
||||
raise TooManyRequests("Legendasdivx.pt :: HTTP Error %s", e)
|
||||
except Exception as e:
|
||||
logger.error("LegendasDivx.pt :: Uncaught error: %r", e)
|
||||
raise ServiceUnavailable("LegendasDivx.pt :: Uncaught error: %r", e)
|
||||
|
||||
bsoup = ParserBeautifulSoup(res.content, ['html.parser'])
|
||||
subtitles = self._process_page(video, bsoup, querytext, videoname)
|
||||
|
||||
# search for more than 10 results (legendasdivx uses pagination)
|
||||
# don't throttle - maximum results = 6 * 10
|
||||
MAX_PAGES = 6
|
||||
|
||||
# get number of pages bases on results found
|
||||
page_header = bsoup.find("div", {"class": "pager_bar"})
|
||||
results_found = re.search(r'\((.*?) encontradas\)', page_header.text).group(1) if page_header else 0
|
||||
logger.debug("Legendasdivx.pt :: Found %s subtitles", str(results_found))
|
||||
num_pages = (int(results_found) // 10) + 1
|
||||
num_pages = min(MAX_PAGES, num_pages)
|
||||
|
||||
# process first page
|
||||
subtitles = self._process_page(video, bsoup)
|
||||
|
||||
# more pages?
|
||||
if num_pages > 1:
|
||||
for num_page in range(2, num_pages+1):
|
||||
sleep(1) # another 1 sec before requesting...
|
||||
_search_next = self.searchurl.format(query=querytext) + "&page={0}".format(str(num_page))
|
||||
logger.debug("Legendasdivx.pt :: Moving on to next page: %s", _search_next)
|
||||
res = self.session.get(_search_next)
|
||||
next_page = ParserBeautifulSoup(res.content, ['html.parser'])
|
||||
subs = self._process_page(video, next_page)
|
||||
subtitles.extend(subs)
|
||||
|
||||
return subtitles
|
||||
|
||||
|
@ -269,34 +378,47 @@ class LegendasdivxProvider(Provider):
|
|||
return self.query(video, languages)
|
||||
|
||||
def download_subtitle(self, subtitle):
|
||||
res = self.session.get(subtitle.page_link)
|
||||
if res:
|
||||
if res.text == '500':
|
||||
raise ValueError('Error 500 on server')
|
||||
|
||||
archive = self._get_archive(res.content)
|
||||
# extract the subtitle
|
||||
try:
|
||||
res = self.session.get(subtitle.page_link)
|
||||
res.raise_for_status()
|
||||
except HTTPError as e:
|
||||
if "bloqueado" in res.text.lower():
|
||||
logger.error("LegendasDivx.pt :: Your IP is blocked on this server.")
|
||||
raise IPAddressBlocked("LegendasDivx.pt :: Your IP is blocked on this server.")
|
||||
logger.error("Legendasdivx.pt :: HTTP Error %s", e)
|
||||
raise TooManyRequests("Legendasdivx.pt :: HTTP Error %s", e)
|
||||
except Exception as e:
|
||||
logger.error("LegendasDivx.pt :: Uncaught error: %r", e)
|
||||
raise ServiceUnavailable("LegendasDivx.pt :: Uncaught error: %r", e)
|
||||
|
||||
# make sure we haven't maxed out our daily limit
|
||||
if (res.status_code == 200 and 'limite de downloads diário atingido' in res.text.lower()):
|
||||
logger.error("LegendasDivx.pt :: Daily download limit reached!")
|
||||
raise DownloadLimitExceeded("Legendasdivx.pt :: Daily download limit reached!")
|
||||
|
||||
archive = self._get_archive(res.content)
|
||||
# extract the subtitle
|
||||
if archive:
|
||||
subtitle_content = self._get_subtitle_from_archive(archive, subtitle)
|
||||
subtitle.content = fix_line_ending(subtitle_content)
|
||||
subtitle.normalize()
|
||||
|
||||
return subtitle
|
||||
raise ValueError('Problems conecting to the server')
|
||||
if subtitle_content:
|
||||
subtitle.content = fix_line_ending(subtitle_content)
|
||||
subtitle.normalize()
|
||||
return subtitle
|
||||
return
|
||||
|
||||
def _get_archive(self, content):
|
||||
# open the archive
|
||||
# stole^H^H^H^H^H inspired from subvix provider
|
||||
archive_stream = io.BytesIO(content)
|
||||
if rarfile.is_rarfile(archive_stream):
|
||||
logger.debug('Identified rar archive')
|
||||
logger.debug('Legendasdivx.pt :: Identified rar archive')
|
||||
archive = rarfile.RarFile(archive_stream)
|
||||
elif zipfile.is_zipfile(archive_stream):
|
||||
logger.debug('Identified zip archive')
|
||||
logger.debug('Legendasdivx.pt :: Identified zip archive')
|
||||
archive = zipfile.ZipFile(archive_stream)
|
||||
else:
|
||||
# raise ParseResponseError('Unsupported compressed format')
|
||||
raise Exception('Unsupported compressed format')
|
||||
|
||||
logger.error('Legendasdivx.pt :: Unsupported compressed format')
|
||||
return None
|
||||
return archive
|
||||
|
||||
def _get_subtitle_from_archive(self, archive, subtitle):
|
||||
|
@ -305,7 +427,7 @@ class LegendasdivxProvider(Provider):
|
|||
_tmp.remove('.txt')
|
||||
_subtitle_extensions = tuple(_tmp)
|
||||
_max_score = 0
|
||||
_scores = get_scores (subtitle.video)
|
||||
_scores = get_scores(subtitle.video)
|
||||
|
||||
for name in archive.namelist():
|
||||
# discard hidden files
|
||||
|
@ -316,26 +438,27 @@ class LegendasdivxProvider(Provider):
|
|||
if not name.lower().endswith(_subtitle_extensions):
|
||||
continue
|
||||
|
||||
_guess = guessit (name)
|
||||
_guess = guessit(name)
|
||||
if isinstance(subtitle.video, Episode):
|
||||
logger.debug ("guessing %s" % name)
|
||||
logger.debug("subtitle S{}E{} video S{}E{}".format(_guess['season'],_guess['episode'],subtitle.video.season,subtitle.video.episode))
|
||||
logger.debug("Legendasdivx.pt :: guessing %s", name)
|
||||
logger.debug("Legendasdivx.pt :: subtitle S%sE%s video S%sE%s", _guess['season'], _guess['episode'], subtitle.video.season, subtitle.video.episode)
|
||||
|
||||
if subtitle.video.episode != _guess['episode'] or subtitle.video.season != _guess['season']:
|
||||
logger.debug('subtitle does not match video, skipping')
|
||||
logger.debug('Legendasdivx.pt :: subtitle does not match video, skipping')
|
||||
continue
|
||||
|
||||
matches = set()
|
||||
matches |= guess_matches (subtitle.video, _guess)
|
||||
logger.debug('srt matches: %s' % matches)
|
||||
_score = sum ((_scores.get (match, 0) for match in matches))
|
||||
matches |= guess_matches(subtitle.video, _guess)
|
||||
logger.debug('Legendasdivx.pt :: sub matches: %s', matches)
|
||||
_score = sum((_scores.get(match, 0) for match in matches))
|
||||
if _score > _max_score:
|
||||
_max_name = name
|
||||
_max_score = _score
|
||||
logger.debug("new max: {} {}".format(name, _score))
|
||||
logger.debug("Legendasdivx.pt :: new max: %s %s", name, _score)
|
||||
|
||||
if _max_score > 0:
|
||||
logger.debug("returning from archive: {} scored {}".format(_max_name, _max_score))
|
||||
logger.debug("Legendasdivx.pt :: returning from archive: %s scored %s", _max_name, _max_score)
|
||||
return archive.read(_max_name)
|
||||
|
||||
raise ParseResponseError('Can not find the subtitle in the compressed file')
|
||||
logger.error("Legendasdivx.pt :: No subtitle found on compressed file. Max score was 0")
|
||||
return None
|
||||
|
|
|
@ -44,6 +44,12 @@ class OpenSubtitlesSubtitle(_OpenSubtitlesSubtitle):
|
|||
self.wrong_fps = False
|
||||
self.skip_wrong_fps = skip_wrong_fps
|
||||
|
||||
def get_fps(self):
|
||||
try:
|
||||
return float(self.fps)
|
||||
except:
|
||||
return None
|
||||
|
||||
def get_matches(self, video, hearing_impaired=False):
|
||||
matches = super(OpenSubtitlesSubtitle, self).get_matches(video)
|
||||
|
||||
|
@ -138,11 +144,9 @@ class OpenSubtitlesProvider(ProviderRetryMixin, _OpenSubtitlesProvider):
|
|||
return ServerProxy(url, SubZeroRequestsTransport(use_https=self.use_ssl, timeout=timeout or self.timeout,
|
||||
user_agent=os.environ.get("SZ_USER_AGENT", "Sub-Zero/2")))
|
||||
|
||||
def log_in(self, server_url=None):
|
||||
if server_url:
|
||||
self.terminate()
|
||||
|
||||
self.server = self.get_server_proxy(server_url)
|
||||
def log_in_url(self, server_url):
|
||||
self.token = None
|
||||
self.server = self.get_server_proxy(server_url)
|
||||
|
||||
response = self.retry(
|
||||
lambda: checked(
|
||||
|
@ -155,6 +159,25 @@ class OpenSubtitlesProvider(ProviderRetryMixin, _OpenSubtitlesProvider):
|
|||
logger.debug('Logged in with token %r', self.token[:10]+"X"*(len(self.token)-10))
|
||||
|
||||
region.set("os_token", bytearray(self.token, encoding='utf-8'))
|
||||
region.set("os_server_url", bytearray(server_url, encoding='utf-8'))
|
||||
|
||||
def log_in(self):
|
||||
logger.info('Logging in')
|
||||
|
||||
try:
|
||||
self.log_in_url(self.vip_url if self.is_vip else self.default_url)
|
||||
|
||||
except Unauthorized:
|
||||
if self.is_vip:
|
||||
logger.info("VIP server login failed, falling back")
|
||||
try:
|
||||
self.log_in_url(self.default_url)
|
||||
except Unauthorized:
|
||||
pass
|
||||
|
||||
if not self.token:
|
||||
logger.error("Login failed, please check your credentials")
|
||||
raise Unauthorized
|
||||
|
||||
def use_token_or_login(self, func):
|
||||
if not self.token:
|
||||
|
@ -167,45 +190,18 @@ class OpenSubtitlesProvider(ProviderRetryMixin, _OpenSubtitlesProvider):
|
|||
return func()
|
||||
|
||||
def initialize(self):
|
||||
if self.is_vip:
|
||||
self.server = self.get_server_proxy(self.vip_url)
|
||||
logger.info("Using VIP server")
|
||||
token_cache = region.get("os_token")
|
||||
url_cache = region.get("os_server_url")
|
||||
|
||||
if token_cache is not NO_VALUE and url_cache is not NO_VALUE:
|
||||
self.token = token_cache.decode("utf-8")
|
||||
self.server = self.get_server_proxy(url_cache.decode("utf-8"))
|
||||
logger.debug("Using previous login token: %r", self.token[:10] + "X" * (len(self.token) - 10))
|
||||
else:
|
||||
self.server = self.get_server_proxy(self.default_url)
|
||||
self.server = None
|
||||
self.token = None
|
||||
|
||||
logger.info('Logging in')
|
||||
|
||||
token = str(region.get("os_token"))
|
||||
if token is not NO_VALUE:
|
||||
try:
|
||||
logger.debug('Trying previous token: %r', token[:10]+"X"*(len(token)-10))
|
||||
checked(lambda: self.server.NoOperation(token))
|
||||
self.token = token
|
||||
logger.debug("Using previous login token: %r", token[:10]+"X"*(len(token)-10))
|
||||
return
|
||||
except (NoSession, Unauthorized):
|
||||
logger.debug('Token not valid.')
|
||||
pass
|
||||
|
||||
try:
|
||||
self.log_in()
|
||||
|
||||
except Unauthorized:
|
||||
if self.is_vip:
|
||||
logger.info("VIP server login failed, falling back")
|
||||
self.log_in(self.default_url)
|
||||
if self.token:
|
||||
return
|
||||
|
||||
logger.error("Login failed, please check your credentials")
|
||||
|
||||
def terminate(self):
|
||||
if self.token:
|
||||
try:
|
||||
checked(lambda: self.server.LogOut(self.token))
|
||||
except:
|
||||
logger.error("Logout failed: %s", traceback.format_exc())
|
||||
|
||||
self.server = None
|
||||
self.token = None
|
||||
|
||||
|
|
|
@ -13,7 +13,6 @@ from guessit import guessit
|
|||
from subliminal_patch.providers import Provider
|
||||
from subliminal_patch.subtitle import Subtitle
|
||||
from subliminal_patch.utils import sanitize, fix_inconsistent_naming
|
||||
from subliminal.exceptions import ProviderError
|
||||
from subliminal.utils import sanitize_release_group
|
||||
from subliminal.subtitle import guess_matches
|
||||
from subliminal.video import Episode, Movie
|
||||
|
@ -35,25 +34,31 @@ def fix_tv_naming(title):
|
|||
"Marvel's Luke Cage": "Luke Cage",
|
||||
"Marvel's Iron Fist": "Iron Fist",
|
||||
"Marvel's Jessica Jones": "Jessica Jones",
|
||||
"DC's Legends of Tomorrow": "Legends of Tomorrow"
|
||||
"DC's Legends of Tomorrow": "Legends of Tomorrow",
|
||||
"Doctor Who (2005)": "Doctor Who",
|
||||
}, True)
|
||||
|
||||
class SubsSabBzSubtitle(Subtitle):
|
||||
"""SubsSabBz Subtitle."""
|
||||
provider_name = 'subssabbz'
|
||||
|
||||
def __init__(self, langauge, filename, type, video, link):
|
||||
def __init__(self, langauge, filename, type, video, link, fps, num_cds):
|
||||
super(SubsSabBzSubtitle, self).__init__(langauge)
|
||||
self.langauge = langauge
|
||||
self.filename = filename
|
||||
self.page_link = link
|
||||
self.type = type
|
||||
self.video = video
|
||||
self.fps = fps
|
||||
self.num_cds = num_cds
|
||||
self.release_info = os.path.splitext(filename)[0]
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
return self.filename
|
||||
return self.page_link + self.filename
|
||||
|
||||
def get_fps(self):
|
||||
return self.fps
|
||||
|
||||
def make_picklable(self):
|
||||
self.content = None
|
||||
|
@ -75,13 +80,21 @@ class SubsSabBzSubtitle(Subtitle):
|
|||
if video_filename == subtitle_filename:
|
||||
matches.add('hash')
|
||||
|
||||
matches |= guess_matches(video, guessit(self.filename, {'type': self.type}))
|
||||
if video.year and self.year == video.year:
|
||||
matches.add('year')
|
||||
|
||||
if isinstance(video, Movie):
|
||||
if video.imdb_id and self.imdb_id == video.imdb_id:
|
||||
matches.add('imdb_id')
|
||||
|
||||
matches |= guess_matches(video, guessit(self.title, {'type': self.type, 'allowed_countries': [None]}))
|
||||
matches |= guess_matches(video, guessit(self.filename, {'type': self.type, 'allowed_countries': [None]}))
|
||||
return matches
|
||||
|
||||
|
||||
class SubsSabBzProvider(Provider):
|
||||
"""SubsSabBz Provider."""
|
||||
languages = {Language('por', 'BR')} | {Language(l) for l in [
|
||||
languages = {Language(l) for l in [
|
||||
'bul', 'eng'
|
||||
]}
|
||||
|
||||
|
@ -135,19 +148,51 @@ class SubsSabBzProvider(Provider):
|
|||
soup = BeautifulSoup(response.content, 'lxml')
|
||||
rows = soup.findAll('tr', {'class': 'subs-row'})
|
||||
|
||||
# Search on first 20 rows only
|
||||
for row in rows[:20]:
|
||||
# Search on first 25 rows only
|
||||
for row in rows[:25]:
|
||||
a_element_wrapper = row.find('td', { 'class': 'c2field' })
|
||||
if a_element_wrapper:
|
||||
element = a_element_wrapper.find('a')
|
||||
if element:
|
||||
link = element.get('href')
|
||||
element = row.find('a', href = re.compile(r'.*showuser=.*'))
|
||||
uploader = element.get_text() if element else None
|
||||
notes = element.get('onmouseover')
|
||||
title = element.get_text()
|
||||
|
||||
try:
|
||||
year = int(str(element.next_sibling).strip(' ()'))
|
||||
except:
|
||||
year = None
|
||||
|
||||
td = row.findAll('td')
|
||||
|
||||
try:
|
||||
num_cds = int(td[6].get_text())
|
||||
except:
|
||||
num_cds = None
|
||||
|
||||
try:
|
||||
fps = float(td[7].get_text())
|
||||
except:
|
||||
fps = None
|
||||
|
||||
try:
|
||||
uploader = td[8].get_text()
|
||||
except:
|
||||
uploader = None
|
||||
|
||||
try:
|
||||
imdb_id = re.findall(r'imdb.com/title/(tt\d+)/?$', td[9].find('a').get('href'))[0]
|
||||
except:
|
||||
imdb_id = None
|
||||
|
||||
logger.info('Found subtitle link %r', link)
|
||||
sub = self.download_archive_and_add_subtitle_files(link, language, video)
|
||||
for s in sub:
|
||||
sub = self.download_archive_and_add_subtitle_files(link, language, video, fps, num_cds)
|
||||
for s in sub:
|
||||
s.title = title
|
||||
s.notes = notes
|
||||
s.year = year
|
||||
s.uploader = uploader
|
||||
s.imdb_id = imdb_id
|
||||
subtitles = subtitles + sub
|
||||
return subtitles
|
||||
|
||||
|
@ -159,23 +204,24 @@ class SubsSabBzProvider(Provider):
|
|||
pass
|
||||
else:
|
||||
seeking_subtitle_file = subtitle.filename
|
||||
arch = self.download_archive_and_add_subtitle_files(subtitle.page_link, subtitle.language, subtitle.video)
|
||||
arch = self.download_archive_and_add_subtitle_files(subtitle.page_link, subtitle.language, subtitle.video,
|
||||
subtitle.fps, subtitle.num_cds)
|
||||
for s in arch:
|
||||
if s.filename == seeking_subtitle_file:
|
||||
subtitle.content = s.content
|
||||
|
||||
def process_archive_subtitle_files(self, archiveStream, language, video, link):
|
||||
def process_archive_subtitle_files(self, archiveStream, language, video, link, fps, num_cds):
|
||||
subtitles = []
|
||||
type = 'episode' if isinstance(video, Episode) else 'movie'
|
||||
for file_name in archiveStream.namelist():
|
||||
for file_name in sorted(archiveStream.namelist()):
|
||||
if file_name.lower().endswith(('.srt', '.sub')):
|
||||
logger.info('Found subtitle file %r', file_name)
|
||||
subtitle = SubsSabBzSubtitle(language, file_name, type, video, link)
|
||||
subtitle.content = archiveStream.read(file_name)
|
||||
subtitle = SubsSabBzSubtitle(language, file_name, type, video, link, fps, num_cds)
|
||||
subtitle.content = fix_line_ending(archiveStream.read(file_name))
|
||||
subtitles.append(subtitle)
|
||||
return subtitles
|
||||
|
||||
def download_archive_and_add_subtitle_files(self, link, language, video ):
|
||||
def download_archive_and_add_subtitle_files(self, link, language, video, fps, num_cds):
|
||||
logger.info('Downloading subtitle %r', link)
|
||||
request = self.session.get(link, headers={
|
||||
'Referer': 'http://subs.sab.bz/index.php?'
|
||||
|
@ -184,8 +230,9 @@ class SubsSabBzProvider(Provider):
|
|||
|
||||
archive_stream = io.BytesIO(request.content)
|
||||
if is_rarfile(archive_stream):
|
||||
return self.process_archive_subtitle_files( RarFile(archive_stream), language, video, link )
|
||||
return self.process_archive_subtitle_files(RarFile(archive_stream), language, video, link, fps, num_cds)
|
||||
elif is_zipfile(archive_stream):
|
||||
return self.process_archive_subtitle_files( ZipFile(archive_stream), language, video, link )
|
||||
return self.process_archive_subtitle_files(ZipFile(archive_stream), language, video, link, fps, num_cds)
|
||||
else:
|
||||
raise ValueError('Not a valid archive')
|
||||
logger.error('Ignore unsupported archive %r', request.headers)
|
||||
return []
|
||||
|
|
|
@ -13,7 +13,6 @@ from guessit import guessit
|
|||
from subliminal_patch.providers import Provider
|
||||
from subliminal_patch.subtitle import Subtitle
|
||||
from subliminal_patch.utils import sanitize, fix_inconsistent_naming
|
||||
from subliminal.exceptions import ProviderError
|
||||
from subliminal.utils import sanitize_release_group
|
||||
from subliminal.subtitle import guess_matches
|
||||
from subliminal.video import Episode, Movie
|
||||
|
@ -34,25 +33,31 @@ def fix_tv_naming(title):
|
|||
return fix_inconsistent_naming(title, {"Marvel's Daredevil": "Daredevil",
|
||||
"Marvel's Luke Cage": "Luke Cage",
|
||||
"Marvel's Iron Fist": "Iron Fist",
|
||||
"DC's Legends of Tomorrow": "Legends of Tomorrow"
|
||||
"DC's Legends of Tomorrow": "Legends of Tomorrow",
|
||||
"Doctor Who (2005)": "Doctor Who",
|
||||
}, True)
|
||||
|
||||
class SubsUnacsSubtitle(Subtitle):
|
||||
"""SubsUnacs Subtitle."""
|
||||
provider_name = 'subsunacs'
|
||||
|
||||
def __init__(self, langauge, filename, type, video, link):
|
||||
def __init__(self, langauge, filename, type, video, link, fps, num_cds):
|
||||
super(SubsUnacsSubtitle, self).__init__(langauge)
|
||||
self.langauge = langauge
|
||||
self.filename = filename
|
||||
self.page_link = link
|
||||
self.type = type
|
||||
self.video = video
|
||||
self.fps = fps
|
||||
self.num_cds = num_cds
|
||||
self.release_info = os.path.splitext(filename)[0]
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
return self.filename
|
||||
return self.page_link + self.filename
|
||||
|
||||
def get_fps(self):
|
||||
return self.fps
|
||||
|
||||
def make_picklable(self):
|
||||
self.content = None
|
||||
|
@ -74,13 +79,17 @@ class SubsUnacsSubtitle(Subtitle):
|
|||
if video_filename == subtitle_filename:
|
||||
matches.add('hash')
|
||||
|
||||
matches |= guess_matches(video, guessit(self.filename, {'type': self.type}))
|
||||
if video.year and self.year == video.year:
|
||||
matches.add('year')
|
||||
|
||||
matches |= guess_matches(video, guessit(self.title, {'type': self.type, 'allowed_countries': [None]}))
|
||||
matches |= guess_matches(video, guessit(self.filename, {'type': self.type, 'allowed_countries': [None]}))
|
||||
return matches
|
||||
|
||||
|
||||
class SubsUnacsProvider(Provider):
|
||||
"""SubsUnacs Provider."""
|
||||
languages = {Language('por', 'BR')} | {Language(l) for l in [
|
||||
languages = {Language(l) for l in [
|
||||
'bul', 'eng'
|
||||
]}
|
||||
|
||||
|
@ -145,11 +154,43 @@ class SubsUnacsProvider(Provider):
|
|||
element = a_element_wrapper.find('a', {'class': 'tooltip'})
|
||||
if element:
|
||||
link = element.get('href')
|
||||
element = row.find('a', href = re.compile(r'.*/search\.php\?t=1\&(memid|u)=.*'))
|
||||
uploader = element.get_text() if element else None
|
||||
notes = element.get('title')
|
||||
title = element.get_text()
|
||||
|
||||
try:
|
||||
year = int(element.find_next_sibling('span', {'class' : 'smGray'}).text.strip('\xa0()'))
|
||||
except:
|
||||
year = None
|
||||
|
||||
td = row.findAll('td')
|
||||
|
||||
try:
|
||||
num_cds = int(td[1].get_text())
|
||||
except:
|
||||
num_cds = None
|
||||
|
||||
try:
|
||||
fps = float(td[2].get_text())
|
||||
except:
|
||||
fps = None
|
||||
|
||||
try:
|
||||
rating = float(td[3].find('img').get('title'))
|
||||
except:
|
||||
rating = None
|
||||
|
||||
try:
|
||||
uploader = td[5].get_text()
|
||||
except:
|
||||
uploader = None
|
||||
|
||||
logger.info('Found subtitle link %r', link)
|
||||
sub = self.download_archive_and_add_subtitle_files('https://subsunacs.net' + link, language, video)
|
||||
for s in sub:
|
||||
sub = self.download_archive_and_add_subtitle_files('https://subsunacs.net' + link, language, video, fps, num_cds)
|
||||
for s in sub:
|
||||
s.title = title
|
||||
s.notes = notes
|
||||
s.year = year
|
||||
s.rating = rating
|
||||
s.uploader = uploader
|
||||
subtitles = subtitles + sub
|
||||
return subtitles
|
||||
|
@ -162,28 +203,29 @@ class SubsUnacsProvider(Provider):
|
|||
pass
|
||||
else:
|
||||
seeking_subtitle_file = subtitle.filename
|
||||
arch = self.download_archive_and_add_subtitle_files(subtitle.page_link, subtitle.language, subtitle.video)
|
||||
arch = self.download_archive_and_add_subtitle_files(subtitle.page_link, subtitle.language, subtitle.video,
|
||||
subtitle.fps, subtitle.num_cds)
|
||||
for s in arch:
|
||||
if s.filename == seeking_subtitle_file:
|
||||
subtitle.content = s.content
|
||||
|
||||
def process_archive_subtitle_files(self, archiveStream, language, video, link):
|
||||
def process_archive_subtitle_files(self, archiveStream, language, video, link, fps, num_cds):
|
||||
subtitles = []
|
||||
type = 'episode' if isinstance(video, Episode) else 'movie'
|
||||
for file_name in archiveStream.namelist():
|
||||
for file_name in sorted(archiveStream.namelist()):
|
||||
if file_name.lower().endswith(('.srt', '.sub', '.txt')):
|
||||
file_is_txt = True if file_name.lower().endswith('.txt') else False
|
||||
if file_is_txt and re.search(r'subsunacs\.net|танете част|прочети|^read ?me|procheti', file_name, re.I):
|
||||
logger.info('Ignore readme txt file %r', file_name)
|
||||
continue
|
||||
logger.info('Found subtitle file %r', file_name)
|
||||
subtitle = SubsUnacsSubtitle(language, file_name, type, video, link)
|
||||
subtitle.content = archiveStream.read(file_name)
|
||||
subtitle = SubsUnacsSubtitle(language, file_name, type, video, link, fps, num_cds)
|
||||
subtitle.content = fix_line_ending(archiveStream.read(file_name))
|
||||
if file_is_txt == False or subtitle.is_valid():
|
||||
subtitles.append(subtitle)
|
||||
return subtitles
|
||||
|
||||
def download_archive_and_add_subtitle_files(self, link, language, video ):
|
||||
def download_archive_and_add_subtitle_files(self, link, language, video, fps, num_cds):
|
||||
logger.info('Downloading subtitle %r', link)
|
||||
request = self.session.get(link, headers={
|
||||
'Referer': 'https://subsunacs.net/search.php'
|
||||
|
@ -192,8 +234,9 @@ class SubsUnacsProvider(Provider):
|
|||
|
||||
archive_stream = io.BytesIO(request.content)
|
||||
if is_rarfile(archive_stream):
|
||||
return self.process_archive_subtitle_files( RarFile(archive_stream), language, video, link )
|
||||
return self.process_archive_subtitle_files(RarFile(archive_stream), language, video, link, fps, num_cds)
|
||||
elif is_zipfile(archive_stream):
|
||||
return self.process_archive_subtitle_files( ZipFile(archive_stream), language, video, link )
|
||||
return self.process_archive_subtitle_files(ZipFile(archive_stream), language, video, link, fps, num_cds)
|
||||
else:
|
||||
raise ValueError('Not a valid archive')
|
||||
logger.error('Ignore unsupported archive %r', request.headers)
|
||||
return []
|
||||
|
|
|
@ -19,6 +19,8 @@ from subliminal.video import Episode
|
|||
logger = logging.getLogger(__name__)
|
||||
article_re = re.compile(r'^([A-Za-z]{1,3}) (.*)$')
|
||||
episode_re = re.compile(r'^(\d+)(-(\d+))*$')
|
||||
episode_name_re = re.compile(r'^(.*?)( [\[(].{2,4}[\])])*$')
|
||||
series_sanitize_re = re.compile(r'^(.*?)( \[\D+\])*$')
|
||||
|
||||
|
||||
class XSubsSubtitle(Subtitle):
|
||||
|
@ -143,7 +145,11 @@ class XSubsProvider(Provider):
|
|||
for show_category in soup.findAll('seriesl'):
|
||||
if show_category.attrs['category'] == u'Σειρές':
|
||||
for show in show_category.findAll('series'):
|
||||
show_ids[sanitize(show.text)] = int(show['srsid'])
|
||||
series = show.text
|
||||
series_match = series_sanitize_re.match(series)
|
||||
if series_match:
|
||||
series = series_match.group(1)
|
||||
show_ids[sanitize(series)] = int(show['srsid'])
|
||||
break
|
||||
logger.debug('Found %d show ids', len(show_ids))
|
||||
|
||||
|
@ -195,6 +201,9 @@ class XSubsProvider(Provider):
|
|||
soup = ParserBeautifulSoup(r.content, ['lxml', 'html.parser'])
|
||||
|
||||
series = soup.find('name').text
|
||||
series_match = episode_name_re.match(series)
|
||||
if series_match:
|
||||
series = series_match.group(1)
|
||||
|
||||
# loop over season rows
|
||||
seasons = soup.findAll('series_group')
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
import logging
|
||||
import re
|
||||
import io
|
||||
import os
|
||||
from random import randint
|
||||
|
@ -13,7 +12,6 @@ from guessit import guessit
|
|||
from subliminal_patch.providers import Provider
|
||||
from subliminal_patch.subtitle import Subtitle
|
||||
from subliminal_patch.utils import sanitize
|
||||
from subliminal.exceptions import ProviderError
|
||||
from subliminal.utils import sanitize_release_group
|
||||
from subliminal.subtitle import guess_matches
|
||||
from subliminal.video import Episode, Movie
|
||||
|
@ -27,18 +25,22 @@ class YavkaNetSubtitle(Subtitle):
|
|||
"""YavkaNet Subtitle."""
|
||||
provider_name = 'yavkanet'
|
||||
|
||||
def __init__(self, langauge, filename, type, video, link):
|
||||
def __init__(self, langauge, filename, type, video, link, fps):
|
||||
super(YavkaNetSubtitle, self).__init__(langauge)
|
||||
self.langauge = langauge
|
||||
self.filename = filename
|
||||
self.page_link = link
|
||||
self.type = type
|
||||
self.video = video
|
||||
self.fps = fps
|
||||
self.release_info = os.path.splitext(filename)[0]
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
return self.filename
|
||||
return self.page_link + self.filename
|
||||
|
||||
def get_fps(self):
|
||||
return self.fps
|
||||
|
||||
def make_picklable(self):
|
||||
self.content = None
|
||||
|
@ -60,7 +62,11 @@ class YavkaNetSubtitle(Subtitle):
|
|||
if video_filename == subtitle_filename:
|
||||
matches.add('hash')
|
||||
|
||||
matches |= guess_matches(video, guessit(self.filename, {'type': self.type}))
|
||||
if video.year and self.year == video.year:
|
||||
matches.add('year')
|
||||
|
||||
matches |= guess_matches(video, guessit(self.title, {'type': self.type, 'allowed_countries': [None]}))
|
||||
matches |= guess_matches(video, guessit(self.filename, {'type': self.type, 'allowed_countries': [None]}))
|
||||
return matches
|
||||
|
||||
|
||||
|
@ -122,18 +128,34 @@ class YavkaNetProvider(Provider):
|
|||
return subtitles
|
||||
|
||||
soup = BeautifulSoup(response.content, 'lxml')
|
||||
rows = soup.findAll('tr', {'class': 'info'})
|
||||
rows = soup.findAll('tr')
|
||||
|
||||
# Search on first 20 rows only
|
||||
for row in rows[:20]:
|
||||
# Search on first 25 rows only
|
||||
for row in rows[:25]:
|
||||
element = row.find('a', {'class': 'selector'})
|
||||
if element:
|
||||
link = element.get('href')
|
||||
notes = element.get('content')
|
||||
title = element.get_text()
|
||||
|
||||
try:
|
||||
year = int(element.find_next_sibling('span').text.strip('()'))
|
||||
except:
|
||||
year = None
|
||||
|
||||
try:
|
||||
fps = float(row.find('span', {'title': 'Кадри в секунда'}).text.strip())
|
||||
except:
|
||||
fps = None
|
||||
|
||||
element = row.find('a', {'class': 'click'})
|
||||
uploader = element.get_text() if element else None
|
||||
logger.info('Found subtitle link %r', link)
|
||||
sub = self.download_archive_and_add_subtitle_files('http://yavka.net/' + link, language, video)
|
||||
for s in sub:
|
||||
sub = self.download_archive_and_add_subtitle_files('http://yavka.net/' + link, language, video, fps)
|
||||
for s in sub:
|
||||
s.title = title
|
||||
s.notes = notes
|
||||
s.year = year
|
||||
s.uploader = uploader
|
||||
subtitles = subtitles + sub
|
||||
return subtitles
|
||||
|
@ -146,23 +168,24 @@ class YavkaNetProvider(Provider):
|
|||
pass
|
||||
else:
|
||||
seeking_subtitle_file = subtitle.filename
|
||||
arch = self.download_archive_and_add_subtitle_files(subtitle.page_link, subtitle.language, subtitle.video)
|
||||
arch = self.download_archive_and_add_subtitle_files(subtitle.page_link, subtitle.language, subtitle.video,
|
||||
subtitle.fps)
|
||||
for s in arch:
|
||||
if s.filename == seeking_subtitle_file:
|
||||
subtitle.content = s.content
|
||||
|
||||
def process_archive_subtitle_files(self, archiveStream, language, video, link):
|
||||
def process_archive_subtitle_files(self, archiveStream, language, video, link, fps):
|
||||
subtitles = []
|
||||
type = 'episode' if isinstance(video, Episode) else 'movie'
|
||||
for file_name in archiveStream.namelist():
|
||||
if file_name.lower().endswith(('.srt', '.sub')):
|
||||
logger.info('Found subtitle file %r', file_name)
|
||||
subtitle = YavkaNetSubtitle(language, file_name, type, video, link)
|
||||
subtitle.content = archiveStream.read(file_name)
|
||||
subtitle = YavkaNetSubtitle(language, file_name, type, video, link, fps)
|
||||
subtitle.content = fix_line_ending(archiveStream.read(file_name))
|
||||
subtitles.append(subtitle)
|
||||
return subtitles
|
||||
|
||||
def download_archive_and_add_subtitle_files(self, link, language, video ):
|
||||
def download_archive_and_add_subtitle_files(self, link, language, video, fps):
|
||||
logger.info('Downloading subtitle %r', link)
|
||||
request = self.session.get(link, headers={
|
||||
'Referer': 'http://yavka.net/subtitles.php'
|
||||
|
@ -171,9 +194,9 @@ class YavkaNetProvider(Provider):
|
|||
|
||||
archive_stream = io.BytesIO(request.content)
|
||||
if is_rarfile(archive_stream):
|
||||
return self.process_archive_subtitle_files( RarFile(archive_stream), language, video, link )
|
||||
return self.process_archive_subtitle_files(RarFile(archive_stream), language, video, link, fps)
|
||||
elif is_zipfile(archive_stream):
|
||||
return self.process_archive_subtitle_files( ZipFile(archive_stream), language, video, link )
|
||||
return self.process_archive_subtitle_files(ZipFile(archive_stream), language, video, link, fps)
|
||||
else:
|
||||
raise ValueError('Not a valid archive')
|
||||
|
||||
logger.error('Ignore unsupported archive %r', request.headers)
|
||||
return []
|
||||
|
|
|
@ -89,6 +89,13 @@ class Subtitle(Subtitle_):
|
|||
def numeric_id(self):
|
||||
raise NotImplemented
|
||||
|
||||
def get_fps(self):
|
||||
"""
|
||||
:return: frames per second or None if not supported
|
||||
:rtype: float
|
||||
"""
|
||||
return None
|
||||
|
||||
def make_picklable(self):
|
||||
"""
|
||||
some subtitle instances might have unpicklable objects stored; clean them up here
|
||||
|
@ -264,10 +271,14 @@ class Subtitle(Subtitle_):
|
|||
else:
|
||||
logger.info("Got format: %s", subs.format)
|
||||
except pysubs2.UnknownFPSError:
|
||||
# if parsing failed, suggest our media file's fps
|
||||
logger.info("No FPS info in subtitle. Using our own media FPS for the MicroDVD subtitle: %s",
|
||||
self.plex_media_fps)
|
||||
subs = pysubs2.SSAFile.from_string(text, fps=self.plex_media_fps)
|
||||
# if parsing failed, use frame rate from provider
|
||||
sub_fps = self.get_fps()
|
||||
if not isinstance(sub_fps, float) or sub_fps < 10.0:
|
||||
# or use our media file's fps as a fallback
|
||||
sub_fps = self.plex_media_fps
|
||||
logger.info("No FPS info in subtitle. Using our own media FPS for the MicroDVD subtitle: %s",
|
||||
self.plex_media_fps)
|
||||
subs = pysubs2.SSAFile.from_string(text, fps=sub_fps)
|
||||
|
||||
unicontent = self.pysubs2_to_unicode(subs)
|
||||
self.content = unicontent.encode(self._guessed_encoding)
|
||||
|
|
|
@ -84,11 +84,10 @@ def _get_localzone(_root='/'):
|
|||
if not etctz:
|
||||
continue
|
||||
tz = pytz.timezone(etctz.replace(' ', '_'))
|
||||
# Disabling this offset valdation due to issue with some timezone: https://github.com/regebro/tzlocal/issues/80
|
||||
# if _root == '/':
|
||||
if _root == '/':
|
||||
# We are using a file in etc to name the timezone.
|
||||
# Verify that the timezone specified there is actually used:
|
||||
# utils.assert_tz_offset(tz)
|
||||
utils.assert_tz_offset(tz)
|
||||
return tz
|
||||
|
||||
except IOError:
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import time
|
||||
import datetime
|
||||
import calendar
|
||||
|
||||
|
||||
def get_system_offset():
|
||||
|
@ -11,8 +13,14 @@ def get_system_offset():
|
|||
|
||||
To keep compatibility with Windows, we're always importing time module here.
|
||||
"""
|
||||
import time
|
||||
if time.daylight and time.localtime().tm_isdst > 0:
|
||||
|
||||
localtime = calendar.timegm(time.localtime())
|
||||
gmtime = calendar.timegm(time.gmtime())
|
||||
offset = gmtime - localtime
|
||||
# We could get the localtime and gmtime on either side of a second switch
|
||||
# so we check that the difference is less than one minute, because nobody
|
||||
# has that small DST differences.
|
||||
if abs(offset - time.altzone) < 60:
|
||||
return -time.altzone
|
||||
else:
|
||||
return -time.timezone
|
||||
|
|
|
@ -87,6 +87,7 @@ win_tz = {'AUS Central Standard Time': 'Australia/Darwin',
|
|||
'Pacific Standard Time (Mexico)': 'America/Tijuana',
|
||||
'Pakistan Standard Time': 'Asia/Karachi',
|
||||
'Paraguay Standard Time': 'America/Asuncion',
|
||||
'Qyzylorda Standard Time': 'Asia/Qyzylorda',
|
||||
'Romance Standard Time': 'Europe/Paris',
|
||||
'Russia Time Zone 10': 'Asia/Srednekolymsk',
|
||||
'Russia Time Zone 11': 'Asia/Kamchatka',
|
||||
|
@ -127,6 +128,7 @@ win_tz = {'AUS Central Standard Time': 'Australia/Darwin',
|
|||
'Ulaanbaatar Standard Time': 'Asia/Ulaanbaatar',
|
||||
'Venezuela Standard Time': 'America/Caracas',
|
||||
'Vladivostok Standard Time': 'Asia/Vladivostok',
|
||||
'Volgograd Standard Time': 'Europe/Volgograd',
|
||||
'W. Australia Standard Time': 'Australia/Perth',
|
||||
'W. Central Africa Standard Time': 'Africa/Lagos',
|
||||
'W. Europe Standard Time': 'Europe/Berlin',
|
||||
|
@ -287,7 +289,7 @@ tz_win = {'Africa/Abidjan': 'Greenwich Standard Time',
|
|||
'America/Mendoza': 'Argentina Standard Time',
|
||||
'America/Menominee': 'Central Standard Time',
|
||||
'America/Merida': 'Central Standard Time (Mexico)',
|
||||
'America/Metlakatla': 'Pacific Standard Time',
|
||||
'America/Metlakatla': 'Alaskan Standard Time',
|
||||
'America/Mexico_City': 'Central Standard Time (Mexico)',
|
||||
'America/Miquelon': 'Saint Pierre Standard Time',
|
||||
'America/Moncton': 'Atlantic Standard Time',
|
||||
|
@ -347,13 +349,13 @@ tz_win = {'Africa/Abidjan': 'Greenwich Standard Time',
|
|||
'America/Winnipeg': 'Central Standard Time',
|
||||
'America/Yakutat': 'Alaskan Standard Time',
|
||||
'America/Yellowknife': 'Mountain Standard Time',
|
||||
'Antarctica/Casey': 'W. Australia Standard Time',
|
||||
'Antarctica/Casey': 'Singapore Standard Time',
|
||||
'Antarctica/Davis': 'SE Asia Standard Time',
|
||||
'Antarctica/DumontDUrville': 'West Pacific Standard Time',
|
||||
'Antarctica/Macquarie': 'Central Pacific Standard Time',
|
||||
'Antarctica/Mawson': 'West Asia Standard Time',
|
||||
'Antarctica/McMurdo': 'New Zealand Standard Time',
|
||||
'Antarctica/Palmer': 'Magallanes Standard Time',
|
||||
'Antarctica/Palmer': 'SA Eastern Standard Time',
|
||||
'Antarctica/Rothera': 'SA Eastern Standard Time',
|
||||
'Antarctica/South_Pole': 'New Zealand Standard Time',
|
||||
'Antarctica/Syowa': 'E. Africa Standard Time',
|
||||
|
@ -424,7 +426,7 @@ tz_win = {'Africa/Abidjan': 'Greenwich Standard Time',
|
|||
'Asia/Pyongyang': 'North Korea Standard Time',
|
||||
'Asia/Qatar': 'Arab Standard Time',
|
||||
'Asia/Qostanay': 'Central Asia Standard Time',
|
||||
'Asia/Qyzylorda': 'West Asia Standard Time',
|
||||
'Asia/Qyzylorda': 'Qyzylorda Standard Time',
|
||||
'Asia/Rangoon': 'Myanmar Standard Time',
|
||||
'Asia/Riyadh': 'Arab Standard Time',
|
||||
'Asia/Saigon': 'SE Asia Standard Time',
|
||||
|
@ -592,7 +594,7 @@ tz_win = {'Africa/Abidjan': 'Greenwich Standard Time',
|
|||
'Europe/Vatican': 'W. Europe Standard Time',
|
||||
'Europe/Vienna': 'W. Europe Standard Time',
|
||||
'Europe/Vilnius': 'FLE Standard Time',
|
||||
'Europe/Volgograd': 'Russian Standard Time',
|
||||
'Europe/Volgograd': 'Volgograd Standard Time',
|
||||
'Europe/Warsaw': 'Central European Standard Time',
|
||||
'Europe/Zagreb': 'Central European Standard Time',
|
||||
'Europe/Zaporozhye': 'FLE Standard Time',
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
apprise=0.8.2
|
||||
apprise=0.8.5
|
||||
apscheduler=3.5.1
|
||||
babelfish=0.5.5
|
||||
backports.functools-lru-cache=1.5
|
||||
|
@ -13,7 +13,6 @@ gitpython=2.1.9
|
|||
guessit=2.1.4
|
||||
guess_language-spirit=0.5.3
|
||||
knowit=0.3.0-dev
|
||||
peewee=3.9.6
|
||||
py-pretty=1
|
||||
pycountry=18.2.23
|
||||
pyga=2.6.1
|
||||
|
@ -25,6 +24,6 @@ six=1.11.0
|
|||
SimpleConfigParser=0.1.0 <-- modified version: do not update!!!
|
||||
stevedore=1.28.0
|
||||
subliminal=2.1.0dev
|
||||
tzlocal=1.5.1
|
||||
tzlocal=2.1b1
|
||||
urllib3=1.23
|
||||
Js2Py=0.63 <-- modified: manually merged from upstream: https://github.com/PiotrDabkowski/Js2Py/pull/192/files
|
||||
|
|
|
@ -265,6 +265,24 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="middle aligned row">
|
||||
<div class="right aligned six wide column">
|
||||
<label>Skip wrong FPS</label>
|
||||
</div>
|
||||
<div class="one wide column">
|
||||
<div id="settings_legendasdivx_skip_wrong_fps" class="ui toggle checkbox" data-ldfps={{settings.legendasdivx.getboolean('skip_wrong_fps')}}>
|
||||
<input type="checkbox" name="settings_legendasdivx_skip_wrong_fps">
|
||||
<label></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="collapsed column">
|
||||
<div class="collapsed center aligned column">
|
||||
<div class="ui basic icon" data-tooltip="Skip Subtitles with a mismatched FPS value; might lead to more results when disabled but also to more false-positives." data-inverted="">
|
||||
<i class="help circle large icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="middle aligned row">
|
||||
|
@ -500,8 +518,9 @@
|
|||
</div>
|
||||
</div>
|
||||
<div id="regielive_option" class="ui grid container">
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="middle aligned row">
|
||||
<div class="right aligned four wide column">
|
||||
<label>Subdivx</label>
|
||||
|
@ -733,7 +752,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div id="titlovi_option" class="ui grid container">
|
||||
<div class="middle aligned row">
|
||||
<div class="middle aligned row">
|
||||
<div class="right aligned six wide column">
|
||||
<label>Username</label>
|
||||
</div>
|
||||
|
@ -755,6 +774,28 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="middle aligned row">
|
||||
<div class="right aligned four wide column">
|
||||
<label>Titrari.ro</label>
|
||||
</div>
|
||||
<div class="one wide column">
|
||||
<div id="titrari" class="ui toggle checkbox provider">
|
||||
<input type="checkbox">
|
||||
<label></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="collapsed column">
|
||||
<div class="collapsed center aligned column">
|
||||
<div class="ui basic icon" data-tooltip="Romanian Subtitles Provider." data-inverted="">
|
||||
<i class="help circle large icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="titrari_option" class="ui grid container">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="middle aligned row">
|
||||
<div class="right aligned four wide column">
|
||||
<label>TVSubtitles</label>
|
||||
|
@ -811,7 +852,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div id="xsubs_option" class="ui grid container">
|
||||
<div class="middle aligned row">
|
||||
<div class="middle aligned row">
|
||||
<div class="right aligned six wide column">
|
||||
<label>Username</label>
|
||||
</div>
|
||||
|
@ -922,6 +963,12 @@
|
|||
$("#settings_opensubtitles_skip_wrong_fps").checkbox('uncheck');
|
||||
}
|
||||
|
||||
if ($('#settings_legendasdivx_skip_wrong_fps').data("ldfps") === "True") {
|
||||
$("#settings_legendasdivx_skip_wrong_fps").checkbox('check');
|
||||
} else {
|
||||
$("#settings_legendasdivx_skip_wrong_fps").checkbox('uncheck');
|
||||
}
|
||||
|
||||
$('#settings_providers').dropdown('clear');
|
||||
$('#settings_providers').dropdown('set selected',{{!enabled_providers}});
|
||||
$('#settings_providers').dropdown();
|
||||
|
@ -949,4 +996,4 @@
|
|||
$('#'+$(this).parent().attr('id')+'_option').hide();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</script>
|
|
@ -403,15 +403,12 @@
|
|||
});
|
||||
|
||||
$('#shutdown').on('click', function(){
|
||||
$.ajax({
|
||||
url: "{{base_url}}shutdown",
|
||||
async: false
|
||||
document.open();
|
||||
document.write('Bazarr has shutdown.');
|
||||
document.close();
|
||||
$.ajax({
|
||||
url: "{{base_url}}shutdown"
|
||||
})
|
||||
.always(function(){
|
||||
document.open();
|
||||
document.write('Bazarr has shutdown.');
|
||||
document.close();
|
||||
});
|
||||
});
|
||||
|
||||
$('#logout').on('click', function(){
|
||||
|
@ -422,11 +419,9 @@
|
|||
$('#loader_text').text("Bazarr is restarting, please wait...");
|
||||
$.ajax({
|
||||
url: "{{base_url}}restart",
|
||||
async: true,
|
||||
error: (function () {
|
||||
setTimeout(function () { setInterval(ping, 2000); }, 8000);
|
||||
})
|
||||
async: true
|
||||
});
|
||||
setTimeout(function () { setInterval(ping, 2000); }, 8000);
|
||||
});
|
||||
|
||||
% from config import settings
|
||||
|
|
Loading…
Reference in New Issue