Initial work on Kodi 19 (and Python 3) support

This commit is contained in:
Odd Stråbø 2020-01-04 03:32:30 +01:00
parent 898b0d1faf
commit a51bf9c2cc
68 changed files with 403 additions and 339 deletions

1
.env Normal file
View file

@ -0,0 +1 @@
PYTHONPATH=jellyfin_kodi

1
.gitignore vendored
View file

@ -1,4 +1,5 @@
*.pyo
__pycache__/
__local__/
machine_guid
/resources/media/Thumbs.db

View file

@ -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"

View file

@ -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__)

View file

@ -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__)

View file

@ -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__)

View file

@ -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)

View file

@ -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

View file

@ -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:

View file

@ -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
##################################################################################################

View file

@ -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,

View file

@ -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

View file

@ -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

View file

@ -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):

View file

@ -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
##################################################################################################

View file

@ -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):

View file

@ -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):

View file

@ -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):

View file

@ -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

View file

@ -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

View file

@ -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']})

View file

@ -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)

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
##################################################################################################

View file

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
#################################################################################################

View file

@ -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):

View file

@ -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)

View file

@ -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
##################################################################################################

View file

@ -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)

View file

@ -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

View file

@ -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:

View file

@ -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
#################################################################################################

View file

@ -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)

View file

@ -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
#################################################################################################

View file

@ -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.

View file

@ -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

View file

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
#################################################################################################

View file

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
#################################################################################################

View file

@ -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

View file

@ -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 = []

View file

@ -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
##################################################################################################

View file

@ -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:

View file

@ -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)

View file

@ -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()

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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
##################################################################################################

View file

@ -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
##################################################################################################

View file

@ -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
##################################################################################################

View file

@ -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
##################################################################################################

View file

@ -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

View file

@ -1,3 +1,4 @@
from __future__ import division, absolute_import, print_function, unicode_literals
create_artist = """
SELECT coalesce(max(idArtist), 1)

View file

@ -1,3 +1,4 @@
from __future__ import division, absolute_import, print_function, unicode_literals
get_cache = """
SELECT cachedurl

View file

@ -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
##################################################################################################

View file

@ -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()

View file

@ -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

View file

@ -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()

View file

@ -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

View file

@ -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))

View file

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
#################################################################################################

View file

@ -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 {}

View file

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
#################################################################################################

View file

@ -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))

View file

@ -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 = {}

View file

@ -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: