diff --git a/.env b/.env new file mode 100644 index 00000000..a8471c01 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +PYTHONPATH=jellyfin_kodi diff --git a/.gitignore b/.gitignore index 9547020e..b344f958 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *.pyo +__pycache__/ __local__/ machine_guid /resources/media/Thumbs.db diff --git a/addon.xml b/addon.xml index b3ab49cc..557bd9bb 100644 --- a/addon.xml +++ b/addon.xml @@ -7,6 +7,8 @@ <import addon="xbmc.python" version="2.25.0"/> <import addon="script.module.requests" version="2.22.0"/> <import addon="script.module.dateutil" version="2.7.3"/> + <import addon="script.module.six" /> + <import addon="script.module.kodi-six" /> <import addon="script.module.addon.signals" version="0.0.1"/> </requires> <extension point="xbmc.python.pluginsource" diff --git a/context.py b/context.py index 8e165a68..513b4be4 100644 --- a/context.py +++ b/context.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################# @@ -6,13 +7,12 @@ import logging import os import sys -import xbmc -import xbmcaddon +from kodi_six import xbmc, xbmcaddon ################################################################################################# __addon__ = xbmcaddon.Addon(id='plugin.video.jellyfin') -__base__ = xbmc.translatePath(os.path.join(__addon__.getAddonInfo('path'), 'jellyfin_kodi')).decode('utf-8') +__base__ = xbmc.translatePath(os.path.join(__addon__.getAddonInfo('path'), 'jellyfin_kodi')) sys.path.insert(0, __base__) diff --git a/context_play.py b/context_play.py index 1bb53e7d..aa657e31 100644 --- a/context_play.py +++ b/context_play.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################# @@ -6,13 +7,12 @@ import logging import os import sys -import xbmc -import xbmcaddon +from kodi_six import xbmc, xbmcaddon ################################################################################################# __addon__ = xbmcaddon.Addon(id='plugin.video.jellyfin') -__base__ = xbmc.translatePath(os.path.join(__addon__.getAddonInfo('path'), 'jellyfin_kodi')).decode('utf-8') +__base__ = xbmc.translatePath(os.path.join(__addon__.getAddonInfo('path'), 'jellyfin_kodi')) sys.path.insert(0, __base__) diff --git a/default.py b/default.py index f2251509..f96d0970 100644 --- a/default.py +++ b/default.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################# @@ -6,13 +7,12 @@ import logging import os import sys -import xbmc -import xbmcaddon +from kodi_six import xbmc, xbmcaddon ################################################################################################# __addon__ = xbmcaddon.Addon(id='plugin.video.jellyfin') -__base__ = xbmc.translatePath(os.path.join(__addon__.getAddonInfo('path'), 'jellyfin_kodi')).decode('utf-8') +__base__ = xbmc.translatePath(os.path.join(__addon__.getAddonInfo('path'), 'jellyfin_kodi')) sys.path.insert(0, __base__) diff --git a/jellyfin_kodi/client.py b/jellyfin_kodi/client.py index 3af18e8a..de03fcdd 100644 --- a/jellyfin_kodi/client.py +++ b/jellyfin_kodi/client.py @@ -1,13 +1,12 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################# import logging import os -import xbmc -import xbmcaddon -import xbmcvfs +from kodi_six import xbmc, xbmcaddon, xbmcvfs from helper import translate, window, settings, addon_id, dialog from helper.utils import create_id @@ -63,7 +62,7 @@ def get_device_name(): Otherwise fallback to the Kodi device name. ''' if not settings('deviceNameOpt.bool'): - device_name = xbmc.getInfoLabel('System.FriendlyName').decode('utf-8') + device_name = xbmc.getInfoLabel('System.FriendlyName') else: device_name = settings('deviceName') device_name = device_name.replace("\"", "_") @@ -86,7 +85,7 @@ def get_device_id(reset=False): if client_id: return client_id - directory = xbmc.translatePath('special://profile/addon_data/plugin.video.jellyfin/').decode('utf-8') + directory = xbmc.translatePath('special://profile/addon_data/plugin.video.jellyfin/') if not xbmcvfs.exists(directory): xbmcvfs.mkdir(directory) @@ -98,7 +97,7 @@ def get_device_id(reset=False): if not client_id or reset: LOG.info("Generating a new GUID.") - client_id = str("%012X" % create_id()) + client_id = str(create_id()) file_guid = xbmcvfs.File(jellyfin_guid, 'w') file_guid.write(client_id) diff --git a/jellyfin_kodi/connect.py b/jellyfin_kodi/connect.py index 9fa86f4b..16694fa3 100644 --- a/jellyfin_kodi/connect.py +++ b/jellyfin_kodi/connect.py @@ -1,11 +1,11 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################## import logging -import xbmc -import xbmcaddon +from kodi_six import xbmc, xbmcaddon import client from database import get_credentials, save_credentials diff --git a/jellyfin_kodi/database/__init__.py b/jellyfin_kodi/database/__init__.py index 44423bf6..844d38a4 100644 --- a/jellyfin_kodi/database/__init__.py +++ b/jellyfin_kodi/database/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- - +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################# import datetime @@ -8,10 +8,10 @@ import json import os import sqlite3 -import xbmc -import xbmcvfs +from kodi_six import xbmc, xbmcvfs +from six import text_type -import jellyfin_db +from database import jellyfin_db from helper import translate, settings, window, dialog from objects import obj @@ -22,9 +22,6 @@ LOG = logging.getLogger("JELLYFIN." + __name__) ################################################################################################# -UNICODE = type(u"") - - class Database(object): ''' This should be called like a context. @@ -76,7 +73,7 @@ class Database(object): def _get_database(self, path, silent=False): - path = xbmc.translatePath(path).decode('utf-8') + path = xbmc.translatePath(path) if not silent: @@ -104,7 +101,7 @@ class Database(object): xbmc.executebuiltin('UpdateLibrary(video)') xbmc.sleep(200) - databases = xbmc.translatePath("special://database/").decode('utf-8') + databases = xbmc.translatePath("special://database/") types = { 'video': "MyVideos", 'music': "MyMusic", @@ -118,19 +115,19 @@ class Database(object): if (file.startswith(database) and not file.endswith('-wal') and not file.endswith('-shm') and not file.endswith('db-journal')): - st = xbmcvfs.Stat(databases + file.decode('utf-8')) + st = xbmcvfs.Stat(databases + file) modified_int = st.st_mtime() - LOG.debug("Database detected: %s time: %s", file.decode('utf-8'), modified_int) + LOG.debug("Database detected: %s time: %s", file, modified_int) if modified_int > modified['time']: modified['time'] = modified_int - modified['file'] = file.decode('utf-8') + modified['file'] = file LOG.info("Discovered database: %s", modified) self.discovered_file = modified['file'] - return xbmc.translatePath("special://database/%s" % modified['file']).decode('utf-8') + return xbmc.translatePath("special://database/%s" % modified['file']) def _sql(self, file): @@ -251,7 +248,7 @@ def reset(): if dialog("yesno", heading="{jellyfin}", line1=translate(33086)): reset_artwork() - addon_data = xbmc.translatePath("special://profile/addon_data/plugin.video.jellyfin/").decode('utf-8') + addon_data = xbmc.translatePath("special://profile/addon_data/plugin.video.jellyfin/") if dialog("yesno", heading="{jellyfin}", line1=translate(33087)): @@ -317,17 +314,17 @@ def reset_artwork(): ''' Remove all existing texture. ''' - thumbnails = xbmc.translatePath('special://thumbnails/').decode('utf-8') + thumbnails = xbmc.translatePath('special://thumbnails/') if xbmcvfs.exists(thumbnails): dirs, ignore = xbmcvfs.listdir(thumbnails) for directory in dirs: - ignore, thumbs = xbmcvfs.listdir(os.path.join(thumbnails, directory.decode('utf-8'))) + ignore, thumbs = xbmcvfs.listdir(os.path.join(thumbnails, directory)) for thumb in thumbs: LOG.debug("DELETE thumbnail %s", thumb) - xbmcvfs.delete(os.path.join(thumbnails, directory.decode('utf-8'), thumb.decode('utf-8'))) + xbmcvfs.delete(os.path.join(thumbnails, directory, thumb)) with Database('texture') as texdb: texdb.cursor.execute("SELECT tbl_name FROM sqlite_master WHERE type='table'") @@ -343,7 +340,7 @@ def reset_artwork(): def get_sync(): - path = xbmc.translatePath("special://profile/addon_data/plugin.video.jellyfin/").decode('utf-8') + path = xbmc.translatePath("special://profile/addon_data/plugin.video.jellyfin/") if not xbmcvfs.exists(path): xbmcvfs.mkdirs(path) @@ -364,7 +361,7 @@ def get_sync(): def save_sync(sync): - path = xbmc.translatePath("special://profile/addon_data/plugin.video.jellyfin/").decode('utf-8') + path = xbmc.translatePath("special://profile/addon_data/plugin.video.jellyfin/") if not xbmcvfs.exists(path): xbmcvfs.mkdirs(path) @@ -373,14 +370,14 @@ def save_sync(sync): with open(os.path.join(path, 'sync.json'), 'wb') as outfile: data = json.dumps(sync, sort_keys=True, indent=4, ensure_ascii=False) - if isinstance(data, UNICODE): + if isinstance(data, text_type): data = data.encode('utf-8') outfile.write(data) def get_credentials(): - path = xbmc.translatePath("special://profile/addon_data/plugin.video.jellyfin/").decode('utf-8') + path = xbmc.translatePath("special://profile/addon_data/plugin.video.jellyfin/") if not xbmcvfs.exists(path): xbmcvfs.mkdirs(path) @@ -424,14 +421,14 @@ def get_credentials(): def save_credentials(credentials): credentials = credentials or {} - path = xbmc.translatePath("special://profile/addon_data/plugin.video.jellyfin/").decode('utf-8') + path = xbmc.translatePath("special://profile/addon_data/plugin.video.jellyfin/") if not xbmcvfs.exists(path): xbmcvfs.mkdirs(path) try: with open(os.path.join(path, 'data.json'), 'wb') as outfile: data = json.dumps(credentials, sort_keys=True, indent=4, ensure_ascii=False) - if isinstance(data, UNICODE): + if isinstance(data, text_type): data = data.encode('utf-8') outfile.write(data) except Exception: diff --git a/jellyfin_kodi/database/jellyfin_db.py b/jellyfin_kodi/database/jellyfin_db.py index ca9cb50b..3367ab51 100644 --- a/jellyfin_kodi/database/jellyfin_db.py +++ b/jellyfin_kodi/database/jellyfin_db.py @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- - +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################# import logging -import queries as QU +from database import queries as QU ################################################################################################## diff --git a/jellyfin_kodi/database/queries.py b/jellyfin_kodi/database/queries.py index 0efc1c75..66582caf 100644 --- a/jellyfin_kodi/database/queries.py +++ b/jellyfin_kodi/database/queries.py @@ -1,3 +1,4 @@ +from __future__ import division, absolute_import, print_function, unicode_literals get_item = """ SELECT kodi_id, kodi_fileid, kodi_pathid, parent_id, media_type, diff --git a/jellyfin_kodi/dialogs/__init__.py b/jellyfin_kodi/dialogs/__init__.py index 5b9b227f..94a8b89c 100644 --- a/jellyfin_kodi/dialogs/__init__.py +++ b/jellyfin_kodi/dialogs/__init__.py @@ -1,4 +1,6 @@ -from serverconnect import ServerConnect -from usersconnect import UsersConnect -from loginmanual import LoginManual -from servermanual import ServerManual +from __future__ import division, absolute_import, print_function, unicode_literals + +from .serverconnect import ServerConnect +from .usersconnect import UsersConnect +from .loginmanual import LoginManual +from .servermanual import ServerManual diff --git a/jellyfin_kodi/dialogs/context.py b/jellyfin_kodi/dialogs/context.py index 63ba0052..454f9275 100644 --- a/jellyfin_kodi/dialogs/context.py +++ b/jellyfin_kodi/dialogs/context.py @@ -1,12 +1,12 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################## import logging import os -import xbmcgui -import xbmcaddon +from kodi_six import xbmcgui, xbmcaddon from helper import window, addon_id diff --git a/jellyfin_kodi/dialogs/loginmanual.py b/jellyfin_kodi/dialogs/loginmanual.py index 3a07c809..c93326d1 100644 --- a/jellyfin_kodi/dialogs/loginmanual.py +++ b/jellyfin_kodi/dialogs/loginmanual.py @@ -1,12 +1,13 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################## import logging import os -import xbmcgui -import xbmcaddon +from six import iteritems +from kodi_six import xbmcgui, xbmcaddon from helper import translate, addon_id @@ -36,7 +37,7 @@ class LoginManual(xbmcgui.WindowXMLDialog): def set_args(self, **kwargs): # connect_manager, user_image, servers - for key, value in kwargs.iteritems(): + for key, value in iteritems(kwargs): setattr(self, key, value) def is_logged_in(self): diff --git a/jellyfin_kodi/dialogs/resume.py b/jellyfin_kodi/dialogs/resume.py index ef10f353..716cda4e 100644 --- a/jellyfin_kodi/dialogs/resume.py +++ b/jellyfin_kodi/dialogs/resume.py @@ -1,11 +1,11 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################## import logging -import xbmc -import xbmcgui +from kodi_six import xbmc, xbmcgui ################################################################################################## diff --git a/jellyfin_kodi/dialogs/serverconnect.py b/jellyfin_kodi/dialogs/serverconnect.py index ef7cb133..101dcd4e 100644 --- a/jellyfin_kodi/dialogs/serverconnect.py +++ b/jellyfin_kodi/dialogs/serverconnect.py @@ -1,11 +1,12 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################## import logging -import xbmc -import xbmcgui +from six import iteritems +from kodi_six import xbmc, xbmcgui from helper import translate from jellyfin.connection_manager import CONNECTION_STATE @@ -44,7 +45,7 @@ class ServerConnect(xbmcgui.WindowXMLDialog): def set_args(self, **kwargs): # connect_manager, user_image, servers - for key, value in kwargs.iteritems(): + for key, value in iteritems(kwargs): setattr(self, key, value) def is_server_selected(self): diff --git a/jellyfin_kodi/dialogs/servermanual.py b/jellyfin_kodi/dialogs/servermanual.py index 2e06e952..c1970fa5 100644 --- a/jellyfin_kodi/dialogs/servermanual.py +++ b/jellyfin_kodi/dialogs/servermanual.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################## @@ -6,8 +7,8 @@ import logging import os import re -import xbmcgui -import xbmcaddon +from six import iteritems +from kodi_six import xbmcgui, xbmcaddon from helper import translate, addon_id from jellyfin.connection_manager import CONNECTION_STATE @@ -28,7 +29,7 @@ ERROR = { } # https://stackoverflow.com/a/17871737/1035647 -_IPV6_PATTERN = "^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$" +_IPV6_PATTERN = r"^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$" _IPV6_RE = re.compile(_IPV6_PATTERN) ################################################################################################## @@ -44,7 +45,7 @@ class ServerManual(xbmcgui.WindowXMLDialog): def set_args(self, **kwargs): # connect_manager, user_image, servers, jellyfin_connect - for key, value in kwargs.iteritems(): + for key, value in iteritems(kwargs): setattr(self, key, value) def is_connected(self): diff --git a/jellyfin_kodi/dialogs/usersconnect.py b/jellyfin_kodi/dialogs/usersconnect.py index 4cf02b62..023f85be 100644 --- a/jellyfin_kodi/dialogs/usersconnect.py +++ b/jellyfin_kodi/dialogs/usersconnect.py @@ -1,11 +1,12 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################## import logging -import xbmc -import xbmcgui +from six import iteritems +from kodi_six import xbmc, xbmcgui ################################################################################################## @@ -34,7 +35,7 @@ class UsersConnect(xbmcgui.WindowXMLDialog): def set_args(self, **kwargs): # connect_manager, user_image, servers - for key, value in kwargs.iteritems(): + for key, value in iteritems(kwargs): setattr(self, key, value) def is_user_selected(self): diff --git a/jellyfin_kodi/downloader.py b/jellyfin_kodi/downloader.py index 54ace2ef..2a94c698 100644 --- a/jellyfin_kodi/downloader.py +++ b/jellyfin_kodi/downloader.py @@ -1,12 +1,14 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################# import logging -import Queue import threading -import xbmc +from six.moves import queue as Queue + +from kodi_six import xbmc import requests from helper import settings, stop, event, window, create_id from jellyfin import Jellyfin diff --git a/jellyfin_kodi/entrypoint/__init__.py b/jellyfin_kodi/entrypoint/__init__.py index a55c913d..23cc4476 100644 --- a/jellyfin_kodi/entrypoint/__init__.py +++ b/jellyfin_kodi/entrypoint/__init__.py @@ -1,11 +1,11 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################# import logging -import xbmc -import xbmcvfs +from kodi_six import xbmc, xbmcvfs from helper import loghandler from jellyfin import Jellyfin diff --git a/jellyfin_kodi/entrypoint/context.py b/jellyfin_kodi/entrypoint/context.py index 337ebf36..01b5f801 100644 --- a/jellyfin_kodi/entrypoint/context.py +++ b/jellyfin_kodi/entrypoint/context.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################# @@ -6,8 +7,7 @@ import json import logging import sys -import xbmc -import xbmcaddon +from kodi_six import xbmc, xbmcaddon import database from dialogs import context @@ -67,7 +67,7 @@ class Context(object): elif self.select_menu(): self.action_menu() - if self._selected_option.decode('utf-8') in (OPTIONS['Delete'], OPTIONS['AddFav'], OPTIONS['RemoveFav']): + if self._selected_option in (OPTIONS['Delete'], OPTIONS['AddFav'], OPTIONS['RemoveFav']): xbmc.sleep(500) xbmc.executebuiltin('Container.Refresh') @@ -91,7 +91,7 @@ class Context(object): else: LOG.info("media is unknown") - return media.decode('utf-8') + return media def get_item_id(self): @@ -140,7 +140,7 @@ class Context(object): def action_menu(self): - selected = self._selected_option.decode('utf-8') + selected = self._selected_option if selected == OPTIONS['Refresh']: TheVoid('RefreshItem', {'ServerId': self.server, 'Id': self.item['Id']}) diff --git a/jellyfin_kodi/entrypoint/default.py b/jellyfin_kodi/entrypoint/default.py index 03e77ae2..ba8a7573 100644 --- a/jellyfin_kodi/entrypoint/default.py +++ b/jellyfin_kodi/entrypoint/default.py @@ -1,19 +1,15 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################# import json import logging import sys -import urlparse -import urllib import os -import xbmc -import xbmcvfs -import xbmcgui -import xbmcplugin -import xbmcaddon +from six.moves.urllib.parse import parse_qsl, urlencode +from kodi_six import xbmc, xbmcvfs, xbmcgui, xbmcplugin, xbmcaddon import client from database import reset, get_sync, Database, jellyfin_db, get_credentials @@ -39,7 +35,7 @@ class Events(object): path = sys.argv[2] try: - params = dict(urlparse.parse_qsl(path[1:])) + params = dict(parse_qsl(path[1:])) except Exception: params = {} @@ -146,7 +142,7 @@ def listing(): context = [] if view_id and node in ('movies', 'tvshows', 'musicvideos', 'music', 'mixed') and view_id not in whitelist: - label = "%s %s" % (label.decode('utf-8'), translate(33166)) + label = "%s %s" % (label, translate(33166)) context.append((translate(33123), "RunPlugin(plugin://plugin.video.jellyfin/?mode=synclib&id=%s)" % view_id)) if view_id and node in ('movies', 'tvshows', 'musicvideos', 'music') and view_id in whitelist: @@ -338,7 +334,7 @@ def browse(media, view_id=None, folder=None, server_id=None): 'folder': item['Id'], 'server': server_id } - path = "%s?%s" % ("plugin://plugin.video.jellyfin/", urllib.urlencode(params)) + path = "%s?%s" % ("plugin://plugin.video.jellyfin/", urlencode(params)) context = [] if item['Type'] in ('Series', 'Season', 'Playlist'): @@ -361,7 +357,7 @@ def browse(media, view_id=None, folder=None, server_id=None): 'folder': 'genres-%s' % item['Id'], 'server': server_id } - path = "%s?%s" % ("plugin://plugin.video.jellyfin/", urllib.urlencode(params)) + path = "%s?%s" % ("plugin://plugin.video.jellyfin/", urlencode(params)) list_li.append((path, li, True)) else: @@ -371,7 +367,7 @@ def browse(media, view_id=None, folder=None, server_id=None): 'mode': "play", 'server': server_id } - path = "%s?%s" % ("plugin://plugin.video.jellyfin/", urllib.urlencode(params)) + path = "%s?%s" % ("plugin://plugin.video.jellyfin/", urlencode(params)) li.setProperty('path', path) context = [(translate(13412), "RunPlugin(plugin://plugin.video.jellyfin/?mode=playlist&id=%s&server=%s)" % (item['Id'], server_id))] @@ -415,7 +411,7 @@ def browse_subfolders(media, view_id, server_id=None): 'folder': view_id if node[0] == 'all' else node[0], 'server': server_id } - path = "%s?%s" % ("plugin://plugin.video.jellyfin/", urllib.urlencode(params)) + path = "%s?%s" % ("plugin://plugin.video.jellyfin/", urlencode(params)) directory(node[1] or view['Name'], path) xbmcplugin.setContent(int(sys.argv[1]), 'files') @@ -440,7 +436,7 @@ def browse_letters(media, view_id, server_id=None): 'folder': 'firstletter-%s' % node, 'server': server_id } - path = "%s?%s" % ("plugin://plugin.video.jellyfin/", urllib.urlencode(params)) + path = "%s?%s" % ("plugin://plugin.video.jellyfin/", urlencode(params)) directory(node, path) xbmcplugin.setContent(int(sys.argv[1]), 'files') @@ -497,7 +493,7 @@ def get_fanart(item_id, path, server_id=None): LOG.info("[ extra fanart ] %s", item_id) objects = Objects() list_li = [] - directory = xbmc.translatePath("special://thumbnails/jellyfin/%s/" % item_id).decode('utf-8') + directory = xbmc.translatePath("special://thumbnails/jellyfin/%s/" % item_id) server = TheVoid('GetServerAddress', {'ServerId': server_id}).get() if not xbmcvfs.exists(directory): @@ -520,7 +516,7 @@ def get_fanart(item_id, path, server_id=None): dirs, files = xbmcvfs.listdir(directory) for file in files: - fanart = os.path.join(directory, file.decode('utf-8')) + fanart = os.path.join(directory, file) li = xbmcgui.ListItem(file, path=fanart) list_li.append((fanart, li, False)) @@ -773,7 +769,7 @@ def get_themes(): from helper.playutils import PlayUtils from helper.xmls import tvtunes_nfo - library = xbmc.translatePath("special://profile/addon_data/plugin.video.jellyfin/library").decode('utf-8') + library = xbmc.translatePath("special://profile/addon_data/plugin.video.jellyfin/library") play = settings('useDirectPaths') == "1" if not xbmcvfs.exists(library + '/'): @@ -803,14 +799,14 @@ def get_themes(): for item in result['Items']: - folder = normalize_string(item['Name'].encode('utf-8')) + folder = normalize_string(item['Name']) items[item['Id']] = folder result = TheVoid('GetThemes', {'Type': "Song", 'Id': view}).get() for item in result['Items']: - folder = normalize_string(item['Name'].encode('utf-8')) + folder = normalize_string(item['Name']) items[item['Id']] = folder for item in items: @@ -828,9 +824,9 @@ def get_themes(): putils = PlayUtils(theme, False, None, server, token) if play: - paths.append(putils.direct_play(theme['MediaSources'][0]).encode('utf-8')) + paths.append(putils.direct_play(theme['MediaSources'][0])) else: - paths.append(putils.direct_url(theme['MediaSources'][0]).encode('utf-8')) + paths.append(putils.direct_url(theme['MediaSources'][0])) tvtunes_nfo(nfo_file, paths) @@ -868,7 +864,7 @@ def backup(): delete_folder(backup) - addon_data = xbmc.translatePath("special://profile/addon_data/plugin.video.jellyfin").decode('utf-8') + addon_data = xbmc.translatePath("special://profile/addon_data/plugin.video.jellyfin") destination_data = os.path.join(backup, "addon_data", "plugin.video.jellyfin") destination_databases = os.path.join(backup, "Database") @@ -883,18 +879,18 @@ def backup(): databases = Objects().objects - db = xbmc.translatePath(databases['jellyfin']).decode('utf-8') + db = xbmc.translatePath(databases['jellyfin']) xbmcvfs.copy(db, os.path.join(destination_databases, db.rsplit('\\', 1)[1])) LOG.info("copied jellyfin.db") - db = xbmc.translatePath(databases['video']).decode('utf-8') + db = xbmc.translatePath(databases['video']) filename = db.rsplit('\\', 1)[1] xbmcvfs.copy(db, os.path.join(destination_databases, filename)) LOG.info("copied %s", filename) if settings('enableMusic.bool'): - db = xbmc.translatePath(databases['music']).decode('utf-8') + db = xbmc.translatePath(databases['music']) filename = db.rsplit('\\', 1)[1] xbmcvfs.copy(db, os.path.join(destination_databases, filename)) LOG.info("copied %s", filename) diff --git a/jellyfin_kodi/entrypoint/service.py b/jellyfin_kodi/entrypoint/service.py index 58fb8ef0..b3b0ed2b 100644 --- a/jellyfin_kodi/entrypoint/service.py +++ b/jellyfin_kodi/entrypoint/service.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################# @@ -9,8 +10,8 @@ from datetime import datetime # Workaround for threads using datetime: _striptime is locked import _strptime # noqa:F401 -import xbmc -import xbmcgui +from kodi_six import xbmc, xbmcgui +from six.moves import reload_module as reload import objects import connect @@ -181,8 +182,8 @@ class Service(xbmc.Monitor): if settings('connectMsg.bool'): - users = [user for user in (settings('additionalUsers') or "").decode('utf-8').split(',') if user] - users.insert(0, settings('username').decode('utf-8')) + users = [user for user in (settings('additionalUsers') or "").split(',') if user] + users.insert(0, settings('username')) dialog("notification", heading="{jellyfin}", message="%s %s" % (translate(33000), ", ".join(users)), icon="{jellyfin}", time=1500, sound=False) diff --git a/jellyfin_kodi/full_sync.py b/jellyfin_kodi/full_sync.py index 5138dd00..a3226a65 100644 --- a/jellyfin_kodi/full_sync.py +++ b/jellyfin_kodi/full_sync.py @@ -1,11 +1,12 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################## import datetime import logging -import xbmc +from kodi_six import xbmc import downloader as server import helper.xmls as xmls diff --git a/jellyfin_kodi/helper/__init__.py b/jellyfin_kodi/helper/__init__.py index a9db000e..77d13ad1 100644 --- a/jellyfin_kodi/helper/__init__.py +++ b/jellyfin_kodi/helper/__init__.py @@ -1,27 +1,29 @@ -from translate import translate -from exceptions import LibraryException +from __future__ import division, absolute_import, print_function, unicode_literals -from utils import addon_id -from utils import window -from utils import settings -from utils import kodi_version -from utils import dialog -from utils import find -from utils import event -from utils import validate -from utils import values -from utils import JSONRPC -from utils import indent -from utils import write_xml -from utils import compare_version -from utils import unzip -from utils import create_id -from utils import convert_to_local as Local -from utils import has_attribute +from .translate import translate +from .exceptions import LibraryException -from wrapper import progress -from wrapper import catch -from wrapper import silent_catch -from wrapper import stop -from wrapper import jellyfin_item -from wrapper import library_check +from .utils import addon_id +from .utils import window +from .utils import settings +from .utils import kodi_version +from .utils import dialog +from .utils import find +from .utils import event +from .utils import validate +from .utils import values +from .utils import JSONRPC +from .utils import indent +from .utils import write_xml +from .utils import compare_version +from .utils import unzip +from .utils import create_id +from .utils import convert_to_local as Local +from .utils import has_attribute + +from .wrapper import progress +from .wrapper import catch +from .wrapper import silent_catch +from .wrapper import stop +from .wrapper import jellyfin_item +from .wrapper import library_check diff --git a/jellyfin_kodi/helper/api.py b/jellyfin_kodi/helper/api.py index e00acc00..2a175455 100644 --- a/jellyfin_kodi/helper/api.py +++ b/jellyfin_kodi/helper/api.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################## diff --git a/jellyfin_kodi/helper/exceptions.py b/jellyfin_kodi/helper/exceptions.py index c2ed4685..c7ca2291 100644 --- a/jellyfin_kodi/helper/exceptions.py +++ b/jellyfin_kodi/helper/exceptions.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################# diff --git a/jellyfin_kodi/helper/loghandler.py b/jellyfin_kodi/helper/loghandler.py index 8fe0ea17..c067a960 100644 --- a/jellyfin_kodi/helper/loghandler.py +++ b/jellyfin_kodi/helper/loghandler.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals from __future__ import absolute_import from __future__ import print_function @@ -9,8 +10,7 @@ import os import logging import traceback -import xbmc -import xbmcaddon +from kodi_six import xbmc, xbmcaddon import database from . import window, settings @@ -18,7 +18,7 @@ from . import window, settings ################################################################################################## __addon__ = xbmcaddon.Addon(id='plugin.video.jellyfin') -__pluginpath__ = xbmc.translatePath(__addon__.getAddonInfo('path').decode('utf-8')) +__pluginpath__ = xbmc.translatePath(__addon__.getAddonInfo('path')) ################################################################################################## @@ -65,15 +65,15 @@ class LogHandler(logging.StreamHandler): if self.mask_info: for server in self.sensitive['Server']: - string = string.replace(server.encode('utf-8') or "{server}", "{jellyfin-server}") + string = string.replace(server or "{server}", "{jellyfin-server}") for token in self.sensitive['Token']: - string = string.replace(token.encode('utf-8') or "{token}", "{jellyfin-token}") + string = string.replace(token or "{token}", "{jellyfin-token}") try: xbmc.log(string, level=xbmc.LOGNOTICE) except UnicodeEncodeError: - xbmc.log(string.encode('utf-8'), level=xbmc.LOGNOTICE) + xbmc.log(string, level=xbmc.LOGNOTICE) @classmethod def _get_log_level(cls, level): diff --git a/jellyfin_kodi/helper/playutils.py b/jellyfin_kodi/helper/playutils.py index 82b77a68..d4fbaec8 100644 --- a/jellyfin_kodi/helper/playutils.py +++ b/jellyfin_kodi/helper/playutils.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################# @@ -7,15 +8,13 @@ import os from uuid import uuid4 import collections -import xbmc -import xbmcvfs +from kodi_six import xbmc, xbmcvfs -import api import client import requests from downloader import TheVoid -from . import translate, settings, window, dialog +from . import translate, settings, window, dialog, api ################################################################################################# @@ -212,7 +211,7 @@ class PlayUtils(object): self.item['PlaybackInfo'].update(self.info) API = api.API(self.item, self.info['ServerAddress']) - window('jellyfinfilename', value=API.get_file_path(source.get('Path')).encode('utf-8')) + window('jellyfinfilename', value=API.get_file_path(source.get('Path'))) def live_stream(self, source): @@ -484,7 +483,7 @@ class PlayUtils(object): LOG.info("[ subtitles/%s ] %s", index, url) if 'Language' in stream: - filename = "Stream.%s.%s" % (stream['Language'].encode('utf-8'), stream['Codec'].encode('utf-8')) + filename = "Stream.%s.%s" % (stream['Language'], stream['Codec']) try: subs.append(self.download_external_subs(url, filename)) @@ -506,7 +505,7 @@ class PlayUtils(object): ''' Download external subtitles to temp folder to be able to have proper names to streams. ''' - temp = xbmc.translatePath("special://profile/addon_data/plugin.video.jellyfin/temp/").decode('utf-8') + temp = xbmc.translatePath("special://profile/addon_data/plugin.video.jellyfin/temp/") if not xbmcvfs.exists(temp): xbmcvfs.mkdir(temp) diff --git a/jellyfin_kodi/helper/translate.py b/jellyfin_kodi/helper/translate.py index 8059d377..d140b149 100644 --- a/jellyfin_kodi/helper/translate.py +++ b/jellyfin_kodi/helper/translate.py @@ -1,11 +1,11 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################## import logging -import xbmc -import xbmcaddon +from kodi_six import xbmc, xbmcaddon ################################################################################################## diff --git a/jellyfin_kodi/helper/utils.py b/jellyfin_kodi/helper/utils.py index f6ca9320..67d4ecd2 100644 --- a/jellyfin_kodi/helper/utils.py +++ b/jellyfin_kodi/helper/utils.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################# @@ -8,18 +9,16 @@ import logging import os import re import unicodedata -import urllib from uuid import uuid4 from distutils.version import LooseVersion from dateutil import tz, parser +from six import text_type, string_types, iteritems +from six.moves.urllib.parse import quote_plus -import xbmc -import xbmcaddon -import xbmcgui -import xbmcvfs +from kodi_six import xbmc, xbmcaddon, xbmcgui, xbmcvfs -from translate import translate +from .translate import translate ################################################################################################# @@ -122,7 +121,7 @@ def find(dict, item): if item in dict: return dict[item] - for key, value in sorted(dict.iteritems(), key=lambda (k, v): (v, k)): + for key, value in sorted(iteritems(dict), key=lambda kv: (kv[1], kv[0])): if re.match(key, item, re.I): return dict[key] @@ -245,7 +244,7 @@ def validate(path): if window('jellyfin_pathverified.bool'): return True - path = path if os.path.supports_unicode_filenames else path.encode('utf-8') + path = path if os.path.supports_unicode_filenames else path if not xbmcvfs.exists(path): LOG.info("Could not find %s", path) @@ -291,14 +290,17 @@ def indent(elem, level=0): def write_xml(content, file): - with open(file, 'w') as infile: + if isinstance(content, text_type): + content = content.encode('utf-8') + + with open(file, 'wb') as infile: # replace apostrophes with double quotes only in xml keys, not texts def replace_apostrophes(match): - return match.group(0).replace("'", '"') - content = re.sub("<(.*?)>", replace_apostrophes, content) + return match.group(0).replace(b"'", b'"') + content = re.sub(b"<(.*?)>", replace_apostrophes, content) - content = content.replace('?>', ' standalone="yes" ?>', 1) + content = content.replace(b'?>', b' standalone="yes" ?>', 1) infile.write(content) @@ -312,7 +314,7 @@ def delete_folder(path): delete_recursive(path, dirs) for file in files: - xbmcvfs.delete(os.path.join(path, file.decode('utf-8'))) + xbmcvfs.delete(os.path.join(path, file)) xbmcvfs.delete(path) @@ -324,20 +326,20 @@ def delete_recursive(path, dirs): ''' Delete files and dirs recursively. ''' for directory in dirs: - dirs2, files = xbmcvfs.listdir(os.path.join(path, directory.decode('utf-8'))) + dirs2, files = xbmcvfs.listdir(os.path.join(path, directory)) for file in files: - xbmcvfs.delete(os.path.join(path, directory.decode('utf-8'), file.decode('utf-8'))) + xbmcvfs.delete(os.path.join(path, directory, file)) - delete_recursive(os.path.join(path, directory.decode('utf-8')), dirs2) - xbmcvfs.rmdir(os.path.join(path, directory.decode('utf-8'))) + delete_recursive(os.path.join(path, directory), dirs2) + xbmcvfs.rmdir(os.path.join(path, directory)) def unzip(path, dest, folder=None): ''' Unzip file. zipfile module seems to fail on android with badziperror. ''' - path = urllib.quote_plus(path) + path = quote_plus(path) root = "zip://" + path + '/' if folder: @@ -352,7 +354,7 @@ def unzip(path, dest, folder=None): unzip_recursive(root, dirs, dest) for file in files: - unzip_file(os.path.join(root, file.decode('utf-8')), os.path.join(dest, file.decode('utf-8'))) + unzip_file(os.path.join(root, file), os.path.join(dest, file)) LOG.info("Unzipped %s", path) @@ -361,8 +363,8 @@ def unzip_recursive(path, dirs, dest): for directory in dirs: - dirs_dir = os.path.join(path, directory.decode('utf-8')) - dest_dir = os.path.join(dest, directory.decode('utf-8')) + dirs_dir = os.path.join(path, directory) + dest_dir = os.path.join(dest, directory) xbmcvfs.mkdir(dest_dir) dirs2, files = xbmcvfs.listdir(dirs_dir) @@ -371,7 +373,7 @@ def unzip_recursive(path, dirs, dest): unzip_recursive(dirs_dir, dirs2, dest_dir) for file in files: - unzip_file(os.path.join(dirs_dir, file.decode('utf-8')), os.path.join(dest_dir, file.decode('utf-8'))) + unzip_file(os.path.join(dirs_dir, file), os.path.join(dest_dir, file)) def unzip_file(path, dest): @@ -390,7 +392,7 @@ def get_zip_directory(path, folder): return os.path.join(path, folder) for directory in dirs: - result = get_zip_directory(os.path.join(path, directory.decode('utf-8')), folder) + result = get_zip_directory(os.path.join(path, directory), folder) if result: return result @@ -408,7 +410,7 @@ def copytree(path, dest): copy_recursive(path, dirs, dest) for file in files: - copy_file(os.path.join(path, file.decode('utf-8')), os.path.join(dest, file.decode('utf-8'))) + copy_file(os.path.join(path, file), os.path.join(dest, file)) LOG.info("Copied %s", path) @@ -417,8 +419,8 @@ def copy_recursive(path, dirs, dest): for directory in dirs: - dirs_dir = os.path.join(path, directory.decode('utf-8')) - dest_dir = os.path.join(dest, directory.decode('utf-8')) + dirs_dir = os.path.join(path, directory) + dest_dir = os.path.join(dest, directory) xbmcvfs.mkdir(dest_dir) dirs2, files = xbmcvfs.listdir(dirs_dir) @@ -427,7 +429,7 @@ def copy_recursive(path, dirs, dest): copy_recursive(dirs_dir, dirs2, dest_dir) for file in files: - copy_file(os.path.join(dirs_dir, file.decode('utf-8')), os.path.join(dest_dir, file.decode('utf-8'))) + copy_file(os.path.join(dirs_dir, file), os.path.join(dest_dir, file)) def copy_file(path, dest): @@ -458,7 +460,7 @@ def normalize_string(text): text = text.strip() text = text.rstrip('.') - text = unicodedata.normalize('NFKD', unicode(text, 'utf-8')).encode('ascii', 'ignore') + text = unicodedata.normalize('NFKD', text_type(text, 'utf-8')).encode('ascii', 'ignore') return text @@ -475,7 +477,7 @@ def convert_to_local(date): ''' Convert the local datetime to local. ''' try: - date = parser.parse(date) if type(date) in (unicode, str) else date + date = parser.parse(date) if isinstance(date, string_types) else date date = date.replace(tzinfo=tz.tzutc()) date = date.astimezone(tz.tzlocal()) @@ -485,6 +487,7 @@ def convert_to_local(date): return str(date) + def has_attribute(obj, name): try: object.__getattribute__(obj, name) diff --git a/jellyfin_kodi/helper/wrapper.py b/jellyfin_kodi/helper/wrapper.py index 746fed5e..ccd49bdd 100644 --- a/jellyfin_kodi/helper/wrapper.py +++ b/jellyfin_kodi/helper/wrapper.py @@ -1,10 +1,11 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################# import logging -import xbmcgui +from kodi_six import xbmcgui from .utils import should_stop from .exceptions import LibraryException diff --git a/jellyfin_kodi/helper/xmls.py b/jellyfin_kodi/helper/xmls.py index cb0d8f39..62b6058c 100644 --- a/jellyfin_kodi/helper/xmls.py +++ b/jellyfin_kodi/helper/xmls.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################# @@ -6,7 +7,7 @@ import logging import os import xml.etree.ElementTree as etree -import xbmc +from kodi_six import xbmc from . import translate, indent, write_xml, dialog, settings @@ -22,7 +23,7 @@ def sources(): ''' Create master lock compatible sources. Also add the kodi.jellyfin.media source. ''' - path = xbmc.translatePath("special://profile/").decode('utf-8') + path = xbmc.translatePath("special://profile/") file = os.path.join(path, 'sources.xml') try: @@ -106,7 +107,7 @@ def advanced_settings(): if settings('useDirectPaths') != "0": return - path = xbmc.translatePath("special://profile/").decode('utf-8') + path = xbmc.translatePath("special://profile/") file = os.path.join(path, 'advancedsettings.xml') try: diff --git a/jellyfin_kodi/jellyfin/__init__.py b/jellyfin_kodi/jellyfin/__init__.py index cffb1e6a..19dba025 100644 --- a/jellyfin_kodi/jellyfin/__init__.py +++ b/jellyfin_kodi/jellyfin/__init__.py @@ -1,10 +1,11 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################# import logging -from client import JellyfinClient +from .client import JellyfinClient from helper import has_attribute ################################################################################################# diff --git a/jellyfin_kodi/jellyfin/api.py b/jellyfin_kodi/jellyfin/api.py index 4eab6872..ba43c4cd 100644 --- a/jellyfin_kodi/jellyfin/api.py +++ b/jellyfin_kodi/jellyfin/api.py @@ -1,4 +1,7 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals + + def jellyfin_url(client, handler): return "%s/%s" % (client.config.data['auth.server'], handler) diff --git a/jellyfin_kodi/jellyfin/client.py b/jellyfin_kodi/jellyfin/client.py index ee9db7fd..d326f409 100644 --- a/jellyfin_kodi/jellyfin/client.py +++ b/jellyfin_kodi/jellyfin/client.py @@ -1,14 +1,15 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################# import logging -import api -from configuration import Config -from http import HTTP -from ws_client import WSClient -from connection_manager import ConnectionManager, CONNECTION_STATE +from . import api +from .configuration import Config +from .http import HTTP +from .ws_client import WSClient +from .connection_manager import ConnectionManager, CONNECTION_STATE ################################################################################################# diff --git a/jellyfin_kodi/jellyfin/configuration.py b/jellyfin_kodi/jellyfin/configuration.py index 06e50088..b73f23ca 100644 --- a/jellyfin_kodi/jellyfin/configuration.py +++ b/jellyfin_kodi/jellyfin/configuration.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ''' This will hold all configs from the client. Configuration set here will be used for the HTTP client. diff --git a/jellyfin_kodi/jellyfin/connection_manager.py b/jellyfin_kodi/jellyfin/connection_manager.py index c3195e49..2b0e4780 100644 --- a/jellyfin_kodi/jellyfin/connection_manager.py +++ b/jellyfin_kodi/jellyfin/connection_manager.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################# @@ -11,8 +12,8 @@ from distutils.version import LooseVersion import urllib3 -from credentials import Credentials -from http import HTTP # noqa: I201,I100 +from .credentials import Credentials +from .http import HTTP # noqa: I201,I100 ################################################################################################# @@ -258,7 +259,7 @@ class ConnectionManager(object): def _server_discovery(self): MULTI_GROUP = ("<broadcast>", 7359) - MESSAGE = "who is JellyfinServer?" + MESSAGE = b"who is JellyfinServer?" sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.settimeout(1.0) # This controls the socket.timeout exception diff --git a/jellyfin_kodi/jellyfin/credentials.py b/jellyfin_kodi/jellyfin/credentials.py index 0b61f3dd..2eeacb98 100644 --- a/jellyfin_kodi/jellyfin/credentials.py +++ b/jellyfin_kodi/jellyfin/credentials.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################# diff --git a/jellyfin_kodi/jellyfin/exceptions.py b/jellyfin_kodi/jellyfin/exceptions.py index aea09675..6cd50511 100644 --- a/jellyfin_kodi/jellyfin/exceptions.py +++ b/jellyfin_kodi/jellyfin/exceptions.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################# diff --git a/jellyfin_kodi/jellyfin/http.py b/jellyfin_kodi/jellyfin/http.py index 87756e54..f4cae0d3 100644 --- a/jellyfin_kodi/jellyfin/http.py +++ b/jellyfin_kodi/jellyfin/http.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################# @@ -7,7 +8,8 @@ import logging import time import requests -from exceptions import HTTPException + +from .exceptions import HTTPException ################################################################################################# @@ -49,13 +51,13 @@ class HTTP(object): if '{server}' in string: if self.config.data['auth.server']: - string = string.decode('utf-8').replace("{server}", self.config.data['auth.server']) + string = string.replace("{server}", self.config.data['auth.server']) else: raise Exception("Server address not set.") if '{UserId}'in string: if self.config.data['auth.user_id']: - string = string.decode('utf-8').replace("{UserId}", self.config.data['auth.user_id']) + string = string.replace("{UserId}", self.config.data['auth.user_id']) else: raise Exception("UserId is not set.") @@ -209,17 +211,17 @@ class HTTP(object): def _authorization(self, data): auth = "MediaBrowser " - auth += "Client=%s, " % self.config.data['app.name'].encode('utf-8') - auth += "Device=%s, " % self.config.data['app.device_name'].encode('utf-8') - auth += "DeviceId=%s, " % self.config.data['app.device_id'].encode('utf-8') - auth += "Version=%s" % self.config.data['app.version'].encode('utf-8') + auth += "Client=%s, " % self.config.data['app.name'] + auth += "Device=%s, " % self.config.data['app.device_name'] + auth += "DeviceId=%s, " % self.config.data['app.device_id'] + auth += "Version=%s" % self.config.data['app.version'] data['headers'].update({'x-emby-authorization': auth}) if self.config.data.get('auth.token') and self.config.data.get('auth.user_id'): - auth += ', UserId=%s' % self.config.data['auth.user_id'].encode('utf-8') - data['headers'].update({'x-emby-authorization': auth, 'X-MediaBrowser-Token': self.config.data['auth.token'].encode('utf-8')}) + auth += ', UserId=%s' % self.config.data['auth.user_id'] + data['headers'].update({'x-emby-authorization': auth, 'X-MediaBrowser-Token': self.config.data['auth.token']}) return data diff --git a/jellyfin_kodi/jellyfin/websocket.py b/jellyfin_kodi/jellyfin/websocket.py index 6cb9c009..2c6a706f 100644 --- a/jellyfin_kodi/jellyfin/websocket.py +++ b/jellyfin_kodi/jellyfin/websocket.py @@ -1,3 +1,4 @@ +from __future__ import division, absolute_import, print_function, unicode_literals """ websocket - WebSocket client library for Python @@ -33,7 +34,7 @@ except ImportError: HAVE_SSL = False -from urlparse import urlparse + import os import array import struct @@ -44,6 +45,10 @@ import threading import time import logging +from six import text_type, string_types, iteritems, int2byte, indexbytes +from six.moves import range +from six.moves.urllib.parse import urlparse + """ websocket python client. ========================= @@ -221,7 +226,7 @@ def create_connection(url, timeout=None, **options): _MAX_INTEGER = (1 << 32) - 1 -_AVAILABLE_KEY_CHARS = range(0x21, 0x2f + 1) + range(0x3a, 0x7e + 1) +_AVAILABLE_KEY_CHARS = list(range(0x21, 0x2f + 1)) + list(range(0x3a, 0x7e + 1)) _MAX_CHAR_BYTE = (1 << 8) - 1 # ref. Websocket gets an update, and it breaks stuff. @@ -304,7 +309,7 @@ class ABNF(object): opcode: operation code. please see OPCODE_XXX. """ - if opcode == ABNF.OPCODE_TEXT and isinstance(data, unicode): + if opcode == ABNF.OPCODE_TEXT and isinstance(data, text_type): data = data.encode("utf-8") # mask must be set if send data from client return ABNF(1, 0, 0, 0, opcode, 1, data) @@ -321,14 +326,14 @@ class ABNF(object): if length >= ABNF.LENGTH_63: raise ValueError("data is too long") - frame_header = chr(self.fin << 7 | self.rsv1 << 6 | self.rsv2 << 5 | self.rsv3 << 4 | self.opcode) + frame_header = int2byte(self.fin << 7 | self.rsv1 << 6 | self.rsv2 << 5 | self.rsv3 << 4 | self.opcode) if length < ABNF.LENGTH_7: - frame_header += chr(self.mask << 7 | length) + frame_header += int2byte(self.mask << 7 | length) elif length < ABNF.LENGTH_16: - frame_header += chr(self.mask << 7 | 0x7e) + frame_header += int2byte(self.mask << 7 | 0x7e) frame_header += struct.pack("!H", length) else: - frame_header += chr(self.mask << 7 | 0x7f) + frame_header += int2byte(self.mask << 7 | 0x7f) frame_header += struct.pack("!Q", length) if not self.mask: @@ -352,7 +357,7 @@ class ABNF(object): """ _m = array.array("B", mask_key) _d = array.array("B", data) - for i in xrange(len(_d)): + for i in range(len(_d)): _d[i] ^= _m[i % 4] return _d.tostring() @@ -475,31 +480,39 @@ class WebSocket(object): self._handshake(hostname, port, resource, **options) def _handshake(self, host, port, resource, **options): + if isinstance(host, string_types): + host = host.encode('utf-8') + if isinstance(resource, string_types): + resource = resource.encode('utf-8') + headers = [] - headers.append("GET %s HTTP/1.1" % resource) - headers.append("Upgrade: websocket") - headers.append("Connection: Upgrade") + headers.append(b"GET %s HTTP/1.1" % resource) + headers.append(b"Upgrade: websocket") + headers.append(b"Connection: Upgrade") if port == 80: hostport = host else: - hostport = "%s:%d" % (host, port) - headers.append("Host: %s" % hostport) + hostport = b"%s:%d" % (host, port) + headers.append(b"Host: %s" % hostport) if "origin" in options: - headers.append("Origin: %s" % options["origin"]) + headers.append(b"Origin: %s" % options["origin"]) else: - headers.append("Origin: http://%s" % hostport) + headers.append(b"Origin: http://%s" % hostport) key = _create_sec_websocket_key() - headers.append("Sec-WebSocket-Key: %s" % key) - headers.append("Sec-WebSocket-Version: %s" % VERSION) + headers.append(b"Sec-WebSocket-Key: %s" % key) + headers.append(b"Sec-WebSocket-Version: %d" % VERSION) if "header" in options: - headers.extend(options["header"]) + for header in options["header"]: + if isinstance(header, string_types): + header = header.encode('utf-8') + headers.extend(header) - headers.append("") - headers.append("") + headers.append(b"") + headers.append(b"") - header_str = "\r\n".join(headers) + header_str = b"\r\n".join(headers) self._send(header_str) if traceEnabled: logger.debug("--- request header ---") @@ -519,7 +532,7 @@ class WebSocket(object): self.connected = True def _validate_header(self, headers, key): - for k, v in _HEADERS_TO_CHECK.iteritems(): + for k, v in iteritems(_HEADERS_TO_CHECK): r = headers.get(k, None) if not r: return False @@ -653,13 +666,13 @@ class WebSocket(object): # Header if self._frame_header is None: self._frame_header = self._recv_strict(2) - b1 = ord(self._frame_header[0]) + b1 = indexbytes(self._frame_header, 0) fin = b1 >> 7 & 1 rsv1 = b1 >> 6 & 1 rsv2 = b1 >> 5 & 1 rsv3 = b1 >> 4 & 1 opcode = b1 & 0xf - b2 = ord(self._frame_header[1]) + b2 = indexbytes(self._frame_header, 1) has_mask = b2 >> 7 & 1 # Frame length if self._frame_length is None: @@ -753,7 +766,7 @@ class WebSocket(object): def _recv(self, bufsize): try: - bytes = self.sock.recv(bufsize) + _bytes = self.sock.recv(bufsize) except socket.timeout as e: raise WebSocketTimeoutException(e.args[0]) except SSLError as e: @@ -761,16 +774,16 @@ class WebSocket(object): raise WebSocketTimeoutException(e.args[0]) else: raise - if not bytes: + if not _bytes: raise WebSocketConnectionClosedException() - return bytes + return _bytes def _recv_strict(self, bufsize): shortage = bufsize - sum(len(x) for x in self._recv_buffer) while shortage > 0: - bytes = self._recv(shortage) - self._recv_buffer.append(bytes) - shortage -= len(bytes) + _bytes = self._recv(shortage) + self._recv_buffer.append(_bytes) + shortage -= len(_bytes) unified = "".join(self._recv_buffer) if shortage == 0: self._recv_buffer = [] diff --git a/jellyfin_kodi/jellyfin/ws_client.py b/jellyfin_kodi/jellyfin/ws_client.py index a19d624d..da2efcbf 100644 --- a/jellyfin_kodi/jellyfin/ws_client.py +++ b/jellyfin_kodi/jellyfin/ws_client.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################# @@ -6,9 +7,9 @@ import json import logging import threading -import xbmc +from kodi_six import xbmc -import websocket +from . import websocket ################################################################################################## diff --git a/jellyfin_kodi/library.py b/jellyfin_kodi/library.py index 0d67c318..33ad98f6 100644 --- a/jellyfin_kodi/library.py +++ b/jellyfin_kodi/library.py @@ -1,14 +1,15 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################## import logging -import Queue import threading from datetime import datetime, timedelta -import xbmc -import xbmcgui +from six.moves import queue as Queue + +from kodi_six import xbmc, xbmcgui from objects import Movies, TVShows, MusicVideos, Music from database import Database, jellyfin_db, get_sync, save_sync @@ -495,7 +496,7 @@ class Library(threading.Thread): "AddLibrarySelection": 33120 } title = titles.get(mode, "Failed to get title {}".format(mode)) - + selection = dialog("multi", translate(title), choices) if selection is None: diff --git a/jellyfin_kodi/monitor.py b/jellyfin_kodi/monitor.py index 1a8b1d5e..a423303e 100644 --- a/jellyfin_kodi/monitor.py +++ b/jellyfin_kodi/monitor.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################# @@ -7,7 +8,7 @@ import json import logging import threading -import xbmc +from kodi_six import xbmc import connect import downloader @@ -144,7 +145,7 @@ class Monitor(xbmc.Monitor): self.void_responder(data, item) elif method == 'GetServerAddress': - + server_address = server.auth.get_server_info(server.auth.server_id)['address'] self.void_responder(data, server_address) @@ -291,7 +292,7 @@ class Monitor(xbmc.Monitor): for additional in users: for user in all_users: - if user['Name'].lower() in additional.decode('utf-8').lower(): + if user['Name'].lower() in additional.lower(): server.jellyfin.session_add_user(server.config.data['app.session'], user['Id'], True) self.additional_users(server) diff --git a/jellyfin_kodi/objects/__init__.py b/jellyfin_kodi/objects/__init__.py index 6d3c8152..469f3809 100644 --- a/jellyfin_kodi/objects/__init__.py +++ b/jellyfin_kodi/objects/__init__.py @@ -1,11 +1,13 @@ -from movies import Movies -from musicvideos import MusicVideos -from tvshows import TVShows -from music import Music -from obj import Objects -from actions import Actions -from actions import PlaylistWorker -from actions import on_play, on_update, special_listener -import utils +from __future__ import division, absolute_import, print_function, unicode_literals + +from .movies import Movies +from .musicvideos import MusicVideos +from .tvshows import TVShows +from .music import Music +from .obj import Objects +from .actions import Actions +from .actions import PlaylistWorker +from .actions import on_play, on_update, special_listener +from . import utils Objects().mapping() diff --git a/jellyfin_kodi/objects/actions.py b/jellyfin_kodi/objects/actions.py index 394d9e2c..6313a446 100644 --- a/jellyfin_kodi/objects/actions.py +++ b/jellyfin_kodi/objects/actions.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################# @@ -7,14 +8,11 @@ import threading import sys from datetime import timedelta -import xbmc -import xbmcgui -import xbmcplugin -import xbmcaddon +from kodi_six import xbmc, xbmcgui, xbmcplugin, xbmcaddon import database from downloader import TheVoid -from obj import Objects +from .obj import Objects from helper import translate, playutils, api, window, settings, dialog from dialogs import resume diff --git a/jellyfin_kodi/objects/kodi/__init__.py b/jellyfin_kodi/objects/kodi/__init__.py index 86ada83f..961219bf 100644 --- a/jellyfin_kodi/objects/kodi/__init__.py +++ b/jellyfin_kodi/objects/kodi/__init__.py @@ -1,6 +1,8 @@ -from kodi import Kodi -from movies import Movies -from musicvideos import MusicVideos -from tvshows import TVShows -from music import Music -from artwork import Artwork +from __future__ import division, absolute_import, print_function, unicode_literals + +from .kodi import Kodi +from .movies import Movies +from .musicvideos import MusicVideos +from .tvshows import TVShows +from .music import Music +from .artwork import Artwork diff --git a/jellyfin_kodi/objects/kodi/artwork.py b/jellyfin_kodi/objects/kodi/artwork.py index 9de81a51..0625d75e 100644 --- a/jellyfin_kodi/objects/kodi/artwork.py +++ b/jellyfin_kodi/objects/kodi/artwork.py @@ -1,17 +1,18 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################# import logging -import urllib -import Queue import threading -import xbmc -import xbmcvfs +from six.moves import queue as Queue +from six.moves.urllib.parse import urlencode -import queries as QU -import queries_texture as QUTEX +from kodi_six import xbmc, xbmcvfs + +from . import queries as QU +from . import queries_texture as QUTEX from helper import settings import requests @@ -139,10 +140,10 @@ class Artwork(object): ''' urlencode needs a utf-string. return the result as unicode ''' - text = urllib.urlencode({'blahblahblah': text.encode('utf-8')}) + text = urlencode({'blahblahblah': text}) text = text[13:] - return text.decode('utf-8') + return text def add_worker(self): @@ -170,7 +171,7 @@ class Artwork(object): except TypeError: LOG.debug("Could not find cached url: %s", url) else: - thumbnails = xbmc.translatePath("special://thumbnails/%s" % cached).decode('utf-8') + thumbnails = xbmc.translatePath("special://thumbnails/%s" % cached) xbmcvfs.delete(thumbnails) texturedb.cursor.execute(QUTEX.delete_cache, (url,)) LOG.info("DELETE cached %s", cached) diff --git a/jellyfin_kodi/objects/kodi/kodi.py b/jellyfin_kodi/objects/kodi/kodi.py index 844d3ca4..e34d57d2 100644 --- a/jellyfin_kodi/objects/kodi/kodi.py +++ b/jellyfin_kodi/objects/kodi/kodi.py @@ -1,11 +1,12 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################## import logging -import artwork -import queries as QU +from . import artwork +from . import queries as QU from helper import values ################################################################################################## diff --git a/jellyfin_kodi/objects/kodi/movies.py b/jellyfin_kodi/objects/kodi/movies.py index 38fd9329..40c442a1 100644 --- a/jellyfin_kodi/objects/kodi/movies.py +++ b/jellyfin_kodi/objects/kodi/movies.py @@ -1,11 +1,12 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################## import logging -from kodi import Kodi -import queries as QU +from .kodi import Kodi +from . import queries as QU ################################################################################################## diff --git a/jellyfin_kodi/objects/kodi/music.py b/jellyfin_kodi/objects/kodi/music.py index 6b676b1d..89213e94 100644 --- a/jellyfin_kodi/objects/kodi/music.py +++ b/jellyfin_kodi/objects/kodi/music.py @@ -1,11 +1,12 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################## import logging -import queries_music as QU -from kodi import Kodi +from . import queries_music as QU +from .kodi import Kodi ################################################################################################## diff --git a/jellyfin_kodi/objects/kodi/musicvideos.py b/jellyfin_kodi/objects/kodi/musicvideos.py index 764a79ba..70de6cdf 100644 --- a/jellyfin_kodi/objects/kodi/musicvideos.py +++ b/jellyfin_kodi/objects/kodi/musicvideos.py @@ -1,11 +1,12 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################## import logging -import queries as QU -from kodi import Kodi +from . import queries as QU +from .kodi import Kodi ################################################################################################## diff --git a/jellyfin_kodi/objects/kodi/queries.py b/jellyfin_kodi/objects/kodi/queries.py index 75fb15da..fea7dba6 100644 --- a/jellyfin_kodi/objects/kodi/queries.py +++ b/jellyfin_kodi/objects/kodi/queries.py @@ -1,3 +1,4 @@ +from __future__ import division, absolute_import, print_function, unicode_literals ''' Queries for the Kodi database. obj reflect key/value to retrieve from jellyfin items. Some functions require additional information, therefore obj do not always reflect diff --git a/jellyfin_kodi/objects/kodi/queries_music.py b/jellyfin_kodi/objects/kodi/queries_music.py index b9105a7a..836883bc 100644 --- a/jellyfin_kodi/objects/kodi/queries_music.py +++ b/jellyfin_kodi/objects/kodi/queries_music.py @@ -1,3 +1,4 @@ +from __future__ import division, absolute_import, print_function, unicode_literals create_artist = """ SELECT coalesce(max(idArtist), 1) diff --git a/jellyfin_kodi/objects/kodi/queries_texture.py b/jellyfin_kodi/objects/kodi/queries_texture.py index 25960941..d60505b7 100644 --- a/jellyfin_kodi/objects/kodi/queries_texture.py +++ b/jellyfin_kodi/objects/kodi/queries_texture.py @@ -1,3 +1,4 @@ +from __future__ import division, absolute_import, print_function, unicode_literals get_cache = """ SELECT cachedurl diff --git a/jellyfin_kodi/objects/kodi/tvshows.py b/jellyfin_kodi/objects/kodi/tvshows.py index 4d620e87..b9feda4d 100644 --- a/jellyfin_kodi/objects/kodi/tvshows.py +++ b/jellyfin_kodi/objects/kodi/tvshows.py @@ -1,11 +1,12 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################## import logging -import queries as QU -from kodi import Kodi +from . import queries as QU +from .kodi import Kodi ################################################################################################## diff --git a/jellyfin_kodi/objects/movies.py b/jellyfin_kodi/objects/movies.py index ffb57906..5cd8f3ab 100644 --- a/jellyfin_kodi/objects/movies.py +++ b/jellyfin_kodi/objects/movies.py @@ -1,13 +1,14 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################## import logging -import urllib +from six.moves.urllib.parse import urlencode import downloader as server -from obj import Objects -from kodi import Movies as KodiDb, queries as QU +from .obj import Objects +from .kodi import Movies as KodiDb, queries as QU from database import jellyfin_db, queries as QUEM from helper import api, stop, validate, jellyfin_item, library_check, values, settings, Local @@ -177,12 +178,12 @@ class Movies(KodiDb): else: obj['Path'] = "plugin://plugin.video.jellyfin/%s/" % obj['LibraryId'] params = { - 'filename': obj['Filename'].encode('utf-8'), + 'filename': obj['Filename'], 'id': obj['Id'], 'dbid': obj['MovieId'], 'mode': "play" } - obj['Filename'] = "%s?%s" % (obj['Path'], urllib.urlencode(params)) + obj['Filename'] = "%s?%s" % (obj['Path'], urlencode(params)) @stop() @jellyfin_item() diff --git a/jellyfin_kodi/objects/music.py b/jellyfin_kodi/objects/music.py index 81f37e9c..be8b5922 100644 --- a/jellyfin_kodi/objects/music.py +++ b/jellyfin_kodi/objects/music.py @@ -1,12 +1,13 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################## import datetime import logging -from obj import Objects -from kodi import Music as KodiDb, queries_music as QU +from .obj import Objects +from .kodi import Music as KodiDb, queries_music as QU from database import jellyfin_db, queries as QUEM from helper import api, stop, validate, jellyfin_item, values, library_check, Local diff --git a/jellyfin_kodi/objects/musicvideos.py b/jellyfin_kodi/objects/musicvideos.py index fe24e6cc..b4cca7b2 100644 --- a/jellyfin_kodi/objects/musicvideos.py +++ b/jellyfin_kodi/objects/musicvideos.py @@ -1,14 +1,15 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################## import datetime import logging import re -import urllib +from six.moves.urllib.parse import urlencode -from obj import Objects -from kodi import MusicVideos as KodiDb, queries as QU +from .obj import Objects +from .kodi import MusicVideos as KodiDb, queries as QU from database import jellyfin_db, queries as QUEM from helper import api, stop, validate, library_check, jellyfin_item, values, Local @@ -165,12 +166,12 @@ class MusicVideos(KodiDb): else: obj['Path'] = "plugin://plugin.video.jellyfin/%s/" % obj['LibraryId'] params = { - 'filename': obj['Filename'].encode('utf-8'), + 'filename': obj['Filename'], 'id': obj['Id'], 'dbid': obj['MvideoId'], 'mode': "play" } - obj['Filename'] = "%s?%s" % (obj['Path'], urllib.urlencode(params)) + obj['Filename'] = "%s?%s" % (obj['Path'], urlencode(params)) @stop() @jellyfin_item() diff --git a/jellyfin_kodi/objects/obj.py b/jellyfin_kodi/objects/obj.py index ac1a7aa2..1cca13e4 100644 --- a/jellyfin_kodi/objects/obj.py +++ b/jellyfin_kodi/objects/obj.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################## @@ -6,6 +7,8 @@ import json import logging import os +from six import iteritems + ################################################################################################## LOG = logging.getLogger("JELLYFIN." + __name__) @@ -53,7 +56,7 @@ class Objects(object): mapping = self.objects[mapping_name] - for key, value in mapping.iteritems(): + for key, value in iteritems(mapping): self.mapped_item[key] = None params = value.split(',') @@ -144,7 +147,7 @@ class Objects(object): result = False - for key, value in filters.iteritems(): + for key, value in iteritems(filters): inverse = False diff --git a/jellyfin_kodi/objects/tvshows.py b/jellyfin_kodi/objects/tvshows.py index 9922641d..9eedf007 100644 --- a/jellyfin_kodi/objects/tvshows.py +++ b/jellyfin_kodi/objects/tvshows.py @@ -1,14 +1,16 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################## import logging import sqlite3 -import urllib from ntpath import dirname -from obj import Objects -from kodi import TVShows as KodiDb, queries as QU +from six.moves.urllib.parse import urlencode + +from .obj import Objects +from .kodi import TVShows as KodiDb, queries as QU import downloader as server from database import jellyfin_db, queries as QUEM from helper import api, stop, validate, jellyfin_item, library_check, settings, values, Local @@ -392,12 +394,12 @@ class TVShows(KodiDb): else: obj['Path'] = "plugin://plugin.video.jellyfin/%s/" % obj['SeriesId'] params = { - 'filename': obj['Filename'].encode('utf-8'), + 'filename': obj['Filename'], 'id': obj['Id'], 'dbid': obj['EpisodeId'], 'mode': "play" } - obj['Filename'] = "%s?%s" % (obj['Path'], urllib.urlencode(params)) + obj['Filename'] = "%s?%s" % (obj['Path'], urlencode(params)) def get_show_id(self, obj): obj['ShowId'] = self.jellyfin_db.get_item_by_id(*values(obj, QUEM.get_item_series_obj)) diff --git a/jellyfin_kodi/objects/utils.py b/jellyfin_kodi/objects/utils.py index f0da50c8..cbed4e74 100644 --- a/jellyfin_kodi/objects/utils.py +++ b/jellyfin_kodi/objects/utils.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################# diff --git a/jellyfin_kodi/player.py b/jellyfin_kodi/player.py index 0d342058..cd758fb1 100644 --- a/jellyfin_kodi/player.py +++ b/jellyfin_kodi/player.py @@ -1,12 +1,12 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################# import logging import os -import xbmc -import xbmcvfs +from kodi_six import xbmc, xbmcvfs from objects.obj import Objects from helper import translate, api, window, settings, dialog, event, silent_catch, JSONRPC @@ -85,7 +85,7 @@ class Player(xbmc.Player): return for item in items: - if item['Path'] == current_file.decode('utf-8'): + if item['Path'] == current_file: items.pop(items.index(item)) break @@ -412,13 +412,13 @@ class Player(xbmc.Player): LOG.info("<[ transcode/%s ]", item['Id']) item['Server'].jellyfin.close_transcode(item['DeviceId']) - path = xbmc.translatePath("special://profile/addon_data/plugin.video.jellyfin/temp/").decode('utf-8') + path = xbmc.translatePath("special://profile/addon_data/plugin.video.jellyfin/temp/") if xbmcvfs.exists(path): dirs, files = xbmcvfs.listdir(path) for file in files: - xbmcvfs.delete(os.path.join(path, file.decode('utf-8'))) + xbmcvfs.delete(os.path.join(path, file)) result = item['Server'].jellyfin.get_item(item['Id']) or {} diff --git a/jellyfin_kodi/setup.py b/jellyfin_kodi/setup.py index 77cce34b..3305c485 100644 --- a/jellyfin_kodi/setup.py +++ b/jellyfin_kodi/setup.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################# diff --git a/jellyfin_kodi/views.py b/jellyfin_kodi/views.py index a236ed58..b75063f7 100644 --- a/jellyfin_kodi/views.py +++ b/jellyfin_kodi/views.py @@ -1,15 +1,15 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################# import logging import os import shutil -import urllib import xml.etree.ElementTree as etree -import xbmc -import xbmcvfs +from six.moves.urllib.parse import urlencode +from kodi_six import xbmc, xbmcvfs from database import Database, jellyfin_db, get_sync, save_sync from helper import translate, api, indent, write_xml, window, event @@ -108,13 +108,13 @@ def verify_kodi_defaults(): ''' Make sure we have the kodi default folder in place. ''' - node_path = xbmc.translatePath("special://profile/library/video").decode('utf-8') + node_path = xbmc.translatePath("special://profile/library/video") if not xbmcvfs.exists(node_path): try: shutil.copytree( - src=xbmc.translatePath("special://xbmc/system/library/video").decode('utf-8'), - dst=xbmc.translatePath("special://profile/library/video").decode('utf-8')) + src=xbmc.translatePath("special://xbmc/system/library/video"), + dst=xbmc.translatePath("special://profile/library/video")) except Exception as error: LOG.warning(error) xbmcvfs.mkdir(node_path) @@ -129,7 +129,7 @@ def verify_kodi_defaults(): indent(xml) write_xml(etree.tostring(xml, 'UTF-8'), file) - playlist_path = xbmc.translatePath("special://profile/playlists/video").decode('utf-8') + playlist_path = xbmc.translatePath("special://profile/playlists/video") if not xbmcvfs.exists(playlist_path): xbmcvfs.mkdirs(playlist_path) @@ -223,8 +223,8 @@ class Views(object): ''' Set up playlists, video nodes, window prop. ''' - node_path = xbmc.translatePath("special://profile/library/video").decode('utf-8') - playlist_path = xbmc.translatePath("special://profile/playlists/video").decode('utf-8') + node_path = xbmc.translatePath("special://profile/library/video") + playlist_path = xbmc.translatePath("special://profile/playlists/video") index = 0 with Database('jellyfin') as jellyfindb: @@ -779,16 +779,16 @@ class Views(object): window_prop = "Jellyfin.nodes.%s" % index window('%s.index' % window_prop, path.replace('all.xml', "")) # dir - window('%s.title' % window_prop, view['Name'].encode('utf-8')) + window('%s.title' % window_prop, view['Name']) window('%s.content' % window_prop, path) elif node == 'browse': window_prop = "Jellyfin.nodes.%s" % index - window('%s.title' % window_prop, view['Name'].encode('utf-8')) + window('%s.title' % window_prop, view['Name']) else: window_prop = "Jellyfin.nodes.%s.%s" % (index, node) - window('%s.title' % window_prop, node_label.encode('utf-8')) + window('%s.title' % window_prop, node_label) window('%s.content' % window_prop, path) window('%s.id' % window_prop, view['Id']) @@ -831,17 +831,17 @@ class Views(object): window_prop = "Jellyfin.wnodes.%s" % index window('%s.index' % window_prop, path.replace('all.xml', "")) # dir - window('%s.title' % window_prop, view['Name'].encode('utf-8')) + window('%s.title' % window_prop, view['Name']) window('%s.content' % window_prop, path) elif node == 'browse': window_prop = "Jellyfin.wnodes.%s" % index - window('%s.title' % window_prop, view['Name'].encode('utf-8')) + window('%s.title' % window_prop, view['Name']) window('%s.content' % window_prop, path) else: window_prop = "Jellyfin.wnodes.%s.%s" % (index, node) - window('%s.title' % window_prop, node_label.encode('utf-8')) + window('%s.title' % window_prop, node_label) window('%s.content' % window_prop, path) window('%s.id' % window_prop, view['Id']) @@ -881,7 +881,7 @@ class Views(object): 'mode': "nextepisodes", 'limit': self.limit } - return "%s?%s" % ("plugin://plugin.video.jellyfin/", urllib.urlencode(params)) + return "%s?%s" % ("plugin://plugin.video.jellyfin/", urlencode(params)) def window_browse(self, view, node=None): @@ -896,7 +896,7 @@ class Views(object): if node: params['folder'] = node - return "%s?%s" % ("plugin://plugin.video.jellyfin/", urllib.urlencode(params)) + return "%s?%s" % ("plugin://plugin.video.jellyfin/", urlencode(params)) def window_clear(self, name=None): @@ -932,23 +932,23 @@ class Views(object): ''' Remove all jellyfin playlists. ''' - path = xbmc.translatePath("special://profile/playlists/video/").decode('utf-8') + path = xbmc.translatePath("special://profile/playlists/video/") _, files = xbmcvfs.listdir(path) for file in files: - if file.decode('utf-8').startswith('jellyfin'): - self.delete_playlist(os.path.join(path, file.decode('utf-8'))) + if file.startswith('jellyfin'): + self.delete_playlist(os.path.join(path, file)) def delete_playlist_by_id(self, view_id): ''' Remove playlist based based on view_id. ''' - path = xbmc.translatePath("special://profile/playlists/video/").decode('utf-8') + path = xbmc.translatePath("special://profile/playlists/video/") _, files = xbmcvfs.listdir(path) for file in files: - file = file.decode('utf-8') + file = file if file.startswith('jellyfin') and file.endswith('%s.xsp' % view_id): - self.delete_playlist(os.path.join(path, file.decode('utf-8'))) + self.delete_playlist(os.path.join(path, file)) def delete_node(self, path): @@ -959,37 +959,37 @@ class Views(object): ''' Remove node and children files. ''' - path = xbmc.translatePath("special://profile/library/video/").decode('utf-8') + path = xbmc.translatePath("special://profile/library/video/") dirs, files = xbmcvfs.listdir(path) for file in files: if file.startswith('jellyfin'): - self.delete_node(os.path.join(path, file.decode('utf-8'))) + self.delete_node(os.path.join(path, file)) for directory in dirs: if directory.startswith('jellyfin'): - _, files = xbmcvfs.listdir(os.path.join(path, directory.decode('utf-8'))) + _, files = xbmcvfs.listdir(os.path.join(path, directory)) for file in files: - self.delete_node(os.path.join(path, directory.decode('utf-8'), file.decode('utf-8'))) + self.delete_node(os.path.join(path, directory, file)) - xbmcvfs.rmdir(os.path.join(path, directory.decode('utf-8'))) + xbmcvfs.rmdir(os.path.join(path, directory)) def delete_node_by_id(self, view_id): ''' Remove node and children files based on view_id. ''' - path = xbmc.translatePath("special://profile/library/video/").decode('utf-8') + path = xbmc.translatePath("special://profile/library/video/") dirs, files = xbmcvfs.listdir(path) for directory in dirs: if directory.startswith('jellyfin') and directory.endswith(view_id): - _, files = xbmcvfs.listdir(os.path.join(path, directory.decode('utf-8'))) + _, files = xbmcvfs.listdir(os.path.join(path, directory)) for file in files: - self.delete_node(os.path.join(path, directory.decode('utf-8'), file.decode('utf-8'))) + self.delete_node(os.path.join(path, directory, file)) - xbmcvfs.rmdir(os.path.join(path, directory.decode('utf-8'))) + xbmcvfs.rmdir(os.path.join(path, directory)) diff --git a/jellyfin_kodi/webservice.py b/jellyfin_kodi/webservice.py index dc68e69a..db63a07a 100644 --- a/jellyfin_kodi/webservice.py +++ b/jellyfin_kodi/webservice.py @@ -1,14 +1,15 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################# -import BaseHTTPServer +from six.moves import BaseHTTPServer import logging -import httplib +from six.moves import http_client as httplib import threading -import urlparse +from six.moves.urllib.parse import parse_qsl -import xbmc +from kodi_six import xbmc ################################################################################################# @@ -97,7 +98,7 @@ class requestHandler(BaseHTTPServer.BaseHTTPRequestHandler): if '?' in path: path = path.split('?', 1)[1] - params = dict(urlparse.parse_qsl(path)) + params = dict(parse_qsl(path)) except Exception: params = {} diff --git a/service.py b/service.py index f807e14f..a1951880 100644 --- a/service.py +++ b/service.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################# @@ -7,13 +8,12 @@ import os import threading import sys -import xbmc -import xbmcaddon +from kodi_six import xbmc, xbmcaddon ################################################################################################# __addon__ = xbmcaddon.Addon(id='plugin.video.jellyfin') -__base__ = xbmc.translatePath(os.path.join(__addon__.getAddonInfo('path'), 'jellyfin_kodi')).decode('utf-8') +__base__ = xbmc.translatePath(os.path.join(__addon__.getAddonInfo('path'), 'jellyfin_kodi')) sys.path.insert(0, __base__) @@ -54,11 +54,11 @@ class ServiceManager(threading.Thread): LOG.exception(error) if service is not None: - - if 'ExitService' not in error: + # TODO: fix this properly as to not match on str() + if 'ExitService' not in str(error): service.shutdown() - if 'RestartService' in error: + if 'RestartService' in str(error): service.reload_objects() self.exception = error @@ -79,7 +79,7 @@ if __name__ == "__main__": session.start() session.join() # Block until the thread exits. - if 'RestartService' in session.exception: + if 'RestartService' in str(session.exception): continue except Exception as error: