From 645952c61aba3cccb5ca919be966a7ba02d853fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Louis=20V=C3=A9zina?= <5130500+morpheus65535@users.noreply.github.com> Date: Fri, 13 Sep 2019 15:12:26 -0400 Subject: [PATCH] WIP --- bazarr.py | 22 +- bazarr/analytics.py | 5 +- bazarr/check_update.py | 1 + bazarr/config.py | 1 + bazarr/database.py | 1 + bazarr/embedded_subs_reader.py | 1 + bazarr/get_args.py | 1 + bazarr/get_episodes.py | 1 + bazarr/get_languages.py | 1 + bazarr/get_movies.py | 44 +- bazarr/get_providers.py | 5 +- bazarr/get_series.py | 27 +- bazarr/get_subtitle.py | 30 +- bazarr/helper.py | 3 +- bazarr/init.py | 5 +- bazarr/libs.py | 1 + bazarr/list_subtitles.py | 6 +- bazarr/logger.py | 8 +- bazarr/main.py | 39 +- bazarr/notifier.py | 1 + bazarr/queueconfig.py | 1 + bazarr/scheduler.py | 1 + bazarr/utils.py | 1 + libs/ConfigParser2.py | 797 ----------- libs/bs4/AUTHORS.txt | 43 - libs/bs4/COPYING.txt | 27 - libs/bs4/NEWS.txt | 1190 ----------------- libs/bs4/README.txt | 63 - libs/bs4/TODO.txt | 31 - libs/bs4/__init__.py | 265 ++-- libs/bs4/builder/__init__.py | 70 +- libs/bs4/builder/_html5lib.py | 40 +- libs/bs4/builder/_htmlparser.py | 72 +- libs/bs4/builder/_lxml.py | 90 +- libs/bs4/dammit.py | 40 +- libs/bs4/diagnose.py | 77 +- libs/bs4/element.py | 851 +++++------- libs/bs4/testing.py | 270 +++- libs/bs4/tests/test_html5lib.py | 50 +- libs/bs4/tests/test_htmlparser.py | 19 +- libs/bs4/tests/test_lxml.py | 32 +- libs/bs4/tests/test_soup.py | 132 +- libs/bs4/tests/test_tree.py | 299 ++++- libs/concurrent/__init__.py | 3 - libs/concurrent/futures/__init__.py | 23 - libs/concurrent/futures/_base.py | 607 --------- libs/concurrent/futures/process.py | 359 ----- libs/concurrent/futures/thread.py | 134 -- libs/deathbycaptcha.py | 443 +++--- libs/decorator.py | 37 +- libs/dogpile/__init__.py | 2 +- libs/dogpile/cache/region.py | 206 +-- libs/dogpile/cache/util.py | 7 +- libs/dogpile/lock.py | 99 +- libs/dogpile/util/compat.py | 22 + libs/dogpile/util/nameregistry.py | 8 +- libs/dogpile/util/readwrite_lock.py | 16 +- libs/{enum => enum2.7}/LICENSE | 0 libs/{enum => enum2.7}/README | 0 libs/{enum => enum2.7}/__init__.py | 0 libs/{enum => enum2.7}/doc/enum.pdf | 0 libs/{enum => enum2.7}/doc/enum.rst | 0 libs/{enum => enum2.7}/test.py | 0 libs/ipaddress.py | 12 +- libs/simpleconfigparser/__init__.py | 10 +- libs/six.py | 65 +- libs/subliminal/__init__.py | 2 +- libs/subliminal/cli.py | 5 +- libs/subliminal/core.py | 84 +- libs/subliminal/exceptions.py | 4 +- libs/subliminal/extensions.py | 5 +- libs/subliminal/providers/__init__.py | 3 - libs/subliminal/providers/addic7ed.py | 84 +- libs/subliminal/providers/legendastv.py | 298 ++--- libs/subliminal/providers/napiprojekt.py | 15 +- libs/subliminal/providers/opensubtitles.py | 31 +- libs/subliminal/providers/podnapisi.py | 54 +- libs/subliminal/providers/shooter.py | 6 +- libs/subliminal/providers/subscenter.py | 30 +- libs/subliminal/providers/thesubdb.py | 6 +- libs/subliminal/providers/tvsubtitles.py | 38 +- libs/subliminal/refiners/tvdb.py | 3 +- libs/subliminal/score.py | 2 +- libs/subliminal/subtitle.py | 12 +- libs/subliminal/video.py | 30 +- libs/subliminal_patch/core.py | 13 +- libs/subliminal_patch/exceptions.py | 10 + libs/subliminal_patch/http.py | 12 +- libs/subliminal_patch/pitcher.py | 4 +- libs/subliminal_patch/refiners/tvdb.py | 2 +- libs/subliminal_patch/subtitle.py | 6 +- libs/subzero/lib/__init__.py | 8 +- libs/subzero/lib/geezip.py | 2 +- libs/subzero/modification/__init__.py | 6 +- .../modification/dictionaries/__init__.py | 2 +- .../subzero/modification/dictionaries/data.py | 4 +- libs/subzero/modification/main.py | 14 +- libs/subzero/modification/mods/__init__.py | 2 +- libs/subzero/modification/mods/common.py | 30 +- .../modification/mods/hearing_impaired.py | 30 +- libs/subzero/modification/mods/ocr_fixes.py | 6 +- libs/subzero/modification/mods/offset.py | 2 +- libs/{yaml => yaml2.7}/__init__.py | 0 libs/{yaml => yaml2.7}/composer.py | 0 libs/{yaml => yaml2.7}/constructor.py | 0 libs/{yaml => yaml2.7}/cyaml.py | 0 libs/{yaml => yaml2.7}/dumper.py | 0 libs/{yaml => yaml2.7}/emitter.py | 0 libs/{yaml => yaml2.7}/error.py | 0 libs/{yaml => yaml2.7}/events.py | 0 libs/{yaml => yaml2.7}/loader.py | 0 libs/{yaml => yaml2.7}/nodes.py | 0 libs/{yaml => yaml2.7}/parser.py | 0 libs/{yaml => yaml2.7}/reader.py | 0 libs/{yaml => yaml2.7}/representer.py | 0 libs/{yaml => yaml2.7}/resolver.py | 0 libs/{yaml => yaml2.7}/scanner.py | 0 libs/{yaml => yaml2.7}/serializer.py | 0 libs/{yaml => yaml2.7}/tokens.py | 0 119 files changed, 2332 insertions(+), 5250 deletions(-) delete mode 100644 libs/ConfigParser2.py delete mode 100644 libs/bs4/AUTHORS.txt delete mode 100644 libs/bs4/COPYING.txt delete mode 100644 libs/bs4/NEWS.txt delete mode 100644 libs/bs4/README.txt delete mode 100644 libs/bs4/TODO.txt delete mode 100644 libs/concurrent/__init__.py delete mode 100644 libs/concurrent/futures/__init__.py delete mode 100644 libs/concurrent/futures/_base.py delete mode 100644 libs/concurrent/futures/process.py delete mode 100644 libs/concurrent/futures/thread.py rename libs/{enum => enum2.7}/LICENSE (100%) rename libs/{enum => enum2.7}/README (100%) rename libs/{enum => enum2.7}/__init__.py (100%) rename libs/{enum => enum2.7}/doc/enum.pdf (100%) rename libs/{enum => enum2.7}/doc/enum.rst (100%) rename libs/{enum => enum2.7}/test.py (100%) rename libs/{yaml => yaml2.7}/__init__.py (100%) rename libs/{yaml => yaml2.7}/composer.py (100%) rename libs/{yaml => yaml2.7}/constructor.py (100%) rename libs/{yaml => yaml2.7}/cyaml.py (100%) rename libs/{yaml => yaml2.7}/dumper.py (100%) rename libs/{yaml => yaml2.7}/emitter.py (100%) rename libs/{yaml => yaml2.7}/error.py (100%) rename libs/{yaml => yaml2.7}/events.py (100%) rename libs/{yaml => yaml2.7}/loader.py (100%) rename libs/{yaml => yaml2.7}/nodes.py (100%) rename libs/{yaml => yaml2.7}/parser.py (100%) rename libs/{yaml => yaml2.7}/reader.py (100%) rename libs/{yaml => yaml2.7}/representer.py (100%) rename libs/{yaml => yaml2.7}/resolver.py (100%) rename libs/{yaml => yaml2.7}/scanner.py (100%) rename libs/{yaml => yaml2.7}/serializer.py (100%) rename libs/{yaml => yaml2.7}/tokens.py (100%) diff --git a/bazarr.py b/bazarr.py index 4448dc16c..12a3e382b 100644 --- a/bazarr.py +++ b/bazarr.py @@ -1,5 +1,7 @@ # coding=utf-8 +from __future__ import absolute_import +from __future__ import print_function import subprocess as sp import time import os @@ -12,14 +14,16 @@ from bazarr.get_args import args def check_python_version(): python_version = platform.python_version_tuple() minimum_python_version_tuple = (2, 7, 13) + minimum_python3_version_tuple = (3, 6, 0) minimum_python_version = ".".join(str(i) for i in minimum_python_version_tuple) + minimum_python3_version = ".".join(str(i) for i in minimum_python3_version_tuple) - if int(python_version[0]) > minimum_python_version_tuple[0]: - print "Python 3 isn't supported. Please use Python " + minimum_python_version + " or greater." + if int(python_version[0]) == minimum_python3_version_tuple[0] and int(python_version[1]) < minimum_python3_version_tuple[1]: + print("Python " + minimum_python3_version + " or greater required. Current version is " + platform.python_version() + ". Please upgrade Python.") os._exit(0) - elif int(python_version[1]) < minimum_python_version_tuple[1] or int(python_version[2].rstrip('+')) < minimum_python_version_tuple[2]: - print "Python " + minimum_python_version + " or greater required. Current version is " + platform.python_version() + ". Please upgrade Python." + elif int(python_version[0]) == minimum_python_version_tuple[0] and (int(python_version[1]) < minimum_python_version_tuple[1] or int(python_version[2].rstrip('+')) < minimum_python_version_tuple[2]): + print("Python " + minimum_python_version + " or greater required. Current version is " + platform.python_version() + ". Please upgrade Python.") os._exit(0) @@ -32,10 +36,10 @@ def start_bazarr(): script = [sys.executable, "-u", os.path.normcase(os.path.join(dir_name, 'bazarr', 'main.py'))] + sys.argv[1:] ep = sp.Popen(script, stdout=sp.PIPE, stderr=sp.STDOUT, stdin=sp.PIPE) - print "Bazarr starting..." + print("Bazarr starting...") try: for line in iter(ep.stdout.readline, ''): - sys.stdout.write(line) + sys.stdout.buffer.write(line) except KeyboardInterrupt: pass @@ -60,16 +64,16 @@ if __name__ == '__main__': try: os.remove(stopfile) except: - print 'Unable to delete stop file.' + print('Unable to delete stop file.') else: - print 'Bazarr exited.' + print('Bazarr exited.') os._exit(0) if os.path.exists(restartfile): try: os.remove(restartfile) except: - print 'Unable to delete restart file.' + print('Unable to delete restart file.') else: start_bazarr() diff --git a/bazarr/analytics.py b/bazarr/analytics.py index a751dc477..64c147bc2 100644 --- a/bazarr/analytics.py +++ b/bazarr/analytics.py @@ -1,6 +1,7 @@ # coding=utf-8 -import cPickle as pickle +from __future__ import absolute_import +import six.moves.cPickle as pickle import base64 import random import platform @@ -30,7 +31,7 @@ def track_event(category=None, action=None, label=None): visitor = pickle.loads(base64.b64decode(settings.analytics.visitor)) except: visitor = Visitor() - unique_id = long(random.getrandbits(32)) + unique_id = int(random.getrandbits(32)) visitor.unique_id = unique_id session = Session() diff --git a/bazarr/check_update.py b/bazarr/check_update.py index 08b1946fc..033e34896 100644 --- a/bazarr/check_update.py +++ b/bazarr/check_update.py @@ -1,4 +1,5 @@ # coding=utf-8 +from __future__ import absolute_import import os import logging import json diff --git a/bazarr/config.py b/bazarr/config.py index d5b606e8e..810cb4d54 100644 --- a/bazarr/config.py +++ b/bazarr/config.py @@ -1,4 +1,5 @@ # coding=utf-8 +from __future__ import absolute_import import os from simpleconfigparser import simpleconfigparser diff --git a/bazarr/database.py b/bazarr/database.py index bee060907..8ec8b02b9 100644 --- a/bazarr/database.py +++ b/bazarr/database.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import import os import atexit diff --git a/bazarr/embedded_subs_reader.py b/bazarr/embedded_subs_reader.py index df9625d4c..4d4819d38 100644 --- a/bazarr/embedded_subs_reader.py +++ b/bazarr/embedded_subs_reader.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import import enzyme import logging import os diff --git a/bazarr/get_args.py b/bazarr/get_args.py index a92052729..3eb99c2a9 100644 --- a/bazarr/get_args.py +++ b/bazarr/get_args.py @@ -1,4 +1,5 @@ # coding=utf-8 +from __future__ import absolute_import import os import argparse diff --git a/bazarr/get_episodes.py b/bazarr/get_episodes.py index 09a1220b8..c1f280d1d 100644 --- a/bazarr/get_episodes.py +++ b/bazarr/get_episodes.py @@ -1,4 +1,5 @@ # coding=utf-8 +from __future__ import absolute_import import os import requests import logging diff --git a/bazarr/get_languages.py b/bazarr/get_languages.py index ab71deb3f..a3658c1a4 100644 --- a/bazarr/get_languages.py +++ b/bazarr/get_languages.py @@ -1,5 +1,6 @@ # coding=utf-8 +from __future__ import absolute_import import os import pycountry diff --git a/bazarr/get_movies.py b/bazarr/get_movies.py index 6a754ae32..303dbcc90 100644 --- a/bazarr/get_movies.py +++ b/bazarr/get_movies.py @@ -1,5 +1,6 @@ # coding=utf-8 +from __future__ import absolute_import import os import requests import logging @@ -13,6 +14,7 @@ from list_subtitles import store_subtitles_movie, list_missing_subtitles_movies, from get_subtitle import movies_download_subtitles from database import TableMovies, wal_cleaning +import six def update_all_movies(): @@ -82,7 +84,7 @@ def update_movies(): if movie["path"] != None and movie['movieFile']['relativePath'] != None: try: - overview = unicode(movie['overview']) + overview = six.text_type(movie['overview']) except: overview = "" try: @@ -136,27 +138,27 @@ def update_movies(): audioCodec = None # Add movies in radarr to current movies list - current_movies_radarr.append(unicode(movie['tmdbId'])) + current_movies_radarr.append(six.text_type(movie['tmdbId'])) - if unicode(movie['tmdbId']) in current_movies_db_list: + if six.text_type(movie['tmdbId']) in current_movies_db_list: movies_to_update.append({'radarr_id': movie["id"], - 'title': unicode(movie["title"]), - 'path': unicode(movie["path"] + separator + movie['movieFile']['relativePath']), - 'tmdb_id': unicode(movie["tmdbId"]), - 'poster': unicode(poster), - 'fanart': unicode(fanart), - 'audio_language': unicode(profile_id_to_language(movie['qualityProfileId'], audio_profiles)), + 'title': six.text_type(movie["title"]), + 'path': six.text_type(movie["path"] + separator + movie['movieFile']['relativePath']), + 'tmdb_id': six.text_type(movie["tmdbId"]), + 'poster': six.text_type(poster), + 'fanart': six.text_type(fanart), + 'audio_language': six.text_type(profile_id_to_language(movie['qualityProfileId'], audio_profiles)), 'scene_name': sceneName, - 'monitored': unicode(bool(movie['monitored'])), - 'year': unicode(movie['year']), - 'sort_title': unicode(movie['sortTitle']), - 'alternative_titles': unicode(alternativeTitles), - 'format': unicode(format), - 'resolution': unicode(resolution), - 'video_codec': unicode(videoCodec), - 'audio_codec': unicode(audioCodec), - 'overview': unicode(overview), - 'imdb_id': unicode(imdbId)}) + 'monitored': six.text_type(bool(movie['monitored'])), + 'year': six.text_type(movie['year']), + 'sort_title': six.text_type(movie['sortTitle']), + 'alternative_titles': six.text_type(alternativeTitles), + 'format': six.text_type(format), + 'resolution': six.text_type(resolution), + 'video_codec': six.text_type(videoCodec), + 'audio_codec': six.text_type(audioCodec), + 'overview': six.text_type(overview), + 'imdb_id': six.text_type(imdbId)}) else: if movie_default_enabled is True: movies_to_add.append({'radarr_id': movie["id"], @@ -171,7 +173,7 @@ def update_movies(): 'fanart': fanart, 'audio_language': profile_id_to_language(movie['qualityProfileId'], audio_profiles), 'scene_name': sceneName, - 'monitored': unicode(bool(movie['monitored'])), + 'monitored': six.text_type(bool(movie['monitored'])), 'sort_title': movie['sortTitle'], 'year': movie['year'], 'alternative_titles': alternativeTitles, @@ -191,7 +193,7 @@ def update_movies(): 'fanart': fanart, 'audio_language': profile_id_to_language(movie['qualityProfileId'], audio_profiles), 'scene_name': sceneName, - 'monitored': unicode(bool(movie['monitored'])), + 'monitored': six.text_type(bool(movie['monitored'])), 'sort_title': movie['sortTitle'], 'year': movie['year'], 'alternative_titles': alternativeTitles, diff --git a/bazarr/get_providers.py b/bazarr/get_providers.py index 461a21615..5f4d84884 100644 --- a/bazarr/get_providers.py +++ b/bazarr/get_providers.py @@ -1,4 +1,5 @@ # coding=utf-8 +from __future__ import absolute_import import os import datetime import logging @@ -159,8 +160,8 @@ def provider_throttle(name, exception): def throttled_count(name): global throttle_count - if name in throttle_count.keys(): - if 'count' in throttle_count[name].keys(): + if name in list(throttle_count.keys()): + if 'count' in list(throttle_count[name].keys()): for key, value in throttle_count[name].items(): if key == 'count': value += 1 diff --git a/bazarr/get_series.py b/bazarr/get_series.py index b3ac34d28..a453f65ca 100644 --- a/bazarr/get_series.py +++ b/bazarr/get_series.py @@ -1,5 +1,7 @@ # coding=utf-8 +from __future__ import absolute_import +from __future__ import print_function import os import requests import logging @@ -12,6 +14,7 @@ from config import settings, url_sonarr from list_subtitles import list_missing_subtitles from database import TableShows from utils import get_sonarr_version +import six def update_series(): @@ -60,7 +63,7 @@ def update_series(): for i, show in enumerate(r.json(), 1): notifications.write(msg="Getting series data from Sonarr...", queue='get_series', item=i, length=seriesListLength) try: - overview = unicode(show['overview']) + overview = six.text_type(show['overview']) except: overview = "" try: @@ -82,17 +85,17 @@ def update_series(): current_shows_sonarr.append(show['tvdbId']) if show['tvdbId'] in current_shows_db_list: - series_to_update.append({'title': unicode(show["title"]), - 'path': unicode(show["path"]), + series_to_update.append({'title': six.text_type(show["title"]), + 'path': six.text_type(show["path"]), 'tvdb_id': int(show["tvdbId"]), 'sonarr_series_id': int(show["id"]), - 'overview': unicode(overview), - 'poster': unicode(poster), - 'fanart': unicode(fanart), - 'audio_language': unicode(profile_id_to_language((show['qualityProfileId'] if get_sonarr_version().startswith('2') else show['languageProfileId']), audio_profiles)), - 'sort_title': unicode(show['sortTitle']), - 'year': unicode(show['year']), - 'alternate_titles': unicode(alternateTitles)}) + 'overview': six.text_type(overview), + 'poster': six.text_type(poster), + 'fanart': six.text_type(fanart), + 'audio_language': six.text_type(profile_id_to_language((show['qualityProfileId'] if get_sonarr_version().startswith('2') else show['languageProfileId']), audio_profiles)), + 'sort_title': six.text_type(show['sortTitle']), + 'year': six.text_type(show['year']), + 'alternate_titles': six.text_type(alternateTitles)}) else: if serie_default_enabled is True: series_to_add.append({'title': show["title"], @@ -161,9 +164,9 @@ def update_series(): removed_series = list(set(current_shows_db_list) - set(current_shows_sonarr)) for series in removed_series: - print TableShows.delete().where( + print(TableShows.delete().where( TableShows.tvdb_id == series - ).execute() + ).execute()) logging.debug('BAZARR All series synced from Sonarr into database.') diff --git a/bazarr/get_subtitle.py b/bazarr/get_subtitle.py index a12fadd92..c2567ead4 100644 --- a/bazarr/get_subtitle.py +++ b/bazarr/get_subtitle.py @@ -1,12 +1,13 @@ # coding=utf-8 +from __future__ import absolute_import import os import sys import ast import logging import subprocess import time -import cPickle as pickle +import six.moves.cPickle as pickle import codecs import types import re @@ -37,6 +38,9 @@ from database import TableShows, TableEpisodes, TableMovies, TableHistory, Table from peewee import fn, JOIN from analytics import track_event +import six +from six.moves import range +from functools import reduce def get_video(path, title, sceneName, use_scenename, providers=None, media_type="movie"): @@ -91,11 +95,11 @@ def get_scores(video, media_type, min_score_movie_perc=60 * 100 / 120.0, min_sco """ max_score = 120.0 min_score = max_score * min_score_movie_perc / 100.0 - scores = subliminal_scores.movie_scores.keys() + scores = list(subliminal_scores.movie_scores.keys()) if media_type == "series": max_score = 360.0 min_score = max_score * min_score_series_perc / 100.0 - scores = subliminal_scores.episode_scores.keys() + scores = list(subliminal_scores.episode_scores.keys()) if video.is_special: min_score = max_score * min_score_special_ep / 100.0 @@ -119,7 +123,7 @@ def download_subtitle(path, language, hi, forced, providers, providers_auth, sce hi = "force non-HI" language_set = set() - if not isinstance(language, types.ListType): + if not isinstance(language, list): language = [language] if forced == "True": @@ -185,7 +189,7 @@ def download_subtitle(path, language, hi, forced, providers, providers_auth, sce saved_any = False if downloaded_subtitles: - for video, subtitles in downloaded_subtitles.iteritems(): + for video, subtitles in six.iteritems(downloaded_subtitles): if not subtitles: continue @@ -221,10 +225,10 @@ def download_subtitle(path, language, hi, forced, providers, providers_auth, sce else: action = "downloaded" if video.used_scene_name: - message = downloaded_language + is_forced_string + " subtitles " + action + " from " + downloaded_provider + " with a score of " + unicode( + message = downloaded_language + is_forced_string + " subtitles " + action + " from " + downloaded_provider + " with a score of " + six.text_type( round(subtitle.score * 100 / max_score, 2)) + "% using this scene name: " + sceneName else: - message = downloaded_language + is_forced_string + " subtitles " + action + " from " + downloaded_provider + " with a score of " + unicode( + message = downloaded_language + is_forced_string + " subtitles " + action + " from " + downloaded_provider + " with a score of " + six.text_type( round(subtitle.score * 100 / max_score, 2)) + "% using filename guessing." if use_postprocessing is True: @@ -444,7 +448,7 @@ def manual_download_subtitle(path, language, hi, forced, subtitle, provider, pro downloaded_path = saved_subtitle.storage_path logging.debug('BAZARR Subtitles file saved to disk: ' + downloaded_path) is_forced_string = " forced" if subtitle.language.forced else "" - message = downloaded_language + is_forced_string + " subtitles downloaded from " + downloaded_provider + " with a score of " + unicode( + message = downloaded_language + is_forced_string + " subtitles downloaded from " + downloaded_provider + " with a score of " + six.text_type( score) + "% using manual search." if use_postprocessing is True: @@ -749,7 +753,7 @@ def wanted_download_subtitles(path, l, count_episodes): for episode in episodes_details: attempt = episode.failed_attempts - if type(attempt) == unicode: + if type(attempt) == six.text_type: attempt = ast.literal_eval(attempt) for language in ast.literal_eval(episode.missing_subtitles): if attempt is None: @@ -762,7 +766,7 @@ def wanted_download_subtitles(path, l, count_episodes): TableEpisodes.update( { - TableEpisodes.failed_attempts: unicode(attempt) + TableEpisodes.failed_attempts: six.text_type(attempt) } ).where( TableEpisodes.sonarr_episode_id == episode.sonarr_episode_id @@ -818,7 +822,7 @@ def wanted_download_subtitles_movie(path, l, count_movies): for movie in movies_details: attempt = movie.failed_attempts - if type(attempt) == unicode: + if type(attempt) == six.text_type: attempt = ast.literal_eval(attempt) for language in ast.literal_eval(movie.missing_subtitles): if attempt is None: @@ -831,7 +835,7 @@ def wanted_download_subtitles_movie(path, l, count_movies): TableMovies.update( { - TableMovies.failed_attempts: unicode(attempt) + TableMovies.failed_attempts: six.text_type(attempt) } ).where( TableMovies.radarr_id == movie.radarr_id @@ -991,7 +995,7 @@ def refine_from_db(path, video): TableMovies.audio_codec, TableMovies.imdb_id ).where( - TableMovies.path == unicode(path_replace_reverse_movie(path)) + TableMovies.path == six.text_type(path_replace_reverse_movie(path)) ).first() if data: diff --git a/bazarr/helper.py b/bazarr/helper.py index 9519651bf..6cd409b5a 100644 --- a/bazarr/helper.py +++ b/bazarr/helper.py @@ -1,4 +1,5 @@ # coding=utf-8 +from __future__ import absolute_import import ast import os import re @@ -126,7 +127,7 @@ def force_unicode(s): :param s: string :return: unicode string """ - if not isinstance(s, types.UnicodeType): + if not isinstance(s, str): try: s = s.decode("utf-8") except UnicodeDecodeError: diff --git a/bazarr/init.py b/bazarr/init.py index 87ff2a9d7..a0cc711a5 100644 --- a/bazarr/init.py +++ b/bazarr/init.py @@ -1,12 +1,13 @@ # coding=utf-8 +from __future__ import absolute_import import os import logging import time import rarfile from cork import Cork -from ConfigParser2 import ConfigParser +from backports import configparser2 from config import settings from check_update import check_releases from get_args import args @@ -66,7 +67,7 @@ if not os.path.exists(os.path.join(args.config_dir, 'config', 'releases.txt')): config_file = os.path.normpath(os.path.join(args.config_dir, 'config', 'config.ini')) -cfg = ConfigParser() +cfg = configparser2.ConfigParser() def init_binaries(): diff --git a/bazarr/libs.py b/bazarr/libs.py index 37b8809ac..5cab7b473 100644 --- a/bazarr/libs.py +++ b/bazarr/libs.py @@ -1,5 +1,6 @@ # coding=utf-8 +from __future__ import absolute_import import os import sys diff --git a/bazarr/list_subtitles.py b/bazarr/list_subtitles.py index dcac64f4b..cc8432dcf 100644 --- a/bazarr/list_subtitles.py +++ b/bazarr/list_subtitles.py @@ -1,5 +1,6 @@ # coding=utf-8 +from __future__ import absolute_import import gc import os import babelfish @@ -24,6 +25,7 @@ from helper import path_replace, path_replace_movie, path_replace_reverse, \ from queueconfig import notifications from embedded_subs_reader import embedded_subs_reader +import six gc.enable() @@ -63,7 +65,7 @@ def store_subtitles(file): logging.exception("BAZARR unable to index external subtitles.") pass else: - for subtitle, language in subtitles.iteritems(): + for subtitle, language in six.iteritems(subtitles): subtitle_path = get_external_subtitles_path(file, subtitle) if str(os.path.splitext(subtitle)[0]).lower().endswith(tuple(brazilian_portuguese)): logging.debug("BAZARR external subtitles detected: " + "pb") @@ -155,7 +157,7 @@ def store_subtitles_movie(file): logging.exception("BAZARR unable to index external subtitles.") pass else: - for subtitle, language in subtitles.iteritems(): + for subtitle, language in six.iteritems(subtitles): if str(os.path.splitext(subtitle)[0]).lower().endswith(tuple(brazilian_portuguese)) is True: logging.debug("BAZARR external subtitles detected: " + "pb") actual_subtitles.append( diff --git a/bazarr/logger.py b/bazarr/logger.py index 90931270f..03334354f 100644 --- a/bazarr/logger.py +++ b/bazarr/logger.py @@ -1,5 +1,6 @@ # coding=utf-8 +from __future__ import absolute_import import os import logging import re @@ -9,6 +10,7 @@ import platform from logging.handlers import TimedRotatingFileHandler from get_args import args from config import settings +import six logger = logging.getLogger() @@ -107,10 +109,10 @@ class MyFilter(logging.Filter): class ArgsFilteringFilter(logging.Filter): def filter_args(self, record, func): - if isinstance(record.args, (types.ListType, types.TupleType)): + if isinstance(record.args, (list, tuple)): final_args = [] for arg in record.args: - if not isinstance(arg, basestring): + if not isinstance(arg, six.string_types): final_args.append(arg) continue @@ -118,7 +120,7 @@ class ArgsFilteringFilter(logging.Filter): record.args = type(record.args)(final_args) elif isinstance(record.args, dict): for key, arg in record.args.items(): - if not isinstance(arg, basestring): + if not isinstance(arg, six.string_types): continue record.args[key] = func(arg) diff --git a/bazarr/main.py b/bazarr/main.py index 33917be77..819c16bd6 100644 --- a/bazarr/main.py +++ b/bazarr/main.py @@ -1,5 +1,8 @@ # coding=utf-8 +import six +from six.moves import zip +from functools import reduce bazarr_version = '0.8.2' import gc @@ -12,7 +15,7 @@ import pretty import math import ast import hashlib -import urllib +import six.moves.urllib.request, six.moves.urllib.parse, six.moves.urllib.error import warnings import queueconfig import platform @@ -1575,12 +1578,12 @@ def save_settings(): settings_death_by_captcha_username = request.forms.get('settings_death_by_captcha_username') settings_death_by_captcha_password = request.forms.get('settings_death_by_captcha_password') - before = (unicode(settings.general.ip), int(settings.general.port), unicode(settings.general.base_url), - unicode(settings.general.path_mappings), unicode(settings.general.getboolean('use_sonarr')), - unicode(settings.general.getboolean('use_radarr')), unicode(settings.general.path_mappings_movie)) - after = (unicode(settings_general_ip), int(settings_general_port), unicode(settings_general_baseurl), - unicode(settings_general_pathmapping), unicode(settings_general_use_sonarr), - unicode(settings_general_use_radarr), unicode(settings_general_pathmapping_movie)) + before = (six.text_type(settings.general.ip), int(settings.general.port), six.text_type(settings.general.base_url), + six.text_type(settings.general.path_mappings), six.text_type(settings.general.getboolean('use_sonarr')), + six.text_type(settings.general.getboolean('use_radarr')), six.text_type(settings.general.path_mappings_movie)) + after = (six.text_type(settings_general_ip), int(settings_general_port), six.text_type(settings_general_baseurl), + six.text_type(settings_general_pathmapping), six.text_type(settings_general_use_sonarr), + six.text_type(settings_general_use_radarr), six.text_type(settings_general_pathmapping_movie)) settings.general.ip = text_type(settings_general_ip) settings.general.port = text_type(settings_general_port) @@ -1645,7 +1648,7 @@ def save_settings(): settings_proxy_password = request.forms.get('settings_proxy_password') settings_proxy_exclude = request.forms.get('settings_proxy_exclude') - before_proxy_password = (unicode(settings.proxy.type), unicode(settings.proxy.exclude)) + before_proxy_password = (six.text_type(settings.proxy.type), six.text_type(settings.proxy.exclude)) if before_proxy_password[0] != settings_proxy_type: configured() if before_proxy_password[1] == settings_proxy_password: @@ -2029,7 +2032,7 @@ def remove_subtitles(): history_log(0, sonarrSeriesId, sonarrEpisodeId, result) except OSError as e: logging.exception('BAZARR cannot delete subtitles file: ' + subtitlesPath) - store_subtitles(unicode(episodePath)) + store_subtitles(six.text_type(episodePath)) list_missing_subtitles(sonarrSeriesId) @@ -2048,7 +2051,7 @@ def remove_subtitles_movie(): history_log_movie(0, radarrId, result) except OSError as e: logging.exception('BAZARR cannot delete subtitles file: ' + subtitlesPath) - store_subtitles_movie(unicode(moviePath)) + store_subtitles_movie(six.text_type(moviePath)) list_missing_subtitles_movies(radarrId) @@ -2082,7 +2085,7 @@ def get_subtitle(): score = result[4] history_log(1, sonarrSeriesId, sonarrEpisodeId, message, path, language_code, provider, score) send_notifications(sonarrSeriesId, sonarrEpisodeId, message) - store_subtitles(unicode(episodePath)) + store_subtitles(six.text_type(episodePath)) list_missing_subtitles(sonarrSeriesId) redirect(ref) except OSError: @@ -2140,7 +2143,7 @@ def manual_get_subtitle(): score = result[4] history_log(2, sonarrSeriesId, sonarrEpisodeId, message, path, language_code, provider, score) send_notifications(sonarrSeriesId, sonarrEpisodeId, message) - store_subtitles(unicode(episodePath)) + store_subtitles(six.text_type(episodePath)) list_missing_subtitles(sonarrSeriesId) redirect(ref) except OSError: @@ -2184,7 +2187,7 @@ def perform_manual_upload_subtitle(): score = 360 history_log(4, sonarrSeriesId, sonarrEpisodeId, message, path, language_code, provider, score) send_notifications(sonarrSeriesId, sonarrEpisodeId, message) - store_subtitles(unicode(episodePath)) + store_subtitles(six.text_type(episodePath)) list_missing_subtitles(sonarrSeriesId) redirect(ref) @@ -2221,7 +2224,7 @@ def get_subtitle_movie(): score = result[4] history_log_movie(1, radarrId, message, path, language_code, provider, score) send_notifications_movie(radarrId, message) - store_subtitles_movie(unicode(moviePath)) + store_subtitles_movie(six.text_type(moviePath)) list_missing_subtitles_movies(radarrId) redirect(ref) except OSError: @@ -2277,7 +2280,7 @@ def manual_get_subtitle_movie(): score = result[4] history_log_movie(2, radarrId, message, path, language_code, provider, score) send_notifications_movie(radarrId, message) - store_subtitles_movie(unicode(moviePath)) + store_subtitles_movie(six.text_type(moviePath)) list_missing_subtitles_movies(radarrId) redirect(ref) except OSError: @@ -2320,7 +2323,7 @@ def perform_manual_upload_subtitle_movie(): score = 120 history_log_movie(4, radarrId, message, path, language_code, provider, score) send_notifications_movie(radarrId, message) - store_subtitles_movie(unicode(moviePath)) + store_subtitles_movie(six.text_type(moviePath)) list_missing_subtitles_movies(radarrId) redirect(ref) @@ -2421,7 +2424,7 @@ def api_history(): @route(base_url + 'test_url//', method='GET') @custom_auth_basic(check_credentials) def test_url(protocol, url): - url = urllib.unquote(url) + url = six.moves.urllib.parse.unquote(url) try: result = requests.get(protocol + "://" + url, allow_redirects=False, verify=False).json()['version'] except: @@ -2433,7 +2436,7 @@ def test_url(protocol, url): @route(base_url + 'test_notification//', method='GET') @custom_auth_basic(check_credentials) def test_notification(protocol, provider): - provider = urllib.unquote(provider) + provider = six.moves.urllib.parse.unquote(provider) apobj = apprise.Apprise() apobj.add(protocol + "://" + provider) diff --git a/bazarr/notifier.py b/bazarr/notifier.py index 9a0eea98f..3b4fbea85 100644 --- a/bazarr/notifier.py +++ b/bazarr/notifier.py @@ -1,5 +1,6 @@ # coding=utf-8 +from __future__ import absolute_import import apprise import os import logging diff --git a/bazarr/queueconfig.py b/bazarr/queueconfig.py index fd33c1850..5ba427d59 100644 --- a/bazarr/queueconfig.py +++ b/bazarr/queueconfig.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import from collections import deque import json diff --git a/bazarr/scheduler.py b/bazarr/scheduler.py index e7580ae2e..dee28bfbb 100644 --- a/bazarr/scheduler.py +++ b/bazarr/scheduler.py @@ -1,5 +1,6 @@ # coding=utf-8 +from __future__ import absolute_import from get_episodes import sync_episodes, update_all_episodes from get_movies import update_movies, update_all_movies from get_series import update_series diff --git a/bazarr/utils.py b/bazarr/utils.py index c3dde6c56..f86d35191 100644 --- a/bazarr/utils.py +++ b/bazarr/utils.py @@ -1,5 +1,6 @@ # coding=utf-8 +from __future__ import absolute_import import os import time import platform diff --git a/libs/ConfigParser2.py b/libs/ConfigParser2.py deleted file mode 100644 index 4ec642a5d..000000000 --- a/libs/ConfigParser2.py +++ /dev/null @@ -1,797 +0,0 @@ -"""Configuration file parser. - -A setup file consists of sections, lead by a "[section]" header, -and followed by "name: value" entries, with continuations and such in -the style of RFC 822. - -The option values can contain format strings which refer to other values in -the same section, or values in a special [DEFAULT] section. - -For example: - - something: %(dir)s/whatever - -would resolve the "%(dir)s" to the value of dir. All reference -expansions are done late, on demand. - -Intrinsic defaults can be specified by passing them into the -ConfigParser constructor as a dictionary. - -class: - -ConfigParser -- responsible for parsing a list of - configuration files, and managing the parsed database. - - methods: - - __init__(defaults=None) - create the parser and specify a dictionary of intrinsic defaults. The - keys must be strings, the values must be appropriate for %()s string - interpolation. Note that `__name__' is always an intrinsic default; - its value is the section's name. - - sections() - return all the configuration section names, sans DEFAULT - - has_section(section) - return whether the given section exists - - has_option(section, option) - return whether the given option exists in the given section - - options(section) - return list of configuration options for the named section - - read(filenames) - read and parse the list of named configuration files, given by - name. A single filename is also allowed. Non-existing files - are ignored. Return list of successfully read files. - - readfp(fp, filename=None) - read and parse one configuration file, given as a file object. - The filename defaults to fp.name; it is only used in error - messages (if fp has no `name' attribute, the string `' is used). - - get(section, option, raw=False, vars=None) - return a string value for the named option. All % interpolations are - expanded in the return values, based on the defaults passed into the - constructor and the DEFAULT section. Additional substitutions may be - provided using the `vars' argument, which must be a dictionary whose - contents override any pre-existing defaults. - - getint(section, options) - like get(), but convert value to an integer - - getfloat(section, options) - like get(), but convert value to a float - - getboolean(section, options) - like get(), but convert value to a boolean (currently case - insensitively defined as 0, false, no, off for False, and 1, true, - yes, on for True). Returns False or True. - - items(section, raw=False, vars=None) - return a list of tuples with (name, value) for each option - in the section. - - remove_section(section) - remove the given file section and all its options - - remove_option(section, option) - remove the given option from the given section - - set(section, option, value) - set the given option - - write(fp) - write the configuration state in .ini format -""" - -try: - from collections import OrderedDict as _default_dict -except ImportError: - # fallback for setup.py which hasn't yet built _collections - _default_dict = dict - -import re - -__all__ = ["NoSectionError", "DuplicateSectionError", "NoOptionError", - "InterpolationError", "InterpolationDepthError", - "InterpolationSyntaxError", "ParsingError", - "MissingSectionHeaderError", - "ConfigParser", "SafeConfigParser", "RawConfigParser", - "DEFAULTSECT", "MAX_INTERPOLATION_DEPTH"] - -DEFAULTSECT = "DEFAULT" - -MAX_INTERPOLATION_DEPTH = 10 - - - -# exception classes -class Error(Exception): - """Base class for ConfigParser exceptions.""" - - def _get_message(self): - """Getter for 'message'; needed only to override deprecation in - BaseException.""" - return self.__message - - def _set_message(self, value): - """Setter for 'message'; needed only to override deprecation in - BaseException.""" - self.__message = value - - # BaseException.message has been deprecated since Python 2.6. To prevent - # DeprecationWarning from popping up over this pre-existing attribute, use - # a new property that takes lookup precedence. - message = property(_get_message, _set_message) - - def __init__(self, msg=''): - self.message = msg - Exception.__init__(self, msg) - - def __repr__(self): - return self.message - - __str__ = __repr__ - -class NoSectionError(Error): - """Raised when no section matches a requested option.""" - - def __init__(self, section): - Error.__init__(self, 'No section: %r' % (section,)) - self.section = section - self.args = (section, ) - -class DuplicateSectionError(Error): - """Raised when a section is multiply-created.""" - - def __init__(self, section): - Error.__init__(self, "Section %r already exists" % section) - self.section = section - self.args = (section, ) - -class NoOptionError(Error): - """A requested option was not found.""" - - def __init__(self, option, section): - Error.__init__(self, "No option %r in section: %r" % - (option, section)) - self.option = option - self.section = section - self.args = (option, section) - -class InterpolationError(Error): - """Base class for interpolation-related exceptions.""" - - def __init__(self, option, section, msg): - Error.__init__(self, msg) - self.option = option - self.section = section - self.args = (option, section, msg) - -class InterpolationMissingOptionError(InterpolationError): - """A string substitution required a setting which was not available.""" - - def __init__(self, option, section, rawval, reference): - msg = ("Bad value substitution:\n" - "\tsection: [%s]\n" - "\toption : %s\n" - "\tkey : %s\n" - "\trawval : %s\n" - % (section, option, reference, rawval)) - InterpolationError.__init__(self, option, section, msg) - self.reference = reference - self.args = (option, section, rawval, reference) - -class InterpolationSyntaxError(InterpolationError): - """Raised when the source text into which substitutions are made - does not conform to the required syntax.""" - -class InterpolationDepthError(InterpolationError): - """Raised when substitutions are nested too deeply.""" - - def __init__(self, option, section, rawval): - msg = ("Value interpolation too deeply recursive:\n" - "\tsection: [%s]\n" - "\toption : %s\n" - "\trawval : %s\n" - % (section, option, rawval)) - InterpolationError.__init__(self, option, section, msg) - self.args = (option, section, rawval) - -class ParsingError(Error): - """Raised when a configuration file does not follow legal syntax.""" - - def __init__(self, filename): - Error.__init__(self, 'File contains parsing errors: %s' % filename) - self.filename = filename - self.errors = [] - self.args = (filename, ) - - def append(self, lineno, line): - self.errors.append((lineno, line)) - self.message += '\n\t[line %2d]: %s' % (lineno, line) - -class MissingSectionHeaderError(ParsingError): - """Raised when a key-value pair is found before any section header.""" - - def __init__(self, filename, lineno, line): - Error.__init__( - self, - 'File contains no section headers.\nfile: %s, line: %d\n%r' % - (filename, lineno, line)) - self.filename = filename - self.lineno = lineno - self.line = line - self.args = (filename, lineno, line) - - -class RawConfigParser: - def __init__(self, defaults=None, dict_type=_default_dict, - allow_no_value=False): - self._dict = dict_type - self._sections = self._dict() - self._defaults = self._dict() - if allow_no_value: - self._optcre = self.OPTCRE_NV - else: - self._optcre = self.OPTCRE - if defaults: - for key, value in defaults.items(): - self._defaults[self.optionxform(key)] = value - self.comment_store = None ## used for storing comments in ini - - - def defaults(self): - return self._defaults - - def sections(self): - """Return a list of section names, excluding [DEFAULT]""" - # self._sections will never have [DEFAULT] in it - return self._sections.keys() - - def add_section(self, section): - """Create a new section in the configuration. - - Raise DuplicateSectionError if a section by the specified name - already exists. Raise ValueError if name is DEFAULT or any of it's - case-insensitive variants. - """ - if section.lower() == "default": - raise ValueError, 'Invalid section name: %s' % section - - if section in self._sections: - raise DuplicateSectionError(section) - self._sections[section] = self._dict() - - def has_section(self, section): - """Indicate whether the named section is present in the configuration. - - The DEFAULT section is not acknowledged. - """ - return section in self._sections - - def options(self, section): - """Return a list of option names for the given section name.""" - try: - opts = self._sections[section].copy() - except KeyError: - raise NoSectionError(section) - opts.update(self._defaults) - if '__name__' in opts: - del opts['__name__'] - return opts.keys() - - def read(self, filenames): - """Read and parse a filename or a list of filenames. - - Files that cannot be opened are silently ignored; this is - designed so that you can specify a list of potential - configuration file locations (e.g. current directory, user's - home directory, systemwide directory), and all existing - configuration files in the list will be read. A single - filename may also be given. - - Return list of successfully read files. - """ - if isinstance(filenames, basestring): - filenames = [filenames] - read_ok = [] - for filename in filenames: - try: - fp = open(filename) - except IOError: - continue - self._read(fp, filename) - fp.close() - read_ok.append(filename) - return read_ok - - def readfp(self, fp, filename=None): - """Like read() but the argument must be a file-like object. - - The `fp' argument must have a `readline' method. Optional - second argument is the `filename', which if not given, is - taken from fp.name. If fp has no `name' attribute, `' is - used. - - """ - if filename is None: - try: - filename = fp.name - except AttributeError: - filename = '' - self._read(fp, filename) - - def get(self, section, option): - opt = self.optionxform(option) - if section not in self._sections: - if section != DEFAULTSECT: - raise NoSectionError(section) - if opt in self._defaults: - return self._defaults[opt] - else: - raise NoOptionError(option, section) - elif opt in self._sections[section]: - return self._sections[section][opt] - elif opt in self._defaults: - return self._defaults[opt] - else: - raise NoOptionError(option, section) - - def items(self, section): - try: - d2 = self._sections[section] - except KeyError: - if section != DEFAULTSECT: - raise NoSectionError(section) - d2 = self._dict() - d = self._defaults.copy() - d.update(d2) - if "__name__" in d: - del d["__name__"] - return d.items() - - def _get(self, section, conv, option): - return conv(self.get(section, option)) - - def getint(self, section, option): - return self._get(section, int, option) - - def getfloat(self, section, option): - return self._get(section, float, option) - - _boolean_states = {'1': True, 'yes': True, 'true': True, 'on': True, - '0': False, 'no': False, 'false': False, 'off': False} - - def getboolean(self, section, option): - v = self.get(section, option) - if v.lower() not in self._boolean_states: - raise ValueError, 'Not a boolean: %s' % v - return self._boolean_states[v.lower()] - - def optionxform(self, optionstr): - return optionstr.lower() - - def has_option(self, section, option): - """Check for the existence of a given option in a given section.""" - if not section or section == DEFAULTSECT: - option = self.optionxform(option) - return option in self._defaults - elif section not in self._sections: - return False - else: - option = self.optionxform(option) - return (option in self._sections[section] - or option in self._defaults) - - def set(self, section, option, value=None): - """Set an option.""" - if not section or section == DEFAULTSECT: - sectdict = self._defaults - else: - try: - sectdict = self._sections[section] - except KeyError: - raise NoSectionError(section) - sectdict[self.optionxform(option)] = value - - def write(self, fp): - """Write an .ini-format representation of the configuration state.""" - if self._defaults: - fp.write("[%s]\n" % DEFAULTSECT) - for (key, value) in self._defaults.items(): - fp.write("%s = %s\n" % (key, str(value).replace('\n', '\n\t'))) - fp.write("\n") - for section in self._sections: - fp.write("[%s]\n" % section) - for (key, value) in self._sections[section].items(): - if key == "__name__": - continue - if (value is not None) or (self._optcre == self.OPTCRE): - key = " = ".join((key, str(value).replace('\n', '\n\t'))) - fp.write("%s\n" % (key)) - fp.write("\n") - - def remove_option(self, section, option): - """Remove an option.""" - if not section or section == DEFAULTSECT: - sectdict = self._defaults - else: - try: - sectdict = self._sections[section] - except KeyError: - raise NoSectionError(section) - option = self.optionxform(option) - existed = option in sectdict - if existed: - del sectdict[option] - return existed - - def remove_section(self, section): - """Remove a file section.""" - existed = section in self._sections - if existed: - del self._sections[section] - return existed - - # - # Regular expressions for parsing section headers and options. - # - SECTCRE = re.compile( - r'\[' # [ - r'(?P
[^]]+)' # very permissive! - r'\]' # ] - ) - OPTCRE = re.compile( - r'(?P