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 *.pyo
__pycache__/
__local__/ __local__/
machine_guid machine_guid
/resources/media/Thumbs.db /resources/media/Thumbs.db

View file

@ -7,6 +7,8 @@
<import addon="xbmc.python" version="2.25.0"/> <import addon="xbmc.python" version="2.25.0"/>
<import addon="script.module.requests" version="2.22.0"/> <import addon="script.module.requests" version="2.22.0"/>
<import addon="script.module.dateutil" version="2.7.3"/> <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"/> <import addon="script.module.addon.signals" version="0.0.1"/>
</requires> </requires>
<extension point="xbmc.python.pluginsource" <extension point="xbmc.python.pluginsource"

View file

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
################################################################################################# #################################################################################################
@ -6,13 +7,12 @@ import logging
import os import os
import sys import sys
import xbmc from kodi_six import xbmc, xbmcaddon
import xbmcaddon
################################################################################################# #################################################################################################
__addon__ = xbmcaddon.Addon(id='plugin.video.jellyfin') __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__) sys.path.insert(0, __base__)

View file

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
################################################################################################# #################################################################################################
@ -6,13 +7,12 @@ import logging
import os import os
import sys import sys
import xbmc from kodi_six import xbmc, xbmcaddon
import xbmcaddon
################################################################################################# #################################################################################################
__addon__ = xbmcaddon.Addon(id='plugin.video.jellyfin') __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__) sys.path.insert(0, __base__)

View file

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
################################################################################################# #################################################################################################
@ -6,13 +7,12 @@ import logging
import os import os
import sys import sys
import xbmc from kodi_six import xbmc, xbmcaddon
import xbmcaddon
################################################################################################# #################################################################################################
__addon__ = xbmcaddon.Addon(id='plugin.video.jellyfin') __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__) sys.path.insert(0, __base__)

View file

@ -1,13 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
################################################################################################# #################################################################################################
import logging import logging
import os import os
import xbmc from kodi_six import xbmc, xbmcaddon, xbmcvfs
import xbmcaddon
import xbmcvfs
from helper import translate, window, settings, addon_id, dialog from helper import translate, window, settings, addon_id, dialog
from helper.utils import create_id from helper.utils import create_id
@ -63,7 +62,7 @@ def get_device_name():
Otherwise fallback to the Kodi device name. Otherwise fallback to the Kodi device name.
''' '''
if not settings('deviceNameOpt.bool'): if not settings('deviceNameOpt.bool'):
device_name = xbmc.getInfoLabel('System.FriendlyName').decode('utf-8') device_name = xbmc.getInfoLabel('System.FriendlyName')
else: else:
device_name = settings('deviceName') device_name = settings('deviceName')
device_name = device_name.replace("\"", "_") device_name = device_name.replace("\"", "_")
@ -86,7 +85,7 @@ def get_device_id(reset=False):
if client_id: if client_id:
return 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): if not xbmcvfs.exists(directory):
xbmcvfs.mkdir(directory) xbmcvfs.mkdir(directory)
@ -98,7 +97,7 @@ def get_device_id(reset=False):
if not client_id or reset: if not client_id or reset:
LOG.info("Generating a new GUID.") 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 = xbmcvfs.File(jellyfin_guid, 'w')
file_guid.write(client_id) file_guid.write(client_id)

View file

@ -1,11 +1,11 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
################################################################################################## ##################################################################################################
import logging import logging
import xbmc from kodi_six import xbmc, xbmcaddon
import xbmcaddon
import client import client
from database import get_credentials, save_credentials from database import get_credentials, save_credentials

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
################################################################################################# #################################################################################################
import datetime import datetime
@ -8,10 +8,10 @@ import json
import os import os
import sqlite3 import sqlite3
import xbmc from kodi_six import xbmc, xbmcvfs
import xbmcvfs from six import text_type
import jellyfin_db from database import jellyfin_db
from helper import translate, settings, window, dialog from helper import translate, settings, window, dialog
from objects import obj from objects import obj
@ -22,9 +22,6 @@ LOG = logging.getLogger("JELLYFIN." + __name__)
################################################################################################# #################################################################################################
UNICODE = type(u"")
class Database(object): class Database(object):
''' This should be called like a context. ''' This should be called like a context.
@ -76,7 +73,7 @@ class Database(object):
def _get_database(self, path, silent=False): def _get_database(self, path, silent=False):
path = xbmc.translatePath(path).decode('utf-8') path = xbmc.translatePath(path)
if not silent: if not silent:
@ -104,7 +101,7 @@ class Database(object):
xbmc.executebuiltin('UpdateLibrary(video)') xbmc.executebuiltin('UpdateLibrary(video)')
xbmc.sleep(200) xbmc.sleep(200)
databases = xbmc.translatePath("special://database/").decode('utf-8') databases = xbmc.translatePath("special://database/")
types = { types = {
'video': "MyVideos", 'video': "MyVideos",
'music': "MyMusic", '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')): 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() 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']: if modified_int > modified['time']:
modified['time'] = modified_int modified['time'] = modified_int
modified['file'] = file.decode('utf-8') modified['file'] = file
LOG.info("Discovered database: %s", modified) LOG.info("Discovered database: %s", modified)
self.discovered_file = modified['file'] 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): def _sql(self, file):
@ -251,7 +248,7 @@ def reset():
if dialog("yesno", heading="{jellyfin}", line1=translate(33086)): if dialog("yesno", heading="{jellyfin}", line1=translate(33086)):
reset_artwork() 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)): if dialog("yesno", heading="{jellyfin}", line1=translate(33087)):
@ -317,17 +314,17 @@ def reset_artwork():
''' Remove all existing texture. ''' Remove all existing texture.
''' '''
thumbnails = xbmc.translatePath('special://thumbnails/').decode('utf-8') thumbnails = xbmc.translatePath('special://thumbnails/')
if xbmcvfs.exists(thumbnails): if xbmcvfs.exists(thumbnails):
dirs, ignore = xbmcvfs.listdir(thumbnails) dirs, ignore = xbmcvfs.listdir(thumbnails)
for directory in dirs: 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: for thumb in thumbs:
LOG.debug("DELETE thumbnail %s", thumb) 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: with Database('texture') as texdb:
texdb.cursor.execute("SELECT tbl_name FROM sqlite_master WHERE type='table'") texdb.cursor.execute("SELECT tbl_name FROM sqlite_master WHERE type='table'")
@ -343,7 +340,7 @@ def reset_artwork():
def get_sync(): 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): if not xbmcvfs.exists(path):
xbmcvfs.mkdirs(path) xbmcvfs.mkdirs(path)
@ -364,7 +361,7 @@ def get_sync():
def save_sync(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): if not xbmcvfs.exists(path):
xbmcvfs.mkdirs(path) xbmcvfs.mkdirs(path)
@ -373,14 +370,14 @@ def save_sync(sync):
with open(os.path.join(path, 'sync.json'), 'wb') as outfile: with open(os.path.join(path, 'sync.json'), 'wb') as outfile:
data = json.dumps(sync, sort_keys=True, indent=4, ensure_ascii=False) 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') data = data.encode('utf-8')
outfile.write(data) outfile.write(data)
def get_credentials(): 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): if not xbmcvfs.exists(path):
xbmcvfs.mkdirs(path) xbmcvfs.mkdirs(path)
@ -424,14 +421,14 @@ def get_credentials():
def save_credentials(credentials): def save_credentials(credentials):
credentials = credentials or {} 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): if not xbmcvfs.exists(path):
xbmcvfs.mkdirs(path) xbmcvfs.mkdirs(path)
try: try:
with open(os.path.join(path, 'data.json'), 'wb') as outfile: with open(os.path.join(path, 'data.json'), 'wb') as outfile:
data = json.dumps(credentials, sort_keys=True, indent=4, ensure_ascii=False) 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') data = data.encode('utf-8')
outfile.write(data) outfile.write(data)
except Exception: except Exception:

View file

@ -1,10 +1,10 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
################################################################################################# #################################################################################################
import logging 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 = """ get_item = """
SELECT kodi_id, kodi_fileid, kodi_pathid, parent_id, media_type, SELECT kodi_id, kodi_fileid, kodi_pathid, parent_id, media_type,

View file

@ -1,4 +1,6 @@
from serverconnect import ServerConnect from __future__ import division, absolute_import, print_function, unicode_literals
from usersconnect import UsersConnect
from loginmanual import LoginManual from .serverconnect import ServerConnect
from servermanual import ServerManual from .usersconnect import UsersConnect
from .loginmanual import LoginManual
from .servermanual import ServerManual

View file

@ -1,12 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
################################################################################################## ##################################################################################################
import logging import logging
import os import os
import xbmcgui from kodi_six import xbmcgui, xbmcaddon
import xbmcaddon
from helper import window, addon_id from helper import window, addon_id

View file

@ -1,12 +1,13 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
################################################################################################## ##################################################################################################
import logging import logging
import os import os
import xbmcgui from six import iteritems
import xbmcaddon from kodi_six import xbmcgui, xbmcaddon
from helper import translate, addon_id from helper import translate, addon_id
@ -36,7 +37,7 @@ class LoginManual(xbmcgui.WindowXMLDialog):
def set_args(self, **kwargs): def set_args(self, **kwargs):
# connect_manager, user_image, servers # connect_manager, user_image, servers
for key, value in kwargs.iteritems(): for key, value in iteritems(kwargs):
setattr(self, key, value) setattr(self, key, value)
def is_logged_in(self): def is_logged_in(self):

View file

@ -1,11 +1,11 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
################################################################################################## ##################################################################################################
import logging import logging
import xbmc from kodi_six import xbmc, xbmcgui
import xbmcgui
################################################################################################## ##################################################################################################

View file

@ -1,11 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
################################################################################################## ##################################################################################################
import logging import logging
import xbmc from six import iteritems
import xbmcgui from kodi_six import xbmc, xbmcgui
from helper import translate from helper import translate
from jellyfin.connection_manager import CONNECTION_STATE from jellyfin.connection_manager import CONNECTION_STATE
@ -44,7 +45,7 @@ class ServerConnect(xbmcgui.WindowXMLDialog):
def set_args(self, **kwargs): def set_args(self, **kwargs):
# connect_manager, user_image, servers # connect_manager, user_image, servers
for key, value in kwargs.iteritems(): for key, value in iteritems(kwargs):
setattr(self, key, value) setattr(self, key, value)
def is_server_selected(self): def is_server_selected(self):

View file

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
################################################################################################## ##################################################################################################
@ -6,8 +7,8 @@ import logging
import os import os
import re import re
import xbmcgui from six import iteritems
import xbmcaddon from kodi_six import xbmcgui, xbmcaddon
from helper import translate, addon_id from helper import translate, addon_id
from jellyfin.connection_manager import CONNECTION_STATE from jellyfin.connection_manager import CONNECTION_STATE
@ -28,7 +29,7 @@ ERROR = {
} }
# https://stackoverflow.com/a/17871737/1035647 # 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) _IPV6_RE = re.compile(_IPV6_PATTERN)
################################################################################################## ##################################################################################################
@ -44,7 +45,7 @@ class ServerManual(xbmcgui.WindowXMLDialog):
def set_args(self, **kwargs): def set_args(self, **kwargs):
# connect_manager, user_image, servers, jellyfin_connect # connect_manager, user_image, servers, jellyfin_connect
for key, value in kwargs.iteritems(): for key, value in iteritems(kwargs):
setattr(self, key, value) setattr(self, key, value)
def is_connected(self): def is_connected(self):

View file

@ -1,11 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
################################################################################################## ##################################################################################################
import logging import logging
import xbmc from six import iteritems
import xbmcgui from kodi_six import xbmc, xbmcgui
################################################################################################## ##################################################################################################
@ -34,7 +35,7 @@ class UsersConnect(xbmcgui.WindowXMLDialog):
def set_args(self, **kwargs): def set_args(self, **kwargs):
# connect_manager, user_image, servers # connect_manager, user_image, servers
for key, value in kwargs.iteritems(): for key, value in iteritems(kwargs):
setattr(self, key, value) setattr(self, key, value)
def is_user_selected(self): def is_user_selected(self):

View file

@ -1,12 +1,14 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
################################################################################################# #################################################################################################
import logging import logging
import Queue
import threading import threading
import xbmc from six.moves import queue as Queue
from kodi_six import xbmc
import requests import requests
from helper import settings, stop, event, window, create_id from helper import settings, stop, event, window, create_id
from jellyfin import Jellyfin from jellyfin import Jellyfin

View file

@ -1,11 +1,11 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
################################################################################################# #################################################################################################
import logging import logging
import xbmc from kodi_six import xbmc, xbmcvfs
import xbmcvfs
from helper import loghandler from helper import loghandler
from jellyfin import Jellyfin from jellyfin import Jellyfin

View file

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
################################################################################################# #################################################################################################
@ -6,8 +7,7 @@ import json
import logging import logging
import sys import sys
import xbmc from kodi_six import xbmc, xbmcaddon
import xbmcaddon
import database import database
from dialogs import context from dialogs import context
@ -67,7 +67,7 @@ class Context(object):
elif self.select_menu(): elif self.select_menu():
self.action_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.sleep(500)
xbmc.executebuiltin('Container.Refresh') xbmc.executebuiltin('Container.Refresh')
@ -91,7 +91,7 @@ class Context(object):
else: else:
LOG.info("media is unknown") LOG.info("media is unknown")
return media.decode('utf-8') return media
def get_item_id(self): def get_item_id(self):
@ -140,7 +140,7 @@ class Context(object):
def action_menu(self): def action_menu(self):
selected = self._selected_option.decode('utf-8') selected = self._selected_option
if selected == OPTIONS['Refresh']: if selected == OPTIONS['Refresh']:
TheVoid('RefreshItem', {'ServerId': self.server, 'Id': self.item['Id']}) TheVoid('RefreshItem', {'ServerId': self.server, 'Id': self.item['Id']})

View file

@ -1,19 +1,15 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
################################################################################################# #################################################################################################
import json import json
import logging import logging
import sys import sys
import urlparse
import urllib
import os import os
import xbmc from six.moves.urllib.parse import parse_qsl, urlencode
import xbmcvfs from kodi_six import xbmc, xbmcvfs, xbmcgui, xbmcplugin, xbmcaddon
import xbmcgui
import xbmcplugin
import xbmcaddon
import client import client
from database import reset, get_sync, Database, jellyfin_db, get_credentials from database import reset, get_sync, Database, jellyfin_db, get_credentials
@ -39,7 +35,7 @@ class Events(object):
path = sys.argv[2] path = sys.argv[2]
try: try:
params = dict(urlparse.parse_qsl(path[1:])) params = dict(parse_qsl(path[1:]))
except Exception: except Exception:
params = {} params = {}
@ -146,7 +142,7 @@ def listing():
context = [] context = []
if view_id and node in ('movies', 'tvshows', 'musicvideos', 'music', 'mixed') and view_id not in whitelist: 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)) 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: 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'], 'folder': item['Id'],
'server': server_id 'server': server_id
} }
path = "%s?%s" % ("plugin://plugin.video.jellyfin/", urllib.urlencode(params)) path = "%s?%s" % ("plugin://plugin.video.jellyfin/", urlencode(params))
context = [] context = []
if item['Type'] in ('Series', 'Season', 'Playlist'): 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'], 'folder': 'genres-%s' % item['Id'],
'server': server_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)) list_li.append((path, li, True))
else: else:
@ -371,7 +367,7 @@ def browse(media, view_id=None, folder=None, server_id=None):
'mode': "play", 'mode': "play",
'server': server_id '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) li.setProperty('path', path)
context = [(translate(13412), "RunPlugin(plugin://plugin.video.jellyfin/?mode=playlist&id=%s&server=%s)" % (item['Id'], server_id))] 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], 'folder': view_id if node[0] == 'all' else node[0],
'server': server_id '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) directory(node[1] or view['Name'], path)
xbmcplugin.setContent(int(sys.argv[1]), 'files') xbmcplugin.setContent(int(sys.argv[1]), 'files')
@ -440,7 +436,7 @@ def browse_letters(media, view_id, server_id=None):
'folder': 'firstletter-%s' % node, 'folder': 'firstletter-%s' % node,
'server': server_id '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) directory(node, path)
xbmcplugin.setContent(int(sys.argv[1]), 'files') 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) LOG.info("[ extra fanart ] %s", item_id)
objects = Objects() objects = Objects()
list_li = [] 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() server = TheVoid('GetServerAddress', {'ServerId': server_id}).get()
if not xbmcvfs.exists(directory): if not xbmcvfs.exists(directory):
@ -520,7 +516,7 @@ def get_fanart(item_id, path, server_id=None):
dirs, files = xbmcvfs.listdir(directory) dirs, files = xbmcvfs.listdir(directory)
for file in files: 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) li = xbmcgui.ListItem(file, path=fanart)
list_li.append((fanart, li, False)) list_li.append((fanart, li, False))
@ -773,7 +769,7 @@ def get_themes():
from helper.playutils import PlayUtils from helper.playutils import PlayUtils
from helper.xmls import tvtunes_nfo 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" play = settings('useDirectPaths') == "1"
if not xbmcvfs.exists(library + '/'): if not xbmcvfs.exists(library + '/'):
@ -803,14 +799,14 @@ def get_themes():
for item in result['Items']: for item in result['Items']:
folder = normalize_string(item['Name'].encode('utf-8')) folder = normalize_string(item['Name'])
items[item['Id']] = folder items[item['Id']] = folder
result = TheVoid('GetThemes', {'Type': "Song", 'Id': view}).get() result = TheVoid('GetThemes', {'Type': "Song", 'Id': view}).get()
for item in result['Items']: for item in result['Items']:
folder = normalize_string(item['Name'].encode('utf-8')) folder = normalize_string(item['Name'])
items[item['Id']] = folder items[item['Id']] = folder
for item in items: for item in items:
@ -828,9 +824,9 @@ def get_themes():
putils = PlayUtils(theme, False, None, server, token) putils = PlayUtils(theme, False, None, server, token)
if play: if play:
paths.append(putils.direct_play(theme['MediaSources'][0]).encode('utf-8')) paths.append(putils.direct_play(theme['MediaSources'][0]))
else: 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) tvtunes_nfo(nfo_file, paths)
@ -868,7 +864,7 @@ def backup():
delete_folder(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_data = os.path.join(backup, "addon_data", "plugin.video.jellyfin")
destination_databases = os.path.join(backup, "Database") destination_databases = os.path.join(backup, "Database")
@ -883,18 +879,18 @@ def backup():
databases = Objects().objects 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])) xbmcvfs.copy(db, os.path.join(destination_databases, db.rsplit('\\', 1)[1]))
LOG.info("copied jellyfin.db") LOG.info("copied jellyfin.db")
db = xbmc.translatePath(databases['video']).decode('utf-8') db = xbmc.translatePath(databases['video'])
filename = db.rsplit('\\', 1)[1] filename = db.rsplit('\\', 1)[1]
xbmcvfs.copy(db, os.path.join(destination_databases, filename)) xbmcvfs.copy(db, os.path.join(destination_databases, filename))
LOG.info("copied %s", filename) LOG.info("copied %s", filename)
if settings('enableMusic.bool'): if settings('enableMusic.bool'):
db = xbmc.translatePath(databases['music']).decode('utf-8') db = xbmc.translatePath(databases['music'])
filename = db.rsplit('\\', 1)[1] filename = db.rsplit('\\', 1)[1]
xbmcvfs.copy(db, os.path.join(destination_databases, filename)) xbmcvfs.copy(db, os.path.join(destination_databases, filename))
LOG.info("copied %s", filename) LOG.info("copied %s", filename)

View file

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*- # -*- 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 # Workaround for threads using datetime: _striptime is locked
import _strptime # noqa:F401 import _strptime # noqa:F401
import xbmc from kodi_six import xbmc, xbmcgui
import xbmcgui from six.moves import reload_module as reload
import objects import objects
import connect import connect
@ -181,8 +182,8 @@ class Service(xbmc.Monitor):
if settings('connectMsg.bool'): if settings('connectMsg.bool'):
users = [user for user in (settings('additionalUsers') or "").decode('utf-8').split(',') if user] users = [user for user in (settings('additionalUsers') or "").split(',') if user]
users.insert(0, settings('username').decode('utf-8')) users.insert(0, settings('username'))
dialog("notification", heading="{jellyfin}", message="%s %s" % (translate(33000), ", ".join(users)), dialog("notification", heading="{jellyfin}", message="%s %s" % (translate(33000), ", ".join(users)),
icon="{jellyfin}", time=1500, sound=False) icon="{jellyfin}", time=1500, sound=False)

View file

@ -1,11 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
################################################################################################## ##################################################################################################
import datetime import datetime
import logging import logging
import xbmc from kodi_six import xbmc
import downloader as server import downloader as server
import helper.xmls as xmls import helper.xmls as xmls

View file

@ -1,27 +1,29 @@
from translate import translate from __future__ import division, absolute_import, print_function, unicode_literals
from exceptions import LibraryException
from utils import addon_id from .translate import translate
from utils import window from .exceptions import LibraryException
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 .utils import addon_id
from wrapper import catch from .utils import window
from wrapper import silent_catch from .utils import settings
from wrapper import stop from .utils import kodi_version
from wrapper import jellyfin_item from .utils import dialog
from wrapper import library_check 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 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
################################################################################################## ##################################################################################################

View file

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

View file

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
from __future__ import absolute_import from __future__ import absolute_import
from __future__ import print_function from __future__ import print_function
@ -9,8 +10,7 @@ import os
import logging import logging
import traceback import traceback
import xbmc from kodi_six import xbmc, xbmcaddon
import xbmcaddon
import database import database
from . import window, settings from . import window, settings
@ -18,7 +18,7 @@ from . import window, settings
################################################################################################## ##################################################################################################
__addon__ = xbmcaddon.Addon(id='plugin.video.jellyfin') __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: if self.mask_info:
for server in self.sensitive['Server']: 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']: 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: try:
xbmc.log(string, level=xbmc.LOGNOTICE) xbmc.log(string, level=xbmc.LOGNOTICE)
except UnicodeEncodeError: except UnicodeEncodeError:
xbmc.log(string.encode('utf-8'), level=xbmc.LOGNOTICE) xbmc.log(string, level=xbmc.LOGNOTICE)
@classmethod @classmethod
def _get_log_level(cls, level): def _get_log_level(cls, level):

View file

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
################################################################################################# #################################################################################################
@ -7,15 +8,13 @@ import os
from uuid import uuid4 from uuid import uuid4
import collections import collections
import xbmc from kodi_six import xbmc, xbmcvfs
import xbmcvfs
import api
import client import client
import requests import requests
from downloader import TheVoid 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) self.item['PlaybackInfo'].update(self.info)
API = api.API(self.item, self.info['ServerAddress']) 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): def live_stream(self, source):
@ -484,7 +483,7 @@ class PlayUtils(object):
LOG.info("[ subtitles/%s ] %s", index, url) LOG.info("[ subtitles/%s ] %s", index, url)
if 'Language' in stream: 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: try:
subs.append(self.download_external_subs(url, filename)) subs.append(self.download_external_subs(url, filename))
@ -506,7 +505,7 @@ class PlayUtils(object):
''' Download external subtitles to temp folder ''' Download external subtitles to temp folder
to be able to have proper names to streams. 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): if not xbmcvfs.exists(temp):
xbmcvfs.mkdir(temp) xbmcvfs.mkdir(temp)

View file

@ -1,11 +1,11 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
################################################################################################## ##################################################################################################
import logging import logging
import xbmc from kodi_six import xbmc, xbmcaddon
import xbmcaddon
################################################################################################## ##################################################################################################

View file

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
################################################################################################# #################################################################################################
@ -8,18 +9,16 @@ import logging
import os import os
import re import re
import unicodedata import unicodedata
import urllib
from uuid import uuid4 from uuid import uuid4
from distutils.version import LooseVersion from distutils.version import LooseVersion
from dateutil import tz, parser from dateutil import tz, parser
from six import text_type, string_types, iteritems
from six.moves.urllib.parse import quote_plus
import xbmc from kodi_six import xbmc, xbmcaddon, xbmcgui, xbmcvfs
import xbmcaddon
import xbmcgui
import xbmcvfs
from translate import translate from .translate import translate
################################################################################################# #################################################################################################
@ -122,7 +121,7 @@ def find(dict, item):
if item in dict: if item in dict:
return dict[item] 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): if re.match(key, item, re.I):
return dict[key] return dict[key]
@ -245,7 +244,7 @@ def validate(path):
if window('jellyfin_pathverified.bool'): if window('jellyfin_pathverified.bool'):
return True 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): if not xbmcvfs.exists(path):
LOG.info("Could not find %s", path) LOG.info("Could not find %s", path)
@ -291,14 +290,17 @@ def indent(elem, level=0):
def write_xml(content, file): 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 # replace apostrophes with double quotes only in xml keys, not texts
def replace_apostrophes(match): def replace_apostrophes(match):
return match.group(0).replace("'", '"') return match.group(0).replace(b"'", b'"')
content = re.sub("<(.*?)>", replace_apostrophes, content) content = re.sub(b"<(.*?)>", replace_apostrophes, content)
content = content.replace('?>', ' standalone="yes" ?>', 1) content = content.replace(b'?>', b' standalone="yes" ?>', 1)
infile.write(content) infile.write(content)
@ -312,7 +314,7 @@ def delete_folder(path):
delete_recursive(path, dirs) delete_recursive(path, dirs)
for file in files: for file in files:
xbmcvfs.delete(os.path.join(path, file.decode('utf-8'))) xbmcvfs.delete(os.path.join(path, file))
xbmcvfs.delete(path) xbmcvfs.delete(path)
@ -324,20 +326,20 @@ def delete_recursive(path, dirs):
''' Delete files and dirs recursively. ''' Delete files and dirs recursively.
''' '''
for directory in dirs: 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: 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) delete_recursive(os.path.join(path, directory), dirs2)
xbmcvfs.rmdir(os.path.join(path, directory.decode('utf-8'))) xbmcvfs.rmdir(os.path.join(path, directory))
def unzip(path, dest, folder=None): def unzip(path, dest, folder=None):
''' Unzip file. zipfile module seems to fail on android with badziperror. ''' Unzip file. zipfile module seems to fail on android with badziperror.
''' '''
path = urllib.quote_plus(path) path = quote_plus(path)
root = "zip://" + path + '/' root = "zip://" + path + '/'
if folder: if folder:
@ -352,7 +354,7 @@ def unzip(path, dest, folder=None):
unzip_recursive(root, dirs, dest) unzip_recursive(root, dirs, dest)
for file in files: 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) LOG.info("Unzipped %s", path)
@ -361,8 +363,8 @@ def unzip_recursive(path, dirs, dest):
for directory in dirs: for directory in dirs:
dirs_dir = os.path.join(path, directory.decode('utf-8')) dirs_dir = os.path.join(path, directory)
dest_dir = os.path.join(dest, directory.decode('utf-8')) dest_dir = os.path.join(dest, directory)
xbmcvfs.mkdir(dest_dir) xbmcvfs.mkdir(dest_dir)
dirs2, files = xbmcvfs.listdir(dirs_dir) dirs2, files = xbmcvfs.listdir(dirs_dir)
@ -371,7 +373,7 @@ def unzip_recursive(path, dirs, dest):
unzip_recursive(dirs_dir, dirs2, dest_dir) unzip_recursive(dirs_dir, dirs2, dest_dir)
for file in files: 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): def unzip_file(path, dest):
@ -390,7 +392,7 @@ def get_zip_directory(path, folder):
return os.path.join(path, folder) return os.path.join(path, folder)
for directory in dirs: 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: if result:
return result return result
@ -408,7 +410,7 @@ def copytree(path, dest):
copy_recursive(path, dirs, dest) copy_recursive(path, dirs, dest)
for file in files: 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) LOG.info("Copied %s", path)
@ -417,8 +419,8 @@ def copy_recursive(path, dirs, dest):
for directory in dirs: for directory in dirs:
dirs_dir = os.path.join(path, directory.decode('utf-8')) dirs_dir = os.path.join(path, directory)
dest_dir = os.path.join(dest, directory.decode('utf-8')) dest_dir = os.path.join(dest, directory)
xbmcvfs.mkdir(dest_dir) xbmcvfs.mkdir(dest_dir)
dirs2, files = xbmcvfs.listdir(dirs_dir) dirs2, files = xbmcvfs.listdir(dirs_dir)
@ -427,7 +429,7 @@ def copy_recursive(path, dirs, dest):
copy_recursive(dirs_dir, dirs2, dest_dir) copy_recursive(dirs_dir, dirs2, dest_dir)
for file in files: 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): def copy_file(path, dest):
@ -458,7 +460,7 @@ def normalize_string(text):
text = text.strip() text = text.strip()
text = text.rstrip('.') 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 return text
@ -475,7 +477,7 @@ def convert_to_local(date):
''' Convert the local datetime to local. ''' Convert the local datetime to local.
''' '''
try: 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.replace(tzinfo=tz.tzutc())
date = date.astimezone(tz.tzlocal()) date = date.astimezone(tz.tzlocal())
@ -485,6 +487,7 @@ def convert_to_local(date):
return str(date) return str(date)
def has_attribute(obj, name): def has_attribute(obj, name):
try: try:
object.__getattribute__(obj, name) object.__getattribute__(obj, name)

View file

@ -1,10 +1,11 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
################################################################################################# #################################################################################################
import logging import logging
import xbmcgui from kodi_six import xbmcgui
from .utils import should_stop from .utils import should_stop
from .exceptions import LibraryException from .exceptions import LibraryException

View file

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
################################################################################################# #################################################################################################
@ -6,7 +7,7 @@ import logging
import os import os
import xml.etree.ElementTree as etree import xml.etree.ElementTree as etree
import xbmc from kodi_six import xbmc
from . import translate, indent, write_xml, dialog, settings from . import translate, indent, write_xml, dialog, settings
@ -22,7 +23,7 @@ def sources():
''' Create master lock compatible sources. ''' Create master lock compatible sources.
Also add the kodi.jellyfin.media source. 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') file = os.path.join(path, 'sources.xml')
try: try:
@ -106,7 +107,7 @@ def advanced_settings():
if settings('useDirectPaths') != "0": if settings('useDirectPaths') != "0":
return return
path = xbmc.translatePath("special://profile/").decode('utf-8') path = xbmc.translatePath("special://profile/")
file = os.path.join(path, 'advancedsettings.xml') file = os.path.join(path, 'advancedsettings.xml')
try: try:

View file

@ -1,10 +1,11 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
################################################################################################# #################################################################################################
import logging import logging
from client import JellyfinClient from .client import JellyfinClient
from helper import has_attribute from helper import has_attribute
################################################################################################# #################################################################################################

View file

@ -1,4 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
def jellyfin_url(client, handler): def jellyfin_url(client, handler):
return "%s/%s" % (client.config.data['auth.server'], handler) return "%s/%s" % (client.config.data['auth.server'], handler)

View file

@ -1,14 +1,15 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
################################################################################################# #################################################################################################
import logging import logging
import api from . import api
from configuration import Config from .configuration import Config
from http import HTTP from .http import HTTP
from ws_client import WSClient from .ws_client import WSClient
from connection_manager import ConnectionManager, CONNECTION_STATE from .connection_manager import ConnectionManager, CONNECTION_STATE
################################################################################################# #################################################################################################

View file

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
''' This will hold all configs from the client. ''' This will hold all configs from the client.
Configuration set here will be used for the HTTP client. Configuration set here will be used for the HTTP client.

View file

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
################################################################################################# #################################################################################################
@ -11,8 +12,8 @@ from distutils.version import LooseVersion
import urllib3 import urllib3
from credentials import Credentials from .credentials import Credentials
from http import HTTP # noqa: I201,I100 from .http import HTTP # noqa: I201,I100
################################################################################################# #################################################################################################
@ -258,7 +259,7 @@ class ConnectionManager(object):
def _server_discovery(self): def _server_discovery(self):
MULTI_GROUP = ("<broadcast>", 7359) MULTI_GROUP = ("<broadcast>", 7359)
MESSAGE = "who is JellyfinServer?" MESSAGE = b"who is JellyfinServer?"
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.settimeout(1.0) # This controls the socket.timeout exception sock.settimeout(1.0) # This controls the socket.timeout exception

View file

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

View file

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

View file

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
################################################################################################# #################################################################################################
@ -7,7 +8,8 @@ import logging
import time import time
import requests import requests
from exceptions import HTTPException
from .exceptions import HTTPException
################################################################################################# #################################################################################################
@ -49,13 +51,13 @@ class HTTP(object):
if '{server}' in string: if '{server}' in string:
if self.config.data['auth.server']: 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: else:
raise Exception("Server address not set.") raise Exception("Server address not set.")
if '{UserId}'in string: if '{UserId}'in string:
if self.config.data['auth.user_id']: 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: else:
raise Exception("UserId is not set.") raise Exception("UserId is not set.")
@ -209,17 +211,17 @@ class HTTP(object):
def _authorization(self, data): def _authorization(self, data):
auth = "MediaBrowser " auth = "MediaBrowser "
auth += "Client=%s, " % self.config.data['app.name'].encode('utf-8') auth += "Client=%s, " % self.config.data['app.name']
auth += "Device=%s, " % self.config.data['app.device_name'].encode('utf-8') auth += "Device=%s, " % self.config.data['app.device_name']
auth += "DeviceId=%s, " % self.config.data['app.device_id'].encode('utf-8') auth += "DeviceId=%s, " % self.config.data['app.device_id']
auth += "Version=%s" % self.config.data['app.version'].encode('utf-8') auth += "Version=%s" % self.config.data['app.version']
data['headers'].update({'x-emby-authorization': auth}) data['headers'].update({'x-emby-authorization': auth})
if self.config.data.get('auth.token') and self.config.data.get('auth.user_id'): 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') auth += ', UserId=%s' % self.config.data['auth.user_id']
data['headers'].update({'x-emby-authorization': auth, 'X-MediaBrowser-Token': self.config.data['auth.token'].encode('utf-8')}) data['headers'].update({'x-emby-authorization': auth, 'X-MediaBrowser-Token': self.config.data['auth.token']})
return data return data

View file

@ -1,3 +1,4 @@
from __future__ import division, absolute_import, print_function, unicode_literals
""" """
websocket - WebSocket client library for Python websocket - WebSocket client library for Python
@ -33,7 +34,7 @@ except ImportError:
HAVE_SSL = False HAVE_SSL = False
from urlparse import urlparse
import os import os
import array import array
import struct import struct
@ -44,6 +45,10 @@ import threading
import time import time
import logging 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. websocket python client.
========================= =========================
@ -221,7 +226,7 @@ def create_connection(url, timeout=None, **options):
_MAX_INTEGER = (1 << 32) - 1 _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 _MAX_CHAR_BYTE = (1 << 8) - 1
# ref. Websocket gets an update, and it breaks stuff. # ref. Websocket gets an update, and it breaks stuff.
@ -304,7 +309,7 @@ class ABNF(object):
opcode: operation code. please see OPCODE_XXX. 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") data = data.encode("utf-8")
# mask must be set if send data from client # mask must be set if send data from client
return ABNF(1, 0, 0, 0, opcode, 1, data) return ABNF(1, 0, 0, 0, opcode, 1, data)
@ -321,14 +326,14 @@ class ABNF(object):
if length >= ABNF.LENGTH_63: if length >= ABNF.LENGTH_63:
raise ValueError("data is too long") 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: if length < ABNF.LENGTH_7:
frame_header += chr(self.mask << 7 | length) frame_header += int2byte(self.mask << 7 | length)
elif length < ABNF.LENGTH_16: 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) frame_header += struct.pack("!H", length)
else: else:
frame_header += chr(self.mask << 7 | 0x7f) frame_header += int2byte(self.mask << 7 | 0x7f)
frame_header += struct.pack("!Q", length) frame_header += struct.pack("!Q", length)
if not self.mask: if not self.mask:
@ -352,7 +357,7 @@ class ABNF(object):
""" """
_m = array.array("B", mask_key) _m = array.array("B", mask_key)
_d = array.array("B", data) _d = array.array("B", data)
for i in xrange(len(_d)): for i in range(len(_d)):
_d[i] ^= _m[i % 4] _d[i] ^= _m[i % 4]
return _d.tostring() return _d.tostring()
@ -475,31 +480,39 @@ class WebSocket(object):
self._handshake(hostname, port, resource, **options) self._handshake(hostname, port, resource, **options)
def _handshake(self, host, 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 = []
headers.append("GET %s HTTP/1.1" % resource) headers.append(b"GET %s HTTP/1.1" % resource)
headers.append("Upgrade: websocket") headers.append(b"Upgrade: websocket")
headers.append("Connection: Upgrade") headers.append(b"Connection: Upgrade")
if port == 80: if port == 80:
hostport = host hostport = host
else: else:
hostport = "%s:%d" % (host, port) hostport = b"%s:%d" % (host, port)
headers.append("Host: %s" % hostport) headers.append(b"Host: %s" % hostport)
if "origin" in options: if "origin" in options:
headers.append("Origin: %s" % options["origin"]) headers.append(b"Origin: %s" % options["origin"])
else: else:
headers.append("Origin: http://%s" % hostport) headers.append(b"Origin: http://%s" % hostport)
key = _create_sec_websocket_key() key = _create_sec_websocket_key()
headers.append("Sec-WebSocket-Key: %s" % key) headers.append(b"Sec-WebSocket-Key: %s" % key)
headers.append("Sec-WebSocket-Version: %s" % VERSION) headers.append(b"Sec-WebSocket-Version: %d" % VERSION)
if "header" in options: 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(b"")
headers.append("") headers.append(b"")
header_str = "\r\n".join(headers) header_str = b"\r\n".join(headers)
self._send(header_str) self._send(header_str)
if traceEnabled: if traceEnabled:
logger.debug("--- request header ---") logger.debug("--- request header ---")
@ -519,7 +532,7 @@ class WebSocket(object):
self.connected = True self.connected = True
def _validate_header(self, headers, key): 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) r = headers.get(k, None)
if not r: if not r:
return False return False
@ -653,13 +666,13 @@ class WebSocket(object):
# Header # Header
if self._frame_header is None: if self._frame_header is None:
self._frame_header = self._recv_strict(2) self._frame_header = self._recv_strict(2)
b1 = ord(self._frame_header[0]) b1 = indexbytes(self._frame_header, 0)
fin = b1 >> 7 & 1 fin = b1 >> 7 & 1
rsv1 = b1 >> 6 & 1 rsv1 = b1 >> 6 & 1
rsv2 = b1 >> 5 & 1 rsv2 = b1 >> 5 & 1
rsv3 = b1 >> 4 & 1 rsv3 = b1 >> 4 & 1
opcode = b1 & 0xf opcode = b1 & 0xf
b2 = ord(self._frame_header[1]) b2 = indexbytes(self._frame_header, 1)
has_mask = b2 >> 7 & 1 has_mask = b2 >> 7 & 1
# Frame length # Frame length
if self._frame_length is None: if self._frame_length is None:
@ -753,7 +766,7 @@ class WebSocket(object):
def _recv(self, bufsize): def _recv(self, bufsize):
try: try:
bytes = self.sock.recv(bufsize) _bytes = self.sock.recv(bufsize)
except socket.timeout as e: except socket.timeout as e:
raise WebSocketTimeoutException(e.args[0]) raise WebSocketTimeoutException(e.args[0])
except SSLError as e: except SSLError as e:
@ -761,16 +774,16 @@ class WebSocket(object):
raise WebSocketTimeoutException(e.args[0]) raise WebSocketTimeoutException(e.args[0])
else: else:
raise raise
if not bytes: if not _bytes:
raise WebSocketConnectionClosedException() raise WebSocketConnectionClosedException()
return bytes return _bytes
def _recv_strict(self, bufsize): def _recv_strict(self, bufsize):
shortage = bufsize - sum(len(x) for x in self._recv_buffer) shortage = bufsize - sum(len(x) for x in self._recv_buffer)
while shortage > 0: while shortage > 0:
bytes = self._recv(shortage) _bytes = self._recv(shortage)
self._recv_buffer.append(bytes) self._recv_buffer.append(_bytes)
shortage -= len(bytes) shortage -= len(_bytes)
unified = "".join(self._recv_buffer) unified = "".join(self._recv_buffer)
if shortage == 0: if shortage == 0:
self._recv_buffer = [] self._recv_buffer = []

View file

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
################################################################################################# #################################################################################################
@ -6,9 +7,9 @@ import json
import logging import logging
import threading import threading
import xbmc from kodi_six import xbmc
import websocket from . import websocket
################################################################################################## ##################################################################################################

View file

@ -1,14 +1,15 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
################################################################################################## ##################################################################################################
import logging import logging
import Queue
import threading import threading
from datetime import datetime, timedelta from datetime import datetime, timedelta
import xbmc from six.moves import queue as Queue
import xbmcgui
from kodi_six import xbmc, xbmcgui
from objects import Movies, TVShows, MusicVideos, Music from objects import Movies, TVShows, MusicVideos, Music
from database import Database, jellyfin_db, get_sync, save_sync from database import Database, jellyfin_db, get_sync, save_sync

View file

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
################################################################################################# #################################################################################################
@ -7,7 +8,7 @@ import json
import logging import logging
import threading import threading
import xbmc from kodi_six import xbmc
import connect import connect
import downloader import downloader
@ -291,7 +292,7 @@ class Monitor(xbmc.Monitor):
for additional in users: for additional in users:
for user in all_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) server.jellyfin.session_add_user(server.config.data['app.session'], user['Id'], True)
self.additional_users(server) self.additional_users(server)

View file

@ -1,11 +1,13 @@
from movies import Movies from __future__ import division, absolute_import, print_function, unicode_literals
from musicvideos import MusicVideos
from tvshows import TVShows from .movies import Movies
from music import Music from .musicvideos import MusicVideos
from obj import Objects from .tvshows import TVShows
from actions import Actions from .music import Music
from actions import PlaylistWorker from .obj import Objects
from actions import on_play, on_update, special_listener from .actions import Actions
import utils from .actions import PlaylistWorker
from .actions import on_play, on_update, special_listener
from . import utils
Objects().mapping() Objects().mapping()

View file

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
################################################################################################# #################################################################################################
@ -7,14 +8,11 @@ import threading
import sys import sys
from datetime import timedelta from datetime import timedelta
import xbmc from kodi_six import xbmc, xbmcgui, xbmcplugin, xbmcaddon
import xbmcgui
import xbmcplugin
import xbmcaddon
import database import database
from downloader import TheVoid from downloader import TheVoid
from obj import Objects from .obj import Objects
from helper import translate, playutils, api, window, settings, dialog from helper import translate, playutils, api, window, settings, dialog
from dialogs import resume from dialogs import resume

View file

@ -1,6 +1,8 @@
from kodi import Kodi from __future__ import division, absolute_import, print_function, unicode_literals
from movies import Movies
from musicvideos import MusicVideos from .kodi import Kodi
from tvshows import TVShows from .movies import Movies
from music import Music from .musicvideos import MusicVideos
from artwork import Artwork from .tvshows import TVShows
from .music import Music
from .artwork import Artwork

View file

@ -1,17 +1,18 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
################################################################################################# #################################################################################################
import logging import logging
import urllib
import Queue
import threading import threading
import xbmc from six.moves import queue as Queue
import xbmcvfs from six.moves.urllib.parse import urlencode
import queries as QU from kodi_six import xbmc, xbmcvfs
import queries_texture as QUTEX
from . import queries as QU
from . import queries_texture as QUTEX
from helper import settings from helper import settings
import requests import requests
@ -139,10 +140,10 @@ class Artwork(object):
''' urlencode needs a utf-string. ''' urlencode needs a utf-string.
return the result as unicode return the result as unicode
''' '''
text = urllib.urlencode({'blahblahblah': text.encode('utf-8')}) text = urlencode({'blahblahblah': text})
text = text[13:] text = text[13:]
return text.decode('utf-8') return text
def add_worker(self): def add_worker(self):
@ -170,7 +171,7 @@ class Artwork(object):
except TypeError: except TypeError:
LOG.debug("Could not find cached url: %s", url) LOG.debug("Could not find cached url: %s", url)
else: else:
thumbnails = xbmc.translatePath("special://thumbnails/%s" % cached).decode('utf-8') thumbnails = xbmc.translatePath("special://thumbnails/%s" % cached)
xbmcvfs.delete(thumbnails) xbmcvfs.delete(thumbnails)
texturedb.cursor.execute(QUTEX.delete_cache, (url,)) texturedb.cursor.execute(QUTEX.delete_cache, (url,))
LOG.info("DELETE cached %s", cached) LOG.info("DELETE cached %s", cached)

View file

@ -1,11 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
################################################################################################## ##################################################################################################
import logging import logging
import artwork from . import artwork
import queries as QU from . import queries as QU
from helper import values from helper import values
################################################################################################## ##################################################################################################

View file

@ -1,11 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
################################################################################################## ##################################################################################################
import logging import logging
from kodi import Kodi from .kodi import Kodi
import queries as QU from . import queries as QU
################################################################################################## ##################################################################################################

View file

@ -1,11 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
################################################################################################## ##################################################################################################
import logging import logging
import queries_music as QU from . import queries_music as QU
from kodi import Kodi from .kodi import Kodi
################################################################################################## ##################################################################################################

View file

@ -1,11 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
################################################################################################## ##################################################################################################
import logging import logging
import queries as QU from . import queries as QU
from kodi import Kodi 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. ''' 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 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 = """ create_artist = """
SELECT coalesce(max(idArtist), 1) SELECT coalesce(max(idArtist), 1)

View file

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

View file

@ -1,11 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
################################################################################################## ##################################################################################################
import logging import logging
import queries as QU from . import queries as QU
from kodi import Kodi from .kodi import Kodi
################################################################################################## ##################################################################################################

View file

@ -1,13 +1,14 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
################################################################################################## ##################################################################################################
import logging import logging
import urllib from six.moves.urllib.parse import urlencode
import downloader as server import downloader as server
from obj import Objects from .obj import Objects
from kodi import Movies as KodiDb, queries as QU from .kodi import Movies as KodiDb, queries as QU
from database import jellyfin_db, queries as QUEM from database import jellyfin_db, queries as QUEM
from helper import api, stop, validate, jellyfin_item, library_check, values, settings, Local from helper import api, stop, validate, jellyfin_item, library_check, values, settings, Local
@ -177,12 +178,12 @@ class Movies(KodiDb):
else: else:
obj['Path'] = "plugin://plugin.video.jellyfin/%s/" % obj['LibraryId'] obj['Path'] = "plugin://plugin.video.jellyfin/%s/" % obj['LibraryId']
params = { params = {
'filename': obj['Filename'].encode('utf-8'), 'filename': obj['Filename'],
'id': obj['Id'], 'id': obj['Id'],
'dbid': obj['MovieId'], 'dbid': obj['MovieId'],
'mode': "play" 'mode': "play"
} }
obj['Filename'] = "%s?%s" % (obj['Path'], urllib.urlencode(params)) obj['Filename'] = "%s?%s" % (obj['Path'], urlencode(params))
@stop() @stop()
@jellyfin_item() @jellyfin_item()

View file

@ -1,12 +1,13 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
################################################################################################## ##################################################################################################
import datetime import datetime
import logging import logging
from obj import Objects from .obj import Objects
from kodi import Music as KodiDb, queries_music as QU from .kodi import Music as KodiDb, queries_music as QU
from database import jellyfin_db, queries as QUEM from database import jellyfin_db, queries as QUEM
from helper import api, stop, validate, jellyfin_item, values, library_check, Local from helper import api, stop, validate, jellyfin_item, values, library_check, Local

View file

@ -1,14 +1,15 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
################################################################################################## ##################################################################################################
import datetime import datetime
import logging import logging
import re import re
import urllib from six.moves.urllib.parse import urlencode
from obj import Objects from .obj import Objects
from kodi import MusicVideos as KodiDb, queries as QU from .kodi import MusicVideos as KodiDb, queries as QU
from database import jellyfin_db, queries as QUEM from database import jellyfin_db, queries as QUEM
from helper import api, stop, validate, library_check, jellyfin_item, values, Local from helper import api, stop, validate, library_check, jellyfin_item, values, Local
@ -165,12 +166,12 @@ class MusicVideos(KodiDb):
else: else:
obj['Path'] = "plugin://plugin.video.jellyfin/%s/" % obj['LibraryId'] obj['Path'] = "plugin://plugin.video.jellyfin/%s/" % obj['LibraryId']
params = { params = {
'filename': obj['Filename'].encode('utf-8'), 'filename': obj['Filename'],
'id': obj['Id'], 'id': obj['Id'],
'dbid': obj['MvideoId'], 'dbid': obj['MvideoId'],
'mode': "play" 'mode': "play"
} }
obj['Filename'] = "%s?%s" % (obj['Path'], urllib.urlencode(params)) obj['Filename'] = "%s?%s" % (obj['Path'], urlencode(params))
@stop() @stop()
@jellyfin_item() @jellyfin_item()

View file

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
################################################################################################## ##################################################################################################
@ -6,6 +7,8 @@ import json
import logging import logging
import os import os
from six import iteritems
################################################################################################## ##################################################################################################
LOG = logging.getLogger("JELLYFIN." + __name__) LOG = logging.getLogger("JELLYFIN." + __name__)
@ -53,7 +56,7 @@ class Objects(object):
mapping = self.objects[mapping_name] mapping = self.objects[mapping_name]
for key, value in mapping.iteritems(): for key, value in iteritems(mapping):
self.mapped_item[key] = None self.mapped_item[key] = None
params = value.split(',') params = value.split(',')
@ -144,7 +147,7 @@ class Objects(object):
result = False result = False
for key, value in filters.iteritems(): for key, value in iteritems(filters):
inverse = False inverse = False

View file

@ -1,14 +1,16 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
################################################################################################## ##################################################################################################
import logging import logging
import sqlite3 import sqlite3
import urllib
from ntpath import dirname from ntpath import dirname
from obj import Objects from six.moves.urllib.parse import urlencode
from kodi import TVShows as KodiDb, queries as QU
from .obj import Objects
from .kodi import TVShows as KodiDb, queries as QU
import downloader as server import downloader as server
from database import jellyfin_db, queries as QUEM from database import jellyfin_db, queries as QUEM
from helper import api, stop, validate, jellyfin_item, library_check, settings, values, Local from helper import api, stop, validate, jellyfin_item, library_check, settings, values, Local
@ -392,12 +394,12 @@ class TVShows(KodiDb):
else: else:
obj['Path'] = "plugin://plugin.video.jellyfin/%s/" % obj['SeriesId'] obj['Path'] = "plugin://plugin.video.jellyfin/%s/" % obj['SeriesId']
params = { params = {
'filename': obj['Filename'].encode('utf-8'), 'filename': obj['Filename'],
'id': obj['Id'], 'id': obj['Id'],
'dbid': obj['EpisodeId'], 'dbid': obj['EpisodeId'],
'mode': "play" '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): def get_show_id(self, obj):
obj['ShowId'] = self.jellyfin_db.get_item_by_id(*values(obj, QUEM.get_item_series_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 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
################################################################################################# #################################################################################################

View file

@ -1,12 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
################################################################################################# #################################################################################################
import logging import logging
import os import os
import xbmc from kodi_six import xbmc, xbmcvfs
import xbmcvfs
from objects.obj import Objects from objects.obj import Objects
from helper import translate, api, window, settings, dialog, event, silent_catch, JSONRPC from helper import translate, api, window, settings, dialog, event, silent_catch, JSONRPC
@ -85,7 +85,7 @@ class Player(xbmc.Player):
return return
for item in items: for item in items:
if item['Path'] == current_file.decode('utf-8'): if item['Path'] == current_file:
items.pop(items.index(item)) items.pop(items.index(item))
break break
@ -412,13 +412,13 @@ class Player(xbmc.Player):
LOG.info("<[ transcode/%s ]", item['Id']) LOG.info("<[ transcode/%s ]", item['Id'])
item['Server'].jellyfin.close_transcode(item['DeviceId']) 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): if xbmcvfs.exists(path):
dirs, files = xbmcvfs.listdir(path) dirs, files = xbmcvfs.listdir(path)
for file in files: 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 {} result = item['Server'].jellyfin.get_item(item['Id']) or {}

View file

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

View file

@ -1,15 +1,15 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
################################################################################################# #################################################################################################
import logging import logging
import os import os
import shutil import shutil
import urllib
import xml.etree.ElementTree as etree import xml.etree.ElementTree as etree
import xbmc from six.moves.urllib.parse import urlencode
import xbmcvfs from kodi_six import xbmc, xbmcvfs
from database import Database, jellyfin_db, get_sync, save_sync from database import Database, jellyfin_db, get_sync, save_sync
from helper import translate, api, indent, write_xml, window, event 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. ''' 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): if not xbmcvfs.exists(node_path):
try: try:
shutil.copytree( shutil.copytree(
src=xbmc.translatePath("special://xbmc/system/library/video").decode('utf-8'), src=xbmc.translatePath("special://xbmc/system/library/video"),
dst=xbmc.translatePath("special://profile/library/video").decode('utf-8')) dst=xbmc.translatePath("special://profile/library/video"))
except Exception as error: except Exception as error:
LOG.warning(error) LOG.warning(error)
xbmcvfs.mkdir(node_path) xbmcvfs.mkdir(node_path)
@ -129,7 +129,7 @@ def verify_kodi_defaults():
indent(xml) indent(xml)
write_xml(etree.tostring(xml, 'UTF-8'), file) 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): if not xbmcvfs.exists(playlist_path):
xbmcvfs.mkdirs(playlist_path) xbmcvfs.mkdirs(playlist_path)
@ -223,8 +223,8 @@ class Views(object):
''' Set up playlists, video nodes, window prop. ''' Set up playlists, video nodes, window prop.
''' '''
node_path = xbmc.translatePath("special://profile/library/video").decode('utf-8') node_path = xbmc.translatePath("special://profile/library/video")
playlist_path = xbmc.translatePath("special://profile/playlists/video").decode('utf-8') playlist_path = xbmc.translatePath("special://profile/playlists/video")
index = 0 index = 0
with Database('jellyfin') as jellyfindb: with Database('jellyfin') as jellyfindb:
@ -779,16 +779,16 @@ class Views(object):
window_prop = "Jellyfin.nodes.%s" % index window_prop = "Jellyfin.nodes.%s" % index
window('%s.index' % window_prop, path.replace('all.xml', "")) # dir 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) window('%s.content' % window_prop, path)
elif node == 'browse': elif node == 'browse':
window_prop = "Jellyfin.nodes.%s" % index window_prop = "Jellyfin.nodes.%s" % index
window('%s.title' % window_prop, view['Name'].encode('utf-8')) window('%s.title' % window_prop, view['Name'])
else: else:
window_prop = "Jellyfin.nodes.%s.%s" % (index, node) 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.content' % window_prop, path)
window('%s.id' % window_prop, view['Id']) window('%s.id' % window_prop, view['Id'])
@ -831,17 +831,17 @@ class Views(object):
window_prop = "Jellyfin.wnodes.%s" % index window_prop = "Jellyfin.wnodes.%s" % index
window('%s.index' % window_prop, path.replace('all.xml', "")) # dir 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) window('%s.content' % window_prop, path)
elif node == 'browse': elif node == 'browse':
window_prop = "Jellyfin.wnodes.%s" % index 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) window('%s.content' % window_prop, path)
else: else:
window_prop = "Jellyfin.wnodes.%s.%s" % (index, node) 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.content' % window_prop, path)
window('%s.id' % window_prop, view['Id']) window('%s.id' % window_prop, view['Id'])
@ -881,7 +881,7 @@ class Views(object):
'mode': "nextepisodes", 'mode': "nextepisodes",
'limit': self.limit '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): def window_browse(self, view, node=None):
@ -896,7 +896,7 @@ class Views(object):
if node: if node:
params['folder'] = 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): def window_clear(self, name=None):
@ -932,23 +932,23 @@ class Views(object):
''' Remove all jellyfin playlists. ''' 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) _, files = xbmcvfs.listdir(path)
for file in files: for file in files:
if file.decode('utf-8').startswith('jellyfin'): if file.startswith('jellyfin'):
self.delete_playlist(os.path.join(path, file.decode('utf-8'))) self.delete_playlist(os.path.join(path, file))
def delete_playlist_by_id(self, view_id): def delete_playlist_by_id(self, view_id):
''' Remove playlist based based on 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) _, files = xbmcvfs.listdir(path)
for file in files: for file in files:
file = file.decode('utf-8') file = file
if file.startswith('jellyfin') and file.endswith('%s.xsp' % view_id): 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): def delete_node(self, path):
@ -959,37 +959,37 @@ class Views(object):
''' Remove node and children files. ''' 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) dirs, files = xbmcvfs.listdir(path)
for file in files: for file in files:
if file.startswith('jellyfin'): 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: for directory in dirs:
if directory.startswith('jellyfin'): 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: 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): def delete_node_by_id(self, view_id):
''' Remove node and children files based on 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) dirs, files = xbmcvfs.listdir(path)
for directory in dirs: for directory in dirs:
if directory.startswith('jellyfin') and directory.endswith(view_id): 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: 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 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
################################################################################################# #################################################################################################
import BaseHTTPServer from six.moves import BaseHTTPServer
import logging import logging
import httplib from six.moves import http_client as httplib
import threading 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: if '?' in path:
path = path.split('?', 1)[1] path = path.split('?', 1)[1]
params = dict(urlparse.parse_qsl(path)) params = dict(parse_qsl(path))
except Exception: except Exception:
params = {} params = {}

View file

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
################################################################################################# #################################################################################################
@ -7,13 +8,12 @@ import os
import threading import threading
import sys import sys
import xbmc from kodi_six import xbmc, xbmcaddon
import xbmcaddon
################################################################################################# #################################################################################################
__addon__ = xbmcaddon.Addon(id='plugin.video.jellyfin') __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__) sys.path.insert(0, __base__)
@ -54,11 +54,11 @@ class ServiceManager(threading.Thread):
LOG.exception(error) LOG.exception(error)
if service is not None: if service is not None:
# TODO: fix this properly as to not match on str()
if 'ExitService' not in error: if 'ExitService' not in str(error):
service.shutdown() service.shutdown()
if 'RestartService' in error: if 'RestartService' in str(error):
service.reload_objects() service.reload_objects()
self.exception = error self.exception = error
@ -79,7 +79,7 @@ if __name__ == "__main__":
session.start() session.start()
session.join() # Block until the thread exits. session.join() # Block until the thread exits.
if 'RestartService' in session.exception: if 'RestartService' in str(session.exception):
continue continue
except Exception as error: except Exception as error: