diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
new file mode 100644
index 000000000..72cc270d2
--- /dev/null
+++ b/.git-blame-ignore-revs
@@ -0,0 +1,2 @@
+# Tool: black
+77637622125a187c5b9cbe72b78c8bd3b26f754a
diff --git a/build.py b/build.py
index bf4495240..148441f36 100755
--- a/build.py
+++ b/build.py
@@ -35,46 +35,48 @@ def create_addon_xml(config: dict, source: str, py_version: str) -> None:
Create addon.xml from template file
"""
# Load template file
- with open('{}/.build/template.xml'.format(source), 'r') as f:
+ with open("{}/.build/template.xml".format(source), "r") as f:
tree = ET.parse(f)
root = tree.getroot()
# Populate dependencies in template
- dependencies = config['dependencies'].get(py_version)
+ dependencies = config["dependencies"].get(py_version)
for dep in dependencies:
- ET.SubElement(root.find('requires'), 'import', attrib=dep)
+ ET.SubElement(root.find("requires"), "import", attrib=dep)
# Populate version string
- addon_version = config.get('version')
- root.attrib['version'] = '{}+{}'.format(addon_version, py_version)
+ addon_version = config.get("version")
+ root.attrib["version"] = "{}+{}".format(addon_version, py_version)
# Populate Changelog
- date = datetime.today().strftime('%Y-%m-%d')
- changelog = config.get('changelog')
- for section in root.findall('extension'):
- news = section.findall('news')
+ date = datetime.today().strftime("%Y-%m-%d")
+ changelog = config.get("changelog")
+ for section in root.findall("extension"):
+ news = section.findall("news")
if news:
- news[0].text = 'v{} ({}):\n{}'.format(addon_version, date, changelog)
+ news[0].text = "v{} ({}):\n{}".format(addon_version, date, changelog)
# Format xml tree
indent(root)
# Write addon.xml
- tree.write('{}/addon.xml'.format(source), encoding='utf-8', xml_declaration=True)
+ tree.write("{}/addon.xml".format(source), encoding="utf-8", xml_declaration=True)
def zip_files(py_version: str, source: str, target: str, dev: bool) -> None:
"""
Create installable addon zip archive
"""
- archive_name = 'plugin.video.jellyfin+{}.zip'.format(py_version)
+ archive_name = "plugin.video.jellyfin+{}.zip".format(py_version)
- with zipfile.ZipFile('{}/{}'.format(target, archive_name), 'w') as z:
+ with zipfile.ZipFile("{}/{}".format(target, archive_name), "w") as z:
for root, dirs, files in os.walk(args.source):
for filename in filter(file_filter, files):
file_path = os.path.join(root, filename)
if dev or folder_filter(file_path):
- relative_path = os.path.join('plugin.video.jellyfin', os.path.relpath(file_path, source))
+ relative_path = os.path.join(
+ "plugin.video.jellyfin", os.path.relpath(file_path, source)
+ )
z.write(file_path, relative_path)
@@ -83,10 +85,12 @@ def file_filter(file_name: str) -> bool:
True if file_name is meant to be included
"""
return (
- not (file_name.startswith('plugin.video.jellyfin') and file_name.endswith('.zip'))
- and not file_name.endswith('.pyo')
- and not file_name.endswith('.pyc')
- and not file_name.endswith('.pyd')
+ not (
+ file_name.startswith("plugin.video.jellyfin") and file_name.endswith(".zip")
+ )
+ and not file_name.endswith(".pyo")
+ and not file_name.endswith(".pyc")
+ and not file_name.endswith(".pyd")
)
@@ -95,13 +99,13 @@ def folder_filter(folder_name: str) -> bool:
True if folder_name is meant to be included
"""
filters = [
- '.ci',
- '.git',
- '.github',
- '.build',
- '.mypy_cache',
- '.pytest_cache',
- '__pycache__',
+ ".ci",
+ ".git",
+ ".github",
+ ".build",
+ ".mypy_cache",
+ ".pytest_cache",
+ "__pycache__",
]
for f in filters:
if f in folder_name.split(os.path.sep):
@@ -110,33 +114,22 @@ def folder_filter(folder_name: str) -> bool:
return True
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser(description="Build flags:")
+ parser.add_argument("--version", type=str, choices=("py2", "py3"), default="py3")
-if __name__ == '__main__':
- parser = argparse.ArgumentParser(description='Build flags:')
- parser.add_argument(
- '--version',
- type=str,
- choices=('py2', 'py3'),
- default='py3')
+ parser.add_argument("--source", type=Path, default=Path(__file__).absolute().parent)
- parser.add_argument(
- '--source',
- type=Path,
- default=Path(__file__).absolute().parent)
+ parser.add_argument("--target", type=Path, default=Path(__file__).absolute().parent)
- parser.add_argument(
- '--target',
- type=Path,
- default=Path(__file__).absolute().parent)
-
- parser.add_argument('--dev', dest='dev', action='store_true')
+ parser.add_argument("--dev", dest="dev", action="store_true")
parser.set_defaults(dev=False)
args = parser.parse_args()
# Load config file
- config_path = os.path.join(args.source, 'release.yaml')
- with open(config_path, 'r') as fh:
+ config_path = os.path.join(args.source, "release.yaml")
+ with open(config_path, "r") as fh:
release_config = yaml.safe_load(fh)
create_addon_xml(release_config, args.source, args.version)
diff --git a/jellyfin_kodi/client.py b/jellyfin_kodi/client.py
index 3aac72cbc..e38e52f7c 100644
--- a/jellyfin_kodi/client.py
+++ b/jellyfin_kodi/client.py
@@ -18,73 +18,69 @@
def get_addon_name():
-
- ''' Used for logging.
- '''
- return xbmcaddon.Addon(addon_id()).getAddonInfo('name').upper()
+ """Used for logging."""
+ return xbmcaddon.Addon(addon_id()).getAddonInfo("name").upper()
def get_version():
- return xbmcaddon.Addon(addon_id()).getAddonInfo('version')
+ return xbmcaddon.Addon(addon_id()).getAddonInfo("version")
def get_platform():
- if xbmc.getCondVisibility('system.platform.osx'):
+ if xbmc.getCondVisibility("system.platform.osx"):
return "OSX"
- elif xbmc.getCondVisibility('System.HasAddon(service.coreelec.settings)'):
+ elif xbmc.getCondVisibility("System.HasAddon(service.coreelec.settings)"):
return "CoreElec"
- elif xbmc.getCondVisibility('System.HasAddon(service.libreelec.settings)'):
+ elif xbmc.getCondVisibility("System.HasAddon(service.libreelec.settings)"):
return "LibreElec"
- elif xbmc.getCondVisibility('System.HasAddon(service.osmc.settings)'):
+ elif xbmc.getCondVisibility("System.HasAddon(service.osmc.settings)"):
return "OSMC"
- elif xbmc.getCondVisibility('system.platform.atv2'):
+ elif xbmc.getCondVisibility("system.platform.atv2"):
return "ATV2"
- elif xbmc.getCondVisibility('system.platform.ios'):
+ elif xbmc.getCondVisibility("system.platform.ios"):
return "iOS"
- elif xbmc.getCondVisibility('system.platform.windows'):
+ elif xbmc.getCondVisibility("system.platform.windows"):
return "Windows"
- elif xbmc.getCondVisibility('system.platform.android'):
+ elif xbmc.getCondVisibility("system.platform.android"):
return "Linux/Android"
- elif xbmc.getCondVisibility('system.platform.linux.raspberrypi'):
+ elif xbmc.getCondVisibility("system.platform.linux.raspberrypi"):
return "Linux/RPi"
- elif xbmc.getCondVisibility('system.platform.linux'):
+ elif xbmc.getCondVisibility("system.platform.linux"):
return "Linux"
else:
return "Unknown"
def get_device_name():
-
- ''' Detect the device name. If deviceNameOpt, then
- use the device name in the add-on settings.
- Otherwise, fallback to the Kodi device name.
- '''
- if not settings('deviceNameOpt.bool'):
- device_name = xbmc.getInfoLabel('System.FriendlyName')
+ """Detect the device name. If deviceNameOpt, then
+ use the device name in the add-on settings.
+ Otherwise, fallback to the Kodi device name.
+ """
+ if not settings("deviceNameOpt.bool"):
+ device_name = xbmc.getInfoLabel("System.FriendlyName")
else:
- device_name = settings('deviceName')
- device_name = device_name.replace("\"", "_")
+ device_name = settings("deviceName")
+ device_name = device_name.replace('"', "_")
device_name = device_name.replace("/", "_")
return device_name
def get_device_id(reset=False):
+ """Return the device_id if already loaded.
+ It will load from jellyfin_guid file. If it's a fresh
+ setup, it will generate a new GUID to uniquely
+ identify the setup for all users.
- ''' Return the device_id if already loaded.
- It will load from jellyfin_guid file. If it's a fresh
- setup, it will generate a new GUID to uniquely
- identify the setup for all users.
-
- window prop: jellyfin_deviceId
- '''
- client_id = window('jellyfin_deviceId')
+ window prop: jellyfin_deviceId
+ """
+ client_id = window("jellyfin_deviceId")
if client_id:
return client_id
- directory = translate_path('special://profile/addon_data/plugin.video.jellyfin/')
+ directory = translate_path("special://profile/addon_data/plugin.video.jellyfin/")
if not xbmcvfs.exists(directory):
xbmcvfs.mkdir(directory)
@@ -97,27 +93,27 @@ def get_device_id(reset=False):
LOG.debug("Generating a new GUID.")
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.close()
LOG.debug("DeviceId loaded: %s", client_id)
- window('jellyfin_deviceId', value=client_id)
+ window("jellyfin_deviceId", value=client_id)
return client_id
def reset_device_id():
- window('jellyfin_deviceId', clear=True)
+ window("jellyfin_deviceId", clear=True)
get_device_id(True)
dialog("ok", "{jellyfin}", translate(33033))
- xbmc.executebuiltin('RestartApp')
+ xbmc.executebuiltin("RestartApp")
def get_info():
return {
- 'DeviceName': get_device_name(),
- 'Version': get_version(),
- 'DeviceId': get_device_id()
+ "DeviceName": get_device_name(),
+ "Version": get_version(),
+ "DeviceId": get_device_id(),
}
diff --git a/jellyfin_kodi/connect.py b/jellyfin_kodi/connect.py
index 7566ce909..8a3f2b1db 100644
--- a/jellyfin_kodi/connect.py
+++ b/jellyfin_kodi/connect.py
@@ -16,7 +16,7 @@
##################################################################################################
LOG = LazyLogger(__name__)
-XML_PATH = (xbmcaddon.Addon(addon_id()).getAddonInfo('path'), "default", "1080i")
+XML_PATH = (xbmcaddon.Addon(addon_id()).getAddonInfo("path"), "default", "1080i")
##################################################################################################
@@ -27,36 +27,37 @@ def __init__(self):
self.info = client.get_info()
def register(self, server_id=None, options={}):
-
- ''' Login into server. If server is None, then it will show the proper prompts to login, etc.
- If a server id is specified then only a login dialog will be shown for that server.
- '''
- LOG.info("--[ server/%s ]", server_id or 'default')
+ """Login into server. If server is None, then it will show the proper prompts to login, etc.
+ If a server id is specified then only a login dialog will be shown for that server.
+ """
+ LOG.info("--[ server/%s ]", server_id or "default")
credentials = dict(get_credentials())
- servers = credentials['Servers']
+ servers = credentials["Servers"]
- if server_id is None and credentials['Servers']:
- credentials['Servers'] = [credentials['Servers'][0]]
+ if server_id is None and credentials["Servers"]:
+ credentials["Servers"] = [credentials["Servers"][0]]
- elif credentials['Servers']:
+ elif credentials["Servers"]:
- for server in credentials['Servers']:
+ for server in credentials["Servers"]:
- if server['Id'] == server_id:
- credentials['Servers'] = [server]
+ if server["Id"] == server_id:
+ credentials["Servers"] = [server]
- server_select = server_id is None and not settings('SyncInstallRunDone.bool')
- new_credentials = self.register_client(credentials, options, server_id, server_select)
+ server_select = server_id is None and not settings("SyncInstallRunDone.bool")
+ new_credentials = self.register_client(
+ credentials, options, server_id, server_select
+ )
for server in servers:
- if server['Id'] == new_credentials['Servers'][0]['Id']:
- server = new_credentials['Servers'][0]
+ if server["Id"] == new_credentials["Servers"][0]["Id"]:
+ server = new_credentials["Servers"][0]
break
else:
- servers = new_credentials['Servers']
+ servers = new_credentials["Servers"]
- credentials['Servers'] = servers
+ credentials["Servers"] = servers
save_credentials(credentials)
try:
@@ -65,36 +66,39 @@ def register(self, server_id=None, options={}):
LOG.error(error)
def get_ssl(self):
-
- ''' Returns boolean value.
- True: verify connection.
- '''
- return settings('sslverify.bool')
+ """Returns boolean value.
+ True: verify connection.
+ """
+ return settings("sslverify.bool")
def get_client(self, server_id=None):
-
- ''' Get Jellyfin client.
- '''
+ """Get Jellyfin client."""
client = Jellyfin(server_id)
- client.config.app("Kodi", self.info['Version'], self.info['DeviceName'], self.info['DeviceId'])
- client.config.data['http.user_agent'] = "Jellyfin-Kodi/%s" % self.info['Version']
- client.config.data['auth.ssl'] = self.get_ssl()
+ client.config.app(
+ "Kodi", self.info["Version"], self.info["DeviceName"], self.info["DeviceId"]
+ )
+ client.config.data["http.user_agent"] = (
+ "Jellyfin-Kodi/%s" % self.info["Version"]
+ )
+ client.config.data["auth.ssl"] = self.get_ssl()
return client
- def register_client(self, credentials=None, options=None, server_id=None, server_selection=False):
+ def register_client(
+ self, credentials=None, options=None, server_id=None, server_selection=False
+ ):
client = self.get_client(server_id)
self.client = client
self.connect_manager = client.auth
if server_id is None:
- client.config.data['app.default'] = True
+ client.config.data["app.default"] = True
try:
state = client.authenticate(credentials or {}, options or {})
- if state['State'] == CONNECTION_STATE['SignedIn']:
+ if state["State"] == CONNECTION_STATE["SignedIn"]:
client.callback_ws = event
if server_id is None: # Only assign for default server
@@ -102,66 +106,77 @@ def register_client(self, credentials=None, options=None, server_id=None, server
client.callback = event
self.get_user(client)
- settings('serverName', client.config.data['auth.server-name'])
- settings('server', client.config.data['auth.server'])
+ settings("serverName", client.config.data["auth.server-name"])
+ settings("server", client.config.data["auth.server"])
- event('ServerOnline', {'ServerId': server_id})
- event('LoadServer', {'ServerId': server_id})
+ event("ServerOnline", {"ServerId": server_id})
+ event("LoadServer", {"ServerId": server_id})
- return state['Credentials']
+ return state["Credentials"]
- elif (server_selection or state['State'] == CONNECTION_STATE['ServerSelection'] or state['State'] == CONNECTION_STATE['Unavailable'] and not settings('SyncInstallRunDone.bool')):
- state['Credentials']['Servers'] = [self.select_servers(state)]
+ elif (
+ server_selection
+ or state["State"] == CONNECTION_STATE["ServerSelection"]
+ or state["State"] == CONNECTION_STATE["Unavailable"]
+ and not settings("SyncInstallRunDone.bool")
+ ):
+ state["Credentials"]["Servers"] = [self.select_servers(state)]
- elif state['State'] == CONNECTION_STATE['ServerSignIn']:
- if 'ExchangeToken' not in state['Servers'][0]:
+ elif state["State"] == CONNECTION_STATE["ServerSignIn"]:
+ if "ExchangeToken" not in state["Servers"][0]:
self.login()
- elif state['State'] == CONNECTION_STATE['Unavailable'] and state.get('Status_Code', 0) == 401:
+ elif (
+ state["State"] == CONNECTION_STATE["Unavailable"]
+ and state.get("Status_Code", 0) == 401
+ ):
# If the saved credentials don't work, restart the addon to force the password dialog to open
- window('jellyfin.restart', clear=True)
+ window("jellyfin.restart", clear=True)
- elif state['State'] == CONNECTION_STATE['Unavailable']:
- raise HTTPException('ServerUnreachable', {})
+ elif state["State"] == CONNECTION_STATE["Unavailable"]:
+ raise HTTPException("ServerUnreachable", {})
- return self.register_client(state['Credentials'], options, server_id, False)
+ return self.register_client(state["Credentials"], options, server_id, False)
except RuntimeError as error:
LOG.exception(error)
- xbmc.executebuiltin('Addon.OpenSettings(%s)' % addon_id())
+ xbmc.executebuiltin("Addon.OpenSettings(%s)" % addon_id())
- raise Exception('User sign in interrupted')
+ raise Exception("User sign in interrupted")
except HTTPException as error:
- if error.status == 'ServerUnreachable':
- event('ServerUnreachable', {'ServerId': server_id})
+ if error.status == "ServerUnreachable":
+ event("ServerUnreachable", {"ServerId": server_id})
return client.get_credentials()
def get_user(self, client):
-
- ''' Save user info.
- '''
+ """Save user info."""
self.user = client.jellyfin.get_user()
- settings('username', self.user['Name'])
+ settings("username", self.user["Name"])
- if 'PrimaryImageTag' in self.user:
- server_address = client.auth.get_server_info(client.auth.server_id)['address']
- window('JellyfinUserImage', api.API(self.user, server_address).get_user_artwork(self.user['Id']))
+ if "PrimaryImageTag" in self.user:
+ server_address = client.auth.get_server_info(client.auth.server_id)[
+ "address"
+ ]
+ window(
+ "JellyfinUserImage",
+ api.API(self.user, server_address).get_user_artwork(self.user["Id"]),
+ )
def select_servers(self, state=None):
- state = state or self.connect_manager.connect({'enableAutoLogin': False})
+ state = state or self.connect_manager.connect({"enableAutoLogin": False})
user = {}
dialog = ServerConnect("script-jellyfin-connect-server.xml", *XML_PATH)
dialog.set_args(
connect_manager=self.connect_manager,
- username=user.get('DisplayName', ""),
- user_image=user.get('ImageUrl'),
- servers=self.connect_manager.get_available_servers()
+ username=user.get("DisplayName", ""),
+ user_image=user.get("ImageUrl"),
+ servers=self.connect_manager.get_available_servers(),
)
dialog.doModal()
@@ -182,9 +197,7 @@ def select_servers(self, state=None):
return self.select_servers()
def setup_manual_server(self):
-
- ''' Setup manual servers
- '''
+ """Setup manual servers"""
client = self.get_client()
client.set_credentials(get_credentials())
manager = client.auth
@@ -198,11 +211,9 @@ def setup_manual_server(self):
save_credentials(credentials)
def manual_server(self, manager=None):
-
- ''' Return server or raise error.
- '''
+ """Return server or raise error."""
dialog = ServerManual("script-jellyfin-connect-server-manual.xml", *XML_PATH)
- dialog.set_args(**{'connect_manager': manager or self.connect_manager})
+ dialog.set_args(**{"connect_manager": manager or self.connect_manager})
dialog.doModal()
if dialog.is_connected():
@@ -213,7 +224,9 @@ def manual_server(self, manager=None):
def login(self):
users = self.connect_manager.get_public_users()
- server = self.connect_manager.get_server_info(self.connect_manager.server_id)['address']
+ server = self.connect_manager.get_server_info(self.connect_manager.server_id)[
+ "address"
+ ]
if not users:
try:
@@ -222,14 +235,14 @@ def login(self):
raise RuntimeError("No user selected")
dialog = UsersConnect("script-jellyfin-connect-users.xml", *XML_PATH)
- dialog.set_args(**{'server': server, 'users': users})
+ dialog.set_args(**{"server": server, "users": users})
dialog.doModal()
if dialog.is_user_selected():
user = dialog.get_user()
- username = user['Name']
+ username = user["Name"]
- if user['HasPassword']:
+ if user["HasPassword"]:
LOG.debug("User has password, present manual login")
try:
return self.login_manual(username)
@@ -249,14 +262,12 @@ def login(self):
return self.login()
def setup_login_manual(self):
-
- ''' Setup manual login by itself for default server.
- '''
+ """Setup manual login by itself for default server."""
client = self.get_client()
client.set_credentials(get_credentials())
manager = client.auth
- username = settings('username')
+ username = settings("username")
try:
self.login_manual(user=username, manager=manager)
except RuntimeError:
@@ -266,11 +277,14 @@ def setup_login_manual(self):
save_credentials(credentials)
def login_manual(self, user=None, manager=None):
-
- ''' Return manual login user authenticated or raise error.
- '''
+ """Return manual login user authenticated or raise error."""
dialog = LoginManual("script-jellyfin-connect-login-manual.xml", *XML_PATH)
- dialog.set_args(**{'connect_manager': manager or self.connect_manager, 'username': user or {}})
+ dialog.set_args(
+ **{
+ "connect_manager": manager or self.connect_manager,
+ "username": user or {},
+ }
+ )
dialog.doModal()
if dialog.is_logged_in():
@@ -279,15 +293,13 @@ def login_manual(self, user=None, manager=None):
raise RuntimeError("User is not authenticated")
def remove_server(self, server_id):
-
- ''' Stop client and remove server.
- '''
+ """Stop client and remove server."""
Jellyfin(server_id).close()
credentials = get_credentials()
- for server in credentials['Servers']:
- if server['Id'] == server_id:
- credentials['Servers'].remove(server)
+ for server in credentials["Servers"]:
+ if server["Id"] == server_id:
+ credentials["Servers"].remove(server)
break
diff --git a/jellyfin_kodi/database/__init__.py b/jellyfin_kodi/database/__init__.py
index e48716679..b92ff1892 100644
--- a/jellyfin_kodi/database/__init__.py
+++ b/jellyfin_kodi/database/__init__.py
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
+
#################################################################################################
import datetime
@@ -28,51 +29,56 @@
class Database(object):
+ """This should be called like a context.
+ i.e. with Database('jellyfin') as db:
+ db.cursor
+ db.conn.commit()
+ """
- ''' This should be called like a context.
- i.e. with Database('jellyfin') as db:
- db.cursor
- db.conn.commit()
- '''
timeout = 120
discovered = False
discovered_file = None
def __init__(self, db_file=None, commit_close=True):
-
- ''' file: jellyfin, texture, music, video, :memory: or path to file
- '''
+ """file: jellyfin, texture, music, video, :memory: or path to file"""
self.db_file = db_file or "video"
self.commit_close = commit_close
def __enter__(self):
-
- ''' Open the connection and return the Database class.
- This is to allow for the cursor, conn and others to be accessible.
- '''
+ """Open the connection and return the Database class.
+ This is to allow for the cursor, conn and others to be accessible.
+ """
self.path = self._sql(self.db_file)
self.conn = sqlite3.connect(self.path, timeout=self.timeout)
self.cursor = self.conn.cursor()
- if self.db_file in ('video', 'music', 'texture', 'jellyfin'):
- self.conn.execute("PRAGMA journal_mode=WAL") # to avoid writing conflict with kodi
+ if self.db_file in ("video", "music", "texture", "jellyfin"):
+ self.conn.execute(
+ "PRAGMA journal_mode=WAL"
+ ) # to avoid writing conflict with kodi
LOG.debug("--->[ database: %s ] %s", self.db_file, id(self.conn))
- if not window('jellyfin_db_check.bool') and self.db_file == 'jellyfin':
+ if not window("jellyfin_db_check.bool") and self.db_file == "jellyfin":
- window('jellyfin_db_check.bool', True)
+ window("jellyfin_db_check.bool", True)
jellyfin_tables(self.cursor)
self.conn.commit()
# Migration for #162
- if self.db_file == 'music':
- query = self.conn.execute('SELECT * FROM path WHERE strPath LIKE "%/emby/%"')
+ if self.db_file == "music":
+ query = self.conn.execute(
+ 'SELECT * FROM path WHERE strPath LIKE "%/emby/%"'
+ )
contents = query.fetchall()
if contents:
for item in contents:
- new_path = item[1].replace('/emby/', '/')
- self.conn.execute('UPDATE path SET strPath = "{}" WHERE idPath = "{}"'.format(new_path, item[0]))
+ new_path = item[1].replace("/emby/", "/")
+ self.conn.execute(
+ 'UPDATE path SET strPath = "{}" WHERE idPath = "{}"'.format(
+ new_path, item[0]
+ )
+ )
return self
@@ -97,68 +103,68 @@ def _get_database(self, path, silent=False):
return path
def _discover_database(self, database):
+ """Use UpdateLibrary(video) to update the date modified
+ on the database file used by Kodi.
+ """
+ if database == "video":
- ''' Use UpdateLibrary(video) to update the date modified
- on the database file used by Kodi.
- '''
- if database == 'video':
-
- xbmc.executebuiltin('UpdateLibrary(video)')
+ xbmc.executebuiltin("UpdateLibrary(video)")
xbmc.sleep(200)
databases = translate_path("special://database/")
- types = {
- 'video': "MyVideos",
- 'music': "MyMusic",
- 'texture': "Textures"
- }
+ types = {"video": "MyVideos", "music": "MyMusic", "texture": "Textures"}
database = types[database]
dirs, files = xbmcvfs.listdir(databases)
- target = {'db_file': '', 'version': 0}
+ target = {"db_file": "", "version": 0}
for db_file in reversed(files):
- if (db_file.startswith(database)
- and not db_file.endswith('-wal')
- and not db_file.endswith('-shm')
- and not db_file.endswith('db-journal')):
-
- version_string = re.search('{}(.*).db'.format(database), db_file)
+ if (
+ db_file.startswith(database)
+ and not db_file.endswith("-wal")
+ and not db_file.endswith("-shm")
+ and not db_file.endswith("db-journal")
+ ):
+
+ version_string = re.search("{}(.*).db".format(database), db_file)
version = int(version_string.group(1))
- if version > target['version']:
- target['db_file'] = db_file
- target['version'] = version
+ if version > target["version"]:
+ target["db_file"] = db_file
+ target["version"] = version
LOG.debug("Discovered database: %s", target)
- self.discovered_file = target['db_file']
+ self.discovered_file = target["db_file"]
- return translate_path("special://database/%s" % target['db_file'])
+ return translate_path("special://database/%s" % target["db_file"])
def _sql(self, db_file):
-
- ''' Get the database path based on the file objects/obj_map.json
- Compatible check, in the event multiple db version are supported with the same Kodi version.
- Discover by file as a last resort.
- '''
+ """Get the database path based on the file objects/obj_map.json
+ Compatible check, in the event multiple db version are supported with the same Kodi version.
+ Discover by file as a last resort.
+ """
databases = obj.Objects().objects
- if db_file not in ('video', 'music', 'texture') or databases.get('database_set%s' % db_file):
+ if db_file not in ("video", "music", "texture") or databases.get(
+ "database_set%s" % db_file
+ ):
return self._get_database(databases[db_file], True)
- discovered = self._discover_database(db_file) if not databases.get('database_set%s' % db_file) else None
+ discovered = (
+ self._discover_database(db_file)
+ if not databases.get("database_set%s" % db_file)
+ else None
+ )
databases[db_file] = discovered
self.discovered = True
- databases['database_set%s' % db_file] = True
+ databases["database_set%s" % db_file] = True
LOG.info("Database locked in: %s", databases[db_file])
return databases[db_file]
def __exit__(self, exc_type, exc_val, exc_tb):
-
- ''' Close the connection and cursor.
- '''
+ """Close the connection and cursor."""
changes = self.conn.total_changes
if exc_type is not None: # errors raised
@@ -175,41 +181,43 @@ def __exit__(self, exc_type, exc_val, exc_tb):
def jellyfin_tables(cursor):
-
- ''' Create the tables for the jellyfin database.
- jellyfin, view, version
- '''
+ """Create the tables for the jellyfin database.
+ jellyfin, view, version
+ """
cursor.execute(
"""CREATE TABLE IF NOT EXISTS jellyfin(
jellyfin_id TEXT UNIQUE, media_folder TEXT, jellyfin_type TEXT, media_type TEXT,
kodi_id INTEGER, kodi_fileid INTEGER, kodi_pathid INTEGER, parent_id INTEGER,
- checksum INTEGER, jellyfin_parent_id TEXT)""")
+ checksum INTEGER, jellyfin_parent_id TEXT)"""
+ )
cursor.execute(
"""CREATE TABLE IF NOT EXISTS view(
- view_id TEXT UNIQUE, view_name TEXT, media_type TEXT)""")
+ view_id TEXT UNIQUE, view_name TEXT, media_type TEXT)"""
+ )
cursor.execute("CREATE TABLE IF NOT EXISTS version(idVersion TEXT)")
columns = cursor.execute("SELECT * FROM jellyfin")
- if 'jellyfin_parent_id' not in [description[0] for description in columns.description]:
+ if "jellyfin_parent_id" not in [
+ description[0] for description in columns.description
+ ]:
LOG.debug("Add missing column jellyfin_parent_id")
cursor.execute("ALTER TABLE jellyfin ADD COLUMN jellyfin_parent_id 'TEXT'")
def reset():
-
- ''' Reset both the jellyfin database and the kodi database.
- '''
+ """Reset both the jellyfin database and the kodi database."""
from ..views import Views
+
views = Views()
if not dialog("yesno", "{jellyfin}", translate(33074)):
return
- window('jellyfin_should_stop.bool', True)
+ window("jellyfin_should_stop.bool", True)
count = 10
- while window('jellyfin_sync.bool'):
+ while window("jellyfin_sync.bool"):
LOG.info("Sync is running...")
count -= 1
@@ -239,12 +247,12 @@ def reset():
if xbmcvfs.exists(os.path.join(ADDON_DATA, "sync.json")):
xbmcvfs.delete(os.path.join(ADDON_DATA, "sync.json"))
- settings('enableMusic.bool', False)
- settings('MinimumSetup', "")
- settings('MusicRescan.bool', False)
- settings('SyncInstallRunDone.bool', False)
+ settings("enableMusic.bool", False)
+ settings("MinimumSetup", "")
+ settings("MusicRescan.bool", False)
+ settings("SyncInstallRunDone.bool", False)
dialog("ok", "{jellyfin}", translate(33088))
- xbmc.executebuiltin('RestartApp')
+ xbmc.executebuiltin("RestartApp")
def reset_kodi():
@@ -256,18 +264,20 @@ def reset_kodi():
name = table[0]
# These tables are populated by Kodi and we shouldn't wipe them
- if name not in ['version', 'videoversiontype']:
+ if name not in ["version", "videoversiontype"]:
videodb.cursor.execute("DELETE FROM " + name)
- if settings('enableMusic.bool') or dialog("yesno", "{jellyfin}", translate(33162)):
+ if settings("enableMusic.bool") or dialog("yesno", "{jellyfin}", translate(33162)):
- with Database('music') as musicdb:
- musicdb.cursor.execute("SELECT tbl_name FROM sqlite_master WHERE type='table'")
+ with Database("music") as musicdb:
+ musicdb.cursor.execute(
+ "SELECT tbl_name FROM sqlite_master WHERE type='table'"
+ )
for table in musicdb.cursor.fetchall():
name = table[0]
- if name != 'version':
+ if name != "version":
musicdb.cursor.execute("DELETE FROM " + name)
LOG.info("[ reset kodi ]")
@@ -275,13 +285,15 @@ def reset_kodi():
def reset_jellyfin():
- with Database('jellyfin') as jellyfindb:
- jellyfindb.cursor.execute("SELECT tbl_name FROM sqlite_master WHERE type='table'")
+ with Database("jellyfin") as jellyfindb:
+ jellyfindb.cursor.execute(
+ "SELECT tbl_name FROM sqlite_master WHERE type='table'"
+ )
for table in jellyfindb.cursor.fetchall():
name = table[0]
- if name not in ('version', 'view'):
+ if name not in ("version", "view"):
jellyfindb.cursor.execute("DELETE FROM " + name)
jellyfindb.cursor.execute("DROP table IF EXISTS jellyfin")
@@ -292,10 +304,8 @@ def reset_jellyfin():
def reset_artwork():
-
- ''' Remove all existing texture.
- '''
- thumbnails = translate_path('special://thumbnails/')
+ """Remove all existing texture."""
+ thumbnails = translate_path("special://thumbnails/")
if xbmcvfs.exists(thumbnails):
dirs, ignore = xbmcvfs.listdir(thumbnails)
@@ -307,13 +317,13 @@ def reset_artwork():
LOG.debug("DELETE thumbnail %s", thumb)
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'")
for table in texdb.cursor.fetchall():
name = table[0]
- if name != 'version':
+ if name != "version":
texdb.cursor.execute("DELETE FROM " + name)
LOG.info("[ reset artwork ]")
@@ -327,18 +337,18 @@ def get_sync():
xbmcvfs.mkdirs(ADDON_DATA)
try:
- with open(os.path.join(ADDON_DATA, 'sync.json'), 'rb') as infile:
+ with open(os.path.join(ADDON_DATA, "sync.json"), "rb") as infile:
sync = json.load(infile)
except Exception:
sync = {}
- sync['Libraries'] = sync.get('Libraries', [])
- sync['RestorePoint'] = sync.get('RestorePoint', {})
- sync['Whitelist'] = list(set(sync.get('Whitelist', [])))
- sync['SortedViews'] = sync.get('SortedViews', [])
+ sync["Libraries"] = sync.get("Libraries", [])
+ sync["RestorePoint"] = sync.get("RestorePoint", {})
+ sync["Whitelist"] = list(set(sync.get("Whitelist", [])))
+ sync["SortedViews"] = sync.get("SortedViews", [])
# Temporary cleanup from #494/#511, remove in a future version
- sync['Libraries'] = [lib_id for lib_id in sync['Libraries'] if ',' not in lib_id]
+ sync["Libraries"] = [lib_id for lib_id in sync["Libraries"] if "," not in lib_id]
return sync
@@ -348,12 +358,12 @@ def save_sync(sync):
if not xbmcvfs.exists(ADDON_DATA):
xbmcvfs.mkdirs(ADDON_DATA)
- sync['Date'] = datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ')
+ sync["Date"] = datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")
- with open(os.path.join(ADDON_DATA, 'sync.json'), 'wb') as outfile:
+ with open(os.path.join(ADDON_DATA, "sync.json"), "wb") as outfile:
data = json.dumps(sync, sort_keys=True, indent=4, ensure_ascii=False)
if isinstance(data, text_type):
- data = data.encode('utf-8')
+ data = data.encode("utf-8")
outfile.write(data)
@@ -365,30 +375,30 @@ def get_credentials():
xbmcvfs.mkdirs(ADDON_DATA)
try:
- with open(os.path.join(ADDON_DATA, 'data.json'), 'rb') as infile:
+ with open(os.path.join(ADDON_DATA, "data.json"), "rb") as infile:
credentials = json.load(infile)
except IOError:
credentials = {}
- credentials['Servers'] = credentials.get('Servers', [])
+ credentials["Servers"] = credentials.get("Servers", [])
# Migration for #145
# TODO: CLEANUP for 1.0.0 release
- for server in credentials['Servers']:
+ for server in credentials["Servers"]:
# Functionality removed in #60
- if 'RemoteAddress' in server:
- del server['RemoteAddress']
- if 'ManualAddress' in server:
- server['address'] = server['ManualAddress']
- del server['ManualAddress']
+ if "RemoteAddress" in server:
+ del server["RemoteAddress"]
+ if "ManualAddress" in server:
+ server["address"] = server["ManualAddress"]
+ del server["ManualAddress"]
# If manual is present, local should always be here, but better to be safe
- if 'LocalAddress' in server:
- del server['LocalAddress']
- elif 'LocalAddress' in server:
- server['address'] = server['LocalAddress']
- del server['LocalAddress']
- if 'LastConnectionMode' in server:
- del server['LastConnectionMode']
+ if "LocalAddress" in server:
+ del server["LocalAddress"]
+ elif "LocalAddress" in server:
+ server["address"] = server["LocalAddress"]
+ del server["LocalAddress"]
+ if "LastConnectionMode" in server:
+ del server["LastConnectionMode"]
return credentials
@@ -399,21 +409,21 @@ def save_credentials(credentials):
if not xbmcvfs.exists(ADDON_DATA):
xbmcvfs.mkdirs(ADDON_DATA)
try:
- with open(os.path.join(ADDON_DATA, 'data.json'), 'wb') as outfile:
+ with open(os.path.join(ADDON_DATA, "data.json"), "wb") as outfile:
data = json.dumps(credentials, sort_keys=True, indent=4, ensure_ascii=False)
if isinstance(data, text_type):
- data = data.encode('utf-8')
+ data = data.encode("utf-8")
outfile.write(data)
except Exception:
LOG.exception("Failed to save credentials:")
def get_item(kodi_id, media):
-
- ''' Get jellyfin item based on kodi id and media.
- '''
- with Database('jellyfin') as jellyfindb:
- item = jellyfin_db.JellyfinDatabase(jellyfindb.cursor).get_full_item_by_kodi_id(kodi_id, media)
+ """Get jellyfin item based on kodi id and media."""
+ with Database("jellyfin") as jellyfindb:
+ item = jellyfin_db.JellyfinDatabase(jellyfindb.cursor).get_full_item_by_kodi_id(
+ kodi_id, media
+ )
if not item:
LOG.debug("Not an jellyfin item")
diff --git a/jellyfin_kodi/database/jellyfin_db.py b/jellyfin_kodi/database/jellyfin_db.py
index 0cbe9dd6b..5201e9326 100644
--- a/jellyfin_kodi/database/jellyfin_db.py
+++ b/jellyfin_kodi/database/jellyfin_db.py
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
+
#################################################################################################
from . import queries as QU
@@ -14,7 +15,7 @@
##################################################################################################
-class JellyfinDatabase():
+class JellyfinDatabase:
def __init__(self, cursor):
self.cursor = cursor
@@ -32,9 +33,7 @@ def update_reference(self, *args):
self.cursor.execute(QU.update_reference, args)
def update_parent_id(self, *args):
-
- ''' Parent_id is the parent Kodi id.
- '''
+ """Parent_id is the parent Kodi id."""
self.cursor.execute(QU.update_parent, args)
def get_item_id_by_parent_id(self, *args):
@@ -160,8 +159,8 @@ def get_version(self):
return self.cursor.fetchone()
def add_version(self, *args):
- '''
+ """
We only ever want one value here, so erase the existing contents first
- '''
+ """
self.cursor.execute(QU.delete_version)
self.cursor.execute(QU.add_version, args)
diff --git a/jellyfin_kodi/database/queries.py b/jellyfin_kodi/database/queries.py
index 52d688460..41a59cda3 100644
--- a/jellyfin_kodi/database/queries.py
+++ b/jellyfin_kodi/database/queries.py
@@ -94,16 +94,126 @@
media_type, parent_id, checksum, media_folder, jellyfin_parent_id)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
"""
-add_reference_movie_obj = ["{Id}", "{MovieId}", "{FileId}", "{PathId}", "Movie", "movie", None, "{Checksum}", "{LibraryId}", "{JellyfinParentId}"]
-add_reference_boxset_obj = ["{Id}", "{SetId}", None, None, "BoxSet", "set", None, "{Checksum}", None, None]
-add_reference_tvshow_obj = ["{Id}", "{ShowId}", None, "{PathId}", "Series", "tvshow", None, "{Checksum}", "{LibraryId}", "{JellyfinParentId}"]
-add_reference_season_obj = ["{Id}", "{SeasonId}", None, None, "Season", "season", "{ShowId}", None, None, None]
-add_reference_pool_obj = ["{SeriesId}", "{ShowId}", None, "{PathId}", "Series", "tvshow", None, "{Checksum}", "{LibraryId}", None]
-add_reference_episode_obj = ["{Id}", "{EpisodeId}", "{FileId}", "{PathId}", "Episode", "episode", "{SeasonId}", "{Checksum}", None, "{JellyfinParentId}"]
-add_reference_mvideo_obj = ["{Id}", "{MvideoId}", "{FileId}", "{PathId}", "MusicVideo", "musicvideo", None, "{Checksum}", "{LibraryId}", "{JellyfinParentId}"]
-add_reference_artist_obj = ["{Id}", "{ArtistId}", None, None, "{ArtistType}", "artist", None, "{Checksum}", "{LibraryId}", "{JellyfinParentId}"]
-add_reference_album_obj = ["{Id}", "{AlbumId}", None, None, "MusicAlbum", "album", None, "{Checksum}", "{LibraryId}", "{JellyfinParentId}"]
-add_reference_song_obj = ["{Id}", "{SongId}", None, "{PathId}", "Audio", "song", "{AlbumId}", "{Checksum}", "{LibraryId}", "{JellyfinParentId}"]
+add_reference_movie_obj = [
+ "{Id}",
+ "{MovieId}",
+ "{FileId}",
+ "{PathId}",
+ "Movie",
+ "movie",
+ None,
+ "{Checksum}",
+ "{LibraryId}",
+ "{JellyfinParentId}",
+]
+add_reference_boxset_obj = [
+ "{Id}",
+ "{SetId}",
+ None,
+ None,
+ "BoxSet",
+ "set",
+ None,
+ "{Checksum}",
+ None,
+ None,
+]
+add_reference_tvshow_obj = [
+ "{Id}",
+ "{ShowId}",
+ None,
+ "{PathId}",
+ "Series",
+ "tvshow",
+ None,
+ "{Checksum}",
+ "{LibraryId}",
+ "{JellyfinParentId}",
+]
+add_reference_season_obj = [
+ "{Id}",
+ "{SeasonId}",
+ None,
+ None,
+ "Season",
+ "season",
+ "{ShowId}",
+ None,
+ None,
+ None,
+]
+add_reference_pool_obj = [
+ "{SeriesId}",
+ "{ShowId}",
+ None,
+ "{PathId}",
+ "Series",
+ "tvshow",
+ None,
+ "{Checksum}",
+ "{LibraryId}",
+ None,
+]
+add_reference_episode_obj = [
+ "{Id}",
+ "{EpisodeId}",
+ "{FileId}",
+ "{PathId}",
+ "Episode",
+ "episode",
+ "{SeasonId}",
+ "{Checksum}",
+ None,
+ "{JellyfinParentId}",
+]
+add_reference_mvideo_obj = [
+ "{Id}",
+ "{MvideoId}",
+ "{FileId}",
+ "{PathId}",
+ "MusicVideo",
+ "musicvideo",
+ None,
+ "{Checksum}",
+ "{LibraryId}",
+ "{JellyfinParentId}",
+]
+add_reference_artist_obj = [
+ "{Id}",
+ "{ArtistId}",
+ None,
+ None,
+ "{ArtistType}",
+ "artist",
+ None,
+ "{Checksum}",
+ "{LibraryId}",
+ "{JellyfinParentId}",
+]
+add_reference_album_obj = [
+ "{Id}",
+ "{AlbumId}",
+ None,
+ None,
+ "MusicAlbum",
+ "album",
+ None,
+ "{Checksum}",
+ "{LibraryId}",
+ "{JellyfinParentId}",
+]
+add_reference_song_obj = [
+ "{Id}",
+ "{SongId}",
+ None,
+ "{PathId}",
+ "Audio",
+ "song",
+ "{AlbumId}",
+ "{Checksum}",
+ "{LibraryId}",
+ "{JellyfinParentId}",
+]
add_view = """
INSERT OR REPLACE INTO view(view_id, view_name, media_type)
VALUES (?, ?, ?)
diff --git a/jellyfin_kodi/dialogs/context.py b/jellyfin_kodi/dialogs/context.py
index 6d48a5034..8e8eb5db2 100644
--- a/jellyfin_kodi/dialogs/context.py
+++ b/jellyfin_kodi/dialogs/context.py
@@ -47,8 +47,8 @@ def get_selected(self):
def onInit(self):
- if window('JellyfinUserImage'):
- self.getControl(USER_IMAGE).setImage(window('JellyfinUserImage'))
+ if window("JellyfinUserImage"):
+ self.getControl(USER_IMAGE).setImage(window("JellyfinUserImage"))
LOG.info("options: %s", self._options)
self.list_ = self.getControl(LIST)
@@ -63,21 +63,35 @@ def onAction(self, action):
if action in (ACTION_BACK, ACTION_PARENT_DIR, ACTION_PREVIOUS_MENU):
self.close()
- if action in (ACTION_SELECT_ITEM, ACTION_MOUSE_LEFT_CLICK) and self.getFocusId() == LIST:
+ if (
+ action in (ACTION_SELECT_ITEM, ACTION_MOUSE_LEFT_CLICK)
+ and self.getFocusId() == LIST
+ ):
option = self.list_.getSelectedItem()
self.selected_option = ensure_text(option.getLabel())
- LOG.info('option selected: %s', self.selected_option)
+ LOG.info("option selected: %s", self.selected_option)
self.close()
def _add_editcontrol(self, x, y, height, width, password=0):
- media = os.path.join(xbmcaddon.Addon(addon_id()).getAddonInfo('path'), 'resources', 'skins', 'default', 'media')
- control = xbmcgui.ControlImage(0, 0, 0, 0,
- filename=os.path.join(media, "white.png"),
- aspectRatio=0,
- colorDiffuse="ff111111")
+ media = os.path.join(
+ xbmcaddon.Addon(addon_id()).getAddonInfo("path"),
+ "resources",
+ "skins",
+ "default",
+ "media",
+ )
+ control = xbmcgui.ControlImage(
+ 0,
+ 0,
+ 0,
+ 0,
+ filename=os.path.join(media, "white.png"),
+ aspectRatio=0,
+ colorDiffuse="ff111111",
+ )
control.setPosition(x, y)
control.setHeight(height)
control.setWidth(width)
diff --git a/jellyfin_kodi/dialogs/loginmanual.py b/jellyfin_kodi/dialogs/loginmanual.py
index 1d70bec2d..c63dd519f 100644
--- a/jellyfin_kodi/dialogs/loginmanual.py
+++ b/jellyfin_kodi/dialogs/loginmanual.py
@@ -18,7 +18,7 @@
CANCEL = 201
ERROR_TOGGLE = 202
ERROR_MSG = 203
-ERROR = {'Invalid': 1, 'Empty': 2}
+ERROR = {"Invalid": 1, "Empty": 2}
##################################################################################################
@@ -76,7 +76,7 @@ def onClick(self, control):
if not user:
# Display error
- self._error(ERROR['Empty'], translate('empty_user'))
+ self._error(ERROR["Empty"], translate("empty_user"))
LOG.error("Username cannot be null")
elif self._login(user, password):
@@ -88,7 +88,7 @@ def onClick(self, control):
def onAction(self, action):
- if self.error == ERROR['Empty'] and self.user_field.getText():
+ if self.error == ERROR["Empty"] and self.user_field.getText():
self._disable_error()
if action in (ACTION_BACK, ACTION_PARENT_DIR, ACTION_PREVIOUS_MENU):
@@ -102,12 +102,12 @@ def _add_editcontrol(self, x, y, height, width, password=False):
textColor="FF00A4DC",
disabledColor="FF888888",
focusTexture="-",
- noFocusTexture="-"
+ noFocusTexture="-",
)
# TODO: Kodi 17 compat removal cleanup
if kodi_version() < 18:
- kwargs['isPassword'] = password
+ kwargs["isPassword"] = password
control = xbmcgui.ControlEdit(0, 0, 0, 0, **kwargs)
@@ -126,11 +126,13 @@ def _add_editcontrol(self, x, y, height, width, password=False):
def _login(self, username, password):
- server = self.connect_manager.get_server_info(self.connect_manager.server_id)['address']
+ server = self.connect_manager.get_server_info(self.connect_manager.server_id)[
+ "address"
+ ]
result = self.connect_manager.login(server, username, password)
if not result:
- self._error(ERROR['Invalid'], translate('invalid_auth'))
+ self._error(ERROR["Invalid"], translate("invalid_auth"))
return False
else:
self._user = result
@@ -140,9 +142,9 @@ def _error(self, state, message):
self.error = state
self.error_msg.setLabel(message)
- self.error_toggle.setVisibleCondition('true')
+ self.error_toggle.setVisibleCondition("true")
def _disable_error(self):
self.error = None
- self.error_toggle.setVisibleCondition('false')
+ self.error_toggle.setVisibleCondition("false")
diff --git a/jellyfin_kodi/dialogs/serverconnect.py b/jellyfin_kodi/dialogs/serverconnect.py
index 73d720587..adeffbe5a 100644
--- a/jellyfin_kodi/dialogs/serverconnect.py
+++ b/jellyfin_kodi/dialogs/serverconnect.py
@@ -64,8 +64,10 @@ def onInit(self):
self.list_ = self.getControl(LIST)
for server in self.servers:
- server_type = "wifi" if server.get('ExchangeToken') else "network"
- self.list_.addItem(self._add_listitem(server['Name'], server['Id'], server_type))
+ server_type = "wifi" if server.get("ExchangeToken") else "network"
+ self.list_.addItem(
+ self._add_listitem(server["Name"], server["Id"], server_type)
+ )
if self.user_image is not None:
self.getControl(USER_IMAGE).setImage(self.user_image)
@@ -77,8 +79,8 @@ def onInit(self):
def _add_listitem(cls, label, server_id, server_type):
item = xbmcgui.ListItem(label)
- item.setProperty('id', server_id)
- item.setProperty('server_type', server_type)
+ item.setProperty("id", server_id)
+ item.setProperty("server_type", server_type)
return item
@@ -87,14 +89,17 @@ def onAction(self, action):
if action in (ACTION_BACK, ACTION_PREVIOUS_MENU, ACTION_PARENT_DIR):
self.close()
- if action in (ACTION_SELECT_ITEM, ACTION_MOUSE_LEFT_CLICK) and self.getFocusId() == LIST:
+ if (
+ action in (ACTION_SELECT_ITEM, ACTION_MOUSE_LEFT_CLICK)
+ and self.getFocusId() == LIST
+ ):
server = self.list_.getSelectedItem()
- selected_id = server.getProperty('id')
- LOG.info('Server Id selected: %s', selected_id)
+ selected_id = server.getProperty("id")
+ LOG.info("Server Id selected: %s", selected_id)
if self._connect_server(selected_id):
- self.message_box.setVisibleCondition('false')
+ self.message_box.setVisibleCondition("false")
self.close()
def onClick(self, control):
@@ -109,19 +114,19 @@ def onClick(self, control):
def _connect_server(self, server_id):
server = self.connect_manager.get_server_info(server_id)
- self.message.setLabel("%s %s..." % (translate(30610), server['Name']))
+ self.message.setLabel("%s %s..." % (translate(30610), server["Name"]))
- self.message_box.setVisibleCondition('true')
- self.busy.setVisibleCondition('true')
+ self.message_box.setVisibleCondition("true")
+ self.busy.setVisibleCondition("true")
result = self.connect_manager.connect_to_server(server)
- if result['State'] == CONNECTION_STATE['Unavailable']:
- self.busy.setVisibleCondition('false')
+ if result["State"] == CONNECTION_STATE["Unavailable"]:
+ self.busy.setVisibleCondition("false")
self.message.setLabel(translate(30609))
return False
else:
xbmc.sleep(1000)
- self._selected_server = result['Servers'][0]
+ self._selected_server = result["Servers"][0]
return True
diff --git a/jellyfin_kodi/dialogs/servermanual.py b/jellyfin_kodi/dialogs/servermanual.py
index 551c840e0..8f4b44bb9 100644
--- a/jellyfin_kodi/dialogs/servermanual.py
+++ b/jellyfin_kodi/dialogs/servermanual.py
@@ -22,10 +22,7 @@
CANCEL = 201
ERROR_TOGGLE = 202
ERROR_MSG = 203
-ERROR = {
- 'Invalid': 1,
- 'Empty': 2
-}
+ERROR = {"Invalid": 1, "Empty": 2}
# https://stackoverflow.com/a/17871737/1035647
_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]))$"
@@ -77,7 +74,7 @@ def onClick(self, control):
if not server:
# Display error
- self._error(ERROR['Empty'], translate('empty_server'))
+ self._error(ERROR["Empty"], translate("empty_server"))
LOG.error("Server cannot be null")
elif self._connect_to_server(server):
@@ -89,7 +86,7 @@ def onClick(self, control):
def onAction(self, action):
- if self.error == ERROR['Empty'] and self.host_field.getText():
+ if self.error == ERROR["Empty"] and self.host_field.getText():
self._disable_error()
if action in (ACTION_BACK, ACTION_PARENT_DIR, ACTION_PREVIOUS_MENU):
@@ -97,13 +94,18 @@ def onAction(self, action):
def _add_editcontrol(self, x, y, height, width):
- control = xbmcgui.ControlEdit(0, 0, 0, 0,
- label="",
- font="font13",
- textColor="FF00A4DC",
- disabledColor="FF888888",
- focusTexture="-",
- noFocusTexture="-")
+ control = xbmcgui.ControlEdit(
+ 0,
+ 0,
+ 0,
+ 0,
+ label="",
+ font="font13",
+ textColor="FF00A4DC",
+ disabledColor="FF888888",
+ focusTexture="-",
+ noFocusTexture="-",
+ )
control.setPosition(x, y)
control.setHeight(height)
control.setWidth(width)
@@ -118,25 +120,25 @@ def _connect_to_server(self, server):
self._message("%s %s..." % (translate(30610), server))
result = self.connect_manager.connect_to_address(server)
- if result['State'] == CONNECTION_STATE['Unavailable']:
+ if result["State"] == CONNECTION_STATE["Unavailable"]:
self._message(translate(30609))
return False
else:
- self._server = result['Servers'][0]
+ self._server = result["Servers"][0]
return True
def _message(self, message):
self.error_msg.setLabel(message)
- self.error_toggle.setVisibleCondition('true')
+ self.error_toggle.setVisibleCondition("true")
def _error(self, state, message):
self.error = state
self.error_msg.setLabel(message)
- self.error_toggle.setVisibleCondition('true')
+ self.error_toggle.setVisibleCondition("true")
def _disable_error(self):
self.error = None
- self.error_toggle.setVisibleCondition('false')
+ self.error_toggle.setVisibleCondition("false")
diff --git a/jellyfin_kodi/dialogs/usersconnect.py b/jellyfin_kodi/dialogs/usersconnect.py
index 8c1428ef6..1abb8949c 100644
--- a/jellyfin_kodi/dialogs/usersconnect.py
+++ b/jellyfin_kodi/dialogs/usersconnect.py
@@ -52,17 +52,20 @@ def onInit(self):
self.list_ = self.getControl(LIST)
for user in self.users:
- user_image = ("items/logindefault.png" if 'PrimaryImageTag' not in user
- else self._get_user_artwork(user['Id'], 'Primary'))
- self.list_.addItem(self._add_listitem(user['Name'], user['Id'], user_image))
+ user_image = (
+ "items/logindefault.png"
+ if "PrimaryImageTag" not in user
+ else self._get_user_artwork(user["Id"], "Primary")
+ )
+ self.list_.addItem(self._add_listitem(user["Name"], user["Id"], user_image))
self.setFocus(self.list_)
def _add_listitem(self, label, user_id, user_image):
item = xbmcgui.ListItem(label)
- item.setProperty('id', user_id)
- item.setArt({'icon': user_image})
+ item.setProperty("id", user_id)
+ item.setArt({"icon": user_image})
return item
@@ -71,14 +74,17 @@ def onAction(self, action):
if action in (ACTION_BACK, ACTION_PREVIOUS_MENU, ACTION_PARENT_DIR):
self.close()
- if action in (ACTION_SELECT_ITEM, ACTION_MOUSE_LEFT_CLICK) and self.getFocusId() == LIST:
+ if (
+ action in (ACTION_SELECT_ITEM, ACTION_MOUSE_LEFT_CLICK)
+ and self.getFocusId() == LIST
+ ):
user = self.list_.getSelectedItem()
- selected_id = user.getProperty('id')
- LOG.info('User Id selected: %s', selected_id)
+ selected_id = user.getProperty("id")
+ LOG.info("User Id selected: %s", selected_id)
for user in self.users:
- if user['Id'] == selected_id:
+ if user["Id"] == selected_id:
self._user = user
break
@@ -95,4 +101,8 @@ def onClick(self, control):
def _get_user_artwork(self, user_id, item_type):
# Load user information set by UserClient
- return "%s/Users/%s/Images/%s?Format=original" % (self.server, user_id, item_type)
+ return "%s/Users/%s/Images/%s?Format=original" % (
+ self.server,
+ user_id,
+ item_type,
+ )
diff --git a/jellyfin_kodi/downloader.py b/jellyfin_kodi/downloader.py
index f9fb03ca3..2992af6ee 100644
--- a/jellyfin_kodi/downloader.py
+++ b/jellyfin_kodi/downloader.py
@@ -25,7 +25,7 @@
def get_jellyfinserver_url(handler):
- if handler.startswith('/'):
+ if handler.startswith("/"):
handler = handler[1:]
LOG.info("handler starts with /: %s", handler)
@@ -38,47 +38,55 @@ def _http(action, url, request=None, server_id=None):
if request is None:
request = {}
- request.update({'url': url, 'type': action})
+ request.update({"url": url, "type": action})
return Jellyfin(server_id).http.request(request)
def _get(handler, params=None, server_id=None):
- return _http("GET", get_jellyfinserver_url(handler), {'params': params}, server_id)
+ return _http("GET", get_jellyfinserver_url(handler), {"params": params}, server_id)
def _post(handler, json=None, params=None, server_id=None):
- return _http("POST", get_jellyfinserver_url(handler), {'params': params, 'json': json}, server_id)
+ return _http(
+ "POST",
+ get_jellyfinserver_url(handler),
+ {"params": params, "json": json},
+ server_id,
+ )
def _delete(handler, params=None, server_id=None):
- return _http("DELETE", get_jellyfinserver_url(handler), {'params': params}, server_id)
+ return _http(
+ "DELETE", get_jellyfinserver_url(handler), {"params": params}, server_id
+ )
def validate_view(library_id, item_id):
-
- ''' This confirms a single item from the library matches the view it belongs to.
- Used to detect grouped libraries.
- '''
+ """This confirms a single item from the library matches the view it belongs to.
+ Used to detect grouped libraries.
+ """
try:
- result = _get("Users/{UserId}/Items", {
- 'ParentId': library_id,
- 'Recursive': True,
- 'Ids': item_id
- })
+ result = _get(
+ "Users/{UserId}/Items",
+ {"ParentId": library_id, "Recursive": True, "Ids": item_id},
+ )
except Exception as error:
LOG.exception(error)
return False
- return bool(len(result['Items']))
+ return bool(len(result["Items"]))
def get_single_item(parent_id, media):
- return _get("Users/{UserId}/Items", {
- 'ParentId': parent_id,
- 'Recursive': True,
- 'Limit': 1,
- 'IncludeItemTypes': media
- })
+ return _get(
+ "Users/{UserId}/Items",
+ {
+ "ParentId": parent_id,
+ "Recursive": True,
+ "Limit": 1,
+ "IncludeItemTypes": media,
+ },
+ )
def get_movies_by_boxset(boxset_id):
@@ -90,13 +98,13 @@ def get_movies_by_boxset(boxset_id):
def get_episode_by_show(show_id):
query = {
- 'url': "Shows/%s/Episodes" % show_id,
- 'params': {
- 'EnableUserData': True,
- 'EnableImages': True,
- 'UserId': "{UserId}",
- 'Fields': api.info()
- }
+ "url": "Shows/%s/Episodes" % show_id,
+ "params": {
+ "EnableUserData": True,
+ "EnableImages": True,
+ "UserId": "{UserId}",
+ "Fields": api.info(),
+ },
}
for items in _get_items(query):
yield items
@@ -105,14 +113,14 @@ def get_episode_by_show(show_id):
def get_episode_by_season(show_id, season_id):
query = {
- 'url': "Shows/%s/Episodes" % show_id,
- 'params': {
- 'SeasonId': season_id,
- 'EnableUserData': True,
- 'EnableImages': True,
- 'UserId': "{UserId}",
- 'Fields': api.info()
- }
+ "url": "Shows/%s/Episodes" % show_id,
+ "params": {
+ "SeasonId": season_id,
+ "EnableUserData": True,
+ "EnableImages": True,
+ "UserId": "{UserId}",
+ "Fields": api.info(),
+ },
}
for items in _get_items(query):
yield items
@@ -123,41 +131,41 @@ def get_item_count(parent_id, item_type=None, params=None):
url = "Users/{UserId}/Items"
query_params = {
- 'ParentId': parent_id,
- 'IncludeItemTypes': item_type,
- 'EnableTotalRecordCount': True,
- 'LocationTypes': "FileSystem,Remote,Offline",
- 'Recursive': True,
- 'Limit': 1
+ "ParentId": parent_id,
+ "IncludeItemTypes": item_type,
+ "EnableTotalRecordCount": True,
+ "LocationTypes": "FileSystem,Remote,Offline",
+ "Recursive": True,
+ "Limit": 1,
}
if params:
- query_params['params'].update(params)
+ query_params["params"].update(params)
result = _get(url, query_params)
- return result.get('TotalRecordCount', 1)
+ return result.get("TotalRecordCount", 1)
def get_items(parent_id, item_type=None, basic=False, params=None):
query = {
- 'url': "Users/{UserId}/Items",
- 'params': {
- 'ParentId': parent_id,
- 'IncludeItemTypes': item_type,
- 'SortBy': "SortName",
- 'SortOrder': "Ascending",
- 'Fields': api.basic_info() if basic else api.info(),
- 'CollapseBoxSetItems': False,
- 'IsVirtualUnaired': False,
- 'EnableTotalRecordCount': False,
- 'LocationTypes': "FileSystem,Remote,Offline",
- 'IsMissing': False,
- 'Recursive': True
- }
+ "url": "Users/{UserId}/Items",
+ "params": {
+ "ParentId": parent_id,
+ "IncludeItemTypes": item_type,
+ "SortBy": "SortName",
+ "SortOrder": "Ascending",
+ "Fields": api.basic_info() if basic else api.info(),
+ "CollapseBoxSetItems": False,
+ "IsVirtualUnaired": False,
+ "EnableTotalRecordCount": False,
+ "LocationTypes": "FileSystem,Remote,Offline",
+ "IsMissing": False,
+ "Recursive": True,
+ },
}
if params:
- query['params'].update(params)
+ query["params"].update(params)
for items in _get_items(query):
yield items
@@ -166,20 +174,20 @@ def get_items(parent_id, item_type=None, basic=False, params=None):
def get_artists(parent_id=None):
query = {
- 'url': 'Artists',
- 'params': {
- 'UserId': "{UserId}",
- 'ParentId': parent_id,
- 'SortBy': "SortName",
- 'SortOrder': "Ascending",
- 'Fields': api.music_info(),
- 'CollapseBoxSetItems': False,
- 'IsVirtualUnaired': False,
- 'EnableTotalRecordCount': False,
- 'LocationTypes': "FileSystem,Remote,Offline",
- 'IsMissing': False,
- 'Recursive': True
- }
+ "url": "Artists",
+ "params": {
+ "UserId": "{UserId}",
+ "ParentId": parent_id,
+ "SortBy": "SortName",
+ "SortOrder": "Ascending",
+ "Fields": api.music_info(),
+ "CollapseBoxSetItems": False,
+ "IsVirtualUnaired": False,
+ "EnableTotalRecordCount": False,
+ "LocationTypes": "FileSystem,Remote,Offline",
+ "IsMissing": False,
+ "Recursive": True,
+ },
}
for items in _get_items(query):
@@ -188,48 +196,49 @@ def get_artists(parent_id=None):
@stop
def _get_items(query, server_id=None):
-
- ''' query = {
- 'url': string,
- 'params': dict -- opt, include StartIndex to resume
- }
- '''
- items = {
- 'Items': [],
- 'TotalRecordCount': 0,
- 'RestorePoint': {}
+ """query = {
+ 'url': string,
+ 'params': dict -- opt, include StartIndex to resume
}
+ """
+ items = {"Items": [], "TotalRecordCount": 0, "RestorePoint": {}}
- limit = min(int(settings('limitIndex') or 50), 50)
- dthreads = int(settings('limitThreads') or 3)
+ limit = min(int(settings("limitIndex") or 50), 50)
+ dthreads = int(settings("limitThreads") or 3)
- url = query['url']
- query.setdefault('params', {})
- params = query['params']
+ url = query["url"]
+ query.setdefault("params", {})
+ params = query["params"]
try:
test_params = dict(params)
- test_params['Limit'] = 1
- test_params['EnableTotalRecordCount'] = True
+ test_params["Limit"] = 1
+ test_params["EnableTotalRecordCount"] = True
- items['TotalRecordCount'] = _get(url, test_params, server_id=server_id)['TotalRecordCount']
+ items["TotalRecordCount"] = _get(url, test_params, server_id=server_id)[
+ "TotalRecordCount"
+ ]
except Exception as error:
- LOG.exception("Failed to retrieve the server response %s: %s params:%s", url, error, params)
+ LOG.exception(
+ "Failed to retrieve the server response %s: %s params:%s",
+ url,
+ error,
+ params,
+ )
else:
- params.setdefault('StartIndex', 0)
+ params.setdefault("StartIndex", 0)
def get_query_params(params, start, count):
params_copy = dict(params)
- params_copy['StartIndex'] = start
- params_copy['Limit'] = count
+ params_copy["StartIndex"] = start
+ params_copy["Limit"] = count
return params_copy
query_params = [
get_query_params(params, offset, limit)
- for offset
- in range(params['StartIndex'], items['TotalRecordCount'], limit)
+ for offset in range(params["StartIndex"], items["TotalRecordCount"], limit)
]
# multiprocessing.dummy.Pool completes all requests in multiple threads but has to
@@ -257,27 +266,29 @@ def get_wrapper(params):
# process complete jobs
for job in concurrent.futures.as_completed(jobs):
# get the result
- result = job.result() or {'Items': []}
- query['params'] = jobs[job]
+ result = job.result() or {"Items": []}
+ query["params"] = jobs[job]
# free job memory
del jobs[job]
del job
# Mitigates #216 till the server validates the date provided is valid
- if result['Items'][0].get('ProductionYear'):
+ if result["Items"][0].get("ProductionYear"):
try:
- date(result['Items'][0]['ProductionYear'], 1, 1)
+ date(result["Items"][0]["ProductionYear"], 1, 1)
except ValueError:
- LOG.info('#216 mitigation triggered. Setting ProductionYear to None')
- result['Items'][0]['ProductionYear'] = None
+ LOG.info(
+ "#216 mitigation triggered. Setting ProductionYear to None"
+ )
+ result["Items"][0]["ProductionYear"] = None
- items['Items'].extend(result['Items'])
+ items["Items"].extend(result["Items"])
# Using items to return data and communicate a restore point back to the callee is
# a violation of the SRP. TODO: Separate responsibilities.
- items['RestorePoint'] = query
+ items["RestorePoint"] = query
yield items
- del items['Items'][:]
+ del items["Items"][:]
# release the semaphore again
thread_buffer.release()
@@ -307,25 +318,25 @@ def run(self):
return
request = {
- 'type': "GET",
- 'handler': "Users/{UserId}/Items",
- 'params': {
- 'Ids': ','.join(str(x) for x in item_ids),
- 'Fields': api.info()
- }
+ "type": "GET",
+ "handler": "Users/{UserId}/Items",
+ "params": {
+ "Ids": ",".join(str(x) for x in item_ids),
+ "Fields": api.info(),
+ },
}
try:
result = self.server.http.request(request, s)
- for item in result['Items']:
+ for item in result["Items"]:
- if item['Type'] in self.output:
- self.output[item['Type']].put(item)
+ if item["Type"] in self.output:
+ self.output[item["Type"]].put(item)
except HTTPException as error:
LOG.error("--[ http status: %s ]", error.status)
- if error.status == 'ServerUnreachable':
+ if error.status == "ServerUnreachable":
self.is_done = True
break
@@ -335,5 +346,5 @@ def run(self):
self.queue.task_done()
- if window('jellyfin_should_stop.bool'):
+ if window("jellyfin_should_stop.bool"):
break
diff --git a/jellyfin_kodi/entrypoint/context.py b/jellyfin_kodi/entrypoint/context.py
index dca7e9f92..e5d383b8c 100644
--- a/jellyfin_kodi/entrypoint/context.py
+++ b/jellyfin_kodi/entrypoint/context.py
@@ -17,14 +17,18 @@
#################################################################################################
LOG = LazyLogger(__name__)
-XML_PATH = (xbmcaddon.Addon('plugin.video.jellyfin').getAddonInfo('path'), "default", "1080i")
+XML_PATH = (
+ xbmcaddon.Addon("plugin.video.jellyfin").getAddonInfo("path"),
+ "default",
+ "1080i",
+)
OPTIONS = {
- 'Refresh': translate(30410),
- 'Delete': translate(30409),
- 'Addon': translate(30408),
- 'AddFav': translate(30405),
- 'RemoveFav': translate(30406),
- 'Transcode': translate(30412)
+ "Refresh": translate(30410),
+ "Delete": translate(30409),
+ "Addon": translate(30408),
+ "AddFav": translate(30405),
+ "RemoveFav": translate(30406),
+ "Transcode": translate(30412),
}
#################################################################################################
@@ -39,31 +43,33 @@ def __init__(self, transcode=False, delete=False):
try:
self.kodi_id = sys.listitem.getVideoInfoTag().getDbId() or None
self.media = self.get_media_type()
- self.server_id = sys.listitem.getProperty('jellyfinserver') or None
+ self.server_id = sys.listitem.getProperty("jellyfinserver") or None
self.api_client = Jellyfin(self.server_id).get_client().jellyfin
- item_id = sys.listitem.getProperty('jellyfinid')
+ item_id = sys.listitem.getProperty("jellyfinid")
except AttributeError:
self.server_id = None
- if xbmc.getInfoLabel('ListItem.Property(jellyfinid)'):
- item_id = xbmc.getInfoLabel('ListItem.Property(jellyfinid)')
+ if xbmc.getInfoLabel("ListItem.Property(jellyfinid)"):
+ item_id = xbmc.getInfoLabel("ListItem.Property(jellyfinid)")
else:
- self.kodi_id = xbmc.getInfoLabel('ListItem.DBID')
- self.media = xbmc.getInfoLabel('ListItem.DBTYPE')
+ self.kodi_id = xbmc.getInfoLabel("ListItem.DBID")
+ self.media = xbmc.getInfoLabel("ListItem.DBTYPE")
item_id = None
- addon_data = translate_path("special://profile/addon_data/plugin.video.jellyfin/data.json")
- with open(addon_data, 'rb') as infile:
+ addon_data = translate_path(
+ "special://profile/addon_data/plugin.video.jellyfin/data.json"
+ )
+ with open(addon_data, "rb") as infile:
data = json.load(infile)
try:
- server_data = data['Servers'][0]
- self.api_client.config.data['auth.server'] = server_data.get('address')
- self.api_client.config.data['auth.server-name'] = server_data.get('Name')
- self.api_client.config.data['auth.user_id'] = server_data.get('UserId')
- self.api_client.config.data['auth.token'] = server_data.get('AccessToken')
+ server_data = data["Servers"][0]
+ self.api_client.config.data["auth.server"] = server_data.get("address")
+ self.api_client.config.data["auth.server-name"] = server_data.get("Name")
+ self.api_client.config.data["auth.user_id"] = server_data.get("UserId")
+ self.api_client.config.data["auth.token"] = server_data.get("AccessToken")
except Exception as e:
- LOG.warning('Addon appears to not be configured yet: {}'.format(e))
+ LOG.warning("Addon appears to not be configured yet: {}".format(e))
if self.server_id or item_id:
self.item = self.api_client.get_item(item_id)
@@ -81,26 +87,28 @@ def __init__(self, transcode=False, delete=False):
elif self.select_menu():
self.action_menu()
- if self._selected_option in (OPTIONS['Delete'], OPTIONS['AddFav'], OPTIONS['RemoveFav']):
+ if self._selected_option in (
+ OPTIONS["Delete"],
+ OPTIONS["AddFav"],
+ OPTIONS["RemoveFav"],
+ ):
xbmc.sleep(500)
- xbmc.executebuiltin('Container.Refresh')
+ xbmc.executebuiltin("Container.Refresh")
def get_media_type(self):
-
- ''' Get media type based on sys.listitem. If unfilled, base on visible window.
- '''
+ """Get media type based on sys.listitem. If unfilled, base on visible window."""
media = sys.listitem.getVideoInfoTag().getMediaType()
if not media:
- if xbmc.getCondVisibility('Container.Content(albums)'):
+ if xbmc.getCondVisibility("Container.Content(albums)"):
media = "album"
- elif xbmc.getCondVisibility('Container.Content(artists)'):
+ elif xbmc.getCondVisibility("Container.Content(artists)"):
media = "artist"
- elif xbmc.getCondVisibility('Container.Content(songs)'):
+ elif xbmc.getCondVisibility("Container.Content(songs)"):
media = "song"
- elif xbmc.getCondVisibility('Container.Content(pictures)'):
+ elif xbmc.getCondVisibility("Container.Content(pictures)"):
media = "picture"
else:
LOG.info("media is unknown")
@@ -108,40 +116,37 @@ def get_media_type(self):
return media
def get_item_id(self):
-
- ''' Get synced item from jellyfindb.
- '''
+ """Get synced item from jellyfindb."""
item = database.get_item(self.kodi_id, self.media)
if not item:
return
return {
- 'Id': item[0],
- 'UserData': json.loads(item[4]) if item[4] else {},
- 'Type': item[3]
+ "Id": item[0],
+ "UserData": json.loads(item[4]) if item[4] else {},
+ "Type": item[3],
}
def select_menu(self):
-
- ''' Display the select dialog.
- Favorites, Refresh, Delete (opt), Settings.
- '''
+ """Display the select dialog.
+ Favorites, Refresh, Delete (opt), Settings.
+ """
options = []
- if self.item['Type'] != 'Season':
+ if self.item["Type"] != "Season":
- if self.item['UserData'].get('IsFavorite'):
- options.append(OPTIONS['RemoveFav'])
+ if self.item["UserData"].get("IsFavorite"):
+ options.append(OPTIONS["RemoveFav"])
else:
- options.append(OPTIONS['AddFav'])
+ options.append(OPTIONS["AddFav"])
- options.append(OPTIONS['Refresh'])
+ options.append(OPTIONS["Refresh"])
- if settings('enableContextDelete.bool'):
- options.append(OPTIONS['Delete'])
+ if settings("enableContextDelete.bool"):
+ options.append(OPTIONS["Delete"])
- options.append(OPTIONS['Addon'])
+ options.append(OPTIONS["Addon"])
context_menu = context.ContextMenu("script-jellyfin-context.xml", *XML_PATH)
context_menu.set_options(options)
@@ -156,24 +161,26 @@ def action_menu(self):
selected = self._selected_option
- if selected == OPTIONS['Refresh']:
- self.api_client.refresh_item(self.item['Id'])
+ if selected == OPTIONS["Refresh"]:
+ self.api_client.refresh_item(self.item["Id"])
- elif selected == OPTIONS['AddFav']:
- self.api_client.favorite(self.item['Id'], True)
+ elif selected == OPTIONS["AddFav"]:
+ self.api_client.favorite(self.item["Id"], True)
- elif selected == OPTIONS['RemoveFav']:
- self.api_client.favorite(self.item['Id'], False)
+ elif selected == OPTIONS["RemoveFav"]:
+ self.api_client.favorite(self.item["Id"], False)
- elif selected == OPTIONS['Addon']:
- xbmc.executebuiltin('Addon.OpenSettings(plugin.video.jellyfin)')
+ elif selected == OPTIONS["Addon"]:
+ xbmc.executebuiltin("Addon.OpenSettings(plugin.video.jellyfin)")
- elif selected == OPTIONS['Delete']:
+ elif selected == OPTIONS["Delete"]:
self.delete_item()
def delete_item(self):
- if settings('skipContextMenu.bool') or dialog("yesno", "{jellyfin}", translate(33015)):
- self.api_client.delete_item(self.item['Id'])
+ if settings("skipContextMenu.bool") or dialog(
+ "yesno", "{jellyfin}", translate(33015)
+ ):
+ self.api_client.delete_item(self.item["Id"])
def transcode(self):
filename = xbmc.getInfoLabel("ListItem.Filenameandpath")
diff --git a/jellyfin_kodi/entrypoint/default.py b/jellyfin_kodi/entrypoint/default.py
index d8f6c049a..9b4185f2c 100644
--- a/jellyfin_kodi/entrypoint/default.py
+++ b/jellyfin_kodi/entrypoint/default.py
@@ -14,7 +14,16 @@
from .. import client
from ..database import reset, get_sync, Database, jellyfin_db, get_credentials
from ..objects import Objects, Actions
-from ..helper import translate, event, settings, window, dialog, api, JSONRPC, LazyLogger
+from ..helper import (
+ translate,
+ event,
+ settings,
+ window,
+ dialog,
+ api,
+ JSONRPC,
+ LazyLogger,
+)
from ..helper.utils import JsonDebugPrinter, translate_path, kodi_version
from ..jellyfin import Jellyfin
@@ -35,10 +44,9 @@
class Events(object):
def __init__(self):
-
- ''' Parse the parameters. Reroute to our service.py
- where user is fully identified already.
- '''
+ """Parse the parameters. Reroute to our service.py
+ where user is fully identified already.
+ """
base_url = ADDON_BASE_URL
path = QUERY_STRING
@@ -47,142 +55,193 @@ def __init__(self):
except Exception:
params = {}
- mode = params.get('mode')
- server = params.get('server')
+ mode = params.get("mode")
+ server = params.get("server")
- if server == 'None':
+ if server == "None":
server = None
jellyfin_client = Jellyfin(server).get_client()
api_client = jellyfin_client.jellyfin
- addon_data = translate_path("special://profile/addon_data/plugin.video.jellyfin/data.json")
+ addon_data = translate_path(
+ "special://profile/addon_data/plugin.video.jellyfin/data.json"
+ )
try:
- with open(addon_data, 'rb') as infile:
+ with open(addon_data, "rb") as infile:
data = json.load(infile)
- server_data = data['Servers'][0]
- api_client.config.data['auth.server'] = server_data.get('address')
- api_client.config.data['auth.server-name'] = server_data.get('Name')
- api_client.config.data['auth.user_id'] = server_data.get('UserId')
- api_client.config.data['auth.token'] = server_data.get('AccessToken')
+ server_data = data["Servers"][0]
+ api_client.config.data["auth.server"] = server_data.get("address")
+ api_client.config.data["auth.server-name"] = server_data.get("Name")
+ api_client.config.data["auth.user_id"] = server_data.get("UserId")
+ api_client.config.data["auth.token"] = server_data.get("AccessToken")
except Exception as e:
- LOG.warning('Addon appears to not be configured yet: {}'.format(e))
+ LOG.warning("Addon appears to not be configured yet: {}".format(e))
LOG.info("path: %s params: %s", path, JsonDebugPrinter(params))
- if '/extrafanart' in base_url:
+ if "/extrafanart" in base_url:
jellyfin_path = path[1:]
- jellyfin_id = params.get('id')
+ jellyfin_id = params.get("id")
get_fanart(jellyfin_id, jellyfin_path, server, api_client)
- elif '/Extras' in base_url or '/VideoFiles' in base_url:
+ elif "/Extras" in base_url or "/VideoFiles" in base_url:
jellyfin_path = path[1:]
- jellyfin_id = params.get('id')
+ jellyfin_id = params.get("id")
get_video_extras(jellyfin_id, jellyfin_path, server, api_client)
- elif mode == 'play':
+ elif mode == "play":
- item = api_client.get_item(params['id'])
+ item = api_client.get_item(params["id"])
item["resumePlayback"] = sys.argv[3].split(":")[1] == "true"
- Actions(server, api_client).play(item, params.get('dbid'), params.get('transcode') == 'true', playlist=params.get('playlist') == 'true')
-
- elif mode == 'playlist':
- api_client.post_session(api_client.config.data['app.session'], "Playing", {
- 'PlayCommand': "PlayNow",
- 'ItemIds': params['id'],
- 'StartPositionTicks': 0
- })
- elif mode == 'deviceid':
+ Actions(server, api_client).play(
+ item,
+ params.get("dbid"),
+ params.get("transcode") == "true",
+ playlist=params.get("playlist") == "true",
+ )
+
+ elif mode == "playlist":
+ api_client.post_session(
+ api_client.config.data["app.session"],
+ "Playing",
+ {
+ "PlayCommand": "PlayNow",
+ "ItemIds": params["id"],
+ "StartPositionTicks": 0,
+ },
+ )
+ elif mode == "deviceid":
client.reset_device_id()
- elif mode == 'reset':
+ elif mode == "reset":
reset()
- elif mode == 'delete':
+ elif mode == "delete":
delete_item()
- elif mode == 'refreshboxsets':
- event('SyncLibrary', {'Id': "Boxsets:Refresh"})
- elif mode == 'nextepisodes':
- get_next_episodes(params['id'], params['limit'])
- elif mode == 'browse':
- browse(params.get('type'), params.get('id'), params.get('folder'), server, api_client)
- elif mode == 'synclib':
- event('SyncLibrary', {'Id': params.get('id')})
- elif mode == 'updatelib':
- event('SyncLibrary', {'Id': params.get('id'), 'Update': True})
- elif mode == 'repairlib':
- event('RepairLibrary', {'Id': params.get('id')})
- elif mode == 'removelib':
- event('RemoveLibrary', {'Id': params.get('id')})
- elif mode == 'repairlibs':
- event('RepairLibrarySelection')
- elif mode == 'updatelibs':
- event('SyncLibrarySelection')
- elif mode == 'removelibs':
- event('RemoveLibrarySelection')
- elif mode == 'addlibs':
- event('AddLibrarySelection')
- elif mode == 'addserver':
- event('AddServer')
- elif mode == 'login':
- event('ServerConnect', {'Id': server})
- elif mode == 'removeserver':
- event('RemoveServer', {'Id': server})
- elif mode == 'settings':
- xbmc.executebuiltin('Addon.OpenSettings(plugin.video.jellyfin)')
- elif mode == 'adduser':
+ elif mode == "refreshboxsets":
+ event("SyncLibrary", {"Id": "Boxsets:Refresh"})
+ elif mode == "nextepisodes":
+ get_next_episodes(params["id"], params["limit"])
+ elif mode == "browse":
+ browse(
+ params.get("type"),
+ params.get("id"),
+ params.get("folder"),
+ server,
+ api_client,
+ )
+ elif mode == "synclib":
+ event("SyncLibrary", {"Id": params.get("id")})
+ elif mode == "updatelib":
+ event("SyncLibrary", {"Id": params.get("id"), "Update": True})
+ elif mode == "repairlib":
+ event("RepairLibrary", {"Id": params.get("id")})
+ elif mode == "removelib":
+ event("RemoveLibrary", {"Id": params.get("id")})
+ elif mode == "repairlibs":
+ event("RepairLibrarySelection")
+ elif mode == "updatelibs":
+ event("SyncLibrarySelection")
+ elif mode == "removelibs":
+ event("RemoveLibrarySelection")
+ elif mode == "addlibs":
+ event("AddLibrarySelection")
+ elif mode == "addserver":
+ event("AddServer")
+ elif mode == "login":
+ event("ServerConnect", {"Id": server})
+ elif mode == "removeserver":
+ event("RemoveServer", {"Id": server})
+ elif mode == "settings":
+ xbmc.executebuiltin("Addon.OpenSettings(plugin.video.jellyfin)")
+ elif mode == "adduser":
add_user(api_client)
- elif mode == 'updatepassword':
- event('UpdatePassword')
- elif mode == 'thememedia':
+ elif mode == "updatepassword":
+ event("UpdatePassword")
+ elif mode == "thememedia":
get_themes(api_client)
- elif mode == 'managelibs':
+ elif mode == "managelibs":
manage_libraries()
- elif mode == 'backup':
+ elif mode == "backup":
backup()
- elif mode == 'restartservice':
- window('jellyfin.restart.bool', True)
- elif mode is None and not params and base_url != 'plugin://plugin.video.jellyfin/':
+ elif mode == "restartservice":
+ window("jellyfin.restart.bool", True)
+ elif (
+ mode is None
+ and not params
+ and base_url != "plugin://plugin.video.jellyfin/"
+ ):
# Used when selecting "Browse" from a context menu, see #548
- item_id = base_url.strip('/').split('/')[-1]
- browse('', item_id, None, server, api_client)
+ item_id = base_url.strip("/").split("/")[-1]
+ browse("", item_id, None, server, api_client)
else:
listing()
def listing():
-
- ''' Display all jellyfin nodes and dynamic entries when appropriate.
- '''
- total = int(window('Jellyfin.nodes.total') or 0)
+ """Display all jellyfin nodes and dynamic entries when appropriate."""
+ total = int(window("Jellyfin.nodes.total") or 0)
sync = get_sync()
- whitelist = [x.replace('Mixed:', "") for x in sync['Whitelist']]
- servers = get_credentials()['Servers'][1:]
+ whitelist = [x.replace("Mixed:", "") for x in sync["Whitelist"]]
+ servers = get_credentials()["Servers"][1:]
for i in range(total):
window_prop = "Jellyfin.nodes.%s" % i
- path = window('%s.index' % window_prop)
+ path = window("%s.index" % window_prop)
if not path:
- path = window('%s.content' % window_prop) or window('%s.path' % window_prop)
+ path = window("%s.content" % window_prop) or window("%s.path" % window_prop)
- label = window('%s.title' % window_prop)
- node = window('%s.type' % window_prop)
- artwork = window('%s.artwork' % window_prop)
- view_id = window('%s.id' % window_prop)
+ label = window("%s.title" % window_prop)
+ node = window("%s.type" % window_prop)
+ artwork = window("%s.artwork" % window_prop)
+ view_id = window("%s.id" % window_prop)
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, translate(33166))
- context.append((translate(33123), "RunPlugin(plugin://plugin.video.jellyfin/?mode=synclib&id=%s)" % view_id))
-
- if view_id and node in ('movies', 'tvshows', 'musicvideos', 'music') and view_id in whitelist:
-
- context.append((translate(33136), "RunPlugin(plugin://plugin.video.jellyfin/?mode=updatelib&id=%s)" % view_id))
- context.append((translate(33132), "RunPlugin(plugin://plugin.video.jellyfin/?mode=repairlib&id=%s)" % view_id))
- context.append((translate(33133), "RunPlugin(plugin://plugin.video.jellyfin/?mode=removelib&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
+ ):
+
+ context.append(
+ (
+ translate(33136),
+ "RunPlugin(plugin://plugin.video.jellyfin/?mode=updatelib&id=%s)"
+ % view_id,
+ )
+ )
+ context.append(
+ (
+ translate(33132),
+ "RunPlugin(plugin://plugin.video.jellyfin/?mode=repairlib&id=%s)"
+ % view_id,
+ )
+ )
+ context.append(
+ (
+ translate(33133),
+ "RunPlugin(plugin://plugin.video.jellyfin/?mode=removelib&id=%s)"
+ % view_id,
+ )
+ )
LOG.debug("--[ listing/%s/%s ] %s", node, label, path)
@@ -192,33 +251,52 @@ def listing():
for server in servers:
context = []
- if server.get('ManualAddress'):
- context.append((translate(33141), "RunPlugin(plugin://plugin.video.jellyfin/?mode=removeserver&server=%s)" % server['Id']))
-
- if 'AccessToken' not in server:
- directory("%s (%s)" % (server['Name'], translate(30539)), "plugin://plugin.video.jellyfin/?mode=login&server=%s" % server['Id'], False, context=context)
+ if server.get("ManualAddress"):
+ context.append(
+ (
+ translate(33141),
+ "RunPlugin(plugin://plugin.video.jellyfin/?mode=removeserver&server=%s)"
+ % server["Id"],
+ )
+ )
+
+ if "AccessToken" not in server:
+ directory(
+ "%s (%s)" % (server["Name"], translate(30539)),
+ "plugin://plugin.video.jellyfin/?mode=login&server=%s" % server["Id"],
+ False,
+ context=context,
+ )
else:
- directory(server['Name'], "plugin://plugin.video.jellyfin/?mode=browse&server=%s" % server['Id'], context=context)
+ directory(
+ server["Name"],
+ "plugin://plugin.video.jellyfin/?mode=browse&server=%s" % server["Id"],
+ context=context,
+ )
directory(translate(33194), "plugin://plugin.video.jellyfin/?mode=managelibs", True)
directory(translate(33134), "plugin://plugin.video.jellyfin/?mode=addserver", False)
directory(translate(33054), "plugin://plugin.video.jellyfin/?mode=adduser", False)
directory(translate(5), "plugin://plugin.video.jellyfin/?mode=settings", False)
- directory(translate(33161), "plugin://plugin.video.jellyfin/?mode=updatepassword", False)
+ directory(
+ translate(33161), "plugin://plugin.video.jellyfin/?mode=updatepassword", False
+ )
directory(translate(33058), "plugin://plugin.video.jellyfin/?mode=reset", False)
- directory(translate(33180), "plugin://plugin.video.jellyfin/?mode=restartservice", False)
+ directory(
+ translate(33180), "plugin://plugin.video.jellyfin/?mode=restartservice", False
+ )
- if settings('backupPath'):
- directory(translate(33092), "plugin://plugin.video.jellyfin/?mode=backup", False)
+ if settings("backupPath"):
+ directory(
+ translate(33092), "plugin://plugin.video.jellyfin/?mode=backup", False
+ )
- xbmcplugin.setContent(PROCESS_HANDLE, 'files')
+ xbmcplugin.setContent(PROCESS_HANDLE, "files")
xbmcplugin.endOfDirectory(PROCESS_HANDLE)
def directory(label, path, folder=True, artwork=None, fanart=None, context=None):
-
- ''' Add directory listitem. context should be a list of tuples [(label, action)*]
- '''
+ """Add directory listitem. context should be a list of tuples [(label, action)*]"""
li = dir_listitem(label, path, artwork, fanart)
if context:
@@ -230,44 +308,56 @@ def directory(label, path, folder=True, artwork=None, fanart=None, context=None)
def dir_listitem(label, path, artwork=None, fanart=None):
-
- ''' Gets the icon paths for default node listings
- '''
+ """Gets the icon paths for default node listings"""
li = xbmcgui.ListItem(label, path=path)
- li.setArt({
- "thumb": artwork or "special://home/addons/plugin.video.jellyfin/resources/icon.png",
- "fanart": fanart or "special://home/addons/plugin.video.jellyfin/resources/fanart.png",
- "landscape": artwork or fanart or "special://home/addons/plugin.video.jellyfin/resources/fanart.png",
- })
+ li.setArt(
+ {
+ "thumb": artwork
+ or "special://home/addons/plugin.video.jellyfin/resources/icon.png",
+ "fanart": fanart
+ or "special://home/addons/plugin.video.jellyfin/resources/fanart.png",
+ "landscape": artwork
+ or fanart
+ or "special://home/addons/plugin.video.jellyfin/resources/fanart.png",
+ }
+ )
return li
def manage_libraries():
- directory(translate(33098), "plugin://plugin.video.jellyfin/?mode=refreshboxsets", False)
+ directory(
+ translate(33098), "plugin://plugin.video.jellyfin/?mode=refreshboxsets", False
+ )
directory(translate(33154), "plugin://plugin.video.jellyfin/?mode=addlibs", False)
- directory(translate(33139), "plugin://plugin.video.jellyfin/?mode=updatelibs", False)
- directory(translate(33140), "plugin://plugin.video.jellyfin/?mode=repairlibs", False)
- directory(translate(33184), "plugin://plugin.video.jellyfin/?mode=removelibs", False)
- directory(translate(33060), "plugin://plugin.video.jellyfin/?mode=thememedia", False)
+ directory(
+ translate(33139), "plugin://plugin.video.jellyfin/?mode=updatelibs", False
+ )
+ directory(
+ translate(33140), "plugin://plugin.video.jellyfin/?mode=repairlibs", False
+ )
+ directory(
+ translate(33184), "plugin://plugin.video.jellyfin/?mode=removelibs", False
+ )
+ directory(
+ translate(33060), "plugin://plugin.video.jellyfin/?mode=thememedia", False
+ )
- xbmcplugin.setContent(PROCESS_HANDLE, 'files')
+ xbmcplugin.setContent(PROCESS_HANDLE, "files")
xbmcplugin.endOfDirectory(PROCESS_HANDLE)
def browse(media, view_id=None, folder=None, server_id=None, api_client=None):
-
- ''' Browse content dynamically.
- '''
+ """Browse content dynamically."""
LOG.info("--[ v:%s/%s ] %s", view_id, media, folder)
- if not window('jellyfin_online.bool') and server_id is None:
+ if not window("jellyfin_online.bool") and server_id is None:
monitor = xbmc.Monitor()
for _i in range(300):
- if window('jellyfin_online.bool'):
+ if window("jellyfin_online.bool"):
break
elif monitor.waitForAbort(0.1):
return
@@ -278,140 +368,359 @@ def browse(media, view_id=None, folder=None, server_id=None, api_client=None):
folder = folder.lower() if folder else None
- if folder is None and media in ('homevideos', 'movies', 'books', 'audiobooks'):
+ if folder is None and media in ("homevideos", "movies", "books", "audiobooks"):
return browse_subfolders(media, view_id, server_id)
- if folder and folder == 'firstletter':
+ if folder and folder == "firstletter":
return browse_letters(media, view_id, server_id)
if view_id:
view = api_client.get_item(view_id)
- xbmcplugin.setPluginCategory(PROCESS_HANDLE, view['Name'])
+ xbmcplugin.setPluginCategory(PROCESS_HANDLE, view["Name"])
content_type = "files"
- if media in ('tvshows', 'seasons', 'episodes', 'movies', 'musicvideos', 'songs', 'albums'):
+ if media in (
+ "tvshows",
+ "seasons",
+ "episodes",
+ "movies",
+ "musicvideos",
+ "songs",
+ "albums",
+ ):
content_type = media
- elif media in ('homevideos', 'photos'):
+ elif media in ("homevideos", "photos"):
content_type = "images"
- elif media in ('books', 'audiobooks'):
+ elif media in ("books", "audiobooks"):
content_type = "videos"
- elif media == 'music':
+ elif media == "music":
content_type = "artists"
- if folder == 'recentlyadded':
+ if folder == "recentlyadded":
listing = api_client.get_recently_added(None, view_id, None)
- elif folder == 'genres':
+ elif folder == "genres":
listing = api_client.get_genres(view_id)
- elif media == 'livetv':
+ elif media == "livetv":
listing = api_client.get_channels()
- elif folder == 'unwatched':
- listing = get_filtered_section(view_id, None, None, None, None, None, ['IsUnplayed'], None, server_id, api_client)
- elif folder == 'favorite':
- listing = get_filtered_section(view_id, None, None, None, None, None, ['IsFavorite'], None, server_id, api_client)
- elif folder == 'inprogress':
- listing = get_filtered_section(view_id, None, None, None, None, None, ['IsResumable'], None, server_id, api_client)
- elif folder == 'boxsets':
- listing = get_filtered_section(view_id, get_media_type('boxsets'), None, True, None, None, None, None, server_id, api_client)
- elif folder == 'random':
- listing = get_filtered_section(view_id, get_media_type(content_type), 25, True, "Random", None, None, None, server_id, api_client)
- elif (folder or "").startswith('firstletter-'):
- listing = get_filtered_section(view_id, get_media_type(content_type), None, None, None, None, None, {'NameStartsWith': folder.split('-')[1]}, server_id, api_client)
- elif (folder or "").startswith('genres-'):
- listing = get_filtered_section(view_id, get_media_type(content_type), None, None, None, None, None, {'GenreIds': folder.split('-')[1]}, server_id, api_client)
- elif folder == 'favepisodes':
- listing = get_filtered_section(None, get_media_type(content_type), 25, None, None, None, ['IsFavorite'], None, server_id, api_client)
- elif folder and media == 'playlists':
- listing = get_filtered_section(folder, get_media_type(content_type), None, False, 'None', None, None, None, server_id, api_client)
- elif media == 'homevideos':
- listing = get_filtered_section(folder or view_id, get_media_type(content_type), None, False, None, None, None, None, server_id, api_client)
- elif media in ['movies', 'episodes']:
- listing = get_filtered_section(folder or view_id, get_media_type(content_type), None, True, None, None, None, None, server_id, api_client)
- elif media in ('boxset', 'library'):
- listing = get_filtered_section(folder or view_id, None, None, True, None, None, None, None, server_id, api_client)
- elif media == 'boxsets':
- listing = get_filtered_section(folder or view_id, None, None, False, None, None, ['Boxsets'], None, server_id, api_client)
- elif media == 'tvshows':
- listing = get_filtered_section(folder or view_id, get_media_type(content_type), None, True, None, None, None, None, server_id, api_client)
- elif media == 'seasons':
+ elif folder == "unwatched":
+ listing = get_filtered_section(
+ view_id,
+ None,
+ None,
+ None,
+ None,
+ None,
+ ["IsUnplayed"],
+ None,
+ server_id,
+ api_client,
+ )
+ elif folder == "favorite":
+ listing = get_filtered_section(
+ view_id,
+ None,
+ None,
+ None,
+ None,
+ None,
+ ["IsFavorite"],
+ None,
+ server_id,
+ api_client,
+ )
+ elif folder == "inprogress":
+ listing = get_filtered_section(
+ view_id,
+ None,
+ None,
+ None,
+ None,
+ None,
+ ["IsResumable"],
+ None,
+ server_id,
+ api_client,
+ )
+ elif folder == "boxsets":
+ listing = get_filtered_section(
+ view_id,
+ get_media_type("boxsets"),
+ None,
+ True,
+ None,
+ None,
+ None,
+ None,
+ server_id,
+ api_client,
+ )
+ elif folder == "random":
+ listing = get_filtered_section(
+ view_id,
+ get_media_type(content_type),
+ 25,
+ True,
+ "Random",
+ None,
+ None,
+ None,
+ server_id,
+ api_client,
+ )
+ elif (folder or "").startswith("firstletter-"):
+ listing = get_filtered_section(
+ view_id,
+ get_media_type(content_type),
+ None,
+ None,
+ None,
+ None,
+ None,
+ {"NameStartsWith": folder.split("-")[1]},
+ server_id,
+ api_client,
+ )
+ elif (folder or "").startswith("genres-"):
+ listing = get_filtered_section(
+ view_id,
+ get_media_type(content_type),
+ None,
+ None,
+ None,
+ None,
+ None,
+ {"GenreIds": folder.split("-")[1]},
+ server_id,
+ api_client,
+ )
+ elif folder == "favepisodes":
+ listing = get_filtered_section(
+ None,
+ get_media_type(content_type),
+ 25,
+ None,
+ None,
+ None,
+ ["IsFavorite"],
+ None,
+ server_id,
+ api_client,
+ )
+ elif folder and media == "playlists":
+ listing = get_filtered_section(
+ folder,
+ get_media_type(content_type),
+ None,
+ False,
+ "None",
+ None,
+ None,
+ None,
+ server_id,
+ api_client,
+ )
+ elif media == "homevideos":
+ listing = get_filtered_section(
+ folder or view_id,
+ get_media_type(content_type),
+ None,
+ False,
+ None,
+ None,
+ None,
+ None,
+ server_id,
+ api_client,
+ )
+ elif media in ["movies", "episodes"]:
+ listing = get_filtered_section(
+ folder or view_id,
+ get_media_type(content_type),
+ None,
+ True,
+ None,
+ None,
+ None,
+ None,
+ server_id,
+ api_client,
+ )
+ elif media in ("boxset", "library"):
+ listing = get_filtered_section(
+ folder or view_id,
+ None,
+ None,
+ True,
+ None,
+ None,
+ None,
+ None,
+ server_id,
+ api_client,
+ )
+ elif media == "boxsets":
+ listing = get_filtered_section(
+ folder or view_id,
+ None,
+ None,
+ False,
+ None,
+ None,
+ ["Boxsets"],
+ None,
+ server_id,
+ api_client,
+ )
+ elif media == "tvshows":
+ listing = get_filtered_section(
+ folder or view_id,
+ get_media_type(content_type),
+ None,
+ True,
+ None,
+ None,
+ None,
+ None,
+ server_id,
+ api_client,
+ )
+ elif media == "seasons":
listing = api_client.get_seasons(folder)
- elif media != 'files':
- listing = get_filtered_section(folder or view_id, get_media_type(content_type), None, False, None, None, None, None, server_id, api_client)
+ elif media != "files":
+ listing = get_filtered_section(
+ folder or view_id,
+ get_media_type(content_type),
+ None,
+ False,
+ None,
+ None,
+ None,
+ None,
+ server_id,
+ api_client,
+ )
else:
- listing = get_filtered_section(folder or view_id, None, None, False, None, None, None, None, server_id, api_client)
+ listing = get_filtered_section(
+ folder or view_id,
+ None,
+ None,
+ False,
+ None,
+ None,
+ None,
+ None,
+ server_id,
+ api_client,
+ )
if listing:
actions = Actions(server_id, api_client)
list_li = []
- listing = listing if type(listing) == list else listing.get('Items', [])
+ listing = listing if type(listing) == list else listing.get("Items", [])
for item in listing:
li = xbmcgui.ListItem()
- li.setProperty('jellyfinid', item['Id'])
- li.setProperty('jellyfinserver', server_id)
+ li.setProperty("jellyfinid", item["Id"])
+ li.setProperty("jellyfinserver", server_id)
actions.set_listitem(item, li)
- if item.get('IsFolder'):
+ if item.get("IsFolder"):
params = {
- 'id': view_id or item['Id'],
- 'mode': "browse",
- 'type': get_folder_type(item, media) or media,
- 'folder': item['Id'],
- 'server': server_id
+ "id": view_id or item["Id"],
+ "mode": "browse",
+ "type": get_folder_type(item, media) or media,
+ "folder": item["Id"],
+ "server": server_id,
}
path = "%s?%s" % ("plugin://plugin.video.jellyfin/", urlencode(params))
context = []
- if item['Type'] in ('Series', 'Season', 'Playlist'):
- context.append(("Play", "RunPlugin(plugin://plugin.video.jellyfin/?mode=playlist&id=%s&server=%s)" % (item['Id'], server_id)))
-
- if item['UserData']['Played']:
- context.append((translate(16104), "RunPlugin(plugin://plugin.video.jellyfin/?mode=unwatched&id=%s&server=%s)" % (item['Id'], server_id)))
+ if item["Type"] in ("Series", "Season", "Playlist"):
+ context.append(
+ (
+ "Play",
+ "RunPlugin(plugin://plugin.video.jellyfin/?mode=playlist&id=%s&server=%s)"
+ % (item["Id"], server_id),
+ )
+ )
+
+ if item["UserData"]["Played"]:
+ context.append(
+ (
+ translate(16104),
+ "RunPlugin(plugin://plugin.video.jellyfin/?mode=unwatched&id=%s&server=%s)"
+ % (item["Id"], server_id),
+ )
+ )
else:
- context.append((translate(16103), "RunPlugin(plugin://plugin.video.jellyfin/?mode=watched&id=%s&server=%s)" % (item['Id'], server_id)))
+ context.append(
+ (
+ translate(16103),
+ "RunPlugin(plugin://plugin.video.jellyfin/?mode=watched&id=%s&server=%s)"
+ % (item["Id"], server_id),
+ )
+ )
li.addContextMenuItems(context)
list_li.append((path, li, True))
- elif item['Type'] == 'Genre':
+ elif item["Type"] == "Genre":
params = {
- 'id': view_id or item['Id'],
- 'mode': "browse",
- 'type': get_folder_type(item, media) or media,
- 'folder': 'genres-%s' % item['Id'],
- 'server': server_id
+ "id": view_id or item["Id"],
+ "mode": "browse",
+ "type": get_folder_type(item, media) or media,
+ "folder": "genres-%s" % item["Id"],
+ "server": server_id,
}
path = "%s?%s" % ("plugin://plugin.video.jellyfin/", urlencode(params))
list_li.append((path, li, True))
else:
- if item['Type'] not in ('Photo', 'PhotoAlbum'):
- params = {
- 'id': item['Id'],
- 'mode': "play",
- 'server': server_id
- }
- path = "%s?%s" % ("plugin://plugin.video.jellyfin/", urlencode(params))
- li.setProperty('path', path)
- context = [(translate(13412), "RunPlugin(plugin://plugin.video.jellyfin/?mode=playlist&id=%s&server=%s)" % (item['Id'], server_id))]
-
- if item['UserData']['Played']:
- context.append((translate(16104), "RunPlugin(plugin://plugin.video.jellyfin/?mode=unwatched&id=%s&server=%s)" % (item['Id'], server_id)))
+ if item["Type"] not in ("Photo", "PhotoAlbum"):
+ params = {"id": item["Id"], "mode": "play", "server": server_id}
+ path = "%s?%s" % (
+ "plugin://plugin.video.jellyfin/",
+ urlencode(params),
+ )
+ li.setProperty("path", path)
+ context = [
+ (
+ translate(13412),
+ "RunPlugin(plugin://plugin.video.jellyfin/?mode=playlist&id=%s&server=%s)"
+ % (item["Id"], server_id),
+ )
+ ]
+
+ if item["UserData"]["Played"]:
+ context.append(
+ (
+ translate(16104),
+ "RunPlugin(plugin://plugin.video.jellyfin/?mode=unwatched&id=%s&server=%s)"
+ % (item["Id"], server_id),
+ )
+ )
else:
- context.append((translate(16103), "RunPlugin(plugin://plugin.video.jellyfin/?mode=watched&id=%s&server=%s)" % (item['Id'], server_id)))
+ context.append(
+ (
+ translate(16103),
+ "RunPlugin(plugin://plugin.video.jellyfin/?mode=watched&id=%s&server=%s)"
+ % (item["Id"], server_id),
+ )
+ )
li.addContextMenuItems(context)
- list_li.append((li.getProperty('path'), li, False))
+ list_li.append((li.getProperty("path"), li, False))
xbmcplugin.addDirectoryItems(PROCESS_HANDLE, list_li, len(list_li))
- if content_type == 'images':
+ if content_type == "images":
xbmcplugin.addSortMethod(PROCESS_HANDLE, xbmcplugin.SORT_METHOD_VIDEO_TITLE)
xbmcplugin.addSortMethod(PROCESS_HANDLE, xbmcplugin.SORT_METHOD_DATE)
xbmcplugin.addSortMethod(PROCESS_HANDLE, xbmcplugin.SORT_METHOD_VIDEO_RATING)
@@ -422,99 +731,94 @@ def browse(media, view_id=None, folder=None, server_id=None, api_client=None):
def browse_subfolders(media, view_id, server_id=None):
-
- ''' Display submenus for jellyfin views.
- '''
+ """Display submenus for jellyfin views."""
from ..views import DYNNODES
view = Jellyfin(server_id).get_client().jellyfin.get_item(view_id)
- xbmcplugin.setPluginCategory(PROCESS_HANDLE, view['Name'])
+ xbmcplugin.setPluginCategory(PROCESS_HANDLE, view["Name"])
nodes = DYNNODES[media]
for node in nodes:
params = {
- 'id': view_id,
- 'mode': "browse",
- 'type': media,
- 'folder': view_id if node[0] == 'all' else node[0],
- 'server': server_id
+ "id": view_id,
+ "mode": "browse",
+ "type": media,
+ "folder": view_id if node[0] == "all" else node[0],
+ "server": server_id,
}
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(PROCESS_HANDLE, 'files')
+ xbmcplugin.setContent(PROCESS_HANDLE, "files")
xbmcplugin.endOfDirectory(PROCESS_HANDLE)
def browse_letters(media, view_id, server_id=None):
-
- ''' Display letters as options.
- '''
+ """Display letters as options."""
letters = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ"
view = Jellyfin(server_id).get_client().jellyfin.get_item(view_id)
- xbmcplugin.setPluginCategory(PROCESS_HANDLE, view['Name'])
+ xbmcplugin.setPluginCategory(PROCESS_HANDLE, view["Name"])
for node in letters:
params = {
- 'id': view_id,
- 'mode': "browse",
- 'type': media,
- 'folder': 'firstletter-%s' % node,
- 'server': server_id
+ "id": view_id,
+ "mode": "browse",
+ "type": media,
+ "folder": "firstletter-%s" % node,
+ "server": server_id,
}
path = "%s?%s" % ("plugin://plugin.video.jellyfin/", urlencode(params))
directory(node, path)
- xbmcplugin.setContent(PROCESS_HANDLE, 'files')
+ xbmcplugin.setContent(PROCESS_HANDLE, "files")
xbmcplugin.endOfDirectory(PROCESS_HANDLE)
def get_folder_type(item, content_type=None):
- media = item['Type']
+ media = item["Type"]
- if media == 'Series':
+ if media == "Series":
return "seasons"
- elif media == 'Season':
+ elif media == "Season":
return "episodes"
- elif media == 'BoxSet':
+ elif media == "BoxSet":
return "boxset"
- elif media == 'MusicArtist':
+ elif media == "MusicArtist":
return "albums"
- elif media == 'MusicAlbum':
+ elif media == "MusicAlbum":
return "songs"
- elif media == 'CollectionFolder':
- return item.get('CollectionType', 'library')
- elif media == 'Folder' and content_type == 'music':
+ elif media == "CollectionFolder":
+ return item.get("CollectionType", "library")
+ elif media == "Folder" and content_type == "music":
return "albums"
def get_media_type(media):
- if media == 'movies':
+ if media == "movies":
return "Movie,BoxSet"
- elif media == 'homevideos':
+ elif media == "homevideos":
return "Video,Folder,PhotoAlbum,Photo"
- elif media == 'episodes':
+ elif media == "episodes":
return "Episode"
- elif media == 'boxsets':
+ elif media == "boxsets":
return "BoxSet"
- elif media == 'tvshows':
+ elif media == "tvshows":
return "Series"
- elif media == 'music':
+ elif media == "music":
return "MusicArtist,MusicAlbum,Audio"
def get_fanart(item_id, path, server_id=None, api_client=None):
-
- ''' Get extra fanart for listitems. This is called by skinhelper.
- Images are stored locally, due to the Kodi caching system.
- '''
- if not item_id and 'plugin.video.jellyfin' in path:
- item_id = path.split('/')[-2]
+ """Get extra fanart for listitems. This is called by skinhelper.
+ Images are stored locally, due to the Kodi caching system.
+ """
+ if not item_id and "plugin.video.jellyfin" in path:
+ item_id = path.split("/")[-2]
if not item_id:
return
@@ -528,9 +832,9 @@ def get_fanart(item_id, path, server_id=None, api_client=None):
xbmcvfs.mkdirs(directory)
item = api_client.get_item(item_id)
- obj = objects.map(item, 'Artwork')
+ obj = objects.map(item, "Artwork")
backdrops = api.API(item).get_all_artwork(obj)
- tags = obj['BackdropTags']
+ tags = obj["BackdropTags"]
for index, backdrop in enumerate(backdrops):
@@ -553,12 +857,11 @@ def get_fanart(item_id, path, server_id=None, api_client=None):
def get_video_extras(item_id, path, server_id=None, api_client=None):
-
- ''' Returns the video files for the item as plugin listing, can be used
- to browse actual files or video extras, etc.
- '''
- if not item_id and 'plugin.video.jellyfin' in path:
- item_id = path.split('/')[-2]
+ """Returns the video files for the item as plugin listing, can be used
+ to browse actual files or video extras, etc.
+ """
+ if not item_id and "plugin.video.jellyfin" in path:
+ item_id = path.split("/")[-2]
if not item_id:
return
@@ -595,10 +898,8 @@ def getVideoFiles(jellyfinId,jellyfinPath):
def get_next_episodes(item_id, limit):
-
- ''' Only for synced content.
- '''
- with Database('jellyfin') as jellyfindb:
+ """Only for synced content."""
+ with Database("jellyfin") as jellyfindb:
db = jellyfin_db.JellyfinDatabase(jellyfindb.cursor)
library = db.get_view_name(item_id)
@@ -606,145 +907,173 @@ def get_next_episodes(item_id, limit):
if not library:
return
- result = JSONRPC('VideoLibrary.GetTVShows').execute({
- 'sort': {'order': "descending", 'method': "lastplayed"},
- 'filter': {
- 'and': [
- {'operator': "true", 'field': "inprogress", 'value': ""},
- {'operator': "is", 'field': "tag", 'value': "%s" % library}
- ]},
- 'properties': ['title', 'studio', 'mpaa', 'file', 'art']
- })
+ result = JSONRPC("VideoLibrary.GetTVShows").execute(
+ {
+ "sort": {"order": "descending", "method": "lastplayed"},
+ "filter": {
+ "and": [
+ {"operator": "true", "field": "inprogress", "value": ""},
+ {"operator": "is", "field": "tag", "value": "%s" % library},
+ ]
+ },
+ "properties": ["title", "studio", "mpaa", "file", "art"],
+ }
+ )
try:
- items = result['result']['tvshows']
+ items = result["result"]["tvshows"]
except (KeyError, TypeError):
return
list_li = []
for item in items:
- if settings('ignoreSpecialsNextEpisodes.bool'):
+ if settings("ignoreSpecialsNextEpisodes.bool"):
params = {
- 'tvshowid': item['tvshowid'],
- 'sort': {'method': "episode"},
- 'filter': {
- 'and': [
- {'operator': "lessthan", 'field': "playcount", 'value': "1"},
- {'operator': "greaterthan", 'field': "season", 'value': "0"}
- ]},
- 'properties': [
- "title", "playcount", "season", "episode", "showtitle",
- "plot", "file", "rating", "resume", "tvshowid", "art",
- "streamdetails", "firstaired", "runtime", "writer",
- "dateadded", "lastplayed"
+ "tvshowid": item["tvshowid"],
+ "sort": {"method": "episode"},
+ "filter": {
+ "and": [
+ {"operator": "lessthan", "field": "playcount", "value": "1"},
+ {"operator": "greaterthan", "field": "season", "value": "0"},
+ ]
+ },
+ "properties": [
+ "title",
+ "playcount",
+ "season",
+ "episode",
+ "showtitle",
+ "plot",
+ "file",
+ "rating",
+ "resume",
+ "tvshowid",
+ "art",
+ "streamdetails",
+ "firstaired",
+ "runtime",
+ "writer",
+ "dateadded",
+ "lastplayed",
],
- 'limits': {"end": 1}
+ "limits": {"end": 1},
}
else:
params = {
- 'tvshowid': item['tvshowid'],
- 'sort': {'method': "episode"},
- 'filter': {'operator': "lessthan", 'field': "playcount", 'value': "1"},
- 'properties': [
- "title", "playcount", "season", "episode", "showtitle",
- "plot", "file", "rating", "resume", "tvshowid", "art",
- "streamdetails", "firstaired", "runtime", "writer",
- "dateadded", "lastplayed"
+ "tvshowid": item["tvshowid"],
+ "sort": {"method": "episode"},
+ "filter": {"operator": "lessthan", "field": "playcount", "value": "1"},
+ "properties": [
+ "title",
+ "playcount",
+ "season",
+ "episode",
+ "showtitle",
+ "plot",
+ "file",
+ "rating",
+ "resume",
+ "tvshowid",
+ "art",
+ "streamdetails",
+ "firstaired",
+ "runtime",
+ "writer",
+ "dateadded",
+ "lastplayed",
],
- 'limits': {"end": 1}
+ "limits": {"end": 1},
}
- result = JSONRPC('VideoLibrary.GetEpisodes').execute(params)
+ result = JSONRPC("VideoLibrary.GetEpisodes").execute(params)
try:
- episodes = result['result']['episodes']
+ episodes = result["result"]["episodes"]
except (KeyError, TypeError):
pass
else:
for episode in episodes:
li = create_listitem(episode)
- list_li.append((episode['file'], li))
+ list_li.append((episode["file"], li))
if len(list_li) == limit:
break
xbmcplugin.addDirectoryItems(PROCESS_HANDLE, list_li, len(list_li))
- xbmcplugin.setContent(PROCESS_HANDLE, 'episodes')
+ xbmcplugin.setContent(PROCESS_HANDLE, "episodes")
xbmcplugin.endOfDirectory(PROCESS_HANDLE)
def create_listitem(item):
-
- ''' Listitem based on jsonrpc items.
- '''
- title = item['title']
+ """Listitem based on jsonrpc items."""
+ title = item["title"]
label2 = ""
li = xbmcgui.ListItem(title)
- li.setProperty('IsPlayable', "true")
+ li.setProperty("IsPlayable", "true")
metadata = {
- 'Title': title,
- 'duration': str(item['runtime'] / 60),
- 'Plot': item['plot'],
- 'Playcount': item['playcount']
+ "Title": title,
+ "duration": str(item["runtime"] / 60),
+ "Plot": item["plot"],
+ "Playcount": item["playcount"],
}
if "showtitle" in item:
- metadata['TVshowTitle'] = item['showtitle']
- label2 = item['showtitle']
+ metadata["TVshowTitle"] = item["showtitle"]
+ label2 = item["showtitle"]
if "episodeid" in item:
# Listitem of episode
- metadata['mediatype'] = "episode"
- metadata['dbid'] = item['episodeid']
+ metadata["mediatype"] = "episode"
+ metadata["dbid"] = item["episodeid"]
# TODO: Review once Krypton is RC - probably no longer needed if there's dbid
if "episode" in item:
- episode = item['episode']
- metadata['Episode'] = episode
+ episode = item["episode"]
+ metadata["Episode"] = episode
if "season" in item:
- season = item['season']
- metadata['Season'] = season
+ season = item["season"]
+ metadata["Season"] = season
if season and episode:
episodeno = "s%.2de%.2d" % (season, episode)
- li.setProperty('episodeno', episodeno)
+ li.setProperty("episodeno", episodeno)
label2 = "%s - %s" % (label2, episodeno) if label2 else episodeno
if "firstaired" in item:
- metadata['Premiered'] = item['firstaired']
+ metadata["Premiered"] = item["firstaired"]
if "rating" in item:
- metadata['Rating'] = str(round(float(item['rating']), 1))
+ metadata["Rating"] = str(round(float(item["rating"]), 1))
if "director" in item:
- metadata['Director'] = " / ".join(item['director'])
+ metadata["Director"] = " / ".join(item["director"])
if "writer" in item:
- metadata['Writer'] = " / ".join(item['writer'])
+ metadata["Writer"] = " / ".join(item["writer"])
if "cast" in item:
cast = []
castandrole = []
- for person in item['cast']:
- name = person['name']
+ for person in item["cast"]:
+ name = person["name"]
cast.append(name)
- castandrole.append((name, person['role']))
- metadata['Cast'] = cast
- metadata['CastAndRole'] = castandrole
+ castandrole.append((name, person["role"]))
+ metadata["Cast"] = cast
+ metadata["CastAndRole"] = castandrole
li.setLabel2(label2)
li.setInfo(type="Video", infoLabels=metadata)
- li.setProperty('resumetime', str(item['resume']['position']))
- li.setProperty('totaltime', str(item['resume']['total']))
- li.setArt(item['art'])
- li.setProperty('dbid', str(item['episodeid']))
- li.setProperty('fanart_image', item['art'].get('tvshow.fanart', ''))
+ li.setProperty("resumetime", str(item["resume"]["position"]))
+ li.setProperty("totaltime", str(item["resume"]["total"]))
+ li.setArt(item["art"])
+ li.setProperty("dbid", str(item["episodeid"]))
+ li.setProperty("fanart_image", item["art"].get("tvshow.fanart", ""))
- for key, value in iteritems(item['streamdetails']):
+ for key, value in iteritems(item["streamdetails"]):
for stream in value:
li.addStreamInfo(key, stream)
@@ -752,87 +1081,98 @@ def create_listitem(item):
def add_user(api_client):
-
- ''' Add or remove users from the default server session.
- '''
- if not window('jellyfin_online.bool'):
+ """Add or remove users from the default server session."""
+ if not window("jellyfin_online.bool"):
return
session = api_client.get_device(client.get_device_id())
users = api_client.get_users()
- current = session[0]['AdditionalUsers']
+ current = session[0]["AdditionalUsers"]
- result = dialog("select", translate(33061), [translate(33062), translate(33063)] if current else [translate(33062)])
+ result = dialog(
+ "select",
+ translate(33061),
+ [translate(33062), translate(33063)] if current else [translate(33062)],
+ )
if result < 0:
return
if not result: # Add user
- eligible = [x for x in users if x['Id'] not in [current_user['UserId'] for current_user in current]]
- resp = dialog("select", translate(33064), [x['Name'] for x in eligible])
+ eligible = [
+ x
+ for x in users
+ if x["Id"] not in [current_user["UserId"] for current_user in current]
+ ]
+ resp = dialog("select", translate(33064), [x["Name"] for x in eligible])
if resp < 0:
return
user = eligible[resp]
- event('AddUser', {'Id': user['Id'], 'Add': True})
+ event("AddUser", {"Id": user["Id"], "Add": True})
else: # Remove user
- resp = dialog("select", translate(33064), [x['UserName'] for x in current])
+ resp = dialog("select", translate(33064), [x["UserName"] for x in current])
if resp < 0:
return
user = current[resp]
- event('AddUser', {'Id': user['UserId'], 'Add': False})
+ event("AddUser", {"Id": user["UserId"], "Add": False})
def get_themes(api_client):
-
- ''' Add theme media locally, via strm. This is only for tv tunes.
- If another script is used, adjust this code.
- '''
+ """Add theme media locally, via strm. This is only for tv tunes.
+ If another script is used, adjust this code.
+ """
from ..helper.utils import normalize_string
from ..helper.playutils import PlayUtils
from ..helper.xmls import tvtunes_nfo
- library = translate_path("special://profile/addon_data/plugin.video.jellyfin/library")
- play = settings('useDirectPaths') == "1"
+ library = translate_path(
+ "special://profile/addon_data/plugin.video.jellyfin/library"
+ )
+ play = settings("useDirectPaths") == "1"
- if not xbmcvfs.exists(library + '/'):
+ if not xbmcvfs.exists(library + "/"):
xbmcvfs.mkdir(library)
- if xbmc.getCondVisibility('System.HasAddon(script.tvtunes)'):
+ if xbmc.getCondVisibility("System.HasAddon(script.tvtunes)"):
tvtunes = xbmcaddon.Addon(id="script.tvtunes")
- tvtunes.setSetting('custom_path_enable', "true")
- tvtunes.setSetting('custom_path', library)
+ tvtunes.setSetting("custom_path_enable", "true")
+ tvtunes.setSetting("custom_path", library)
LOG.info("TV Tunes custom path is enabled and set.")
else:
dialog("ok", "{jellyfin}", translate(33152))
return
- with Database('jellyfin') as jellyfindb:
+ with Database("jellyfin") as jellyfindb:
all_views = jellyfin_db.JellyfinDatabase(jellyfindb.cursor).get_views()
- views = [x.view_id for x in all_views if x.media_type in ('movies', 'tvshows', 'mixed')]
+ views = [
+ x.view_id
+ for x in all_views
+ if x.media_type in ("movies", "tvshows", "mixed")
+ ]
items = {}
- server = api_client.config.data['auth.server']
+ server = api_client.config.data["auth.server"]
for view in views:
result = api_client.get_items_theme_video(view)
- for item in result['Items']:
+ for item in result["Items"]:
- folder = normalize_string(item['Name'])
- items[item['Id']] = folder
+ folder = normalize_string(item["Name"])
+ items[item["Id"]] = folder
result = api_client.get_items_theme_song(view)
- for item in result['Items']:
+ for item in result["Items"]:
- folder = normalize_string(item['Name'])
- items[item['Id']] = folder
+ folder = normalize_string(item["Name"])
+ items[item["Id"]] = folder
for item in items:
@@ -845,36 +1185,44 @@ def get_themes(api_client):
themes = api_client.get_themes(item)
paths = []
- for theme in themes['ThemeVideosResult']['Items'] + themes['ThemeSongsResult']['Items']:
+ for theme in (
+ themes["ThemeVideosResult"]["Items"] + themes["ThemeSongsResult"]["Items"]
+ ):
putils = PlayUtils(theme, False, None, server, api_client)
if play:
- paths.append(putils.direct_play(theme['MediaSources'][0]))
+ paths.append(putils.direct_play(theme["MediaSources"][0]))
else:
- paths.append(putils.direct_url(theme['MediaSources'][0]))
+ paths.append(putils.direct_url(theme["MediaSources"][0]))
tvtunes_nfo(nfo_file, paths)
- dialog("notification", heading="{jellyfin}", message=translate(33153), icon="{jellyfin}", time=1000, sound=False)
+ dialog(
+ "notification",
+ heading="{jellyfin}",
+ message=translate(33153),
+ icon="{jellyfin}",
+ time=1000,
+ sound=False,
+ )
def delete_item():
-
- ''' Delete keymap action.
- '''
+ """Delete keymap action."""
from . import context
context.Context(delete=True)
def backup():
-
- ''' Jellyfin backup.
- '''
+ """Jellyfin backup."""
from ..helper.utils import delete_folder, copytree
- path = settings('backupPath')
- folder_name = "Kodi%s.%s" % (kodi_version(), xbmc.getInfoLabel('System.Date(dd-mm-yy)'))
+ path = settings("backupPath")
+ folder_name = "Kodi%s.%s" % (
+ kodi_version(),
+ xbmc.getInfoLabel("System.Date(dd-mm-yy)"),
+ )
folder_name = dialog("input", heading=translate(33089), defaultt=folder_name)
if not folder_name:
@@ -882,7 +1230,7 @@ def backup():
backup = os.path.join(path, folder_name)
- if xbmcvfs.exists(backup + '/'):
+ if xbmcvfs.exists(backup + "/"):
if not dialog("yesno", "{jellyfin}", translate(33090)):
return backup()
@@ -896,7 +1244,13 @@ def backup():
if not xbmcvfs.mkdirs(path) or not xbmcvfs.mkdirs(destination_databases):
LOG.info("Unable to create all directories")
- dialog("notification", heading="{jellyfin}", icon="{jellyfin}", message=translate(33165), sound=False)
+ dialog(
+ "notification",
+ heading="{jellyfin}",
+ icon="{jellyfin}",
+ message=translate(33165),
+ sound=False,
+ )
return
@@ -904,19 +1258,19 @@ def backup():
databases = Objects().objects
- db = translate_path(databases['jellyfin'])
- xbmcvfs.copy(db, os.path.join(destination_databases, db.rsplit('\\', 1)[1]))
+ db = translate_path(databases["jellyfin"])
+ xbmcvfs.copy(db, os.path.join(destination_databases, db.rsplit("\\", 1)[1]))
LOG.info("copied jellyfin.db")
- db = translate_path(databases['video'])
- filename = db.rsplit('\\', 1)[1]
+ db = translate_path(databases["video"])
+ filename = db.rsplit("\\", 1)[1]
xbmcvfs.copy(db, os.path.join(destination_databases, filename))
LOG.info("copied %s", filename)
- if settings('enableMusic.bool'):
+ if settings("enableMusic.bool"):
- db = translate_path(databases['music'])
- filename = db.rsplit('\\', 1)[1]
+ db = translate_path(databases["music"])
+ filename = db.rsplit("\\", 1)[1]
xbmcvfs.copy(db, os.path.join(destination_databases, filename))
LOG.info("copied %s", filename)
@@ -924,35 +1278,43 @@ def backup():
dialog("ok", "{jellyfin}", "%s %s" % (translate(33091), backup))
-def get_filtered_section(parent_id=None, media=None, limit=None, recursive=None, sort=None, sort_order=None,
- filters=None, extra=None, server_id=None, api_client=None):
-
- ''' Get dynamic listings.
- '''
+def get_filtered_section(
+ parent_id=None,
+ media=None,
+ limit=None,
+ recursive=None,
+ sort=None,
+ sort_order=None,
+ filters=None,
+ extra=None,
+ server_id=None,
+ api_client=None,
+):
+ """Get dynamic listings."""
params = {
- 'ParentId': parent_id,
- 'IncludeItemTypes': media,
- 'IsMissing': False,
- 'Recursive': recursive if recursive is not None else True,
- 'Limit': limit,
- 'SortBy': sort or "SortName",
- 'SortOrder': sort_order or "Ascending",
- 'ImageTypeLimit': 1,
- 'IsVirtualUnaired': False,
- 'Fields': browse_info()
+ "ParentId": parent_id,
+ "IncludeItemTypes": media,
+ "IsMissing": False,
+ "Recursive": recursive if recursive is not None else True,
+ "Limit": limit,
+ "SortBy": sort or "SortName",
+ "SortOrder": sort_order or "Ascending",
+ "ImageTypeLimit": 1,
+ "IsVirtualUnaired": False,
+ "Fields": browse_info(),
}
if filters:
- if 'Boxsets' in filters:
- filters.remove('Boxsets')
- params['CollapseBoxSetItems'] = settings('groupedSets.bool')
+ if "Boxsets" in filters:
+ filters.remove("Boxsets")
+ params["CollapseBoxSetItems"] = settings("groupedSets.bool")
- params['Filters'] = ','.join(filters)
+ params["Filters"] = ",".join(filters)
- if settings('getCast.bool'):
- params['Fields'] += ",People"
+ if settings("getCast.bool"):
+ params["Fields"] += ",People"
- if media and 'Photo' in media:
- params['Fields'] += ",Width,Height"
+ if media and "Photo" in media:
+ params["Fields"] += ",Width,Height"
if extra is not None:
params.update(extra)
diff --git a/jellyfin_kodi/entrypoint/service.py b/jellyfin_kodi/entrypoint/service.py
index 8c3b6affe..aaf0d03d8 100644
--- a/jellyfin_kodi/entrypoint/service.py
+++ b/jellyfin_kodi/entrypoint/service.py
@@ -18,7 +18,15 @@
from .. import library
from .. import monitor
from ..views import Views
-from ..helper import translate, window, settings, event, dialog, set_addon_mode, LazyLogger
+from ..helper import (
+ translate,
+ window,
+ settings,
+ event,
+ dialog,
+ set_addon_mode,
+ LazyLogger,
+)
from ..helper.utils import JsonDebugPrinter, translate_path
from ..helper.xmls import verify_kodi_defaults
from ..jellyfin import Jellyfin
@@ -37,83 +45,98 @@ class Service(xbmc.Monitor):
monitor = None
play_event = None
warn = True
- settings = {'last_progress': datetime.today(), 'last_progress_report': datetime.today()}
+ settings = {
+ "last_progress": datetime.today(),
+ "last_progress_report": datetime.today(),
+ }
def __init__(self):
- window('jellyfin_should_stop', clear=True)
-
- self.settings['addon_version'] = client.get_version()
- self.settings['profile'] = translate_path('special://profile')
- self.settings['mode'] = settings('useDirectPaths')
- self.settings['log_level'] = settings('logLevel') or "1"
- self.settings['auth_check'] = True
- self.settings['enable_context'] = settings('enableContext.bool')
- self.settings['enable_context_transcode'] = settings('enableContextTranscode.bool')
- self.settings['kodi_companion'] = settings('kodiCompanion.bool')
- window('jellyfin_kodiProfile', value=self.settings['profile'])
- settings('platformDetected', client.get_platform())
-
- if self.settings['enable_context']:
- window('jellyfin_context.bool', True)
- if self.settings['enable_context_transcode']:
- window('jellyfin_context_transcode.bool', True)
+ window("jellyfin_should_stop", clear=True)
+
+ self.settings["addon_version"] = client.get_version()
+ self.settings["profile"] = translate_path("special://profile")
+ self.settings["mode"] = settings("useDirectPaths")
+ self.settings["log_level"] = settings("logLevel") or "1"
+ self.settings["auth_check"] = True
+ self.settings["enable_context"] = settings("enableContext.bool")
+ self.settings["enable_context_transcode"] = settings(
+ "enableContextTranscode.bool"
+ )
+ self.settings["kodi_companion"] = settings("kodiCompanion.bool")
+ window("jellyfin_kodiProfile", value=self.settings["profile"])
+ settings("platformDetected", client.get_platform())
+
+ if self.settings["enable_context"]:
+ window("jellyfin_context.bool", True)
+ if self.settings["enable_context_transcode"]:
+ window("jellyfin_context_transcode.bool", True)
LOG.info("--->>>[ %s ]", client.get_addon_name())
LOG.info("Version: %s", client.get_version())
- LOG.info("KODI Version: %s", xbmc.getInfoLabel('System.BuildVersion'))
- LOG.info("Platform: %s", settings('platformDetected'))
+ LOG.info("KODI Version: %s", xbmc.getInfoLabel("System.BuildVersion"))
+ LOG.info("Platform: %s", settings("platformDetected"))
LOG.info("Python Version: %s", sys.version)
- LOG.info("Using dynamic paths: %s", settings('useDirectPaths') == "0")
- LOG.info("Log Level: %s", self.settings['log_level'])
+ LOG.info("Using dynamic paths: %s", settings("useDirectPaths") == "0")
+ LOG.info("Log Level: %s", self.settings["log_level"])
verify_kodi_defaults()
- window('jellyfin.connected.bool', True)
- settings('groupedSets.bool', objects.utils.get_grouped_set())
+ window("jellyfin.connected.bool", True)
+ settings("groupedSets.bool", objects.utils.get_grouped_set())
xbmc.Monitor.__init__(self)
def service(self):
+ """Keeps the service monitor going.
+ Exit on Kodi shutdown or profile switch.
- ''' Keeps the service monitor going.
- Exit on Kodi shutdown or profile switch.
-
- if profile switch happens more than once,
- Threads depending on abortRequest will not trigger.
- '''
+ if profile switch happens more than once,
+ Threads depending on abortRequest will not trigger.
+ """
self.monitor = monitor.Monitor()
player = self.monitor.player
self.connect = connect.Connect()
self.start_default()
- self.settings['mode'] = settings('useDirectPaths')
+ self.settings["mode"] = settings("useDirectPaths")
while self.running:
- if window('jellyfin_online.bool'):
+ if window("jellyfin_online.bool"):
- if self.settings['profile'] != window('jellyfin_kodiProfile'):
- LOG.info("[ profile switch ] %s", self.settings['profile'])
+ if self.settings["profile"] != window("jellyfin_kodiProfile"):
+ LOG.info("[ profile switch ] %s", self.settings["profile"])
break
- if player.isPlaying() and player.is_playing_file(player.get_playing_file()):
- difference = datetime.today() - self.settings['last_progress']
+ if player.isPlaying() and player.is_playing_file(
+ player.get_playing_file()
+ ):
+ difference = datetime.today() - self.settings["last_progress"]
if difference.seconds > 10:
- self.settings['last_progress'] = datetime.today()
+ self.settings["last_progress"] = datetime.today()
- update = (datetime.today() - self.settings['last_progress_report']).seconds > 250
- event('ReportProgressRequested', {'Report': update})
+ update = (
+ datetime.today() - self.settings["last_progress_report"]
+ ).seconds > 250
+ event("ReportProgressRequested", {"Report": update})
if update:
- self.settings['last_progress_report'] = datetime.today()
+ self.settings["last_progress_report"] = datetime.today()
- if window('jellyfin.restart.bool'):
+ if window("jellyfin.restart.bool"):
- window('jellyfin.restart', clear=True)
- dialog("notification", heading="{jellyfin}", message=translate(33193), icon="{jellyfin}", time=1000, sound=False)
+ window("jellyfin.restart", clear=True)
+ dialog(
+ "notification",
+ heading="{jellyfin}",
+ message=translate(33193),
+ icon="{jellyfin}",
+ time=1000,
+ sound=False,
+ )
- raise Exception('RestartService')
+ raise Exception("RestartService")
if self.waitForAbort(1):
break
@@ -126,14 +149,14 @@ def start_default(self):
try:
self.connect.register()
- if not settings('SyncInstallRunDone.bool'):
+ if not settings("SyncInstallRunDone.bool"):
set_addon_mode()
except Exception as error:
LOG.exception(error)
def stop_default(self):
- window('jellyfin_online', clear=True)
+ window("jellyfin_online", clear=True)
Jellyfin().close()
if self.library_thread is not None:
@@ -142,59 +165,93 @@ def stop_default(self):
self.library_thread = None
def onNotification(self, sender, method, data):
-
- ''' All notifications are sent via NotifyAll built-in or Kodi.
- Central hub.
- '''
- if sender.lower() not in ('plugin.video.jellyfin', 'xbmc'):
+ """All notifications are sent via NotifyAll built-in or Kodi.
+ Central hub.
+ """
+ if sender.lower() not in ("plugin.video.jellyfin", "xbmc"):
return
- if sender == 'plugin.video.jellyfin':
- method = method.split('.')[1]
-
- if method not in ('ServerUnreachable', 'ServerShuttingDown', 'UserDataChanged', 'ServerConnect',
- 'LibraryChanged', 'ServerOnline', 'SyncLibrary', 'RepairLibrary', 'RemoveLibrary',
- 'SyncLibrarySelection', 'RepairLibrarySelection', 'AddServer',
- 'Unauthorized', 'UserConfigurationUpdated', 'ServerRestarting',
- 'RemoveServer', 'UpdatePassword', 'AddLibrarySelection', 'RemoveLibrarySelection'):
+ if sender == "plugin.video.jellyfin":
+ method = method.split(".")[1]
+
+ if method not in (
+ "ServerUnreachable",
+ "ServerShuttingDown",
+ "UserDataChanged",
+ "ServerConnect",
+ "LibraryChanged",
+ "ServerOnline",
+ "SyncLibrary",
+ "RepairLibrary",
+ "RemoveLibrary",
+ "SyncLibrarySelection",
+ "RepairLibrarySelection",
+ "AddServer",
+ "Unauthorized",
+ "UserConfigurationUpdated",
+ "ServerRestarting",
+ "RemoveServer",
+ "UpdatePassword",
+ "AddLibrarySelection",
+ "RemoveLibrarySelection",
+ ):
return
data = json.loads(data)[0]
else:
- if method not in ('System.OnQuit', 'System.OnSleep', 'System.OnWake'):
+ if method not in ("System.OnQuit", "System.OnSleep", "System.OnWake"):
return
data = json.loads(data)
LOG.debug("[ %s: %s ] %s", sender, method, JsonDebugPrinter(data))
- if method == 'ServerOnline':
- if data.get('ServerId') is None:
+ if method == "ServerOnline":
+ if data.get("ServerId") is None:
- window('jellyfin_online.bool', True)
- self.settings['auth_check'] = True
+ window("jellyfin_online.bool", True)
+ self.settings["auth_check"] = True
self.warn = True
- if settings('connectMsg.bool'):
-
- users = [user for user in (settings('additionalUsers') or "").split(',') if user]
- users.insert(0, settings('username'))
- dialog("notification", heading="{jellyfin}", message="%s %s" % (translate(33000), ", ".join(users)),
- icon="{jellyfin}", time=1500, sound=False)
+ if settings("connectMsg.bool"):
+
+ users = [
+ user
+ for user in (settings("additionalUsers") or "").split(",")
+ if user
+ ]
+ users.insert(0, settings("username"))
+ dialog(
+ "notification",
+ heading="{jellyfin}",
+ message="%s %s" % (translate(33000), ", ".join(users)),
+ icon="{jellyfin}",
+ time=1500,
+ sound=False,
+ )
if self.library_thread is None:
self.library_thread = library.Library(self)
self.library_thread.start()
- elif method in ('ServerUnreachable', 'ServerShuttingDown'):
+ elif method in ("ServerUnreachable", "ServerShuttingDown"):
- if self.warn or data.get('ServerId'):
+ if self.warn or data.get("ServerId"):
- self.warn = data.get('ServerId') is not None
- dialog("notification", heading="{jellyfin}", message=translate(33146) if data.get('ServerId') is None else translate(33149), icon=xbmcgui.NOTIFICATION_ERROR)
+ self.warn = data.get("ServerId") is not None
+ dialog(
+ "notification",
+ heading="{jellyfin}",
+ message=(
+ translate(33146)
+ if data.get("ServerId") is None
+ else translate(33149)
+ ),
+ icon=xbmcgui.NOTIFICATION_ERROR,
+ )
- if data.get('ServerId') is None:
+ if data.get("ServerId") is None:
self.stop_default()
if self.waitForAbort(120):
@@ -202,12 +259,19 @@ def onNotification(self, sender, method, data):
self.start_default()
- elif method == 'Unauthorized':
- dialog("notification", heading="{jellyfin}", message=translate(33147) if data['ServerId'] is None else translate(33148), icon=xbmcgui.NOTIFICATION_ERROR)
+ elif method == "Unauthorized":
+ dialog(
+ "notification",
+ heading="{jellyfin}",
+ message=(
+ translate(33147) if data["ServerId"] is None else translate(33148)
+ ),
+ icon=xbmcgui.NOTIFICATION_ERROR,
+ )
- if data.get('ServerId') is None and self.settings['auth_check']:
+ if data.get("ServerId") is None and self.settings["auth_check"]:
- self.settings['auth_check'] = False
+ self.settings["auth_check"] = False
self.stop_default()
if self.waitForAbort(5):
@@ -215,12 +279,17 @@ def onNotification(self, sender, method, data):
self.start_default()
- elif method == 'ServerRestarting':
- if data.get('ServerId'):
+ elif method == "ServerRestarting":
+ if data.get("ServerId"):
return
- if settings('restartMsg.bool'):
- dialog("notification", heading="{jellyfin}", message=translate(33006), icon="{jellyfin}")
+ if settings("restartMsg.bool"):
+ dialog(
+ "notification",
+ heading="{jellyfin}",
+ message=translate(33006),
+ icon="{jellyfin}",
+ )
self.stop_default()
@@ -229,67 +298,72 @@ def onNotification(self, sender, method, data):
self.start_default()
- elif method == 'ServerConnect':
- self.connect.register(data['Id'])
+ elif method == "ServerConnect":
+ self.connect.register(data["Id"])
xbmc.executebuiltin("Container.Refresh")
- elif method == 'AddServer':
+ elif method == "AddServer":
self.connect.setup_manual_server()
xbmc.executebuiltin("Container.Refresh")
- elif method == 'RemoveServer':
+ elif method == "RemoveServer":
- self.connect.remove_server(data['Id'])
+ self.connect.remove_server(data["Id"])
xbmc.executebuiltin("Container.Refresh")
- elif method == 'UpdatePassword':
+ elif method == "UpdatePassword":
self.connect.setup_login_manual()
- elif method == 'UserDataChanged' and self.library_thread:
- if data.get('ServerId') or not window('jellyfin_startup.bool'):
+ elif method == "UserDataChanged" and self.library_thread:
+ if data.get("ServerId") or not window("jellyfin_startup.bool"):
return
LOG.info("[ UserDataChanged ] %s", data)
- self.library_thread.userdata(data['UserDataList'])
+ self.library_thread.userdata(data["UserDataList"])
- elif method == 'LibraryChanged' and self.library_thread:
- if data.get('ServerId') or not window('jellyfin_startup.bool'):
+ elif method == "LibraryChanged" and self.library_thread:
+ if data.get("ServerId") or not window("jellyfin_startup.bool"):
return
LOG.info("[ LibraryChanged ] %s", data)
- self.library_thread.updated(data['ItemsUpdated'] + data['ItemsAdded'])
- self.library_thread.removed(data['ItemsRemoved'])
+ self.library_thread.updated(data["ItemsUpdated"] + data["ItemsAdded"])
+ self.library_thread.removed(data["ItemsRemoved"])
- elif method == 'System.OnQuit':
- window('jellyfin_should_stop.bool', True)
+ elif method == "System.OnQuit":
+ window("jellyfin_should_stop.bool", True)
self.running = False
- elif method in ('SyncLibrarySelection', 'RepairLibrarySelection', 'AddLibrarySelection', 'RemoveLibrarySelection'):
+ elif method in (
+ "SyncLibrarySelection",
+ "RepairLibrarySelection",
+ "AddLibrarySelection",
+ "RemoveLibrarySelection",
+ ):
self.library_thread.select_libraries(method)
- elif method == 'SyncLibrary':
- if not data.get('Id'):
+ elif method == "SyncLibrary":
+ if not data.get("Id"):
return
- self.library_thread.add_library(data['Id'], data.get('Update', False))
+ self.library_thread.add_library(data["Id"], data.get("Update", False))
xbmc.executebuiltin("Container.Refresh")
- elif method == 'RepairLibrary':
- if not data.get('Id'):
+ elif method == "RepairLibrary":
+ if not data.get("Id"):
return
- libraries = data['Id'].split(',')
+ libraries = data["Id"].split(",")
for lib in libraries:
if not self.library_thread.remove_library(lib):
return
- self.library_thread.add_library(data['Id'])
+ self.library_thread.add_library(data["Id"])
xbmc.executebuiltin("Container.Refresh")
- elif method == 'RemoveLibrary':
- libraries = data['Id'].split(',')
+ elif method == "RemoveLibrary":
+ libraries = data["Id"].split(",")
for lib in libraries:
@@ -298,10 +372,10 @@ def onNotification(self, sender, method, data):
xbmc.executebuiltin("Container.Refresh")
- elif method == 'System.OnSleep':
+ elif method == "System.OnSleep":
LOG.info("-->[ sleep ]")
- window('jellyfin_should_stop.bool', True)
+ window("jellyfin_should_stop.bool", True)
if self.library_thread is not None:
@@ -312,7 +386,7 @@ def onNotification(self, sender, method, data):
self.monitor.server = []
self.monitor.sleep = True
- elif method == 'System.OnWake':
+ elif method == "System.OnWake":
if not self.monitor.sleep:
LOG.warning("System.OnSleep was never called, skip System.OnWake")
@@ -322,14 +396,14 @@ def onNotification(self, sender, method, data):
LOG.info("--<[ sleep ]")
xbmc.sleep(10000) # Allow network to wake up
self.monitor.sleep = False
- window('jellyfin_should_stop', clear=True)
+ window("jellyfin_should_stop", clear=True)
try:
self.connect.register()
except Exception as error:
LOG.exception(error)
- elif method == 'GUI.OnScreensaverDeactivated':
+ elif method == "GUI.OnScreensaverDeactivated":
LOG.info("--<[ screensaver ]")
xbmc.sleep(5000)
@@ -337,60 +411,80 @@ def onNotification(self, sender, method, data):
if self.library_thread is not None:
self.library_thread.fast_sync()
- elif method == 'UserConfigurationUpdated' and data.get('ServerId') is None:
+ elif method == "UserConfigurationUpdated" and data.get("ServerId") is None:
Views().get_views()
def onSettingsChanged(self):
-
- ''' React to setting changes that impact window values.
- '''
- if window('jellyfin_should_stop.bool'):
+ """React to setting changes that impact window values."""
+ if window("jellyfin_should_stop.bool"):
return
- if settings('logLevel') != self.settings['log_level']:
+ if settings("logLevel") != self.settings["log_level"]:
- log_level = settings('logLevel')
- self.settings['logLevel'] = log_level
+ log_level = settings("logLevel")
+ self.settings["logLevel"] = log_level
LOG.info("New log level: %s", log_level)
- if settings('enableContext.bool') != self.settings['enable_context']:
+ if settings("enableContext.bool") != self.settings["enable_context"]:
- window('jellyfin_context', settings('enableContext'))
- self.settings['enable_context'] = settings('enableContext.bool')
- LOG.info("New context setting: %s", self.settings['enable_context'])
+ window("jellyfin_context", settings("enableContext"))
+ self.settings["enable_context"] = settings("enableContext.bool")
+ LOG.info("New context setting: %s", self.settings["enable_context"])
- if settings('enableContextTranscode.bool') != self.settings['enable_context_transcode']:
+ if (
+ settings("enableContextTranscode.bool")
+ != self.settings["enable_context_transcode"]
+ ):
- window('jellyfin_context_transcode', settings('enableContextTranscode'))
- self.settings['enable_context_transcode'] = settings('enableContextTranscode.bool')
- LOG.info("New context transcode setting: %s", self.settings['enable_context_transcode'])
+ window("jellyfin_context_transcode", settings("enableContextTranscode"))
+ self.settings["enable_context_transcode"] = settings(
+ "enableContextTranscode.bool"
+ )
+ LOG.info(
+ "New context transcode setting: %s",
+ self.settings["enable_context_transcode"],
+ )
- if settings('useDirectPaths') != self.settings['mode'] and self.library_thread.started:
+ if (
+ settings("useDirectPaths") != self.settings["mode"]
+ and self.library_thread.started
+ ):
- self.settings['mode'] = settings('useDirectPaths')
- LOG.info("New playback mode setting: %s", self.settings['mode'])
+ self.settings["mode"] = settings("useDirectPaths")
+ LOG.info("New playback mode setting: %s", self.settings["mode"])
- if not self.settings.get('mode_warn'):
+ if not self.settings.get("mode_warn"):
- self.settings['mode_warn'] = True
+ self.settings["mode_warn"] = True
dialog("yesno", "{jellyfin}", translate(33118))
- if settings('kodiCompanion.bool') != self.settings['kodi_companion']:
- self.settings['kodi_companion'] = settings('kodiCompanion.bool')
+ if settings("kodiCompanion.bool") != self.settings["kodi_companion"]:
+ self.settings["kodi_companion"] = settings("kodiCompanion.bool")
- if not self.settings['kodi_companion']:
+ if not self.settings["kodi_companion"]:
dialog("ok", "{jellyfin}", translate(33138))
def reload_objects(self):
-
- ''' Reload objects which depends on the patch module.
- This allows to see the changes in code without restarting the python interpreter.
- '''
- reload_modules = ['objects.movies', 'objects.musicvideos', 'objects.tvshows',
- 'objects.music', 'objects.obj', 'objects.actions', 'objects.kodi.kodi',
- 'objects.kodi.movies', 'objects.kodi.musicvideos', 'objects.kodi.tvshows',
- 'objects.kodi.music', 'objects.kodi.artwork', 'objects.kodi.queries',
- 'objects.kodi.queries_music', 'objects.kodi.queries_texture']
+ """Reload objects which depends on the patch module.
+ This allows to see the changes in code without restarting the python interpreter.
+ """
+ reload_modules = [
+ "objects.movies",
+ "objects.musicvideos",
+ "objects.tvshows",
+ "objects.music",
+ "objects.obj",
+ "objects.actions",
+ "objects.kodi.kodi",
+ "objects.kodi.movies",
+ "objects.kodi.musicvideos",
+ "objects.kodi.tvshows",
+ "objects.kodi.music",
+ "objects.kodi.artwork",
+ "objects.kodi.queries",
+ "objects.kodi.queries_music",
+ "objects.kodi.queries_texture",
+ ]
for mod in reload_modules:
del sys.modules[mod]
@@ -407,14 +501,22 @@ def reload_objects(self):
def shutdown(self):
LOG.info("---<[ EXITING ]")
- window('jellyfin_should_stop.bool', True)
+ window("jellyfin_should_stop.bool", True)
properties = [ # TODO: review
- "jellyfin_state", "jellyfin_serverStatus", "jellyfin_currUser",
-
- "jellyfin_play", "jellyfin_online", "jellyfin.connected", "jellyfin_startup",
- "jellyfin.external", "jellyfin.external_check", "jellyfin_deviceId", "jellyfin_db_check", "jellyfin_pathverified",
- "jellyfin_sync"
+ "jellyfin_state",
+ "jellyfin_serverStatus",
+ "jellyfin_currUser",
+ "jellyfin_play",
+ "jellyfin_online",
+ "jellyfin.connected",
+ "jellyfin_startup",
+ "jellyfin.external",
+ "jellyfin.external_check",
+ "jellyfin_deviceId",
+ "jellyfin_db_check",
+ "jellyfin_pathverified",
+ "jellyfin_sync",
]
for prop in properties:
window(prop, clear=True)
diff --git a/jellyfin_kodi/full_sync.py b/jellyfin_kodi/full_sync.py
index 7463c3007..6143e960a 100644
--- a/jellyfin_kodi/full_sync.py
+++ b/jellyfin_kodi/full_sync.py
@@ -23,11 +23,11 @@
class FullSync(object):
+ """This should be called like a context.
+ i.e. with FullSync('jellyfin') as sync:
+ sync.libraries()
+ """
- ''' This should be called like a context.
- i.e. with FullSync('jellyfin') as sync:
- sync.libraries()
- '''
# Borg - multiple instances, shared state
_shared_state = {}
sync = None
@@ -35,10 +35,9 @@ class FullSync(object):
screensaver = None
def __init__(self, library, server):
-
- ''' You can call all big syncing methods here.
- Initial, update, repair, remove.
- '''
+ """You can call all big syncing methods here.
+ Initial, update, repair, remove.
+ """
self.__dict__ = self._shared_state
if self.running:
@@ -50,78 +49,81 @@ def __init__(self, library, server):
self.server = server
def __enter__(self):
-
- ''' Do everything we need before the sync
- '''
+ """Do everything we need before the sync"""
LOG.info("-->[ fullsync ]")
- if not settings('dbSyncScreensaver.bool'):
+ if not settings("dbSyncScreensaver.bool"):
- xbmc.executebuiltin('InhibitIdleShutdown(true)')
+ xbmc.executebuiltin("InhibitIdleShutdown(true)")
self.screensaver = get_screensaver()
set_screensaver(value="")
self.running = True
- window('jellyfin_sync.bool', True)
+ window("jellyfin_sync.bool", True)
return self
def libraries(self, libraries=None, update=False):
-
- ''' Map the syncing process and start the sync. Ensure only one sync is running.
- '''
- self.direct_path = settings('useDirectPaths') == "1"
+ """Map the syncing process and start the sync. Ensure only one sync is running."""
+ self.direct_path = settings("useDirectPaths") == "1"
self.update_library = update
self.sync = get_sync()
if libraries:
# Can be a single ID or a comma separated list
- libraries = libraries.split(',')
+ libraries = libraries.split(",")
for library_id in libraries:
# Look up library in local Jellyfin database
library = self.get_library(library_id)
if library:
- if library.media_type == 'mixed':
- self.sync['Libraries'].append("Mixed:%s" % library_id)
+ if library.media_type == "mixed":
+ self.sync["Libraries"].append("Mixed:%s" % library_id)
# Include boxsets library
libraries = self.get_libraries()
- boxsets = [row.view_id for row in libraries if row.media_type == 'boxsets']
+ boxsets = [
+ row.view_id
+ for row in libraries
+ if row.media_type == "boxsets"
+ ]
if boxsets:
- self.sync['Libraries'].append('Boxsets:%s' % boxsets[0])
- elif library.media_type == 'movies':
- self.sync['Libraries'].append(library_id)
+ self.sync["Libraries"].append("Boxsets:%s" % boxsets[0])
+ elif library.media_type == "movies":
+ self.sync["Libraries"].append(library_id)
# Include boxsets library
libraries = self.get_libraries()
- boxsets = [row.view_id for row in libraries if row.media_type == 'boxsets']
+ boxsets = [
+ row.view_id
+ for row in libraries
+ if row.media_type == "boxsets"
+ ]
# Verify we're only trying to sync boxsets once
- if boxsets and boxsets[0] not in self.sync['Libraries']:
- self.sync['Libraries'].append('Boxsets:%s' % boxsets[0])
+ if boxsets and boxsets[0] not in self.sync["Libraries"]:
+ self.sync["Libraries"].append("Boxsets:%s" % boxsets[0])
else:
# Only called if the library isn't already known about
- self.sync['Libraries'].append(library_id)
+ self.sync["Libraries"].append(library_id)
else:
- self.sync['Libraries'].append(library_id)
+ self.sync["Libraries"].append(library_id)
else:
self.mapping()
- if not xmls.advanced_settings() and self.sync['Libraries']:
+ if not xmls.advanced_settings() and self.sync["Libraries"]:
self.start()
def get_libraries(self):
- with Database('jellyfin') as jellyfindb:
+ with Database("jellyfin") as jellyfindb:
return jellyfin_db.JellyfinDatabase(jellyfindb.cursor).get_views()
def get_library(self, library_id):
- with Database('jellyfin') as jellyfindb:
+ with Database("jellyfin") as jellyfindb:
return jellyfin_db.JellyfinDatabase(jellyfindb.cursor).get_view(library_id)
def mapping(self):
-
- ''' Load the mapping of the full sync.
- This allows us to restore a previous sync.
- '''
- if self.sync['Libraries']:
+ """Load the mapping of the full sync.
+ This allows us to restore a previous sync.
+ """
+ if self.sync["Libraries"]:
if not dialog("yesno", "{jellyfin}", translate(33102)):
@@ -130,38 +132,48 @@ def mapping(self):
raise LibraryException("ProgressStopped")
else:
- self.sync['Libraries'] = []
- self.sync['RestorePoint'] = {}
+ self.sync["Libraries"] = []
+ self.sync["RestorePoint"] = {}
else:
LOG.info("generate full sync")
libraries = []
for library in self.get_libraries():
- if library.media_type in ('movies', 'tvshows', 'musicvideos', 'music', 'mixed'):
- libraries.append({'Id': library.view_id, 'Name': library.view_name, 'Media': library.media_type})
+ if library.media_type in (
+ "movies",
+ "tvshows",
+ "musicvideos",
+ "music",
+ "mixed",
+ ):
+ libraries.append(
+ {
+ "Id": library.view_id,
+ "Name": library.view_name,
+ "Media": library.media_type,
+ }
+ )
libraries = self.select_libraries(libraries)
- if [x['Media'] for x in libraries if x['Media'] in ('movies', 'mixed')]:
- self.sync['Libraries'].append("Boxsets:")
+ if [x["Media"] for x in libraries if x["Media"] in ("movies", "mixed")]:
+ self.sync["Libraries"].append("Boxsets:")
save_sync(self.sync)
def select_libraries(self, libraries):
+ """Select all or certain libraries to be whitelisted."""
- ''' Select all or certain libraries to be whitelisted.
- '''
-
- choices = [x['Name'] for x in libraries]
+ choices = [x["Name"] for x in libraries]
choices.insert(0, translate(33121))
selection = dialog("multi", translate(33120), choices)
if selection is None:
- raise LibraryException('LibrarySelection')
+ raise LibraryException("LibrarySelection")
elif not selection:
LOG.info("Nothing was selected.")
- raise LibraryException('SyncLibraryLater')
+ raise LibraryException("SyncLibraryLater")
if 0 in selection:
selection = list(range(1, len(libraries) + 1))
@@ -171,96 +183,100 @@ def select_libraries(self, libraries):
for x in selection:
library = libraries[x - 1]
- if library['Media'] != 'mixed':
- selected_libraries.append(library['Id'])
+ if library["Media"] != "mixed":
+ selected_libraries.append(library["Id"])
else:
- selected_libraries.append("Mixed:%s" % library['Id'])
+ selected_libraries.append("Mixed:%s" % library["Id"])
- self.sync['Libraries'] = selected_libraries
+ self.sync["Libraries"] = selected_libraries
return [libraries[x - 1] for x in selection]
def start(self):
-
- ''' Main sync process.
- '''
- LOG.info("starting sync with %s", self.sync['Libraries'])
+ """Main sync process."""
+ LOG.info("starting sync with %s", self.sync["Libraries"])
save_sync(self.sync)
start_time = datetime.datetime.now()
- for library in list(self.sync['Libraries']):
+ for library in list(self.sync["Libraries"]):
self.process_library(library)
- if not library.startswith('Boxsets:') and library not in self.sync['Whitelist']:
- self.sync['Whitelist'].append(library)
+ if (
+ not library.startswith("Boxsets:")
+ and library not in self.sync["Whitelist"]
+ ):
+ self.sync["Whitelist"].append(library)
- self.sync['Libraries'].pop(self.sync['Libraries'].index(library))
- self.sync['RestorePoint'] = {}
+ self.sync["Libraries"].pop(self.sync["Libraries"].index(library))
+ self.sync["RestorePoint"] = {}
elapsed = datetime.datetime.now() - start_time
- settings('SyncInstallRunDone.bool', True)
+ settings("SyncInstallRunDone.bool", True)
self.library.save_last_sync()
save_sync(self.sync)
- xbmc.executebuiltin('UpdateLibrary(video)')
- dialog("notification", heading="{jellyfin}", message="%s %s" % (translate(33025), str(elapsed).split('.')[0]),
- icon="{jellyfin}", sound=False)
- LOG.info("Full sync completed in: %s", str(elapsed).split('.')[0])
+ xbmc.executebuiltin("UpdateLibrary(video)")
+ dialog(
+ "notification",
+ heading="{jellyfin}",
+ message="%s %s" % (translate(33025), str(elapsed).split(".")[0]),
+ icon="{jellyfin}",
+ sound=False,
+ )
+ LOG.info("Full sync completed in: %s", str(elapsed).split(".")[0])
def process_library(self, library_id):
-
- ''' Add a library by its id. Create a node and a playlist whenever appropriate.
- '''
+ """Add a library by its id. Create a node and a playlist whenever appropriate."""
media = {
- 'movies': self.movies,
- 'musicvideos': self.musicvideos,
- 'tvshows': self.tvshows,
- 'music': self.music
+ "movies": self.movies,
+ "musicvideos": self.musicvideos,
+ "tvshows": self.tvshows,
+ "music": self.music,
}
try:
- if library_id.startswith('Boxsets:'):
+ if library_id.startswith("Boxsets:"):
boxset_library = {}
# Initial library sync is 'Boxsets:'
# Refresh from the addon menu is 'Boxsets:Refresh'
# Incremental syncs are 'Boxsets:$library_id'
- sync_id = library_id.split(':')[1]
+ sync_id = library_id.split(":")[1]
- if not sync_id or sync_id == 'Refresh':
+ if not sync_id or sync_id == "Refresh":
libraries = self.get_libraries()
else:
_lib = self.get_library(sync_id)
libraries = [_lib] if _lib else []
for entry in libraries:
- if entry.media_type == 'boxsets':
- boxset_library = {'Id': entry.view_id, 'Name': entry.view_name}
+ if entry.media_type == "boxsets":
+ boxset_library = {"Id": entry.view_id, "Name": entry.view_name}
break
if boxset_library:
- if sync_id == 'Refresh':
+ if sync_id == "Refresh":
self.refresh_boxsets(boxset_library)
else:
self.boxsets(boxset_library)
return
- library = self.server.jellyfin.get_item(library_id.replace('Mixed:', ""))
+ library = self.server.jellyfin.get_item(library_id.replace("Mixed:", ""))
- if library_id.startswith('Mixed:'):
- for mixed in ('movies', 'tvshows'):
+ if library_id.startswith("Mixed:"):
+ for mixed in ("movies", "tvshows"):
media[mixed](library)
- self.sync['RestorePoint'] = {}
+ self.sync["RestorePoint"] = {}
else:
- if library['CollectionType']:
- settings('enableMusic.bool', True)
+ if library["CollectionType"]:
+ settings("enableMusic.bool", True)
- media[library['CollectionType']](library)
+ media[library["CollectionType"]](library)
except LibraryException as error:
- if error.status == 'StopCalled':
+ if error.status == "StopCalled":
save_sync(self.sync)
raise
@@ -282,31 +298,41 @@ def process_library(self, library_id):
def video_database_locks(self):
with self.library.database_lock:
with Database() as videodb:
- with Database('jellyfin') as jellyfindb:
+ with Database("jellyfin") as jellyfindb:
yield videodb, jellyfindb
@progress()
def movies(self, library, dialog):
-
- ''' Process movies from a single library.
- '''
+ """Process movies from a single library."""
processed_ids = []
- for items in server.get_items(library['Id'], "Movie", False, self.sync['RestorePoint'].get('params')):
+ for items in server.get_items(
+ library["Id"], "Movie", False, self.sync["RestorePoint"].get("params")
+ ):
with self.video_database_locks() as (videodb, jellyfindb):
- obj = Movies(self.server, jellyfindb, videodb, self.direct_path, library)
-
- self.sync['RestorePoint'] = items['RestorePoint']
- start_index = items['RestorePoint']['params']['StartIndex']
-
- for index, movie in enumerate(items['Items']):
-
- dialog.update(int((float(start_index + index) / float(items['TotalRecordCount'])) * 100),
- heading="%s: %s" % (translate('addon_name'), library['Name']),
- message=movie['Name'])
+ obj = Movies(
+ self.server, jellyfindb, videodb, self.direct_path, library
+ )
+
+ self.sync["RestorePoint"] = items["RestorePoint"]
+ start_index = items["RestorePoint"]["params"]["StartIndex"]
+
+ for index, movie in enumerate(items["Items"]):
+
+ dialog.update(
+ int(
+ (
+ float(start_index + index)
+ / float(items["TotalRecordCount"])
+ )
+ * 100
+ ),
+ heading="%s: %s" % (translate("addon_name"), library["Name"]),
+ message=movie["Name"],
+ )
obj.movie(movie)
- processed_ids.append(movie['Id'])
+ processed_ids.append(movie["Id"])
with self.video_database_locks() as (videodb, jellyfindb):
obj = Movies(self.server, jellyfindb, videodb, self.direct_path, library)
@@ -316,158 +342,199 @@ def movies(self, library, dialog):
self.movies_compare(library, obj, jellyfindb)
def movies_compare(self, library, obj, jellyfinydb):
-
- ''' Compare entries from library to what's in the jellyfindb. Remove surplus
- '''
+ """Compare entries from library to what's in the jellyfindb. Remove surplus"""
db = jellyfin_db.JellyfinDatabase(jellyfinydb.cursor)
- items = db.get_item_by_media_folder(library['Id'])
+ items = db.get_item_by_media_folder(library["Id"])
current = obj.item_ids
for x in items:
- if x[0] not in current and x[1] == 'Movie':
+ if x[0] not in current and x[1] == "Movie":
obj.remove(x[0])
@progress()
def tvshows(self, library, dialog):
-
- ''' Process tvshows and episodes from a single library.
- '''
+ """Process tvshows and episodes from a single library."""
processed_ids = []
- for items in server.get_items(library['Id'], "Series", False, self.sync['RestorePoint'].get('params')):
+ for items in server.get_items(
+ library["Id"], "Series", False, self.sync["RestorePoint"].get("params")
+ ):
with self.video_database_locks() as (videodb, jellyfindb):
- obj = TVShows(self.server, jellyfindb, videodb, self.direct_path, library, True)
-
- self.sync['RestorePoint'] = items['RestorePoint']
- start_index = items['RestorePoint']['params']['StartIndex']
-
- for index, show in enumerate(items['Items']):
-
- percent = int((float(start_index + index) / float(items['TotalRecordCount'])) * 100)
- message = show['Name']
- dialog.update(percent, heading="%s: %s" % (translate('addon_name'), library['Name']), message=message)
+ obj = TVShows(
+ self.server, jellyfindb, videodb, self.direct_path, library, True
+ )
+
+ self.sync["RestorePoint"] = items["RestorePoint"]
+ start_index = items["RestorePoint"]["params"]["StartIndex"]
+
+ for index, show in enumerate(items["Items"]):
+
+ percent = int(
+ (float(start_index + index) / float(items["TotalRecordCount"]))
+ * 100
+ )
+ message = show["Name"]
+ dialog.update(
+ percent,
+ heading="%s: %s" % (translate("addon_name"), library["Name"]),
+ message=message,
+ )
if obj.tvshow(show) is not False:
- for episodes in server.get_episode_by_show(show['Id']):
- for episode in episodes['Items']:
- if episode.get('Path'):
- dialog.update(percent, message="%s/%s" % (message, episode['Name'][:10]))
+ for episodes in server.get_episode_by_show(show["Id"]):
+ for episode in episodes["Items"]:
+ if episode.get("Path"):
+ dialog.update(
+ percent,
+ message="%s/%s"
+ % (message, episode["Name"][:10]),
+ )
obj.episode(episode)
- processed_ids.append(show['Id'])
+ processed_ids.append(show["Id"])
with self.video_database_locks() as (videodb, jellyfindb):
- obj = TVShows(self.server, jellyfindb, videodb, self.direct_path, library, True)
+ obj = TVShows(
+ self.server, jellyfindb, videodb, self.direct_path, library, True
+ )
obj.item_ids = processed_ids
if self.update_library:
self.tvshows_compare(library, obj, jellyfindb)
def tvshows_compare(self, library, obj, jellyfindb):
-
- ''' Compare entries from library to what's in the jellyfindb. Remove surplus
- '''
+ """Compare entries from library to what's in the jellyfindb. Remove surplus"""
db = jellyfin_db.JellyfinDatabase(jellyfindb.cursor)
- items = db.get_item_by_media_folder(library['Id'])
+ items = db.get_item_by_media_folder(library["Id"])
for x in list(items):
items.extend(obj.get_child(x[0]))
current = obj.item_ids
for x in items:
- if x[0] not in current and x[1] == 'Series':
+ if x[0] not in current and x[1] == "Series":
obj.remove(x[0])
@progress()
def musicvideos(self, library, dialog):
-
- ''' Process musicvideos from a single library.
- '''
+ """Process musicvideos from a single library."""
processed_ids = []
- for items in server.get_items(library['Id'], "MusicVideo", False, self.sync['RestorePoint'].get('params')):
+ for items in server.get_items(
+ library["Id"], "MusicVideo", False, self.sync["RestorePoint"].get("params")
+ ):
with self.video_database_locks() as (videodb, jellyfindb):
- obj = MusicVideos(self.server, jellyfindb, videodb, self.direct_path, library)
-
- self.sync['RestorePoint'] = items['RestorePoint']
- start_index = items['RestorePoint']['params']['StartIndex']
-
- for index, mvideo in enumerate(items['Items']):
-
- dialog.update(int((float(start_index + index) / float(items['TotalRecordCount'])) * 100),
- heading="%s: %s" % (translate('addon_name'), library['Name']),
- message=mvideo['Name'])
+ obj = MusicVideos(
+ self.server, jellyfindb, videodb, self.direct_path, library
+ )
+
+ self.sync["RestorePoint"] = items["RestorePoint"]
+ start_index = items["RestorePoint"]["params"]["StartIndex"]
+
+ for index, mvideo in enumerate(items["Items"]):
+
+ dialog.update(
+ int(
+ (
+ float(start_index + index)
+ / float(items["TotalRecordCount"])
+ )
+ * 100
+ ),
+ heading="%s: %s" % (translate("addon_name"), library["Name"]),
+ message=mvideo["Name"],
+ )
obj.musicvideo(mvideo)
- processed_ids.append(mvideo['Id'])
+ processed_ids.append(mvideo["Id"])
with self.video_database_locks() as (videodb, jellyfindb):
- obj = MusicVideos(self.server, jellyfindb, videodb, self.direct_path, library)
+ obj = MusicVideos(
+ self.server, jellyfindb, videodb, self.direct_path, library
+ )
obj.item_ids = processed_ids
if self.update_library:
self.musicvideos_compare(library, obj, jellyfindb)
def musicvideos_compare(self, library, obj, jellyfindb):
-
- ''' Compare entries from library to what's in the jellyfindb. Remove surplus
- '''
+ """Compare entries from library to what's in the jellyfindb. Remove surplus"""
db = jellyfin_db.JellyfinDatabase(jellyfindb.cursor)
- items = db.get_item_by_media_folder(library['Id'])
+ items = db.get_item_by_media_folder(library["Id"])
current = obj.item_ids
for x in items:
- if x[0] not in current and x[1] == 'MusicVideo':
+ if x[0] not in current and x[1] == "MusicVideo":
obj.remove(x[0])
@progress()
def music(self, library, dialog):
-
- ''' Process artists, album, songs from a single library.
- '''
+ """Process artists, album, songs from a single library."""
with self.library.music_database_lock:
- with Database('music') as musicdb:
- with Database('jellyfin') as jellyfindb:
- obj = Music(self.server, jellyfindb, musicdb, self.direct_path, library)
+ with Database("music") as musicdb:
+ with Database("jellyfin") as jellyfindb:
+ obj = Music(
+ self.server, jellyfindb, musicdb, self.direct_path, library
+ )
- library_id = library['Id']
+ library_id = library["Id"]
- total_items = server.get_item_count(library_id, 'MusicArtist,MusicAlbum,Audio')
+ total_items = server.get_item_count(
+ library_id, "MusicArtist,MusicAlbum,Audio"
+ )
count = 0
- '''
+ """
Music database syncing. Artists must be in the database
before albums, albums before songs. Pulls batches of items
in sizes of setting "Paging - Max items". 'artists',
'albums', and 'songs' are generators containing a dict of
api responses
- '''
+ """
artists = server.get_artists(library_id)
for batch in artists:
- for item in batch['Items']:
- LOG.debug('Artist: {}'.format(item.get('Name')))
+ for item in batch["Items"]:
+ LOG.debug("Artist: {}".format(item.get("Name")))
percent = int((float(count) / float(total_items)) * 100)
- dialog.update(percent, message='Artist: {}'.format(item.get('Name')))
+ dialog.update(
+ percent, message="Artist: {}".format(item.get("Name"))
+ )
obj.artist(item)
count += 1
- albums = server.get_items(library_id, item_type='MusicAlbum', params={'SortBy': 'AlbumArtist'})
+ albums = server.get_items(
+ library_id,
+ item_type="MusicAlbum",
+ params={"SortBy": "AlbumArtist"},
+ )
for batch in albums:
- for item in batch['Items']:
- LOG.debug('Album: {}'.format(item.get('Name')))
+ for item in batch["Items"]:
+ LOG.debug("Album: {}".format(item.get("Name")))
percent = int((float(count) / float(total_items)) * 100)
- dialog.update(percent, message='Album: {} - {}'.format(item.get('AlbumArtist', ''), item.get('Name')))
+ dialog.update(
+ percent,
+ message="Album: {} - {}".format(
+ item.get("AlbumArtist", ""), item.get("Name")
+ ),
+ )
obj.album(item)
count += 1
- songs = server.get_items(library_id, item_type='Audio', params={'SortBy': 'AlbumArtist'})
+ songs = server.get_items(
+ library_id, item_type="Audio", params={"SortBy": "AlbumArtist"}
+ )
for batch in songs:
- for item in batch['Items']:
- LOG.debug('Song: {}'.format(item.get('Name')))
+ for item in batch["Items"]:
+ LOG.debug("Song: {}".format(item.get("Name")))
percent = int((float(count) / float(total_items)) * 100)
- dialog.update(percent, message='Track: {} - {}'.format(item.get('AlbumArtist', ''), item.get('Name')))
+ dialog.update(
+ percent,
+ message="Track: {} - {}".format(
+ item.get("AlbumArtist", ""), item.get("Name")
+ ),
+ )
obj.song(item)
count += 1
@@ -475,45 +542,52 @@ def music(self, library, dialog):
self.music_compare(library, obj, jellyfindb)
def music_compare(self, library, obj, jellyfindb):
-
- ''' Compare entries from library to what's in the jellyfindb. Remove surplus
- '''
+ """Compare entries from library to what's in the jellyfindb. Remove surplus"""
db = jellyfin_db.JellyfinDatabase(jellyfindb.cursor)
- items = db.get_item_by_media_folder(library['Id'])
+ items = db.get_item_by_media_folder(library["Id"])
for x in list(items):
items.extend(obj.get_child(x[0]))
current = obj.item_ids
for x in items:
- if x[0] not in current and x[1] == 'MusicArtist':
+ if x[0] not in current and x[1] == "MusicArtist":
obj.remove(x[0])
@progress(translate(33018))
def boxsets(self, library, dialog=None):
-
- ''' Process all boxsets.
- '''
- for items in server.get_items(library['Id'], "BoxSet", False, self.sync['RestorePoint'].get('params')):
+ """Process all boxsets."""
+ for items in server.get_items(
+ library["Id"], "BoxSet", False, self.sync["RestorePoint"].get("params")
+ ):
with self.video_database_locks() as (videodb, jellyfindb):
- obj = Movies(self.server, jellyfindb, videodb, self.direct_path, library)
-
- self.sync['RestorePoint'] = items['RestorePoint']
- start_index = items['RestorePoint']['params']['StartIndex']
-
- for index, boxset in enumerate(items['Items']):
-
- dialog.update(int((float(start_index + index) / float(items['TotalRecordCount'])) * 100),
- heading="%s: %s" % (translate('addon_name'), translate('boxsets')),
- message=boxset['Name'])
+ obj = Movies(
+ self.server, jellyfindb, videodb, self.direct_path, library
+ )
+
+ self.sync["RestorePoint"] = items["RestorePoint"]
+ start_index = items["RestorePoint"]["params"]["StartIndex"]
+
+ for index, boxset in enumerate(items["Items"]):
+
+ dialog.update(
+ int(
+ (
+ float(start_index + index)
+ / float(items["TotalRecordCount"])
+ )
+ * 100
+ ),
+ heading="%s: %s"
+ % (translate("addon_name"), translate("boxsets")),
+ message=boxset["Name"],
+ )
obj.boxset(boxset)
def refresh_boxsets(self, library):
-
- ''' Delete all existing boxsets and re-add.
- '''
+ """Delete all existing boxsets and re-add."""
with self.video_database_locks() as (videodb, jellyfindb):
obj = Movies(self.server, jellyfindb, videodb, self.direct_path, library)
obj.boxsets_reset()
@@ -522,82 +596,108 @@ def refresh_boxsets(self, library):
@progress(translate(33144))
def remove_library(self, library_id, dialog):
-
- ''' Remove library by their id from the Kodi database.
- '''
+ """Remove library by their id from the Kodi database."""
direct_path = self.library.direct_path
- with Database('jellyfin') as jellyfindb:
+ with Database("jellyfin") as jellyfindb:
db = jellyfin_db.JellyfinDatabase(jellyfindb.cursor)
- library = db.get_view(library_id.replace('Mixed:', ""))
- items = db.get_item_by_media_folder(library_id.replace('Mixed:', ""))
- media = 'music' if library.media_type == 'music' else 'video'
+ library = db.get_view(library_id.replace("Mixed:", ""))
+ items = db.get_item_by_media_folder(library_id.replace("Mixed:", ""))
+ media = "music" if library.media_type == "music" else "video"
- if media == 'music':
- settings('MusicRescan.bool', False)
+ if media == "music":
+ settings("MusicRescan.bool", False)
if items:
- with self.library.music_database_lock if media == 'music' else self.library.database_lock:
+ with (
+ self.library.music_database_lock
+ if media == "music"
+ else self.library.database_lock
+ ):
with Database(media) as kodidb:
count = 0
- if library.media_type == 'mixed':
+ if library.media_type == "mixed":
- movies = [x for x in items if x[1] == 'Movie']
- tvshows = [x for x in items if x[1] == 'Series']
+ movies = [x for x in items if x[1] == "Movie"]
+ tvshows = [x for x in items if x[1] == "Series"]
- obj = Movies(self.server, jellyfindb, kodidb, direct_path, library).remove
+ obj = Movies(
+ self.server, jellyfindb, kodidb, direct_path, library
+ ).remove
for item in movies:
obj(item[0])
- dialog.update(int((float(count) / float(len(items)) * 100)), heading="%s: %s" % (translate('addon_name'), library.view_name))
+ dialog.update(
+ int((float(count) / float(len(items)) * 100)),
+ heading="%s: %s"
+ % (translate("addon_name"), library.view_name),
+ )
count += 1
- obj = TVShows(self.server, jellyfindb, kodidb, direct_path, library).remove
+ obj = TVShows(
+ self.server, jellyfindb, kodidb, direct_path, library
+ ).remove
for item in tvshows:
obj(item[0])
- dialog.update(int((float(count) / float(len(items)) * 100)), heading="%s: %s" % (translate('addon_name'), library.view_name))
+ dialog.update(
+ int((float(count) / float(len(items)) * 100)),
+ heading="%s: %s"
+ % (translate("addon_name"), library.view_name),
+ )
count += 1
else:
- default_args = (self.server, jellyfindb, kodidb, direct_path)
+ default_args = (
+ self.server,
+ jellyfindb,
+ kodidb,
+ direct_path,
+ )
for item in items:
- if item[1] in ('Series', 'Season', 'Episode'):
+ if item[1] in ("Series", "Season", "Episode"):
TVShows(*default_args).remove(item[0])
- elif item[1] in ('Movie', 'BoxSet'):
+ elif item[1] in ("Movie", "BoxSet"):
Movies(*default_args).remove(item[0])
- elif item[1] in ('MusicAlbum', 'MusicArtist', 'AlbumArtist', 'Audio'):
+ elif item[1] in (
+ "MusicAlbum",
+ "MusicArtist",
+ "AlbumArtist",
+ "Audio",
+ ):
Music(*default_args).remove(item[0])
- elif item[1] == 'MusicVideo':
+ elif item[1] == "MusicVideo":
MusicVideos(*default_args).remove(item[0])
- dialog.update(int((float(count) / float(len(items)) * 100)), heading="%s: %s" % (translate('addon_name'), library[0]))
+ dialog.update(
+ int((float(count) / float(len(items)) * 100)),
+ heading="%s: %s"
+ % (translate("addon_name"), library[0]),
+ )
count += 1
self.sync = get_sync()
- if library_id in self.sync['Whitelist']:
- self.sync['Whitelist'].remove(library_id)
+ if library_id in self.sync["Whitelist"]:
+ self.sync["Whitelist"].remove(library_id)
- elif 'Mixed:%s' % library_id in self.sync['Whitelist']:
- self.sync['Whitelist'].remove('Mixed:%s' % library_id)
+ elif "Mixed:%s" % library_id in self.sync["Whitelist"]:
+ self.sync["Whitelist"].remove("Mixed:%s" % library_id)
save_sync(self.sync)
def __exit__(self, exc_type, exc_val, exc_tb):
-
- ''' Exiting sync
- '''
+ """Exiting sync"""
self.running = False
- window('jellyfin_sync', clear=True)
+ window("jellyfin_sync", clear=True)
- if not settings('dbSyncScreensaver.bool') and self.screensaver is not None:
+ if not settings("dbSyncScreensaver.bool") and self.screensaver is not None:
- xbmc.executebuiltin('InhibitIdleShutdown(false)')
+ xbmc.executebuiltin("InhibitIdleShutdown(false)")
set_screensaver(value=self.screensaver)
LOG.info("--<[ fullsync ]")
diff --git a/jellyfin_kodi/helper/api.py b/jellyfin_kodi/helper/api.py
index 7ac839ad2..0174c3edb 100644
--- a/jellyfin_kodi/helper/api.py
+++ b/jellyfin_kodi/helper/api.py
@@ -14,100 +14,102 @@
class API(object):
def __init__(self, item, server=None):
-
- ''' Get item information in special cases.
- server is the server address, provide if your functions requires it.
- '''
+ """Get item information in special cases.
+ server is the server address, provide if your functions requires it.
+ """
self.item = item
self.server = server
def get_playcount(self, played, playcount):
-
- ''' Convert Jellyfin played/playcount into
- the Kodi equivalent. The playcount is tied to the watch status.
- '''
+ """Convert Jellyfin played/playcount into
+ the Kodi equivalent. The playcount is tied to the watch status.
+ """
return (playcount or 1) if played else None
def get_naming(self):
- if self.item['Type'] == 'Episode' and 'SeriesName' in self.item:
- return "%s: %s" % (self.item['SeriesName'], self.item['Name'])
+ if self.item["Type"] == "Episode" and "SeriesName" in self.item:
+ return "%s: %s" % (self.item["SeriesName"], self.item["Name"])
- elif self.item['Type'] == 'MusicAlbum' and 'AlbumArtist' in self.item:
- return "%s: %s" % (self.item['AlbumArtist'], self.item['Name'])
+ elif self.item["Type"] == "MusicAlbum" and "AlbumArtist" in self.item:
+ return "%s: %s" % (self.item["AlbumArtist"], self.item["Name"])
- elif self.item['Type'] == 'Audio' and self.item.get('Artists'):
- return "%s: %s" % (self.item['Artists'][0], self.item['Name'])
+ elif self.item["Type"] == "Audio" and self.item.get("Artists"):
+ return "%s: %s" % (self.item["Artists"][0], self.item["Name"])
- return self.item['Name']
+ return self.item["Name"]
def get_actors(self):
cast = []
- if 'People' in self.item:
- self.get_people_artwork(self.item['People'])
+ if "People" in self.item:
+ self.get_people_artwork(self.item["People"])
- for person in self.item['People']:
+ for person in self.item["People"]:
- if person['Type'] == "Actor":
- cast.append({
- 'name': person['Name'],
- 'role': person.get('Role', "Unknown"),
- 'order': len(cast) + 1,
- 'thumbnail': person['imageurl']
- })
+ if person["Type"] == "Actor":
+ cast.append(
+ {
+ "name": person["Name"],
+ "role": person.get("Role", "Unknown"),
+ "order": len(cast) + 1,
+ "thumbnail": person["imageurl"],
+ }
+ )
return cast
def media_streams(self, video, audio, subtitles):
- return {
- 'video': video or [],
- 'audio': audio or [],
- 'subtitle': subtitles or []
- }
+ return {"video": video or [], "audio": audio or [], "subtitle": subtitles or []}
def video_streams(self, tracks, container=None):
if container:
- container = container.split(',')[0]
+ container = container.split(",")[0]
for track in tracks:
if "DvProfile" in track:
- track['hdrtype'] = "dolbyvision"
- elif track.get('VideoRangeType', '') in ["HDR10", "HDR10Plus"]:
- track['hdrtype'] = "hdr10"
- elif "HLG" in track.get('VideoRangeType', ''):
- track['hdrtype'] = "hlg"
-
- track.update({
- 'hdrtype': track.get('hdrtype', "").lower(),
- 'codec': track.get('Codec', "").lower(),
- 'profile': track.get('Profile', "").lower(),
- 'height': track.get('Height'),
- 'width': track.get('Width'),
- '3d': self.item.get('Video3DFormat'),
- 'aspect': 1.85
- })
-
- if "msmpeg4" in track['codec']:
- track['codec'] = "divx"
-
- elif "mpeg4" in track['codec'] and ("simple profile" in track['profile'] or not track['profile']):
- track['codec'] = "xvid"
-
- elif "h264" in track['codec'] and container in ('mp4', 'mov', 'm4v'):
- track['codec'] = "avc1"
+ track["hdrtype"] = "dolbyvision"
+ elif track.get("VideoRangeType", "") in ["HDR10", "HDR10Plus"]:
+ track["hdrtype"] = "hdr10"
+ elif "HLG" in track.get("VideoRangeType", ""):
+ track["hdrtype"] = "hlg"
+
+ track.update(
+ {
+ "hdrtype": track.get("hdrtype", "").lower(),
+ "codec": track.get("Codec", "").lower(),
+ "profile": track.get("Profile", "").lower(),
+ "height": track.get("Height"),
+ "width": track.get("Width"),
+ "3d": self.item.get("Video3DFormat"),
+ "aspect": 1.85,
+ }
+ )
+
+ if "msmpeg4" in track["codec"]:
+ track["codec"] = "divx"
+
+ elif "mpeg4" in track["codec"] and (
+ "simple profile" in track["profile"] or not track["profile"]
+ ):
+ track["codec"] = "xvid"
+
+ elif "h264" in track["codec"] and container in ("mp4", "mov", "m4v"):
+ track["codec"] = "avc1"
try:
- width, height = self.item.get('AspectRatio', track.get('AspectRatio', "0")).split(':')
- track['aspect'] = round(float(width) / float(height), 6)
+ width, height = self.item.get(
+ "AspectRatio", track.get("AspectRatio", "0")
+ ).split(":")
+ track["aspect"] = round(float(width) / float(height), 6)
except (ValueError, ZeroDivisionError):
- if track['width'] and track['height']:
- track['aspect'] = round(float(track['width'] / track['height']), 6)
+ if track["width"] and track["height"]:
+ track["aspect"] = round(float(track["width"] / track["height"]), 6)
- track['duration'] = self.get_runtime()
+ track["duration"] = self.get_runtime()
return tracks
@@ -115,28 +117,30 @@ def audio_streams(self, tracks):
for track in tracks:
- track.update({
- 'codec': track.get('Codec', "").lower(),
- 'profile': track.get('Profile', "").lower(),
- 'channels': track.get('Channels'),
- 'language': track.get('Language')
- })
+ track.update(
+ {
+ "codec": track.get("Codec", "").lower(),
+ "profile": track.get("Profile", "").lower(),
+ "channels": track.get("Channels"),
+ "language": track.get("Language"),
+ }
+ )
- if "dts-hd ma" in track['profile']:
- track['codec'] = "dtshd_ma"
+ if "dts-hd ma" in track["profile"]:
+ track["codec"] = "dtshd_ma"
- elif "dts-hd hra" in track['profile']:
- track['codec'] = "dtshd_hra"
+ elif "dts-hd hra" in track["profile"]:
+ track["codec"] = "dtshd_hra"
return tracks
def get_runtime(self):
try:
- runtime = self.item['RunTimeTicks'] / 10000000.0
+ runtime = self.item["RunTimeTicks"] / 10000000.0
except KeyError:
- runtime = self.item.get('CumulativeRunTimeTicks', 0) / 10000000.0
+ runtime = self.item.get("CumulativeRunTimeTicks", 0) / 10000000.0
return runtime
@@ -146,7 +150,7 @@ def adjust_resume(cls, resume_seconds):
resume = 0
if resume_seconds:
resume = round(float(resume_seconds), 6)
- jumpback = int(settings('resumeJumpBack'))
+ jumpback = int(settings("resumeJumpBack"))
if resume > jumpback:
# To avoid negative bookmark
resume = resume - jumpback
@@ -156,25 +160,25 @@ def adjust_resume(cls, resume_seconds):
def validate_studio(self, studio_name):
# Convert studio for Kodi to properly detect them
studios = {
- 'abc (us)': "ABC",
- 'fox (us)': "FOX",
- 'mtv (us)': "MTV",
- 'showcase (ca)': "Showcase",
- 'wgn america': "WGN",
- 'bravo (us)': "Bravo",
- 'tnt (us)': "TNT",
- 'comedy central': "Comedy Central (US)"
+ "abc (us)": "ABC",
+ "fox (us)": "FOX",
+ "mtv (us)": "MTV",
+ "showcase (ca)": "Showcase",
+ "wgn america": "WGN",
+ "bravo (us)": "Bravo",
+ "tnt (us)": "TNT",
+ "comedy central": "Comedy Central (US)",
}
return studios.get(studio_name.lower(), studio_name)
def get_overview(self, overview=None):
- overview = overview or self.item.get('Overview')
+ overview = overview or self.item.get("Overview")
if not overview:
return
- overview = overview.replace("\"", "\'")
+ overview = overview.replace('"', "'")
overview = overview.replace("\n", "[CR]")
overview = overview.replace("\r", " ")
overview = overview.replace("
", "[CR]")
@@ -183,7 +187,7 @@ def get_overview(self, overview=None):
def get_mpaa(self, rating=None):
- mpaa = rating or self.item.get('OfficialRating', "")
+ mpaa = rating or self.item.get("OfficialRating", "")
if mpaa in ("NR", "UR"):
# Kodi seems to not like NR, but will accept Not Rated
@@ -197,112 +201,123 @@ def get_mpaa(self, rating=None):
def get_file_path(self, path=None):
if path is None:
- path = self.item.get('Path')
+ path = self.item.get("Path")
if not path:
return ""
- if path.startswith('\\\\'):
- path = path.replace('\\\\', "smb://", 1).replace('\\\\', "\\").replace('\\', "/")
+ if path.startswith("\\\\"):
+ path = (
+ path.replace("\\\\", "smb://", 1)
+ .replace("\\\\", "\\")
+ .replace("\\", "/")
+ )
- if 'Container' in self.item:
+ if "Container" in self.item:
- if self.item['Container'] == 'dvd':
+ if self.item["Container"] == "dvd":
path = "%s/VIDEO_TS/VIDEO_TS.IFO" % path
- elif self.item['Container'] == 'bluray':
+ elif self.item["Container"] == "bluray":
path = "%s/BDMV/index.bdmv" % path
- path = path.replace('\\\\', "\\")
+ path = path.replace("\\\\", "\\")
- if '\\' in path:
- path = path.replace('/', "\\")
+ if "\\" in path:
+ path = path.replace("/", "\\")
- if '://' in path:
- protocol = path.split('://')[0]
+ if "://" in path:
+ protocol = path.split("://")[0]
path = path.replace(protocol, protocol.lower())
return path
def get_user_artwork(self, user_id):
-
- ''' Get jellyfin user profile picture.
- '''
+ """Get jellyfin user profile picture."""
return "%s/Users/%s/Images/Primary?Format=original" % (self.server, user_id)
def get_people_artwork(self, people):
-
- ''' Get people (actor, director, etc) artwork.
- '''
+ """Get people (actor, director, etc) artwork."""
for person in people:
- if 'PrimaryImageTag' in person:
+ if "PrimaryImageTag" in person:
query = "&MaxWidth=400&MaxHeight=400&Index=0"
- person['imageurl'] = self.get_artwork(person['Id'], "Primary", person['PrimaryImageTag'], query)
+ person["imageurl"] = self.get_artwork(
+ person["Id"], "Primary", person["PrimaryImageTag"], query
+ )
else:
- person['imageurl'] = None
+ person["imageurl"] = None
return people
def get_all_artwork(self, obj, parent_info=False):
+ """Get all artwork possible. If parent_info is True,
+ it will fill missing artwork with parent artwork.
- ''' Get all artwork possible. If parent_info is True,
- it will fill missing artwork with parent artwork.
-
- obj is from objects.Objects().map(item, 'Artwork')
- '''
+ obj is from objects.Objects().map(item, 'Artwork')
+ """
query = ""
all_artwork = {
- 'Primary': "",
- 'BoxRear': "",
- 'Art': "",
- 'Banner': "",
- 'Logo': "",
- 'Thumb': "",
- 'Disc': "",
- 'Backdrop': []
+ "Primary": "",
+ "BoxRear": "",
+ "Art": "",
+ "Banner": "",
+ "Logo": "",
+ "Thumb": "",
+ "Disc": "",
+ "Backdrop": [],
}
- if settings('compressArt.bool'):
+ if settings("compressArt.bool"):
query = "&Quality=90"
- if not settings('enableCoverArt.bool'):
+ if not settings("enableCoverArt.bool"):
query += "&EnableImageEnhancers=false"
art_maxheight = [360, 480, 600, 720, 1080, -1]
- maxheight = art_maxheight[int(settings('maxArtResolution') or 5)]
+ maxheight = art_maxheight[int(settings("maxArtResolution") or 5)]
if maxheight != -1:
query += "&MaxHeight=%d" % maxheight
- all_artwork['Backdrop'] = self.get_backdrops(obj['Id'], obj['BackdropTags'] or [], query)
+ all_artwork["Backdrop"] = self.get_backdrops(
+ obj["Id"], obj["BackdropTags"] or [], query
+ )
- for artwork in (obj['Tags'] or []):
- all_artwork[artwork] = self.get_artwork(obj['Id'], artwork, obj['Tags'][artwork], query)
+ for artwork in obj["Tags"] or []:
+ all_artwork[artwork] = self.get_artwork(
+ obj["Id"], artwork, obj["Tags"][artwork], query
+ )
if parent_info:
- if not all_artwork['Backdrop'] and obj['ParentBackdropId']:
- all_artwork['Backdrop'] = self.get_backdrops(obj['ParentBackdropId'], obj['ParentBackdropTags'], query)
+ if not all_artwork["Backdrop"] and obj["ParentBackdropId"]:
+ all_artwork["Backdrop"] = self.get_backdrops(
+ obj["ParentBackdropId"], obj["ParentBackdropTags"], query
+ )
- for art in ('Logo', 'Art', 'Thumb'):
- if not all_artwork[art] and obj['Parent%sId' % art]:
- all_artwork[art] = self.get_artwork(obj['Parent%sId' % art], art, obj['Parent%sTag' % art], query)
+ for art in ("Logo", "Art", "Thumb"):
+ if not all_artwork[art] and obj["Parent%sId" % art]:
+ all_artwork[art] = self.get_artwork(
+ obj["Parent%sId" % art], art, obj["Parent%sTag" % art], query
+ )
- if obj.get('SeriesTag'):
- all_artwork['Series.Primary'] = self.get_artwork(obj['SeriesId'], "Primary", obj['SeriesTag'], query)
+ if obj.get("SeriesTag"):
+ all_artwork["Series.Primary"] = self.get_artwork(
+ obj["SeriesId"], "Primary", obj["SeriesTag"], query
+ )
- if not all_artwork['Primary']:
- all_artwork['Primary'] = all_artwork['Series.Primary']
+ if not all_artwork["Primary"]:
+ all_artwork["Primary"] = all_artwork["Series.Primary"]
- elif not all_artwork['Primary'] and obj.get('AlbumId'):
- all_artwork['Primary'] = self.get_artwork(obj['AlbumId'], "Primary", obj['AlbumTag'], query)
+ elif not all_artwork["Primary"] and obj.get("AlbumId"):
+ all_artwork["Primary"] = self.get_artwork(
+ obj["AlbumId"], "Primary", obj["AlbumTag"], query
+ )
return all_artwork
def get_backdrops(self, item_id, tags, query=None):
-
- ''' Get backdrops based of "BackdropImageTags" in the jellyfin object.
- '''
+ """Get backdrops based of "BackdropImageTags" in the jellyfin object."""
backdrops = []
if item_id is None:
@@ -310,15 +325,19 @@ def get_backdrops(self, item_id, tags, query=None):
for index, tag in enumerate(tags):
- artwork = "%s/Items/%s/Images/Backdrop/%s?Format=original&Tag=%s%s" % (self.server, item_id, index, tag, (query or ""))
+ artwork = "%s/Items/%s/Images/Backdrop/%s?Format=original&Tag=%s%s" % (
+ self.server,
+ item_id,
+ index,
+ tag,
+ (query or ""),
+ )
backdrops.append(artwork)
return backdrops
def get_artwork(self, item_id, image, tag=None, query=None):
-
- ''' Get any type of artwork: Primary, Art, Banner, Logo, Thumb, Disc
- '''
+ """Get any type of artwork: Primary, Art, Banner, Logo, Thumb, Disc"""
if item_id is None:
return ""
diff --git a/jellyfin_kodi/helper/exceptions.py b/jellyfin_kodi/helper/exceptions.py
index 12105ed80..712a64a94 100644
--- a/jellyfin_kodi/helper/exceptions.py
+++ b/jellyfin_kodi/helper/exceptions.py
@@ -9,7 +9,11 @@
class HTTPException(Exception):
# Jellyfin HTTP exception
def __init__(self, status, message):
- warnings.warn(f'{self.__class__.__name__} will be deprecated.', DeprecationWarning, stacklevel=2)
+ warnings.warn(
+ f"{self.__class__.__name__} will be deprecated.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
self.status = status
self.message = message
@@ -26,4 +30,5 @@ class PathValidationException(Exception):
TODO: Investigate the usage of this to see if it can be done better.
"""
+
pass
diff --git a/jellyfin_kodi/helper/lazylogger.py b/jellyfin_kodi/helper/lazylogger.py
index cead1f117..bfd15f4b9 100644
--- a/jellyfin_kodi/helper/lazylogger.py
+++ b/jellyfin_kodi/helper/lazylogger.py
@@ -6,6 +6,7 @@ class LazyLogger(object):
"""`helper.loghandler.getLogger()` is used everywhere.
This class helps to avoid import errors.
"""
+
__logger = None
__logger_name = None
@@ -15,5 +16,6 @@ def __init__(self, logger_name=None):
def __getattr__(self, name):
if self.__logger is None:
from .loghandler import getLogger
+
self.__logger = getLogger(self.__logger_name)
return getattr(self.__logger, name)
diff --git a/jellyfin_kodi/helper/loghandler.py b/jellyfin_kodi/helper/loghandler.py
index ce06ecb12..9d234324b 100644
--- a/jellyfin_kodi/helper/loghandler.py
+++ b/jellyfin_kodi/helper/loghandler.py
@@ -16,8 +16,8 @@
##################################################################################################
-__addon__ = xbmcaddon.Addon(id='plugin.video.jellyfin')
-__pluginpath__ = translate_path(__addon__.getAddonInfo('path'))
+__addon__ = xbmcaddon.Addon(id="plugin.video.jellyfin")
+__pluginpath__ = translate_path(__addon__.getAddonInfo("path"))
##################################################################################################
@@ -36,17 +36,17 @@ def __init__(self):
logging.StreamHandler.__init__(self)
self.setFormatter(MyFormatter())
- self.sensitive = {'Token': [], 'Server': []}
+ self.sensitive = {"Token": [], "Server": []}
- for server in database.get_credentials()['Servers']:
+ for server in database.get_credentials()["Servers"]:
- if server.get('AccessToken'):
- self.sensitive['Token'].append(server['AccessToken'])
+ if server.get("AccessToken"):
+ self.sensitive["Token"].append(server["AccessToken"])
- if server.get('address'):
- self.sensitive['Server'].append(server['address'].split('://')[1])
+ if server.get("address"):
+ self.sensitive["Server"].append(server["address"].split("://")[1])
- self.mask_info = settings('maskInfo.bool')
+ self.mask_info = settings("maskInfo.bool")
if kodi_version() > 18:
self.level = xbmc.LOGINFO
@@ -59,10 +59,10 @@ def emit(self, record):
string = self.format(record)
if self.mask_info:
- for server in self.sensitive['Server']:
+ for server in self.sensitive["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 or "{token}", "{jellyfin-token}")
xbmc.log(string, level=self.level)
@@ -74,10 +74,10 @@ def _get_log_level(cls, level):
logging.ERROR: 0,
logging.WARNING: 0,
logging.INFO: 1,
- logging.DEBUG: 2
+ logging.DEBUG: 2,
}
try:
- log_level = int(settings('logLevel'))
+ log_level = int(settings("logLevel"))
except ValueError:
log_level = 2 # If getting settings fail, we probably want debug logging.
@@ -86,7 +86,9 @@ def _get_log_level(cls, level):
class MyFormatter(logging.Formatter):
- def __init__(self, fmt='%(name)s -> %(levelname)s::%(relpath)s:%(lineno)s %(message)s'):
+ def __init__(
+ self, fmt="%(name)s -> %(levelname)s::%(relpath)s:%(lineno)s %(message)s"
+ ):
logging.Formatter.__init__(self, fmt)
def format(self, record):
@@ -116,14 +118,14 @@ def formatException(self, exc_info):
res.append(o)
- return ''.join(res)
+ return "".join(res)
def _gen_rel_path(self, record):
if record.pathname:
record.relpath = os.path.relpath(record.pathname, __pluginpath__)
-__LOGGER = logging.getLogger('JELLYFIN')
+__LOGGER = logging.getLogger("JELLYFIN")
for handler in __LOGGER.handlers:
__LOGGER.removeHandler(handler)
diff --git a/jellyfin_kodi/helper/playutils.py b/jellyfin_kodi/helper/playutils.py
index 6fc850fcd..baec4b56e 100644
--- a/jellyfin_kodi/helper/playutils.py
+++ b/jellyfin_kodi/helper/playutils.py
@@ -26,81 +26,81 @@ class Transcode(object):
Disabled = 3
MediaDefault = 4
+
#################################################################################################
def set_properties(item, method, server_id=None):
+ """Set all properties for playback detection."""
+ info = item.get("PlaybackInfo") or {}
+
+ current = window("jellyfin_play.json") or []
+ current.append(
+ {
+ "Type": item["Type"],
+ "Id": item["Id"],
+ "Path": info["Path"],
+ "PlayMethod": method,
+ "PlayOption": "Addon" if info.get("PlaySessionId") else "Native",
+ "MediaSourceId": info.get("MediaSourceId", item["Id"]),
+ "Runtime": item.get("RunTimeTicks"),
+ "PlaySessionId": info.get("PlaySessionId", str(uuid4()).replace("-", "")),
+ "ServerId": server_id,
+ "DeviceId": client.get_device_id(),
+ "SubsMapping": info.get("Subtitles"),
+ "AudioStreamIndex": info.get("AudioStreamIndex"),
+ "SubtitleStreamIndex": info.get("SubtitleStreamIndex"),
+ "CurrentPosition": info.get("CurrentPosition"),
+ "CurrentEpisode": info.get("CurrentEpisode"),
+ }
+ )
- ''' Set all properties for playback detection.
- '''
- info = item.get('PlaybackInfo') or {}
-
- current = window('jellyfin_play.json') or []
- current.append({
- 'Type': item['Type'],
- 'Id': item['Id'],
- 'Path': info['Path'],
- 'PlayMethod': method,
- 'PlayOption': 'Addon' if info.get('PlaySessionId') else 'Native',
- 'MediaSourceId': info.get('MediaSourceId', item['Id']),
- 'Runtime': item.get('RunTimeTicks'),
- 'PlaySessionId': info.get('PlaySessionId', str(uuid4()).replace("-", "")),
- 'ServerId': server_id,
- 'DeviceId': client.get_device_id(),
- 'SubsMapping': info.get('Subtitles'),
- 'AudioStreamIndex': info.get('AudioStreamIndex'),
- 'SubtitleStreamIndex': info.get('SubtitleStreamIndex'),
- 'CurrentPosition': info.get('CurrentPosition'),
- 'CurrentEpisode': info.get('CurrentEpisode')
- })
-
- window('jellyfin_play.json', current)
+ window("jellyfin_play.json", current)
class PlayUtils(object):
- def __init__(self, item, force_transcode=False, server_id=None, server=None, api_client=None):
-
- ''' Item will be updated with the property PlaybackInfo, which
- holds all the playback information.
- '''
+ def __init__(
+ self, item, force_transcode=False, server_id=None, server=None, api_client=None
+ ):
+ """Item will be updated with the property PlaybackInfo, which
+ holds all the playback information.
+ """
self.item = item
- self.item['PlaybackInfo'] = {}
+ self.item["PlaybackInfo"] = {}
self.api_client = api_client
self.info = {
- 'ServerId': server_id,
- 'ServerAddress': server,
- 'ForceTranscode': force_transcode,
- 'Token': api_client.config.data['auth.token']
+ "ServerId": server_id,
+ "ServerAddress": server,
+ "ForceTranscode": force_transcode,
+ "Token": api_client.config.data["auth.token"],
}
def get_sources(self, source_id=None):
-
- ''' Return sources based on the optional source_id or the device profile.
- '''
- info = self.api_client.get_play_info(self.item['Id'], self.get_device_profile())
+ """Return sources based on the optional source_id or the device profile."""
+ info = self.api_client.get_play_info(self.item["Id"], self.get_device_profile())
LOG.info(info)
- self.info['PlaySessionId'] = info['PlaySessionId']
+ self.info["PlaySessionId"] = info["PlaySessionId"]
sources = []
- if not info.get('MediaSources'):
+ if not info.get("MediaSources"):
LOG.info("No MediaSources found.")
elif source_id:
for source in info:
- if source['Id'] == source_id:
+ if source["Id"] == source_id:
sources.append(source)
break
- elif not self.is_selection(info) or len(info['MediaSources']) == 1:
+ elif not self.is_selection(info) or len(info["MediaSources"]) == 1:
LOG.info("Skip source selection.")
- sources.append(info['MediaSources'][0])
+ sources.append(info["MediaSources"][0])
else:
- sources.extend([x for x in info['MediaSources']])
+ sources.extend([x for x in info["MediaSources"]])
return sources
@@ -110,7 +110,7 @@ def select_source(self, sources, audio=None, subtitle=None):
selection = []
for source in sources:
- selection.append(source.get('Name', "na"))
+ selection.append(source.get("Name", "na"))
resp = dialog("select", translate(33130), selection)
@@ -127,25 +127,23 @@ def select_source(self, sources, audio=None, subtitle=None):
return source
def is_selection(self, sources):
-
- ''' Do not allow source selection for.
- '''
- if self.item['MediaType'] != 'Video':
+ """Do not allow source selection for."""
+ if self.item["MediaType"] != "Video":
LOG.debug("MediaType is not a video.")
return False
- elif self.item['Type'] == 'TvChannel':
+ elif self.item["Type"] == "TvChannel":
LOG.debug("TvChannel detected.")
return False
- elif len(sources) == 1 and sources[0]['Type'] == 'Placeholder':
+ elif len(sources) == 1 and sources[0]["Type"] == "Placeholder":
LOG.debug("Placeholder detected.")
return False
- elif 'SourceType' in self.item and self.item['SourceType'] != 'Library':
+ elif "SourceType" in self.item and self.item["SourceType"] != "Library":
LOG.debug("SourceType not from library.")
return False
@@ -156,7 +154,7 @@ def is_file_exists(self, source):
self.direct_play(source)
- if xbmcvfs.exists(self.info['Path']):
+ if xbmcvfs.exists(self.info["Path"]):
LOG.info("Path exists.")
return True
@@ -167,7 +165,7 @@ def is_file_exists(self, source):
def is_strm(self, source):
- if source.get('Container') == 'strm' or self.item['Path'].endswith('.strm'):
+ if source.get("Container") == "strm" or self.item["Path"].endswith(".strm"):
LOG.info("strm detected")
return True
@@ -175,31 +173,37 @@ def is_strm(self, source):
return False
def get(self, source, audio=None, subtitle=None):
-
- ''' The server returns sources based on the MaxStreamingBitrate value and other filters.
- prop: jellyfinfilename for ?? I thought it was to pass the real path to subtitle add-ons but it's not working?
- '''
- self.info['MediaSourceId'] = source['Id']
-
- if source.get('RequiresClosing'):
-
- ''' Server returning live tv stream for direct play is hardcoded with 127.0.0.1.
- '''
- self.info['LiveStreamId'] = source['LiveStreamId']
- source['SupportsDirectPlay'] = False
- source['Protocol'] = "LiveTV"
-
- if self.info['ForceTranscode']:
-
- source['SupportsDirectPlay'] = False
- source['SupportsDirectStream'] = False
-
- if source.get('Protocol') == 'Http' or source['SupportsDirectPlay'] and (self.is_strm(source) or not settings('playFromStream.bool') and self.is_file_exists(source)):
+ """The server returns sources based on the MaxStreamingBitrate value and other filters.
+ prop: jellyfinfilename for ?? I thought it was to pass the real path to subtitle add-ons but it's not working?
+ """
+ self.info["MediaSourceId"] = source["Id"]
+
+ if source.get("RequiresClosing"):
+
+ """Server returning live tv stream for direct play is hardcoded with 127.0.0.1."""
+ self.info["LiveStreamId"] = source["LiveStreamId"]
+ source["SupportsDirectPlay"] = False
+ source["Protocol"] = "LiveTV"
+
+ if self.info["ForceTranscode"]:
+
+ source["SupportsDirectPlay"] = False
+ source["SupportsDirectStream"] = False
+
+ if (
+ source.get("Protocol") == "Http"
+ or source["SupportsDirectPlay"]
+ and (
+ self.is_strm(source)
+ or not settings("playFromStream.bool")
+ and self.is_file_exists(source)
+ )
+ ):
LOG.info("--[ direct play ]")
self.direct_play(source)
- elif source['SupportsDirectStream'] or source['SupportsDirectPlay']:
+ elif source["SupportsDirectStream"] or source["SupportsDirectPlay"]:
LOG.info("--[ direct stream ]")
self.direct_url(source)
@@ -208,158 +212,209 @@ def get(self, source, audio=None, subtitle=None):
LOG.info("--[ transcode ]")
self.transcode(source, audio, subtitle)
- self.info['AudioStreamIndex'] = self.info.get('AudioStreamIndex') or source.get('DefaultAudioStreamIndex')
- self.info['SubtitleStreamIndex'] = self.info.get('SubtitleStreamIndex') or source.get('DefaultSubtitleStreamIndex')
- self.item['PlaybackInfo'].update(self.info)
+ self.info["AudioStreamIndex"] = self.info.get("AudioStreamIndex") or source.get(
+ "DefaultAudioStreamIndex"
+ )
+ self.info["SubtitleStreamIndex"] = self.info.get(
+ "SubtitleStreamIndex"
+ ) or source.get("DefaultSubtitleStreamIndex")
+ self.item["PlaybackInfo"].update(self.info)
- API = api.API(self.item, self.info['ServerAddress'])
- window('jellyfinfilename', value=API.get_file_path(source.get('Path')))
+ API = api.API(self.item, self.info["ServerAddress"])
+ window("jellyfinfilename", value=API.get_file_path(source.get("Path")))
def live_stream(self, source):
-
- ''' Get live stream media info.
- '''
- info = self.api_client.get_live_stream(self.item['Id'], self.info['PlaySessionId'], source['OpenToken'], self.get_device_profile())
+ """Get live stream media info."""
+ info = self.api_client.get_live_stream(
+ self.item["Id"],
+ self.info["PlaySessionId"],
+ source["OpenToken"],
+ self.get_device_profile(),
+ )
LOG.info(info)
- if info['MediaSource'].get('RequiresClosing'):
- self.info['LiveStreamId'] = source['LiveStreamId']
+ if info["MediaSource"].get("RequiresClosing"):
+ self.info["LiveStreamId"] = source["LiveStreamId"]
- return info['MediaSource']
+ return info["MediaSource"]
def transcode(self, source, audio=None, subtitle=None):
- if 'TranscodingUrl' not in source:
+ if "TranscodingUrl" not in source:
raise Exception("use get_sources to get transcoding url")
- self.info['Method'] = "Transcode"
+ self.info["Method"] = "Transcode"
- if self.item['MediaType'] == 'Video':
- base, params = source['TranscodingUrl'].split('?')
- url_parsed = params.split('&')
- manual_tracks = ''
+ if self.item["MediaType"] == "Video":
+ base, params = source["TranscodingUrl"].split("?")
+ url_parsed = params.split("&")
+ manual_tracks = ""
# manual bitrate
- url_parsed = [p for p in url_parsed if 'AudioBitrate' not in p and 'VideoBitrate' not in p]
+ url_parsed = [
+ p
+ for p in url_parsed
+ if "AudioBitrate" not in p and "VideoBitrate" not in p
+ ]
- if settings('skipDialogTranscode') != Transcode.Enabled and source.get('MediaStreams'):
+ if settings("skipDialogTranscode") != Transcode.Enabled and source.get(
+ "MediaStreams"
+ ):
# manual tracks
- url_parsed = [p for p in url_parsed if 'AudioStreamIndex' not in p and 'SubtitleStreamIndex' not in p]
+ url_parsed = [
+ p
+ for p in url_parsed
+ if "AudioStreamIndex" not in p and "SubtitleStreamIndex" not in p
+ ]
manual_tracks = self.get_audio_subs(source, audio, subtitle)
audio_bitrate = self.get_transcoding_audio_bitrate()
video_bitrate = self.get_max_bitrate() - audio_bitrate
- params = "%s%s" % ('&'.join(url_parsed), manual_tracks)
- params += "&VideoBitrate=%s&AudioBitrate=%s" % (video_bitrate, audio_bitrate)
+ params = "%s%s" % ("&".join(url_parsed), manual_tracks)
+ params += "&VideoBitrate=%s&AudioBitrate=%s" % (
+ video_bitrate,
+ audio_bitrate,
+ )
- video_type = 'live' if source['Protocol'] == 'LiveTV' else 'master'
- base = base.replace('stream' if 'stream' in base else 'master', video_type, 1)
- self.info['Path'] = "%s%s?%s" % (self.info['ServerAddress'], base, params)
- self.info['Path'] += "&maxWidth=%s&maxHeight=%s" % (self.get_resolution())
+ video_type = "live" if source["Protocol"] == "LiveTV" else "master"
+ base = base.replace(
+ "stream" if "stream" in base else "master", video_type, 1
+ )
+ self.info["Path"] = "%s%s?%s" % (self.info["ServerAddress"], base, params)
+ self.info["Path"] += "&maxWidth=%s&maxHeight=%s" % (self.get_resolution())
else:
- self.info['Path'] = "%s/%s" % (self.info['ServerAddress'], source['TranscodingUrl'])
+ self.info["Path"] = "%s/%s" % (
+ self.info["ServerAddress"],
+ source["TranscodingUrl"],
+ )
- return self.info['Path']
+ return self.info["Path"]
def direct_play(self, source):
- API = api.API(self.item, self.info['ServerAddress'])
- self.info['Method'] = "DirectPlay"
- self.info['Path'] = API.get_file_path(source.get('Path'))
+ API = api.API(self.item, self.info["ServerAddress"])
+ self.info["Method"] = "DirectPlay"
+ self.info["Path"] = API.get_file_path(source.get("Path"))
- return self.info['Path']
+ return self.info["Path"]
def direct_url(self, source):
- self.info['Method'] = "DirectStream"
+ self.info["Method"] = "DirectStream"
- if self.item['Type'] == "Audio":
- self.info['Path'] = "%s/Audio/%s/stream.%s?static=true&api_key=%s" % (
- self.info['ServerAddress'],
- self.item['Id'],
- source.get('Container', "mp4").split(',')[0],
- self.info['Token']
+ if self.item["Type"] == "Audio":
+ self.info["Path"] = "%s/Audio/%s/stream.%s?static=true&api_key=%s" % (
+ self.info["ServerAddress"],
+ self.item["Id"],
+ source.get("Container", "mp4").split(",")[0],
+ self.info["Token"],
)
else:
- self.info['Path'] = "%s/Videos/%s/stream?static=true&MediaSourceId=%s&api_key=%s" % (
- self.info['ServerAddress'],
- self.item['Id'],
- source['Id'],
- self.info['Token']
+ self.info["Path"] = (
+ "%s/Videos/%s/stream?static=true&MediaSourceId=%s&api_key=%s"
+ % (
+ self.info["ServerAddress"],
+ self.item["Id"],
+ source["Id"],
+ self.info["Token"],
+ )
)
- return self.info['Path']
+ return self.info["Path"]
def get_max_bitrate(self):
-
- ''' Get the video quality based on add-on settings.
- Max bit rate supported by server: 2147483 (max signed 32bit integer)
- '''
- bitrate = [500, 1000, 1500, 2000, 2500, 3000, 4000, 5000, 6000,
- 7000, 8000, 9000, 10000, 12000, 14000, 16000, 18000,
- 20000, 25000, 30000, 35000, 40000, 100000, 1000000, 2147483]
- return bitrate[int(settings('maxBitrate') or 24)] * 1000
+ """Get the video quality based on add-on settings.
+ Max bit rate supported by server: 2147483 (max signed 32bit integer)
+ """
+ bitrate = [
+ 500,
+ 1000,
+ 1500,
+ 2000,
+ 2500,
+ 3000,
+ 4000,
+ 5000,
+ 6000,
+ 7000,
+ 8000,
+ 9000,
+ 10000,
+ 12000,
+ 14000,
+ 16000,
+ 18000,
+ 20000,
+ 25000,
+ 30000,
+ 35000,
+ 40000,
+ 100000,
+ 1000000,
+ 2147483,
+ ]
+ return bitrate[int(settings("maxBitrate") or 24)] * 1000
def get_resolution(self):
- return int(xbmc.getInfoLabel('System.ScreenWidth')), int(xbmc.getInfoLabel('System.ScreenHeight'))
+ return int(xbmc.getInfoLabel("System.ScreenWidth")), int(
+ xbmc.getInfoLabel("System.ScreenHeight")
+ )
def get_directplay_video_codec(self):
- codecs = ['h264', 'hevc', 'h265', 'mpeg4', 'mpeg2video', 'vc1', 'vp9', 'av1']
+ codecs = ["h264", "hevc", "h265", "mpeg4", "mpeg2video", "vc1", "vp9", "av1"]
- if settings('transcode_h265.bool'):
- codecs.remove('hevc')
- codecs.remove('h265')
+ if settings("transcode_h265.bool"):
+ codecs.remove("hevc")
+ codecs.remove("h265")
- if settings('transcode_mpeg2.bool'):
- codecs.remove('mpeg2video')
+ if settings("transcode_mpeg2.bool"):
+ codecs.remove("mpeg2video")
- if settings('transcode_vc1.bool'):
- codecs.remove('vc1')
+ if settings("transcode_vc1.bool"):
+ codecs.remove("vc1")
- if settings('transcode_vp9.bool'):
- codecs.remove('vp9')
+ if settings("transcode_vp9.bool"):
+ codecs.remove("vp9")
- if settings('transcode_av1.bool'):
- codecs.remove('av1')
+ if settings("transcode_av1.bool"):
+ codecs.remove("av1")
- return ','.join(codecs)
+ return ",".join(codecs)
def get_transcoding_video_codec(self):
- codecs = ['h264', 'hevc', 'h265', 'mpeg4', 'mpeg2video', 'vc1']
+ codecs = ["h264", "hevc", "h265", "mpeg4", "mpeg2video", "vc1"]
- if settings('transcode_h265.bool'):
- codecs.remove('hevc')
- codecs.remove('h265')
+ if settings("transcode_h265.bool"):
+ codecs.remove("hevc")
+ codecs.remove("h265")
else:
- if settings('videoPreferredCodec') == 'H265/HEVC':
- codecs.insert(2, codecs.pop(codecs.index('h264')))
+ if settings("videoPreferredCodec") == "H265/HEVC":
+ codecs.insert(2, codecs.pop(codecs.index("h264")))
- if settings('transcode_mpeg2.bool'):
- codecs.remove('mpeg2video')
+ if settings("transcode_mpeg2.bool"):
+ codecs.remove("mpeg2video")
- if settings('transcode_vc1.bool'):
- codecs.remove('vc1')
+ if settings("transcode_vc1.bool"):
+ codecs.remove("vc1")
- return ','.join(codecs)
+ return ",".join(codecs)
def get_transcoding_audio_codec(self):
- codecs = ['aac', 'mp3', 'ac3', 'opus', 'flac', 'vorbis']
+ codecs = ["aac", "mp3", "ac3", "opus", "flac", "vorbis"]
- preferred = settings('audioPreferredCodec').lower()
+ preferred = settings("audioPreferredCodec").lower()
if preferred in codecs:
codecs.insert(0, codecs.pop(codecs.index(preferred)))
- return ','.join(codecs)
+ return ",".join(codecs)
def get_transcoding_audio_bitrate(self):
bitrate = [96, 128, 160, 192, 256, 320, 384]
- return bitrate[int(settings('audioBitrate') or 6)] * 1000
+ return bitrate[int(settings("audioBitrate") or 6)] * 1000
def get_device_profile(self):
-
- ''' Get device profile based on the add-on settings.
- '''
+ """Get device profile based on the add-on settings."""
profile = {
"Name": "Kodi",
"MaxStaticBitrate": self.get_max_bitrate(),
@@ -372,154 +427,96 @@ def get_device_profile(self):
"Container": "m3u8",
"AudioCodec": self.get_transcoding_audio_codec(),
"VideoCodec": self.get_transcoding_video_codec(),
- "MaxAudioChannels": settings('audioMaxChannels')
+ "MaxAudioChannels": settings("audioMaxChannels"),
},
- {
- "Type": "Audio"
- },
- {
- "Type": "Photo",
- "Container": "jpeg"
- }
+ {"Type": "Audio"},
+ {"Type": "Photo", "Container": "jpeg"},
],
"DirectPlayProfiles": [
- {
- "Type": "Video",
- "VideoCodec": self.get_directplay_video_codec()
- },
- {
- "Type": "Audio"
- },
- {
- "Type": "Photo"
- }
+ {"Type": "Video", "VideoCodec": self.get_directplay_video_codec()},
+ {"Type": "Audio"},
+ {"Type": "Photo"},
],
"ResponseProfiles": [],
"ContainerProfiles": [],
"CodecProfiles": [],
"SubtitleProfiles": [
- {
- "Format": "srt",
- "Method": "External"
- },
- {
- "Format": "srt",
- "Method": "Embed"
- },
- {
- "Format": "ass",
- "Method": "External"
- },
- {
- "Format": "ass",
- "Method": "Embed"
- },
- {
- "Format": "sub",
- "Method": "Embed"
- },
- {
- "Format": "sub",
- "Method": "External"
- },
- {
- "Format": "ssa",
- "Method": "Embed"
- },
- {
- "Format": "ssa",
- "Method": "External"
- },
- {
- "Format": "smi",
- "Method": "Embed"
- },
- {
- "Format": "smi",
- "Method": "External"
- },
- {
- "Format": "pgssub",
- "Method": "Embed"
- },
- {
- "Format": "pgssub",
- "Method": "External"
- },
- {
- "Format": "dvdsub",
- "Method": "Embed"
- },
- {
- "Format": "dvdsub",
- "Method": "External"
- },
- {
- "Format": "pgs",
- "Method": "Embed"
- },
- {
- "Format": "pgs",
- "Method": "External"
- }
- ]
+ {"Format": "srt", "Method": "External"},
+ {"Format": "srt", "Method": "Embed"},
+ {"Format": "ass", "Method": "External"},
+ {"Format": "ass", "Method": "Embed"},
+ {"Format": "sub", "Method": "Embed"},
+ {"Format": "sub", "Method": "External"},
+ {"Format": "ssa", "Method": "Embed"},
+ {"Format": "ssa", "Method": "External"},
+ {"Format": "smi", "Method": "Embed"},
+ {"Format": "smi", "Method": "External"},
+ {"Format": "pgssub", "Method": "Embed"},
+ {"Format": "pgssub", "Method": "External"},
+ {"Format": "dvdsub", "Method": "Embed"},
+ {"Format": "dvdsub", "Method": "External"},
+ {"Format": "pgs", "Method": "Embed"},
+ {"Format": "pgs", "Method": "External"},
+ ],
}
- if settings('transcodeHi10P.bool'):
- profile['CodecProfiles'].append(
+ if settings("transcodeHi10P.bool"):
+ profile["CodecProfiles"].append(
{
- 'Type': 'Video',
- 'codec': 'h264',
- 'Conditions': [
+ "Type": "Video",
+ "codec": "h264",
+ "Conditions": [
{
- 'Condition': "LessThanEqual",
- 'Property': "VideoBitDepth",
- 'Value': "8"
+ "Condition": "LessThanEqual",
+ "Property": "VideoBitDepth",
+ "Value": "8",
}
- ]
+ ],
}
)
- if settings('transcode_h265_rext.bool'):
- profile['CodecProfiles'].append(
+ if settings("transcode_h265_rext.bool"):
+ profile["CodecProfiles"].append(
{
- 'Type': 'Video',
- 'codec': 'h265,hevc',
- 'Conditions': [
+ "Type": "Video",
+ "codec": "h265,hevc",
+ "Conditions": [
{
- 'Condition': "EqualsAny",
- 'Property': "VideoProfile",
- 'Value': "main|main 10"
+ "Condition": "EqualsAny",
+ "Property": "VideoProfile",
+ "Value": "main|main 10",
}
- ]
+ ],
}
)
- if self.info['ForceTranscode']:
- profile['DirectPlayProfiles'] = []
-
- if self.item['Type'] == 'TvChannel':
- profile['TranscodingProfiles'].insert(0, {
- "Container": "ts",
- "Type": "Video",
- "AudioCodec": "mp3,aac",
- "VideoCodec": "h264",
- "Context": "Streaming",
- "Protocol": "hls",
- "MaxAudioChannels": "2",
- "MinSegments": "1",
- "BreakOnNonKeyFrames": True
- })
+ if self.info["ForceTranscode"]:
+ profile["DirectPlayProfiles"] = []
+
+ if self.item["Type"] == "TvChannel":
+ profile["TranscodingProfiles"].insert(
+ 0,
+ {
+ "Container": "ts",
+ "Type": "Video",
+ "AudioCodec": "mp3,aac",
+ "VideoCodec": "h264",
+ "Context": "Streaming",
+ "Protocol": "hls",
+ "MaxAudioChannels": "2",
+ "MinSegments": "1",
+ "BreakOnNonKeyFrames": True,
+ },
+ )
return profile
def set_external_subs(self, source, listitem):
-
- ''' Try to download external subs locally, so we can label them.
- Since Jellyfin returns all possible tracks together, sort them.
- IsTextSubtitleStream if true, is available to download from server.
- '''
- if not settings('enableExternalSubs.bool') or not source['MediaStreams']:
+ """Try to download external subs locally, so we can label them.
+ Since Jellyfin returns all possible tracks together, sort them.
+ IsTextSubtitleStream if true, is available to download from server.
+ """
+ if not settings("enableExternalSubs.bool") or not source["MediaStreams"]:
return
subs = []
@@ -528,12 +525,19 @@ def set_external_subs(self, source, listitem):
server_settings = self.api_client.get_transcode_settings()
- for stream in source['MediaStreams']:
- if stream['SupportsExternalStream'] and stream['Type'] == 'Subtitle' and stream['DeliveryMethod'] == 'External':
- if not stream['IsExternal'] and not server_settings['EnableSubtitleExtraction']:
+ for stream in source["MediaStreams"]:
+ if (
+ stream["SupportsExternalStream"]
+ and stream["Type"] == "Subtitle"
+ and stream["DeliveryMethod"] == "External"
+ ):
+ if (
+ not stream["IsExternal"]
+ and not server_settings["EnableSubtitleExtraction"]
+ ):
continue
- index = stream['Index']
+ index = stream["Index"]
url = self.get_subtitles(source, stream, index)
if url is None:
@@ -541,8 +545,12 @@ def set_external_subs(self, source, listitem):
LOG.info("[ subtitles/%s ] %s", index, url)
- if 'Language' in stream:
- filename = "%s.%s.%s" % (source['Id'], stream['Language'], stream['Codec'])
+ if "Language" in stream:
+ filename = "%s.%s.%s" % (
+ source["Id"],
+ stream["Language"],
+ stream["Codec"],
+ )
try:
subs.append(self.download_external_subs(url, filename))
@@ -556,15 +564,16 @@ def set_external_subs(self, source, listitem):
kodi += 1
listitem.setSubtitles(subs)
- self.item['PlaybackInfo']['Subtitles'] = mapping
+ self.item["PlaybackInfo"]["Subtitles"] = mapping
@classmethod
def download_external_subs(cls, src, filename):
-
- ''' Download external subtitles to temp folder
- to be able to have proper names to streams.
- '''
- temp = translate_path("special://profile/addon_data/plugin.video.jellyfin/temp/")
+ """Download external subtitles to temp folder
+ to be able to have proper names to streams.
+ """
+ temp = translate_path(
+ "special://profile/addon_data/plugin.video.jellyfin/temp/"
+ )
if not xbmcvfs.exists(temp):
xbmcvfs.mkdir(temp)
@@ -572,59 +581,61 @@ def download_external_subs(cls, src, filename):
path = os.path.join(temp, filename)
try:
- response = requests.get(src, stream=True, verify=settings('sslverify.bool'))
+ response = requests.get(src, stream=True, verify=settings("sslverify.bool"))
response.raise_for_status()
except Exception as error:
LOG.exception(error)
raise
else:
- response.encoding = 'utf-8'
- with open(path, 'wb') as f:
+ response.encoding = "utf-8"
+ with open(path, "wb") as f:
f.write(response.content)
del response
return path
def get_audio_subs(self, source, audio=None, subtitle=None):
+ """For transcoding only
+ Present the list of audio/subs to select from, before playback starts.
- ''' For transcoding only
- Present the list of audio/subs to select from, before playback starts.
-
- Since Jellyfin returns all possible tracks together, sort them.
- IsTextSubtitleStream if true, is available to download from server.
- '''
+ Since Jellyfin returns all possible tracks together, sort them.
+ IsTextSubtitleStream if true, is available to download from server.
+ """
prefs = ""
audio_streams = list()
subs_streams = list()
- streams = source['MediaStreams']
+ streams = source["MediaStreams"]
server_settings = self.api_client.get_transcode_settings()
- allow_burned_subs = settings('allowBurnedSubs.bool')
+ allow_burned_subs = settings("allowBurnedSubs.bool")
for stream in streams:
- index = stream['Index']
- stream_type = stream['Type']
+ index = stream["Index"]
+ stream_type = stream["Type"]
- if stream_type == 'Audio':
+ if stream_type == "Audio":
audio_streams.append(index)
- elif stream_type == 'Subtitle':
- if stream['IsExternal']:
- if not stream['SupportsExternalStream'] and not allow_burned_subs:
+ elif stream_type == "Subtitle":
+ if stream["IsExternal"]:
+ if not stream["SupportsExternalStream"] and not allow_burned_subs:
continue
else:
- avail_for_extraction = stream['SupportsExternalStream'] and server_settings['EnableSubtitleExtraction']
+ avail_for_extraction = (
+ stream["SupportsExternalStream"]
+ and server_settings["EnableSubtitleExtraction"]
+ )
if not avail_for_extraction and not allow_burned_subs:
continue
subs_streams.append(index)
- skip_dialog = int(settings('skipDialogTranscode') or 0)
+ skip_dialog = int(settings("skipDialogTranscode") or 0)
def get_track_title(track_index):
- return streams[track_index]['DisplayTitle'] or ("Track %s" % track_index)
+ return streams[track_index]["DisplayTitle"] or ("Track %s" % track_index)
# Select audio stream
audio_selected = None
@@ -633,7 +644,7 @@ def get_track_title(track_index):
# NOTE: "DefaultAudioStreamIndex" is the default according to Jellyfin.
# The media's default is marked by the "IsDefault" value.
for track_index in audio_streams:
- if streams[track_index]['IsDefault']:
+ if streams[track_index]["IsDefault"]:
audio = track_index
break
@@ -648,16 +659,16 @@ def get_track_title(track_index):
if resp > -1:
audio_selected = audio_streams[resp]
else:
- audio_selected = source['DefaultAudioStreamIndex']
+ audio_selected = source["DefaultAudioStreamIndex"]
elif audio_streams:
# Only one choice
audio_selected = audio_streams[0]
else:
- audio_selected = source['DefaultAudioStreamIndex']
+ audio_selected = source["DefaultAudioStreamIndex"]
if audio_selected is not None:
- self.info['AudioStreamIndex'] = audio_selected
+ self.info["AudioStreamIndex"] = audio_selected
prefs += "&AudioStreamIndex=%s" % audio_selected
# Select audio stream
@@ -665,7 +676,7 @@ def get_track_title(track_index):
if skip_dialog == Transcode.MediaDefault:
for track_index in subs_streams:
- if streams[track_index]['IsDefault']:
+ if streams[track_index]["IsDefault"]:
subtitle = track_index
break
@@ -673,7 +684,9 @@ def get_track_title(track_index):
subtitle_selected = subtitle
elif skip_dialog in (Transcode.Enabled, Transcode.Subtitle) and subs_streams:
- selection = list(['No subtitles']) + list(map(get_track_title, subs_streams))
+ selection = list(["No subtitles"]) + list(
+ map(get_track_title, subs_streams)
+ )
resp = dialog("select", translate(33014), selection) - 1
# Possible responses:
# >=0 Subtitle track
@@ -686,27 +699,36 @@ def get_track_title(track_index):
if subtitle_selected is not None:
server_settings = self.api_client.get_transcode_settings()
stream = streams[track_index]
- if server_settings['EnableSubtitleExtraction'] and stream['SupportsExternalStream']:
- self.info['SubtitleUrl'] = self.get_subtitles(source, stream, subtitle_selected)
- self.info['SubtitleStreamIndex'] = subtitle_selected
+ if (
+ server_settings["EnableSubtitleExtraction"]
+ and stream["SupportsExternalStream"]
+ ):
+ self.info["SubtitleUrl"] = self.get_subtitles(
+ source, stream, subtitle_selected
+ )
+ self.info["SubtitleStreamIndex"] = subtitle_selected
elif allow_burned_subs:
prefs += "&SubtitleStreamIndex=%s" % subtitle_selected
- self.info['SubtitleStreamIndex'] = subtitle_selected
+ self.info["SubtitleStreamIndex"] = subtitle_selected
return prefs
def get_subtitles(self, source, stream, index):
- if stream['IsTextSubtitleStream'] and 'DeliveryUrl' in stream and stream['DeliveryUrl'].lower().startswith('/videos'):
- url = "%s%s" % (self.info['ServerAddress'], stream['DeliveryUrl'])
+ if (
+ stream["IsTextSubtitleStream"]
+ and "DeliveryUrl" in stream
+ and stream["DeliveryUrl"].lower().startswith("/videos")
+ ):
+ url = "%s%s" % (self.info["ServerAddress"], stream["DeliveryUrl"])
else:
url = "%s/Videos/%s/%s/Subtitles/%s/Stream.%s?api_key=%s" % (
- self.info['ServerAddress'],
- self.item['Id'],
- source['Id'],
+ self.info["ServerAddress"],
+ self.item["Id"],
+ source["Id"],
index,
- stream['Codec'],
- self.info['Token']
+ stream["Codec"],
+ self.info["Token"],
)
return url
diff --git a/jellyfin_kodi/helper/translate.py b/jellyfin_kodi/helper/translate.py
index 2714e9ee3..781890c7d 100644
--- a/jellyfin_kodi/helper/translate.py
+++ b/jellyfin_kodi/helper/translate.py
@@ -15,13 +15,11 @@
def translate(string):
-
- ''' Get add-on string. Returns in unicode.
- '''
+ """Get add-on string. Returns in unicode."""
if type(string) != int:
string = STRINGS[string]
- result = xbmcaddon.Addon('plugin.video.jellyfin').getLocalizedString(string)
+ result = xbmcaddon.Addon("plugin.video.jellyfin").getLocalizedString(string)
if not result:
result = xbmc.getLocalizedString(string)
@@ -30,23 +28,23 @@ def translate(string):
STRINGS = {
- 'addon_name': 29999,
- 'playback_mode': 30511,
- 'empty_user': 30613,
- 'empty_user_pass': 30608,
- 'empty_server': 30617,
- 'network_credentials': 30517,
- 'invalid_auth': 33009,
- 'addon_mode': 33036,
- 'native_mode': 33037,
- 'cancel': 30606,
- 'username': 30024,
- 'password': 30602,
- 'gathering': 33021,
- 'boxsets': 30185,
- 'movies': 30302,
- 'tvshows': 30305,
- 'fav_movies': 30180,
- 'fav_tvshows': 30181,
- 'fav_episodes': 30182
+ "addon_name": 29999,
+ "playback_mode": 30511,
+ "empty_user": 30613,
+ "empty_user_pass": 30608,
+ "empty_server": 30617,
+ "network_credentials": 30517,
+ "invalid_auth": 33009,
+ "addon_mode": 33036,
+ "native_mode": 33037,
+ "cancel": 30606,
+ "username": 30024,
+ "password": 30602,
+ "gathering": 33021,
+ "boxsets": 30185,
+ "movies": 30302,
+ "tvshows": 30305,
+ "fav_movies": 30180,
+ "fav_tvshows": 30181,
+ "fav_episodes": 30182,
}
diff --git a/jellyfin_kodi/helper/utils.py b/jellyfin_kodi/helper/utils.py
index 9d4bfdefe..fb651cf68 100644
--- a/jellyfin_kodi/helper/utils.py
+++ b/jellyfin_kodi/helper/utils.py
@@ -40,62 +40,59 @@ def kodi_version():
else:
default_versionstring = "19.1 (19.1.0) Git:20210509-85e05228b4"
- version_string = xbmc.getInfoLabel('System.BuildVersion') or default_versionstring
- return int(version_string.split(' ', 1)[0].split('.', 1)[0])
+ version_string = xbmc.getInfoLabel("System.BuildVersion") or default_versionstring
+ return int(version_string.split(" ", 1)[0].split(".", 1)[0])
def window(key, value=None, clear=False, window_id=10000):
-
- ''' Get or set window properties.
- '''
+ """Get or set window properties."""
window = xbmcgui.Window(window_id)
if clear:
LOG.debug("--[ window clear: %s ]", key)
- window.clearProperty(key.replace('.json', "").replace('.bool', ""))
+ window.clearProperty(key.replace(".json", "").replace(".bool", ""))
elif value is not None:
- if key.endswith('.json'):
+ if key.endswith(".json"):
- key = key.replace('.json', "")
+ key = key.replace(".json", "")
value = json.dumps(value)
- elif key.endswith('.bool'):
+ elif key.endswith(".bool"):
- key = key.replace('.bool', "")
+ key = key.replace(".bool", "")
value = "true" if value else "false"
window.setProperty(key, value)
else:
- result = window.getProperty(key.replace('.json', "").replace('.bool', ""))
+ result = window.getProperty(key.replace(".json", "").replace(".bool", ""))
if result:
- if key.endswith('.json'):
+ if key.endswith(".json"):
result = json.loads(result)
- elif key.endswith('.bool'):
+ elif key.endswith(".bool"):
result = result in ("true", "1")
return result
def settings(setting, value=None):
-
- ''' Get or add add-on settings.
- getSetting returns unicode object.
- '''
+ """Get or add add-on settings.
+ getSetting returns unicode object.
+ """
addon = xbmcaddon.Addon(addon_id())
if value is not None:
- if setting.endswith('.bool'):
+ if setting.endswith(".bool"):
- setting = setting.replace('.bool', "")
+ setting = setting.replace(".bool", "")
value = "true" if value else "false"
addon.setSetting(setting, value)
else:
- result = addon.getSetting(setting.replace('.bool', ""))
+ result = addon.getSetting(setting.replace(".bool", ""))
- if result and setting.endswith('.bool'):
+ if result and setting.endswith(".bool"):
result = result in ("true", "1")
return result
@@ -106,9 +103,7 @@ def create_id():
def find(dict, item):
-
- ''' Find value in dictionary.
- '''
+ """Find value in dictionary."""
if item in dict:
return dict[item]
@@ -119,9 +114,7 @@ def find(dict, item):
def event(method, data=None, sender=None, hexlify=False):
-
- ''' Data is a dictionary.
- '''
+ """Data is a dictionary."""
data = data or {}
sender = sender or "plugin.video.jellyfin"
@@ -132,7 +125,7 @@ def event(method, data=None, sender=None, hexlify=False):
LOG.debug("---[ event: %s/%s ] %s", sender, method, data)
- xbmc.executebuiltin('NotifyAll(%s, %s, %s)' % (sender, method, data))
+ xbmc.executebuiltin("NotifyAll(%s, %s, %s)" % (sender, method, data))
def dialog(dialog_type, *args, **kwargs):
@@ -140,63 +133,58 @@ def dialog(dialog_type, *args, **kwargs):
d = xbmcgui.Dialog()
if "icon" in kwargs:
- kwargs['icon'] = kwargs['icon'].replace(
+ kwargs["icon"] = kwargs["icon"].replace(
"{jellyfin}",
- "special://home/addons/plugin.video.jellyfin/resources/icon.png"
+ "special://home/addons/plugin.video.jellyfin/resources/icon.png",
)
if "heading" in kwargs:
- kwargs['heading'] = kwargs['heading'].replace("{jellyfin}", translate('addon_name'))
+ kwargs["heading"] = kwargs["heading"].replace(
+ "{jellyfin}", translate("addon_name")
+ )
if args:
args = list(args)
- args[0] = args[0].replace("{jellyfin}", translate('addon_name'))
+ args[0] = args[0].replace("{jellyfin}", translate("addon_name"))
types = {
- 'yesno': d.yesno,
- 'ok': d.ok,
- 'notification': d.notification,
- 'input': d.input,
- 'select': d.select,
- 'numeric': d.numeric,
- 'multi': d.multiselect
+ "yesno": d.yesno,
+ "ok": d.ok,
+ "notification": d.notification,
+ "input": d.input,
+ "select": d.select,
+ "numeric": d.numeric,
+ "multi": d.multiselect,
}
return types[dialog_type](*args, **kwargs)
def should_stop():
-
- ''' Checkpoint during the sync process.
- '''
+ """Checkpoint during the sync process."""
if xbmc.Monitor().waitForAbort(0.00001):
return True
- if window('jellyfin_should_stop.bool'):
+ if window("jellyfin_should_stop.bool"):
LOG.info("exiiiiitttinggg")
return True
- return not window('jellyfin_online.bool')
+ return not window("jellyfin_online.bool")
def get_screensaver():
-
- ''' Get the current screensaver value.
- '''
- result = JSONRPC('Settings.getSettingValue').execute({'setting': "screensaver.mode"})
+ """Get the current screensaver value."""
+ result = JSONRPC("Settings.getSettingValue").execute(
+ {"setting": "screensaver.mode"}
+ )
try:
- return result['result']['value']
+ return result["result"]["value"]
except KeyError:
return ""
def set_screensaver(value):
-
- ''' Toggle the screensaver
- '''
- params = {
- 'setting': "screensaver.mode",
- 'value': value
- }
- result = JSONRPC('Settings.setSettingValue').execute(params)
+ """Toggle the screensaver"""
+ params = {"setting": "screensaver.mode", "value": value}
+ result = JSONRPC("Settings.setSettingValue").execute(params)
LOG.info("---[ screensaver/%s ] %s", value, result)
@@ -215,12 +203,12 @@ def __init__(self, method, **kwargs):
def _query(self):
query = {
- 'jsonrpc': self.jsonrpc_version,
- 'id': self.id,
- 'method': self.method,
+ "jsonrpc": self.jsonrpc_version,
+ "id": self.id,
+ "method": self.method,
}
if self.params is not None:
- query['params'] = self.params
+ query["params"] = self.params
return json.dumps(query)
@@ -231,66 +219,68 @@ def execute(self, params=None):
def validate(path):
-
- ''' Verify if path is accessible.
- '''
- if window('jellyfin_pathverified.bool'):
+ """Verify if path is accessible."""
+ if window("jellyfin_pathverified.bool"):
return True
if not xbmcvfs.exists(path):
LOG.info("Could not find %s", path)
- if dialog("yesno", "{jellyfin}", "%s %s. %s" % (translate(33047), path, translate(33048))):
+ if dialog(
+ "yesno",
+ "{jellyfin}",
+ "%s %s. %s" % (translate(33047), path, translate(33048)),
+ ):
return False
- window('jellyfin_pathverified.bool', True)
+ window("jellyfin_pathverified.bool", True)
return True
def validate_bluray_dir(path):
+ """Verify if path/BDMV/ is accessible."""
- ''' Verify if path/BDMV/ is accessible.
- '''
-
- path = path + '/BDMV/'
+ path = path + "/BDMV/"
if not xbmcvfs.exists(path):
return False
- window('jellyfin_pathverified.bool', True)
+ window("jellyfin_pathverified.bool", True)
return True
def validate_dvd_dir(path):
+ """Verify if path/VIDEO_TS/ is accessible."""
- ''' Verify if path/VIDEO_TS/ is accessible.
- '''
-
- path = path + '/VIDEO_TS/'
+ path = path + "/VIDEO_TS/"
if not xbmcvfs.exists(path):
return False
- window('jellyfin_pathverified.bool', True)
+ window("jellyfin_pathverified.bool", True)
return True
def values(item, keys):
-
- ''' Grab the values in the item for a list of keys {key},{key1}....
- If the key has no brackets, the key will be passed as is.
- '''
- return (item[key.replace('{', "").replace('}', "")] if isinstance(key, text_type) and key.startswith('{') else key for key in keys)
+ """Grab the values in the item for a list of keys {key},{key1}....
+ If the key has no brackets, the key will be passed as is.
+ """
+ return (
+ (
+ item[key.replace("{", "").replace("}", "")]
+ if isinstance(key, text_type) and key.startswith("{")
+ else key
+ )
+ for key in keys
+ )
def delete_folder(path):
-
- ''' Delete objects from kodi cache
- '''
+ """Delete objects from kodi cache"""
LOG.debug("--[ delete folder ]")
dirs, files = xbmcvfs.listdir(path)
@@ -305,9 +295,7 @@ def delete_folder(path):
def delete_recursive(path, dirs):
-
- ''' Delete files and dirs recursively.
- '''
+ """Delete files and dirs recursively."""
for directory in dirs:
dirs2, files = xbmcvfs.listdir(os.path.join(path, directory))
@@ -319,11 +307,9 @@ def delete_recursive(path, dirs):
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 = quote_plus(path)
- root = "zip://" + path + '/'
+ root = "zip://" + path + "/"
if folder:
@@ -360,9 +346,7 @@ def unzip_recursive(path, dirs, dest):
def unzip_file(path, dest):
-
- ''' Unzip specific file. Path should start with zip://
- '''
+ """Unzip specific file. Path should start with zip://"""
xbmcvfs.copy(path, dest)
LOG.debug("unzip: %s to %s", path, dest)
@@ -381,9 +365,7 @@ def get_zip_directory(path, folder):
def copytree(path, dest):
-
- ''' Copy folder content from one to another.
- '''
+ """Copy folder content from one to another."""
dirs, files = xbmcvfs.listdir(path)
if not xbmcvfs.exists(dest):
@@ -416,10 +398,8 @@ def copy_recursive(path, dirs, dest):
def copy_file(path, dest):
-
- ''' Copy specific file.
- '''
- if path.endswith('.pyo'):
+ """Copy specific file."""
+ if path.endswith(".pyo"):
return
xbmcvfs.copy(path, dest)
@@ -427,11 +407,10 @@ def copy_file(path, dest):
def normalize_string(text):
-
- ''' For theme media, do not modify unless modified in TV Tunes.
- Remove dots from the last character as windows can not have directories
- with dots at the end
- '''
+ """For theme media, do not modify unless modified in TV Tunes.
+ Remove dots from the last character as windows can not have directories
+ with dots at the end
+ """
text = text.replace(":", "")
text = text.replace("/", "-")
text = text.replace("\\", "-")
@@ -439,26 +418,24 @@ def normalize_string(text):
text = text.replace(">", "")
text = text.replace("*", "")
text = text.replace("?", "")
- text = text.replace('|', "")
+ text = text.replace("|", "")
text = text.strip()
- text = text.rstrip('.')
- text = unicodedata.normalize('NFKD', text_type(text, 'utf-8')).encode('ascii', 'ignore')
+ text = text.rstrip(".")
+ text = unicodedata.normalize("NFKD", text_type(text, "utf-8")).encode(
+ "ascii", "ignore"
+ )
return text
def split_list(itemlist, size):
-
- ''' Split up list in pieces of size. Will generate a list of lists
- '''
- return [itemlist[i:i + size] for i in range(0, len(itemlist), size)]
+ """Split up list in pieces of size. Will generate a list of lists"""
+ return [itemlist[i : i + size] for i in range(0, len(itemlist), size)]
def convert_to_local(date, timezone=tz.tzlocal()):
-
- ''' Convert the local datetime to local.
- '''
+ """Convert the local datetime to local."""
try:
date = parser.parse(date) if isinstance(date, string_types) else date
date = date.replace(tzinfo=tz.tzutc())
@@ -475,9 +452,9 @@ def convert_to_local(date, timezone=tz.tzlocal()):
date.second,
)
else:
- return date.strftime('%Y-%m-%dT%H:%M:%S')
+ return date.strftime("%Y-%m-%dT%H:%M:%S")
except Exception as error:
- LOG.exception('Item date: {} --- {}'.format(str(date), error))
+ LOG.exception("Item date: {} --- {}".format(str(date), error))
return str(date)
@@ -491,28 +468,27 @@ def has_attribute(obj, name):
def set_addon_mode():
+ """Setup playback mode. If native mode selected, check network credentials."""
+ value = dialog(
+ "yesno",
+ translate("playback_mode"),
+ translate(33035),
+ nolabel=translate("addon_mode"),
+ yeslabel=translate("native_mode"),
+ )
- ''' Setup playback mode. If native mode selected, check network credentials.
- '''
- value = dialog("yesno",
- translate('playback_mode'),
- translate(33035),
- nolabel=translate('addon_mode'),
- yeslabel=translate('native_mode'))
-
- settings('useDirectPaths', value="1" if value else "0")
+ settings("useDirectPaths", value="1" if value else "0")
if value:
dialog("ok", "{jellyfin}", translate(33145))
- LOG.info("Add-on playback: %s", settings('useDirectPaths') == "0")
+ LOG.info("Add-on playback: %s", settings("useDirectPaths") == "0")
class JsonDebugPrinter(object):
-
- ''' Helper class to defer converting data to JSON until it is needed.
+ """Helper class to defer converting data to JSON until it is needed.
See: https://github.com/jellyfin/jellyfin-kodi/pull/193
- '''
+ """
def __init__(self, data):
self.data = data
@@ -527,8 +503,8 @@ def get_filesystem_encoding():
if not enc:
enc = sys.getdefaultencoding()
- if not enc or enc == 'ascii':
- enc = 'utf-8'
+ if not enc or enc == "ascii":
+ enc = "utf-8"
return enc
@@ -537,20 +513,20 @@ def find_library(server, item):
from ..database import get_sync
sync = get_sync()
- whitelist = [x.replace('Mixed:', "") for x in sync['Whitelist']]
- ancestors = server.jellyfin.get_ancestors(item['Id'])
+ whitelist = [x.replace("Mixed:", "") for x in sync["Whitelist"]]
+ ancestors = server.jellyfin.get_ancestors(item["Id"])
for ancestor in ancestors:
- if ancestor['Id'] in whitelist:
+ if ancestor["Id"] in whitelist:
return ancestor
- LOG.error('No ancestor found, not syncing item with ID: {}'.format(item['Id']))
+ LOG.error("No ancestor found, not syncing item with ID: {}".format(item["Id"]))
return {}
def translate_path(path):
- '''
+ """
Use new library location for translate path starting in Kodi 19
- '''
+ """
version = kodi_version()
if version > 18:
diff --git a/jellyfin_kodi/helper/wrapper.py b/jellyfin_kodi/helper/wrapper.py
index 1b814da19..8dc59745d 100644
--- a/jellyfin_kodi/helper/wrapper.py
+++ b/jellyfin_kodi/helper/wrapper.py
@@ -19,9 +19,8 @@
def progress(message=None):
+ """Will start and close the progress dialog."""
- ''' Will start and close the progress dialog.
- '''
def decorator(func):
def wrapper(self, item=None, *args, **kwargs):
@@ -29,10 +28,13 @@ def wrapper(self, item=None, *args, **kwargs):
if item and type(item) == dict:
- dialog.create(translate('addon_name'), "%s %s" % (translate('gathering'), item['Name']))
- LOG.info("Processing %s: %s", item['Name'], item['Id'])
+ dialog.create(
+ translate("addon_name"),
+ "%s %s" % (translate("gathering"), item["Name"]),
+ )
+ LOG.info("Processing %s: %s", item["Name"], item["Id"])
else:
- dialog.create(translate('addon_name'), message)
+ dialog.create(translate("addon_name"), message)
LOG.info("Processing %s", message)
if item:
@@ -44,13 +46,13 @@ def wrapper(self, item=None, *args, **kwargs):
return result
return wrapper
+
return decorator
def stop(func):
+ """Wrapper to catch exceptions and return using catch"""
- ''' Wrapper to catch exceptions and return using catch
- '''
def wrapper(*args, **kwargs):
try:
@@ -68,11 +70,12 @@ def wrapper(*args, **kwargs):
def jellyfin_item(func):
+ """Wrapper to retrieve the jellyfin_db item."""
- ''' Wrapper to retrieve the jellyfin_db item.
- '''
def wrapper(self, item, *args, **kwargs):
- e_item = self.jellyfin_db.get_item_by_id(item['Id'] if type(item) == dict else item)
+ e_item = self.jellyfin_db.get_item_by_id(
+ item["Id"] if type(item) == dict else item
+ )
return func(self, item, e_item=e_item, *args, **kwargs)
diff --git a/jellyfin_kodi/helper/xmls.py b/jellyfin_kodi/helper/xmls.py
index 64c93440b..fdf9e3a9f 100644
--- a/jellyfin_kodi/helper/xmls.py
+++ b/jellyfin_kodi/helper/xmls.py
@@ -19,45 +19,42 @@
def tvtunes_nfo(path, urls):
-
- ''' Create tvtunes.nfo
- '''
+ """Create tvtunes.nfo"""
try:
xml = etree.parse(path).getroot()
except Exception:
- xml = etree.Element('tvtunes')
+ xml = etree.Element("tvtunes")
- for elem in xml.getiterator('tvtunes'):
+ for elem in xml.getiterator("tvtunes"):
for file in list(elem):
elem.remove(file)
for url in urls:
- etree.SubElement(xml, 'file').text = url
+ etree.SubElement(xml, "file").text = url
tree = etree.ElementTree(xml)
tree.write(path)
def advanced_settings():
-
- ''' Track the existence of true
- It is incompatible with plugin paths.
- '''
- if settings('useDirectPaths') != "0":
+ """Track the existence of true
+ It is incompatible with plugin paths.
+ """
+ if settings("useDirectPaths") != "0":
return
path = translate_path("special://profile/")
- file = os.path.join(path, 'advancedsettings.xml')
+ file = os.path.join(path, "advancedsettings.xml")
try:
xml = etree.parse(file).getroot()
except Exception:
return
- video = xml.find('videolibrary')
+ video = xml.find("videolibrary")
if video is not None:
- cleanonupdate = video.find('cleanonupdate')
+ cleanonupdate = video.find("cleanonupdate")
if cleanonupdate is not None and cleanonupdate.text == "true":
@@ -68,13 +65,13 @@ def advanced_settings():
tree.write(file)
dialog("ok", "{jellyfin}", translate(33097))
- xbmc.executebuiltin('RestartApp')
+ xbmc.executebuiltin("RestartApp")
return True
+
def verify_kodi_defaults():
- ''' Make sure we have the kodi default folder in place.
- '''
+ """Make sure we have the kodi default folder in place."""
source_base_path = translate_path("special://xbmc/system/library/video")
dest_base_path = translate_path("special://profile/library/video")
@@ -97,11 +94,15 @@ def verify_kodi_defaults():
if not os.path.exists(dest_file):
copy = True
- elif os.path.splitext(file_name)[1].lower() == '.xml':
+ elif os.path.splitext(file_name)[1].lower() == ".xml":
try:
etree.parse(dest_file)
except etree.ParseError:
- LOG.warning("Unable to parse `{}`, recovering from default.".format(dest_file))
+ LOG.warning(
+ "Unable to parse `{}`, recovering from default.".format(
+ dest_file
+ )
+ )
copy = True
if copy:
@@ -112,7 +113,7 @@ def verify_kodi_defaults():
# This code seems to enforce a fixed ordering.
# Is it really desirable to force this on users?
# The default (system wide) order is [10, 20, 30] in Kodi 19.
- for index, node in enumerate(['movies', 'tvshows', 'musicvideos']):
+ for index, node in enumerate(["movies", "tvshows", "musicvideos"]):
file_name = os.path.join(dest_base_path, node, "index.xml")
if xbmcvfs.exists(file_name):
@@ -126,8 +127,8 @@ def verify_kodi_defaults():
tree = None
if tree is not None:
- tree.getroot().set('order', str(17 + index))
- with xbmcvfs.File(file_name, 'w') as f:
+ tree.getroot().set("order", str(17 + index))
+ with xbmcvfs.File(file_name, "w") as f:
f.write(etree.tostring(tree.getroot()))
playlist_path = translate_path("special://profile/playlists/video")
diff --git a/jellyfin_kodi/jellyfin/__init__.py b/jellyfin_kodi/jellyfin/__init__.py
index 6d0bcdec8..c9aee2946 100644
--- a/jellyfin_kodi/jellyfin/__init__.py
+++ b/jellyfin_kodi/jellyfin/__init__.py
@@ -25,22 +25,22 @@ def wrapper(self, *args, **kwargs):
return func(self, *args, **kwargs)
return wrapper
+
return decorator
class Jellyfin(object):
+ """This is your Jellyfinclient, you can create more than one. The server_id is only a temporary thing
+ to communicate with the JellyfinClient().
- ''' This is your Jellyfinclient, you can create more than one. The server_id is only a temporary thing
- to communicate with the JellyfinClient().
-
- from jellyfin_kodi.jellyfin import Jellyfin
+ from jellyfin_kodi.jellyfin import Jellyfin
- Jellyfin('123456').config.data['app']
+ Jellyfin('123456').config.data['app']
- # Permanent client reference
- client = Jellyfin('123456').get_client()
- client.config.data['app']
- '''
+ # Permanent client reference
+ client = Jellyfin('123456').get_client()
+ client.config.data['app']
+ """
# Borg - multiple instances, shared state
_shared_state = {}
@@ -94,7 +94,7 @@ def construct(self):
self.client[self.server_id] = JellyfinClient()
- if self.server_id == 'default':
+ if self.server_id == "default":
LOG.info("---[ START JELLYFINCLIENT ]---")
else:
LOG.info("---[ START JELLYFINCLIENT: %s ]---", self.server_id)
diff --git a/jellyfin_kodi/jellyfin/api.py b/jellyfin_kodi/jellyfin/api.py
index a65c5836f..4ec7ce312 100644
--- a/jellyfin_kodi/jellyfin/api.py
+++ b/jellyfin_kodi/jellyfin/api.py
@@ -15,7 +15,7 @@
def jellyfin_url(client, handler):
- return "%s/%s" % (client.config.data['auth.server'], handler)
+ return "%s/%s" % (client.config.data["auth.server"], handler)
def basic_info():
@@ -42,9 +42,8 @@ def music_info():
class API(object):
+ """All the api calls to the server."""
- ''' All the api calls to the server.
- '''
def __init__(self, client, *args, **kwargs):
self.client = client
self.config = client.config
@@ -54,18 +53,18 @@ def _http(self, action, url, request=None):
if request is None:
request = {}
- request.update({'type': action, 'handler': url})
+ request.update({"type": action, "handler": url})
return self.client.request(request)
def _get(self, handler, params=None):
- return self._http("GET", handler, {'params': params})
+ return self._http("GET", handler, {"params": params})
def _post(self, handler, json=None, params=None):
- return self._http("POST", handler, {'params': params, 'json': json})
+ return self._http("POST", handler, {"params": params, "json": json})
def _delete(self, handler, params=None):
- return self._http("DELETE", handler, {'params': params})
+ return self._http("DELETE", handler, {"params": params})
#################################################################################################
@@ -111,9 +110,17 @@ def videos(self, handler):
def artwork(self, item_id, art, max_width, ext="jpg", index=None):
if index is None:
- return jellyfin_url(self.client, "Items/%s/Images/%s?MaxWidth=%s&format=%s" % (item_id, art, max_width, ext))
-
- return jellyfin_url(self.client, "Items/%s/Images/%s/%s?MaxWidth=%s&format=%s" % (item_id, art, index, max_width, ext))
+ return jellyfin_url(
+ self.client,
+ "Items/%s/Images/%s?MaxWidth=%s&format=%s"
+ % (item_id, art, max_width, ext),
+ )
+
+ return jellyfin_url(
+ self.client,
+ "Items/%s/Images/%s/%s?MaxWidth=%s&format=%s"
+ % (item_id, art, index, max_width, ext),
+ )
#################################################################################################
@@ -140,16 +147,16 @@ def get_item(self, item_id):
return self.users("/Items/%s" % item_id)
def get_items(self, item_ids):
- return self.users("/Items", params={
- 'Ids': ','.join(str(x) for x in item_ids),
- 'Fields': info()
- })
+ return self.users(
+ "/Items",
+ params={"Ids": ",".join(str(x) for x in item_ids), "Fields": info()},
+ )
def get_sessions(self):
- return self.sessions(params={'ControllableByUserId': "{UserId}"})
+ return self.sessions(params={"ControllableByUserId": "{UserId}"})
def get_device(self, device_id):
- return self.sessions(params={'DeviceId': device_id})
+ return self.sessions(params={"DeviceId": device_id})
def post_session(self, session_id, url, params=None, data=None):
return self.sessions("/%s/%s" % (session_id, url), "POST", params, data)
@@ -158,64 +165,68 @@ def get_images(self, item_id):
return self.items("/%s/Images" % item_id)
def get_suggestion(self, media="Movie,Episode", limit=1):
- return self.users("/Suggestions", params={
- 'Type': media,
- 'Limit': limit
- })
+ return self.users("/Suggestions", params={"Type": media, "Limit": limit})
def get_recently_added(self, media=None, parent_id=None, limit=20):
- return self.user_items("/Latest", {
- 'Limit': limit,
- 'UserId': "{UserId}",
- 'IncludeItemTypes': media,
- 'ParentId': parent_id,
- 'Fields': info()
- })
+ return self.user_items(
+ "/Latest",
+ {
+ "Limit": limit,
+ "UserId": "{UserId}",
+ "IncludeItemTypes": media,
+ "ParentId": parent_id,
+ "Fields": info(),
+ },
+ )
def get_next(self, index=None, limit=1):
- return self.shows("/NextUp", {
- 'Limit': limit,
- 'UserId': "{UserId}",
- 'StartIndex': None if index is None else int(index)
- })
+ return self.shows(
+ "/NextUp",
+ {
+ "Limit": limit,
+ "UserId": "{UserId}",
+ "StartIndex": None if index is None else int(index),
+ },
+ )
def get_adjacent_episodes(self, show_id, item_id):
- return self.shows("/%s/Episodes" % show_id, {
- 'UserId': "{UserId}",
- 'AdjacentTo': item_id,
- 'Fields': "Overview"
- })
+ return self.shows(
+ "/%s/Episodes" % show_id,
+ {"UserId": "{UserId}", "AdjacentTo": item_id, "Fields": "Overview"},
+ )
def get_genres(self, parent_id=None):
- return self._get("Genres", {
- 'ParentId': parent_id,
- 'UserId': "{UserId}",
- 'Fields': info()
- })
+ return self._get(
+ "Genres", {"ParentId": parent_id, "UserId": "{UserId}", "Fields": info()}
+ )
def get_recommendation(self, parent_id=None, limit=20):
- return self._get("Movies/Recommendations", {
- 'ParentId': parent_id,
- 'UserId': "{UserId}",
- 'Fields': info(),
- 'Limit': limit
- })
+ return self._get(
+ "Movies/Recommendations",
+ {
+ "ParentId": parent_id,
+ "UserId": "{UserId}",
+ "Fields": info(),
+ "Limit": limit,
+ },
+ )
def get_items_by_letter(self, parent_id=None, media=None, letter=None):
- return self.user_items(params={
- 'ParentId': parent_id,
- 'NameStartsWith': letter,
- 'Fields': info(),
- 'Recursive': True,
- 'IncludeItemTypes': media
- })
+ return self.user_items(
+ params={
+ "ParentId": parent_id,
+ "NameStartsWith": letter,
+ "Fields": info(),
+ "Recursive": True,
+ "IncludeItemTypes": media,
+ }
+ )
def get_channels(self):
- return self._get("LiveTv/Channels", {
- 'UserId': "{UserId}",
- 'EnableImages': True,
- 'EnableUserData': True
- })
+ return self._get(
+ "LiveTv/Channels",
+ {"UserId": "{UserId}", "EnableImages": True, "EnableUserData": True},
+ )
def get_intros(self, item_id):
return self.user_items("/%s/Intros" % item_id)
@@ -230,30 +241,26 @@ def get_local_trailers(self, item_id):
return self.user_items("/%s/LocalTrailers" % item_id)
def get_transcode_settings(self):
- return self._get('System/Configuration/encoding')
+ return self._get("System/Configuration/encoding")
def get_ancestors(self, item_id):
- return self.items("/%s/Ancestors" % item_id, params={
- 'UserId': "{UserId}"
- })
+ return self.items("/%s/Ancestors" % item_id, params={"UserId": "{UserId}"})
def get_items_theme_video(self, parent_id):
- return self.users("/Items", params={
- 'HasThemeVideo': True,
- 'ParentId': parent_id
- })
+ return self.users(
+ "/Items", params={"HasThemeVideo": True, "ParentId": parent_id}
+ )
def get_themes(self, item_id):
- return self.items("/%s/ThemeMedia" % item_id, params={
- 'UserId': "{UserId}",
- 'InheritFromParent': True
- })
+ return self.items(
+ "/%s/ThemeMedia" % item_id,
+ params={"UserId": "{UserId}", "InheritFromParent": True},
+ )
def get_items_theme_song(self, parent_id):
- return self.users("/Items", params={
- 'HasThemeSong': True,
- 'ParentId': parent_id
- })
+ return self.users(
+ "/Items", params={"HasThemeSong": True, "ParentId": parent_id}
+ )
def check_companion_enabled(self):
"""
@@ -262,8 +269,10 @@ def check_companion_enabled(self):
None = Unknown
"""
try:
- plugin_settings = self._get("Jellyfin.Plugin.KodiSyncQueue/GetPluginSettings") or {}
- return plugin_settings.get('IsEnabled')
+ plugin_settings = (
+ self._get("Jellyfin.Plugin.KodiSyncQueue/GetPluginSettings") or {}
+ )
+ return plugin_settings.get("IsEnabled")
except requests.RequestException as e:
LOG.warning("Error checking companion installed state: %s", e)
@@ -277,42 +286,51 @@ def check_companion_enabled(self):
return None
def get_seasons(self, show_id):
- return self.shows("/%s/Seasons" % show_id, params={
- 'UserId': "{UserId}",
- 'EnableImages': True,
- 'Fields': info()
- })
+ return self.shows(
+ "/%s/Seasons" % show_id,
+ params={"UserId": "{UserId}", "EnableImages": True, "Fields": info()},
+ )
def get_date_modified(self, date, parent_id, media=None):
- return self.users("/Items", params={
- 'ParentId': parent_id,
- 'Recursive': False,
- 'IsMissing': False,
- 'IsVirtualUnaired': False,
- 'IncludeItemTypes': media or None,
- 'MinDateLastSaved': date,
- 'Fields': info()
- })
+ return self.users(
+ "/Items",
+ params={
+ "ParentId": parent_id,
+ "Recursive": False,
+ "IsMissing": False,
+ "IsVirtualUnaired": False,
+ "IncludeItemTypes": media or None,
+ "MinDateLastSaved": date,
+ "Fields": info(),
+ },
+ )
def get_userdata_date_modified(self, date, parent_id, media=None):
- return self.users("/Items", params={
- 'ParentId': parent_id,
- 'Recursive': True,
- 'IsMissing': False,
- 'IsVirtualUnaired': False,
- 'IncludeItemTypes': media or None,
- 'MinDateLastSavedForUser': date,
- 'Fields': info()
- })
+ return self.users(
+ "/Items",
+ params={
+ "ParentId": parent_id,
+ "Recursive": True,
+ "IsMissing": False,
+ "IsVirtualUnaired": False,
+ "IncludeItemTypes": media or None,
+ "MinDateLastSavedForUser": date,
+ "Fields": info(),
+ },
+ )
def refresh_item(self, item_id):
- return self.items("/%s/Refresh" % item_id, "POST", json={
- 'Recursive': True,
- 'ImageRefreshMode': "FullRefresh",
- 'MetadataRefreshMode': "FullRefresh",
- 'ReplaceAllImages': False,
- 'ReplaceAllMetadata': True
- })
+ return self.items(
+ "/%s/Refresh" % item_id,
+ "POST",
+ json={
+ "Recursive": True,
+ "ImageRefreshMode": "FullRefresh",
+ "MetadataRefreshMode": "FullRefresh",
+ "ReplaceAllImages": False,
+ "ReplaceAllMetadata": True,
+ },
+ )
def favorite(self, item_id, option=True):
return self.users("/FavoriteItems/%s" % item_id, "POST" if option else "DELETE")
@@ -324,7 +342,9 @@ def post_capabilities(self, data):
return self.sessions("/Capabilities/Full", "POST", json=data)
def session_add_user(self, session_id, user_id, option=True):
- return self.sessions("/%s/User/%s" % (session_id, user_id), "POST" if option else "DELETE")
+ return self.sessions(
+ "/%s/User/%s" % (session_id, user_id), "POST" if option else "DELETE"
+ )
def session_playing(self, data):
return self.sessions("/Playing", "POST", json=data)
@@ -339,115 +359,132 @@ def item_played(self, item_id, watched):
return self.users("/PlayedItems/%s" % item_id, "POST" if watched else "DELETE")
def get_sync_queue(self, date, filters=None):
- return self._get("Jellyfin.Plugin.KodiSyncQueue/{UserId}/GetItems", params={
- 'LastUpdateDT': date,
- 'filter': filters or 'None'
- })
+ return self._get(
+ "Jellyfin.Plugin.KodiSyncQueue/{UserId}/GetItems",
+ params={"LastUpdateDT": date, "filter": filters or "None"},
+ )
def get_server_time(self):
return self._get("Jellyfin.Plugin.KodiSyncQueue/GetServerDateTime")
def get_play_info(self, item_id, profile):
- return self.items("/%s/PlaybackInfo" % item_id, "POST", json={
- 'UserId': "{UserId}",
- 'DeviceProfile': profile,
- 'AutoOpenLiveStream': True
- })
+ return self.items(
+ "/%s/PlaybackInfo" % item_id,
+ "POST",
+ json={
+ "UserId": "{UserId}",
+ "DeviceProfile": profile,
+ "AutoOpenLiveStream": True,
+ },
+ )
def get_live_stream(self, item_id, play_id, token, profile):
- return self._post("LiveStreams/Open", json={
- 'UserId': "{UserId}",
- 'DeviceProfile': profile,
- 'OpenToken': token,
- 'PlaySessionId': play_id,
- 'ItemId': item_id
- })
+ return self._post(
+ "LiveStreams/Open",
+ json={
+ "UserId": "{UserId}",
+ "DeviceProfile": profile,
+ "OpenToken": token,
+ "PlaySessionId": play_id,
+ "ItemId": item_id,
+ },
+ )
def close_live_stream(self, live_id):
- return self._post("LiveStreams/Close", json={
- 'LiveStreamId': live_id
- })
+ return self._post("LiveStreams/Close", json={"LiveStreamId": live_id})
def close_transcode(self, device_id, play_id):
- return self._delete("Videos/ActiveEncodings", params={
- 'DeviceId': device_id,
- 'PlaySessionId': play_id
- })
+ return self._delete(
+ "Videos/ActiveEncodings",
+ params={"DeviceId": device_id, "PlaySessionId": play_id},
+ )
def get_default_headers(self):
auth = "MediaBrowser "
- auth += "Client=%s, " % self.config.data['app.name']
- auth += "Device=%s, " % self.config.data['app.device_name']
- auth += "DeviceId=%s, " % self.config.data['app.device_id']
- auth += "Version=%s" % self.config.data['app.version']
+ auth += "Client=%s, " % self.config.data["app.name"]
+ auth += "Device=%s, " % self.config.data["app.device_name"]
+ auth += "DeviceId=%s, " % self.config.data["app.device_id"]
+ auth += "Version=%s" % self.config.data["app.version"]
return {
"Accept": "application/json",
"Content-type": "application/x-www-form-urlencoded; charset=UTF-8",
- "X-Application": "%s/%s" % (self.config.data['app.name'], self.config.data['app.version']),
+ "X-Application": "%s/%s"
+ % (self.config.data["app.name"], self.config.data["app.version"]),
"Accept-Charset": "UTF-8,*",
"Accept-encoding": "gzip",
- "User-Agent": self.config.data['http.user_agent'] or "%s/%s" % (self.config.data['app.name'], self.config.data['app.version']),
- "x-emby-authorization": ensure_str(auth, 'utf-8')
+ "User-Agent": self.config.data["http.user_agent"]
+ or "%s/%s"
+ % (self.config.data["app.name"], self.config.data["app.version"]),
+ "x-emby-authorization": ensure_str(auth, "utf-8"),
}
- def send_request(self, url, path, method="get", timeout=None, headers=None, data=None):
+ def send_request(
+ self, url, path, method="get", timeout=None, headers=None, data=None
+ ):
request_method = getattr(requests, method.lower())
url = "%s/%s" % (url, path)
request_settings = {
"timeout": timeout or self.default_timeout,
"headers": headers or self.get_default_headers(),
- "data": data
+ "data": data,
}
- request_settings["verify"] = settings('sslverify.bool')
+ request_settings["verify"] = settings("sslverify.bool")
LOG.info("Sending %s request to %s" % (method, path))
- LOG.debug(request_settings['timeout'])
- LOG.debug(request_settings['headers'])
+ LOG.debug(request_settings["timeout"])
+ LOG.debug(request_settings["headers"])
return request_method(url, **request_settings)
def login(self, server_url, username, password=""):
path = "Users/AuthenticateByName"
- auth_data = {
- "username": username,
- "Pw": password
- }
+ auth_data = {"username": username, "Pw": password}
headers = self.get_default_headers()
- headers.update({'Content-type': "application/json"})
+ headers.update({"Content-type": "application/json"})
try:
LOG.info("Trying to login to %s/%s as %s" % (server_url, path, username))
- response = self.send_request(server_url, path, method="post", timeout=10, headers=headers, data=json.dumps(auth_data))
+ response = self.send_request(
+ server_url,
+ path,
+ method="post",
+ timeout=10,
+ headers=headers,
+ data=json.dumps(auth_data),
+ )
if response.status_code == 200:
return response.json()
else:
- LOG.error("Failed to login to server with status code: " + str(response.status_code))
+ LOG.error(
+ "Failed to login to server with status code: "
+ + str(response.status_code)
+ )
LOG.error("Server Response:\n" + str(response.content))
LOG.debug(headers)
return {}
- except Exception as e: # Find exceptions for likely cases i.e, server timeout, etc
+ except (
+ Exception
+ ) as e: # Find exceptions for likely cases i.e, server timeout, etc
LOG.error(e)
return {}
def validate_authentication_token(self, server):
- auth_token_header = {
- 'X-MediaBrowser-Token': server['AccessToken']
- }
+ auth_token_header = {"X-MediaBrowser-Token": server["AccessToken"]}
headers = self.get_default_headers()
headers.update(auth_token_header)
- response = self.send_request(server['address'], "system/info", headers=headers)
+ response = self.send_request(server["address"], "system/info", headers=headers)
if response.status_code == 200:
return response.json()
else:
- return {'Status_Code': response.status_code}
+ return {"Status_Code": response.status_code}
def get_public_info(self, server_address):
response = self.send_request(server_address, "system/info/public")
@@ -459,8 +496,8 @@ def get_public_info(self, server_address):
return {}
def check_redirect(self, server_address):
- ''' Checks if the server is redirecting traffic to a new URL and
+ """Checks if the server is redirecting traffic to a new URL and
returns the URL the server prefers to use
- '''
+ """
response = self.send_request(server_address, "system/info/public")
- return response.url.replace('/system/info/public', '')
+ return response.url.replace("/system/info/public", "")
diff --git a/jellyfin_kodi/jellyfin/client.py b/jellyfin_kodi/jellyfin/client.py
index 5026a73ef..d7802df3e 100644
--- a/jellyfin_kodi/jellyfin/client.py
+++ b/jellyfin_kodi/jellyfin/client.py
@@ -19,11 +19,10 @@
def callback(message, data):
-
- ''' Callback function should receive message, data
- message: string
- data: json dictionary
- '''
+ """Callback function should receive message, data
+ message: string
+ data: json dictionary
+ """
pass
@@ -53,13 +52,13 @@ def authenticate(self, credentials=None, options=None):
self.set_credentials(credentials or {})
state = self.auth.connect(options or {})
- if state['State'] == CONNECTION_STATE['SignedIn']:
+ if state["State"] == CONNECTION_STATE["SignedIn"]:
LOG.info("User is authenticated.")
self.logged_in = True
- self.callback("ServerOnline", {'Id': self.auth.server_id})
+ self.callback("ServerOnline", {"Id": self.auth.server_id})
- state['Credentials'] = self.get_credentials()
+ state["Credentials"] = self.get_credentials()
return state
diff --git a/jellyfin_kodi/jellyfin/configuration.py b/jellyfin_kodi/jellyfin/configuration.py
index c8407dbf8..09a2b2645 100644
--- a/jellyfin_kodi/jellyfin/configuration.py
+++ b/jellyfin_kodi/jellyfin/configuration.py
@@ -1,9 +1,9 @@
# -*- 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.
-'''
+"""
#################################################################################################
@@ -26,28 +26,41 @@ def __init__(self):
self.data = {}
self.http()
- def app(self, name, version, device_name, device_id, capabilities=None, device_pixel_ratio=None):
+ def app(
+ self,
+ name,
+ version,
+ device_name,
+ device_id,
+ capabilities=None,
+ device_pixel_ratio=None,
+ ):
LOG.debug("Begin app constructor.")
- self.data['app.name'] = name
- self.data['app.version'] = version
- self.data['app.device_name'] = device_name
- self.data['app.device_id'] = device_id
- self.data['app.capabilities'] = capabilities
- self.data['app.device_pixel_ratio'] = device_pixel_ratio
- self.data['app.default'] = False
+ self.data["app.name"] = name
+ self.data["app.version"] = version
+ self.data["app.device_name"] = device_name
+ self.data["app.device_id"] = device_id
+ self.data["app.capabilities"] = capabilities
+ self.data["app.device_pixel_ratio"] = device_pixel_ratio
+ self.data["app.default"] = False
def auth(self, server, user_id, token=None, ssl=None):
LOG.debug("Begin auth constructor.")
- self.data['auth.server'] = server
- self.data['auth.user_id'] = user_id
- self.data['auth.token'] = token
- self.data['auth.ssl'] = ssl
-
- def http(self, user_agent=None, max_retries=DEFAULT_HTTP_MAX_RETRIES, timeout=DEFAULT_HTTP_TIMEOUT):
+ self.data["auth.server"] = server
+ self.data["auth.user_id"] = user_id
+ self.data["auth.token"] = token
+ self.data["auth.ssl"] = ssl
+
+ def http(
+ self,
+ user_agent=None,
+ max_retries=DEFAULT_HTTP_MAX_RETRIES,
+ timeout=DEFAULT_HTTP_TIMEOUT,
+ ):
LOG.debug("Begin http constructor.")
- self.data['http.max_retries'] = max_retries
- self.data['http.timeout'] = timeout
- self.data['http.user_agent'] = user_agent
+ self.data["http.max_retries"] = max_retries
+ self.data["http.timeout"] = timeout
+ self.data["http.user_agent"] = user_agent
diff --git a/jellyfin_kodi/jellyfin/connection_manager.py b/jellyfin_kodi/jellyfin/connection_manager.py
index 9e562a636..304f7148e 100644
--- a/jellyfin_kodi/jellyfin/connection_manager.py
+++ b/jellyfin_kodi/jellyfin/connection_manager.py
@@ -20,10 +20,10 @@
LOG = LazyLogger(__name__)
CONNECTION_STATE = {
- 'Unavailable': 0,
- 'ServerSelection': 1,
- 'ServerSignIn': 2,
- 'SignedIn': 3
+ "Unavailable": 0,
+ "ServerSelection": 1,
+ "ServerSignIn": 2,
+ "SignedIn": 3,
}
#################################################################################################
@@ -48,10 +48,10 @@ def revoke_token(self):
LOG.info("revoking token")
- self['server']['AccessToken'] = None
+ self["server"]["AccessToken"] = None
self.credentials.set_credentials(self.credentials.get())
- self.config.data['auth.token'] = None
+ self.config.data["auth.token"] = None
def get_available_servers(self):
@@ -61,11 +61,13 @@ def get_available_servers(self):
credentials = self.credentials.get()
found_servers = self.process_found_servers(self._server_discovery())
- if not found_servers and not credentials['Servers']: # back out right away, no point in continuing
+ if (
+ not found_servers and not credentials["Servers"]
+ ): # back out right away, no point in continuing
LOG.info("Found no servers")
return list()
- servers = list(credentials['Servers'])
+ servers = list(credentials["Servers"])
# Merges servers we already knew with newly found ones
for found_server in found_servers:
@@ -74,8 +76,8 @@ def get_available_servers(self):
except KeyError:
continue
- servers.sort(key=itemgetter('DateLastAccessed'), reverse=True)
- credentials['Servers'] = servers
+ servers.sort(key=itemgetter("DateLastAccessed"), reverse=True)
+ credentials["Servers"] = servers
self.credentials.set(credentials)
return servers
@@ -88,36 +90,35 @@ def login(self, server_url, username, password=None):
if not server_url:
raise AttributeError("server url cannot be empty")
- data = self.API.login(server_url, username, password) # returns empty dict on failure
+ data = self.API.login(
+ server_url, username, password
+ ) # returns empty dict on failure
if not data:
- LOG.info("Failed to login as `"+username+"`")
+ LOG.info("Failed to login as `" + username + "`")
return {}
LOG.info("Successfully logged in as %s" % (username))
# TODO Change when moving to database storage of server details
credentials = self.credentials.get()
- self.config.data['auth.user_id'] = data['User']['Id']
- self.config.data['auth.token'] = data['AccessToken']
+ self.config.data["auth.user_id"] = data["User"]["Id"]
+ self.config.data["auth.token"] = data["AccessToken"]
- for server in credentials['Servers']:
- if server['Id'] == data['ServerId']:
+ for server in credentials["Servers"]:
+ if server["Id"] == data["ServerId"]:
found_server = server
break
else:
return {} # No server found
- found_server['DateLastAccessed'] = datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ')
- found_server['UserId'] = data['User']['Id']
- found_server['AccessToken'] = data['AccessToken']
+ found_server["DateLastAccessed"] = datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ")
+ found_server["UserId"] = data["User"]["Id"]
+ found_server["AccessToken"] = data["AccessToken"]
- self.credentials.add_update_server(credentials['Servers'], found_server)
+ self.credentials.add_update_server(credentials["Servers"], found_server)
- info = {
- 'Id': data['User']['Id'],
- 'IsSignedInOffline': True
- }
+ info = {"Id": data["User"]["Id"], "IsSignedInOffline": True}
self.credentials.add_update_user(server, info)
self.credentials.set_credentials(credentials)
@@ -137,40 +138,44 @@ def connect_to_address(self, address, options={}):
address = response_url
LOG.info("connectToAddress %s succeeded", address)
server = {
- 'address': address,
+ "address": address,
}
server = self.connect_to_server(server, options)
if server is False:
LOG.error("connectToAddress %s failed", address)
- return {'State': CONNECTION_STATE['Unavailable']}
+ return {"State": CONNECTION_STATE["Unavailable"]}
return server
except Exception as error:
LOG.exception(error)
LOG.error("connectToAddress %s failed", address)
- return {'State': CONNECTION_STATE['Unavailable']}
+ return {"State": CONNECTION_STATE["Unavailable"]}
def connect_to_server(self, server, options={}):
LOG.info("begin connectToServer")
try:
- result = self.API.get_public_info(server.get('address'))
+ result = self.API.get_public_info(server.get("address"))
if not result:
- LOG.error("Failed to connect to server: %s" % server.get('address'))
- return {'State': CONNECTION_STATE['Unavailable']}
+ LOG.error("Failed to connect to server: %s" % server.get("address"))
+ return {"State": CONNECTION_STATE["Unavailable"]}
- LOG.info("calling onSuccessfulConnection with server %s", server.get('Name'))
+ LOG.info(
+ "calling onSuccessfulConnection with server %s", server.get("Name")
+ )
self._update_server_info(server, result)
credentials = self.credentials.get()
- return self._after_connect_validated(server, credentials, result, True, options)
+ return self._after_connect_validated(
+ server, credentials, result, True, options
+ )
except Exception as e:
LOG.error(traceback.format_exc())
LOG.error("Failing server connection. ERROR msg: {}".format(e))
- return {'State': CONNECTION_STATE['Unavailable']}
+ return {"State": CONNECTION_STATE["Unavailable"]}
def connect(self, options={}):
@@ -180,9 +185,7 @@ def connect(self, options={}):
LOG.info("connect has %s servers", len(servers))
if not (len(servers)): # No servers provided
- return {
- 'State': ['ServerSelection']
- }
+ return {"State": ["ServerSelection"]}
result = self.connect_to_server(servers[0], options)
LOG.debug("resolving connect with result: %s", result)
@@ -190,7 +193,7 @@ def connect(self, options={}):
return result
def jellyfin_token(self): # Called once monitor.py#163
- return self.get_server_info(self.server_id)['AccessToken']
+ return self.get_server_info(self.server_id)["AccessToken"]
def get_server_info(self, server_id):
@@ -198,14 +201,14 @@ def get_server_info(self, server_id):
LOG.info("server_id is empty")
return {}
- servers = self.credentials.get()['Servers']
+ servers = self.credentials.get()["Servers"]
for server in servers:
- if server['Id'] == server_id:
+ if server["Id"] == server_id:
return server
def get_server_address(self, server_id):
- return self.get_server_info(server_id or self.server_id).get('address')
+ return self.get_server_info(server_id or self.server_id).get("address")
def get_public_users(self):
return self.client.jellyfin.get_public_users()
@@ -258,9 +261,9 @@ def process_found_servers(self, found_servers):
server = self._convert_endpoint_address_to_manual_address(found_server)
info = {
- 'Id': found_server['Id'],
- 'address': server or found_server['Address'],
- 'Name': found_server['Name']
+ "Id": found_server["Id"],
+ "address": server or found_server["Address"],
+ "Name": found_server["Name"],
}
servers.append(info)
@@ -270,11 +273,11 @@ def process_found_servers(self, found_servers):
# TODO: Make IPv6 compatible
def _convert_endpoint_address_to_manual_address(self, info):
- if info.get('Address') and info.get('EndpointAddress'):
- address = info['EndpointAddress'].split(':')[0]
+ if info.get("Address") and info.get("EndpointAddress"):
+ address = info["EndpointAddress"].split(":")[0]
# Determine the port, if any
- parts = info['Address'].split(':')
+ parts = info["Address"].split(":")
if len(parts) > 1:
port_string = parts[len(parts) - 1]
@@ -288,64 +291,70 @@ def _convert_endpoint_address_to_manual_address(self, info):
def _normalize_address(self, address):
# TODO: Try HTTPS first, then HTTP if that fails.
- if '://' not in address:
- address = 'http://' + address
+ if "://" not in address:
+ address = "http://" + address
# Attempt to correct bad input
url = urllib3.util.parse_url(address.strip())
if url.scheme is None:
- url = url._replace(scheme='http')
+ url = url._replace(scheme="http")
- if url.scheme == 'http' and url.port == 80:
+ if url.scheme == "http" and url.port == 80:
url = url._replace(port=None)
- if url.scheme == 'https' and url.port == 443:
+ if url.scheme == "https" and url.port == 443:
url = url._replace(port=None)
return url.url
- def _after_connect_validated(self, server, credentials, system_info, verify_authentication, options):
- if options.get('enableAutoLogin') is False:
+ def _after_connect_validated(
+ self, server, credentials, system_info, verify_authentication, options
+ ):
+ if options.get("enableAutoLogin") is False:
- self.config.data['auth.user_id'] = server.pop('UserId', None)
- self.config.data['auth.token'] = server.pop('AccessToken', None)
+ self.config.data["auth.user_id"] = server.pop("UserId", None)
+ self.config.data["auth.token"] = server.pop("AccessToken", None)
- elif verify_authentication and server.get('AccessToken'):
+ elif verify_authentication and server.get("AccessToken"):
system_info = self.API.validate_authentication_token(server)
- if 'Status_Code' not in system_info:
+ if "Status_Code" not in system_info:
self._update_server_info(server, system_info)
- self.config.data['auth.user_id'] = server['UserId']
- self.config.data['auth.token'] = server['AccessToken']
- system_info['Status_Code'] = 200
+ self.config.data["auth.user_id"] = server["UserId"]
+ self.config.data["auth.token"] = server["AccessToken"]
+ system_info["Status_Code"] = 200
- return self._after_connect_validated(server, credentials, system_info, False, options)
+ return self._after_connect_validated(
+ server, credentials, system_info, False, options
+ )
- server['UserId'] = None
- server['AccessToken'] = None
- system_info['State'] = CONNECTION_STATE['Unavailable']
+ server["UserId"] = None
+ server["AccessToken"] = None
+ system_info["State"] = CONNECTION_STATE["Unavailable"]
return system_info
self._update_server_info(server, system_info)
- server['DateLastAccessed'] = datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ')
- self.credentials.add_update_server(credentials['Servers'], server)
+ server["DateLastAccessed"] = datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ")
+ self.credentials.add_update_server(credentials["Servers"], server)
self.credentials.set(credentials)
- self.server_id = server['Id']
+ self.server_id = server["Id"]
# Update configs
- self.config.data['auth.server'] = server['address']
- self.config.data['auth.server-name'] = server['Name']
- self.config.data['auth.server=id'] = server['Id']
- self.config.data['auth.ssl'] = options.get('ssl', self.config.data['auth.ssl'])
+ self.config.data["auth.server"] = server["address"]
+ self.config.data["auth.server-name"] = server["Name"]
+ self.config.data["auth.server=id"] = server["Id"]
+ self.config.data["auth.ssl"] = options.get("ssl", self.config.data["auth.ssl"])
# Connected
return {
- 'Servers': [server],
- 'State': CONNECTION_STATE['SignedIn']
- if server.get('AccessToken')
- else CONNECTION_STATE['ServerSignIn'],
+ "Servers": [server],
+ "State": (
+ CONNECTION_STATE["SignedIn"]
+ if server.get("AccessToken")
+ else CONNECTION_STATE["ServerSignIn"]
+ ),
}
def _update_server_info(self, server, system_info):
@@ -353,8 +362,8 @@ def _update_server_info(self, server, system_info):
if server is None or system_info is None:
return
- server['Name'] = system_info['ServerName']
- server['Id'] = system_info['Id']
+ server["Name"] = system_info["ServerName"]
+ server["Id"] = system_info["Id"]
- if system_info.get('address'):
- server['address'] = system_info['address']
+ if system_info.get("address"):
+ server["address"] = system_info["address"]
diff --git a/jellyfin_kodi/jellyfin/credentials.py b/jellyfin_kodi/jellyfin/credentials.py
index 638f4af30..1ef3d1645 100644
--- a/jellyfin_kodi/jellyfin/credentials.py
+++ b/jellyfin_kodi/jellyfin/credentials.py
@@ -41,7 +41,7 @@ def _ensure(self):
self.credentials = {}
LOG.debug("credentials initialized with: %s", self.credentials)
- self.credentials['Servers'] = self.credentials.setdefault('Servers', [])
+ self.credentials["Servers"] = self.credentials.setdefault("Servers", [])
def get(self):
self._ensure()
@@ -62,53 +62,55 @@ def _clear(self):
def add_update_user(self, server, user):
- for existing in server.setdefault('Users', []):
- if existing['Id'] == user['Id']:
+ for existing in server.setdefault("Users", []):
+ if existing["Id"] == user["Id"]:
# Merge the data
- existing['IsSignedInOffline'] = True
+ existing["IsSignedInOffline"] = True
break
else:
- server['Users'].append(user)
+ server["Users"].append(user)
def add_update_server(self, servers, server):
- if server.get('Id') is None:
+ if server.get("Id") is None:
raise KeyError("Server['Id'] cannot be null or empty")
# Add default DateLastAccessed if doesn't exist.
- server.setdefault('DateLastAccessed', "1970-01-01T00:00:00Z")
+ server.setdefault("DateLastAccessed", "1970-01-01T00:00:00Z")
for existing in servers:
- if existing['Id'] == server['Id']:
+ if existing["Id"] == server["Id"]:
# Merge the data
- if server.get('DateLastAccessed') and self._date_object(server['DateLastAccessed']) > self._date_object(existing['DateLastAccessed']):
- existing['DateLastAccessed'] = server['DateLastAccessed']
+ if server.get("DateLastAccessed") and self._date_object(
+ server["DateLastAccessed"]
+ ) > self._date_object(existing["DateLastAccessed"]):
+ existing["DateLastAccessed"] = server["DateLastAccessed"]
- if server.get('UserLinkType'):
- existing['UserLinkType'] = server['UserLinkType']
+ if server.get("UserLinkType"):
+ existing["UserLinkType"] = server["UserLinkType"]
- if server.get('AccessToken'):
- existing['AccessToken'] = server['AccessToken']
- existing['UserId'] = server['UserId']
+ if server.get("AccessToken"):
+ existing["AccessToken"] = server["AccessToken"]
+ existing["UserId"] = server["UserId"]
- if server.get('ExchangeToken'):
- existing['ExchangeToken'] = server['ExchangeToken']
+ if server.get("ExchangeToken"):
+ existing["ExchangeToken"] = server["ExchangeToken"]
- if server.get('ManualAddress'):
- existing['ManualAddress'] = server['ManualAddress']
+ if server.get("ManualAddress"):
+ existing["ManualAddress"] = server["ManualAddress"]
- if server.get('LocalAddress'):
- existing['LocalAddress'] = server['LocalAddress']
+ if server.get("LocalAddress"):
+ existing["LocalAddress"] = server["LocalAddress"]
- if server.get('Name'):
- existing['Name'] = server['Name']
+ if server.get("Name"):
+ existing["Name"] = server["Name"]
- if server.get('LastConnectionMode') is not None:
- existing['LastConnectionMode'] = server['LastConnectionMode']
+ if server.get("LastConnectionMode") is not None:
+ existing["LastConnectionMode"] = server["LastConnectionMode"]
- if server.get('ConnectServerId'):
- existing['ConnectServerId'] = server['ConnectServerId']
+ if server.get("ConnectServerId"):
+ existing["ConnectServerId"] = server["ConnectServerId"]
return existing
diff --git a/jellyfin_kodi/jellyfin/http.py b/jellyfin_kodi/jellyfin/http.py
index 646dc0718..0c3282137 100644
--- a/jellyfin_kodi/jellyfin/http.py
+++ b/jellyfin_kodi/jellyfin/http.py
@@ -34,9 +34,13 @@ def start_session(self):
self.session = requests.Session()
- max_retries = self.config.data['http.max_retries']
- self.session.mount("http://", requests.adapters.HTTPAdapter(max_retries=max_retries))
- self.session.mount("https://", requests.adapters.HTTPAdapter(max_retries=max_retries))
+ max_retries = self.config.data["http.max_retries"]
+ self.session.mount(
+ "http://", requests.adapters.HTTPAdapter(max_retries=max_retries)
+ )
+ self.session.mount(
+ "https://", requests.adapters.HTTPAdapter(max_retries=max_retries)
+ )
def stop_session(self):
@@ -51,43 +55,44 @@ def stop_session(self):
def _replace_user_info(self, string):
- if '{server}' in string:
- if self.config.data.get('auth.server', None):
- string = string.replace("{server}", self.config.data['auth.server'])
+ if "{server}" in string:
+ if self.config.data.get("auth.server", None):
+ string = string.replace("{server}", self.config.data["auth.server"])
else:
LOG.debug("Server address not set")
- if '{UserId}' in string:
- if self.config.data.get('auth.user_id', None):
- string = string.replace("{UserId}", self.config.data['auth.user_id'])
+ if "{UserId}" in string:
+ if self.config.data.get("auth.user_id", None):
+ string = string.replace("{UserId}", self.config.data["auth.user_id"])
else:
LOG.debug("UserId is not set.")
return string
def request(self, data, session=None):
-
- ''' Give a chance to retry the connection. Jellyfin sometimes can be slow to answer back
- data dictionary can contain:
- type: GET, POST, etc.
- url: (optional)
- handler: not considered when url is provided (optional)
- params: request parameters (optional)
- json: request body (optional)
- headers: (optional),
- verify: ssl certificate, True (verify using device built-in library) or False
- '''
+ """Give a chance to retry the connection. Jellyfin sometimes can be slow to answer back
+ data dictionary can contain:
+ type: GET, POST, etc.
+ url: (optional)
+ handler: not considered when url is provided (optional)
+ params: request parameters (optional)
+ json: request body (optional)
+ headers: (optional),
+ verify: ssl certificate, True (verify using device built-in library) or False
+ """
if not data:
raise AttributeError("Request cannot be empty")
data = self._request(data)
LOG.debug("--->[ http ] %s", JsonDebugPrinter(data))
- retry = data.pop('retry', 5)
+ retry = data.pop("retry", 5)
while True:
try:
- r = self._requests(session or self.session or requests, data.pop('type', "GET"), **data)
+ r = self._requests(
+ session or self.session or requests, data.pop("type", "GET"), **data
+ )
r.content # release the connection
if not self.keep_alive and self.session is not None:
@@ -104,7 +109,10 @@ def request(self, data, session=None):
continue
LOG.error(error)
- self.client.callback("ServerUnreachable", {'ServerId': self.config.data['auth.server-id']})
+ self.client.callback(
+ "ServerUnreachable",
+ {"ServerId": self.config.data["auth.server-id"]},
+ )
raise HTTPException("ServerUnreachable", error)
@@ -125,12 +133,18 @@ def request(self, data, session=None):
if r.status_code == 401:
- if 'X-Application-Error-Code' in r.headers:
- self.client.callback("AccessRestricted", {'ServerId': self.config.data['auth.server-id']})
+ if "X-Application-Error-Code" in r.headers:
+ self.client.callback(
+ "AccessRestricted",
+ {"ServerId": self.config.data["auth.server-id"]},
+ )
raise HTTPException("AccessRestricted", error)
else:
- self.client.callback("Unauthorized", {'ServerId': self.config.data['auth.server-id']})
+ self.client.callback(
+ "Unauthorized",
+ {"ServerId": self.config.data["auth.server-id"]},
+ )
self.client.auth.revoke_token()
raise HTTPException("Unauthorized", error)
@@ -160,11 +174,13 @@ def request(self, data, session=None):
except requests.exceptions.MissingSchema as error:
LOG.error("Request missing Schema. " + str(error))
- raise HTTPException("MissingSchema", {'Id': self.config.data.get('auth.server', "None")})
+ raise HTTPException(
+ "MissingSchema", {"Id": self.config.data.get("auth.server", "None")}
+ )
else:
try:
- self.config.data['server-time'] = r.headers['Date']
+ self.config.data["server-time"] = r.headers["Date"]
elapsed = int(r.elapsed.total_seconds() * 1000)
response = r.json()
LOG.debug("---<[ http ][%s ms]", elapsed)
@@ -179,15 +195,18 @@ def request(self, data, session=None):
def _request(self, data):
- if 'url' not in data:
- data['url'] = "%s/%s" % (self.config.data.get("auth.server", ""), data.pop('handler', ""))
+ if "url" not in data:
+ data["url"] = "%s/%s" % (
+ self.config.data.get("auth.server", ""),
+ data.pop("handler", ""),
+ )
self._get_header(data)
- data['timeout'] = data.get('timeout') or self.config.data['http.timeout']
- data['verify'] = data.get('verify') or self.config.data.get('auth.ssl', False)
- data['url'] = self._replace_user_info(data['url'])
- self._process_params(data.get('params') or {})
- self._process_params(data.get('json') or {})
+ data["timeout"] = data.get("timeout") or self.config.data["http.timeout"]
+ data["verify"] = data.get("verify") or self.config.data.get("auth.ssl", False)
+ data["url"] = self._replace_user_info(data["url"])
+ self._process_params(data.get("params") or {})
+ self._process_params(data.get("json") or {})
return data
@@ -204,17 +223,24 @@ def _process_params(self, params):
def _get_header(self, data):
- data['headers'] = data.setdefault('headers', {})
-
- if not data['headers']:
- data['headers'].update({
- 'Content-type': "application/json",
- 'Accept-Charset': "UTF-8,*",
- 'Accept-encoding': "gzip",
- 'User-Agent': self.config.data['http.user_agent'] or "%s/%s" % (self.config.data.get('app.name', 'Jellyfin for Kodi'), self.config.data.get('app.version', "0.0.0"))
- })
-
- if 'x-emby-authorization' not in data['headers']:
+ data["headers"] = data.setdefault("headers", {})
+
+ if not data["headers"]:
+ data["headers"].update(
+ {
+ "Content-type": "application/json",
+ "Accept-Charset": "UTF-8,*",
+ "Accept-encoding": "gzip",
+ "User-Agent": self.config.data["http.user_agent"]
+ or "%s/%s"
+ % (
+ self.config.data.get("app.name", "Jellyfin for Kodi"),
+ self.config.data.get("app.version", "0.0.0"),
+ ),
+ }
+ )
+
+ if "x-emby-authorization" not in data["headers"]:
self._authorization(data)
return data
@@ -222,19 +248,26 @@ def _get_header(self, data):
def _authorization(self, data):
auth = "MediaBrowser "
- auth += "Client=%s, " % self.config.data.get('app.name', "Jellyfin for Kodi")
- auth += "Device=%s, " % self.config.data.get('app.device_name', 'Unknown Device')
- auth += "DeviceId=%s, " % self.config.data.get('app.device_id', 'Unknown Device id')
- auth += "Version=%s" % self.config.data.get('app.version', '0.0.0')
-
- data['headers'].update({'x-emby-authorization': ensure_str(auth, 'utf-8')})
-
- if self.config.data.get('auth.token') and self.config.data.get('auth.user_id'):
-
- auth += ', UserId=%s' % self.config.data.get('auth.user_id')
- data['headers'].update({
- 'x-emby-authorization': ensure_str(auth, 'utf-8'),
- 'X-MediaBrowser-Token': self.config.data.get('auth.token')})
+ auth += "Client=%s, " % self.config.data.get("app.name", "Jellyfin for Kodi")
+ auth += "Device=%s, " % self.config.data.get(
+ "app.device_name", "Unknown Device"
+ )
+ auth += "DeviceId=%s, " % self.config.data.get(
+ "app.device_id", "Unknown Device id"
+ )
+ auth += "Version=%s" % self.config.data.get("app.version", "0.0.0")
+
+ data["headers"].update({"x-emby-authorization": ensure_str(auth, "utf-8")})
+
+ if self.config.data.get("auth.token") and self.config.data.get("auth.user_id"):
+
+ auth += ", UserId=%s" % self.config.data.get("auth.user_id")
+ data["headers"].update(
+ {
+ "x-emby-authorization": ensure_str(auth, "utf-8"),
+ "X-MediaBrowser-Token": self.config.data.get("auth.token"),
+ }
+ )
return data
diff --git a/jellyfin_kodi/jellyfin/ws_client.py b/jellyfin_kodi/jellyfin/ws_client.py
index 65da191e0..38a17f8b4 100644
--- a/jellyfin_kodi/jellyfin/ws_client.py
+++ b/jellyfin_kodi/jellyfin/ws_client.py
@@ -14,7 +14,8 @@
# If numpy is installed, the websockets library tries to use it, and then
# kodi hard crashes for reasons I don't even want to pretend to understand
import sys # noqa: E402,I100
-sys.modules['numpy'] = None
+
+sys.modules["numpy"] = None
import websocket # noqa: E402,I201
##################################################################################################
@@ -41,23 +42,29 @@ def send(self, message, data=""):
if self.wsc is None:
raise ValueError("The websocket client is not started.")
- self.wsc.send(json.dumps({'MessageType': message, "Data": data}))
+ self.wsc.send(json.dumps({"MessageType": message, "Data": data}))
def run(self):
monitor = xbmc.Monitor()
- token = self.client.config.data['auth.token']
- device_id = self.client.config.data['app.device_id']
- server = self.client.config.data['auth.server']
- server = server.replace('https://', 'wss://') if server.startswith('https') else server.replace('http://', 'ws://')
+ token = self.client.config.data["auth.token"]
+ device_id = self.client.config.data["app.device_id"]
+ server = self.client.config.data["auth.server"]
+ server = (
+ server.replace("https://", "wss://")
+ if server.startswith("https")
+ else server.replace("http://", "ws://")
+ )
wsc_url = "%s/socket?api_key=%s&device_id=%s" % (server, token, device_id)
LOG.info("Websocket url: %s", wsc_url)
- self.wsc = websocket.WebSocketApp(wsc_url,
- on_open=lambda ws: self.on_open(ws),
- on_message=lambda ws, message: self.on_message(ws, message),
- on_error=lambda ws, error: self.on_error(ws, error))
+ self.wsc = websocket.WebSocketApp(
+ wsc_url,
+ on_open=lambda ws: self.on_open(ws),
+ on_message=lambda ws, message: self.on_message(ws, message),
+ on_error=lambda ws, error: self.on_error(ws, error),
+ )
while not self.stop:
@@ -73,41 +80,42 @@ def on_open(self, ws):
LOG.info("--->[ websocket opened ]")
# Avoid a timing issue where the capabilities are not correctly registered
time.sleep(5)
- if settings('remoteControl.bool'):
- self.client.jellyfin.post_capabilities({
- 'PlayableMediaTypes': "Audio,Video",
- 'SupportsMediaControl': True,
- 'SupportedCommands': (
- "MoveUp,MoveDown,MoveLeft,MoveRight,Select,"
- "Back,ToggleContextMenu,ToggleFullscreen,ToggleOsdMenu,"
- "GoHome,PageUp,NextLetter,GoToSearch,"
- "GoToSettings,PageDown,PreviousLetter,TakeScreenshot,"
- "VolumeUp,VolumeDown,ToggleMute,SendString,DisplayMessage,"
- "SetAudioStreamIndex,SetSubtitleStreamIndex,"
- "SetRepeatMode,Mute,Unmute,SetVolume,"
- "Play,Playstate,PlayNext,PlayMediaSource"
- ),
- })
+ if settings("remoteControl.bool"):
+ self.client.jellyfin.post_capabilities(
+ {
+ "PlayableMediaTypes": "Audio,Video",
+ "SupportsMediaControl": True,
+ "SupportedCommands": (
+ "MoveUp,MoveDown,MoveLeft,MoveRight,Select,"
+ "Back,ToggleContextMenu,ToggleFullscreen,ToggleOsdMenu,"
+ "GoHome,PageUp,NextLetter,GoToSearch,"
+ "GoToSettings,PageDown,PreviousLetter,TakeScreenshot,"
+ "VolumeUp,VolumeDown,ToggleMute,SendString,DisplayMessage,"
+ "SetAudioStreamIndex,SetSubtitleStreamIndex,"
+ "SetRepeatMode,Mute,Unmute,SetVolume,"
+ "Play,Playstate,PlayNext,PlayMediaSource"
+ ),
+ }
+ )
else:
- self.client.jellyfin.post_capabilities({
- "PlayableMediaTypes": "Audio, Video",
- "SupportsMediaControl": False
- })
+ self.client.jellyfin.post_capabilities(
+ {"PlayableMediaTypes": "Audio, Video", "SupportsMediaControl": False}
+ )
def on_message(self, ws, message):
message = json.loads(message)
- data = message.get('Data', {})
+ data = message.get("Data", {})
- if message['MessageType'] in ('RefreshProgress',):
+ if message["MessageType"] in ("RefreshProgress",):
LOG.debug("Ignoring %s", message)
return
- if not self.client.config.data['app.default']:
- data['ServerId'] = self.client.auth.server_id
+ if not self.client.config.data["app.default"]:
+ data["ServerId"] = self.client.auth.server_id
- self.client.callback(message['MessageType'], data)
+ self.client.callback(message["MessageType"], data)
def stop_client(self):
diff --git a/jellyfin_kodi/library.py b/jellyfin_kodi/library.py
index c31276641..a200f37de 100644
--- a/jellyfin_kodi/library.py
+++ b/jellyfin_kodi/library.py
@@ -24,8 +24,8 @@
##################################################################################################
LOG = LazyLogger(__name__)
-LIMIT = int(settings('limitIndex') or 15)
-DTHREADS = int(settings('limitThreads') or 3)
+LIMIT = int(settings("limitIndex") or 15)
+DTHREADS = int(settings("limitThreads") or 3)
TARGET_DB_VERSION = 1
##################################################################################################
@@ -43,8 +43,8 @@ class Library(threading.Thread):
def __init__(self, monitor):
- self.direct_path = settings('useDirectPaths') == "1"
- self.progress_display = int(settings('syncProgress') or 50)
+ self.direct_path = settings("useDirectPaths") == "1"
+ self.progress_display = int(settings("syncProgress") or 50)
self.monitor = monitor
self.player = monitor.monitor.player
self.server = Jellyfin().get_client()
@@ -59,7 +59,7 @@ def __init__(self, monitor):
self.jellyfin_threads = []
self.download_threads = []
self.notify_threads = []
- self.writer_threads = {'updated': [], 'userdata': [], 'removed': []}
+ self.writer_threads = {"updated": [], "userdata": [], "removed": []}
self.database_lock = threading.Lock()
self.music_database_lock = threading.Lock()
@@ -67,16 +67,16 @@ def __init__(self, monitor):
def __new_queues__(self):
return {
- 'Movie': Queue.Queue(),
- 'BoxSet': Queue.Queue(),
- 'MusicVideo': Queue.Queue(),
- 'Series': Queue.Queue(),
- 'Season': Queue.Queue(),
- 'Episode': Queue.Queue(),
- 'MusicAlbum': Queue.Queue(),
- 'MusicArtist': Queue.Queue(),
- 'AlbumArtist': Queue.Queue(),
- 'Audio': Queue.Queue()
+ "Movie": Queue.Queue(),
+ "BoxSet": Queue.Queue(),
+ "MusicVideo": Queue.Queue(),
+ "Series": Queue.Queue(),
+ "Season": Queue.Queue(),
+ "Episode": Queue.Queue(),
+ "MusicAlbum": Queue.Queue(),
+ "MusicArtist": Queue.Queue(),
+ "AlbumArtist": Queue.Queue(),
+ "Audio": Queue.Queue(),
}
def run(self):
@@ -86,7 +86,7 @@ def run(self):
if not self.startup():
self.stop_client()
- window('jellyfin_startup.bool', True)
+ window("jellyfin_startup.bool", True)
while not self.stop_thread:
@@ -105,17 +105,15 @@ def run(self):
LOG.info("---<[ library ]")
def test_databases(self):
-
- ''' Open the databases to test if the file exists.
- '''
- with Database('video'), Database('music'):
+ """Open the databases to test if the file exists."""
+ with Database("video"), Database("music"):
pass
def check_version(self):
- '''
+ """
Checks database version and triggers any required data migrations
- '''
- with Database('jellyfin') as jellyfindb:
+ """
+ with Database("jellyfin") as jellyfindb:
db = jellyfin_db.JellyfinDatabase(jellyfindb.cursor)
db_version = db.get_version()
@@ -124,26 +122,37 @@ def check_version(self):
db.add_version((TARGET_DB_VERSION))
# Video Database Migrations
- with Database('video') as videodb:
+ with Database("video") as videodb:
vid_db = KodiDb(videodb.cursor)
if vid_db.migrations():
- LOG.info('changes detected, reloading skin')
- xbmc.executebuiltin('UpdateLibrary(video)')
- xbmc.executebuiltin('ReloadSkin()')
+ LOG.info("changes detected, reloading skin")
+ xbmc.executebuiltin("UpdateLibrary(video)")
+ xbmc.executebuiltin("ReloadSkin()")
@stop
def service(self):
-
- ''' If error is encountered, it will rerun this function.
- Start new "daemon threads" to process library updates.
- (actual daemon thread is not supported in Kodi)
- '''
- self.download_threads = [thread for thread in self.download_threads if not thread.is_done]
- self.writer_threads['updated'] = [thread for thread in self.writer_threads['updated'] if not thread.is_done]
- self.writer_threads['userdata'] = [thread for thread in self.writer_threads['userdata'] if not thread.is_done]
- self.writer_threads['removed'] = [thread for thread in self.writer_threads['removed'] if not thread.is_done]
-
- if not self.player.isPlayingVideo() or settings('syncDuringPlay.bool') or xbmc.getCondVisibility('VideoPlayer.Content(livetv)'):
+ """If error is encountered, it will rerun this function.
+ Start new "daemon threads" to process library updates.
+ (actual daemon thread is not supported in Kodi)
+ """
+ self.download_threads = [
+ thread for thread in self.download_threads if not thread.is_done
+ ]
+ self.writer_threads["updated"] = [
+ thread for thread in self.writer_threads["updated"] if not thread.is_done
+ ]
+ self.writer_threads["userdata"] = [
+ thread for thread in self.writer_threads["userdata"] if not thread.is_done
+ ]
+ self.writer_threads["removed"] = [
+ thread for thread in self.writer_threads["removed"] if not thread.is_done
+ ]
+
+ if (
+ not self.player.isPlayingVideo()
+ or settings("syncDuringPlay.bool")
+ or xbmc.getCondVisibility("VideoPlayer.Content(livetv)")
+ ):
self.worker_downloads()
self.worker_sort()
@@ -154,7 +163,7 @@ def service(self):
self.worker_notify()
if self.pending_refresh:
- window('jellyfin_sync.bool', True)
+ window("jellyfin_sync.bool", True)
if self.total_updates > self.progress_display:
queue_size = self.worker_queue_size()
@@ -162,58 +171,91 @@ def service(self):
if self.progress_updates is None:
self.progress_updates = xbmcgui.DialogProgressBG()
- self.progress_updates.create(translate('addon_name'), translate(33178))
- self.progress_updates.update(int((float(self.total_updates - queue_size) / float(self.total_updates)) * 100), message="%s: %s" % (translate(33178), queue_size))
+ self.progress_updates.create(
+ translate("addon_name"), translate(33178)
+ )
+ self.progress_updates.update(
+ int(
+ (
+ float(self.total_updates - queue_size)
+ / float(self.total_updates)
+ )
+ * 100
+ ),
+ message="%s: %s" % (translate(33178), queue_size),
+ )
elif queue_size:
- self.progress_updates.update(int((float(self.total_updates - queue_size) / float(self.total_updates)) * 100), message="%s: %s" % (translate(33178), queue_size))
+ self.progress_updates.update(
+ int(
+ (
+ float(self.total_updates - queue_size)
+ / float(self.total_updates)
+ )
+ * 100
+ ),
+ message="%s: %s" % (translate(33178), queue_size),
+ )
else:
- self.progress_updates.update(int((float(self.total_updates - queue_size) / float(self.total_updates)) * 100), message=translate(33178))
-
- if not settings('dbSyncScreensaver.bool') and self.screensaver is None:
-
- xbmc.executebuiltin('InhibitIdleShutdown(true)')
+ self.progress_updates.update(
+ int(
+ (
+ float(self.total_updates - queue_size)
+ / float(self.total_updates)
+ )
+ * 100
+ ),
+ message=translate(33178),
+ )
+
+ if not settings("dbSyncScreensaver.bool") and self.screensaver is None:
+
+ xbmc.executebuiltin("InhibitIdleShutdown(true)")
self.screensaver = get_screensaver()
set_screensaver(value="")
- if (self.pending_refresh and not self.download_threads and not self.writer_threads['updated'] and not self.writer_threads['userdata'] and not self.writer_threads['removed']):
+ if (
+ self.pending_refresh
+ and not self.download_threads
+ and not self.writer_threads["updated"]
+ and not self.writer_threads["userdata"]
+ and not self.writer_threads["removed"]
+ ):
self.pending_refresh = False
self.save_last_sync()
self.total_updates = 0
- window('jellyfin_sync', clear=True)
+ window("jellyfin_sync", clear=True)
if self.progress_updates:
self.progress_updates.close()
self.progress_updates = None
- if not settings('dbSyncScreensaver.bool') and self.screensaver is not None:
+ if not settings("dbSyncScreensaver.bool") and self.screensaver is not None:
- xbmc.executebuiltin('InhibitIdleShutdown(false)')
+ xbmc.executebuiltin("InhibitIdleShutdown(false)")
set_screensaver(value=self.screensaver)
self.screensaver = None
- if xbmc.getCondVisibility('Container.Content(musicvideos)'): # Prevent cursor from moving
- xbmc.executebuiltin('Container.Refresh')
+ if xbmc.getCondVisibility(
+ "Container.Content(musicvideos)"
+ ): # Prevent cursor from moving
+ xbmc.executebuiltin("Container.Refresh")
else: # Update widgets
- xbmc.executebuiltin('UpdateLibrary(video)')
+ xbmc.executebuiltin("UpdateLibrary(video)")
- if xbmc.getCondVisibility('Window.IsMedia'):
- xbmc.executebuiltin('Container.Refresh')
+ if xbmc.getCondVisibility("Window.IsMedia"):
+ xbmc.executebuiltin("Container.Refresh")
def stop_client(self):
self.stop_thread = True
def enable_pending_refresh(self):
-
- ''' When there's an active thread. Let the main thread know.
- '''
+ """When there's an active thread. Let the main thread know."""
self.pending_refresh = True
- window('jellyfin_sync.bool', True)
+ window("jellyfin_sync.bool", True)
def worker_queue_size(self):
-
- ''' Get how many items are queued up for worker threads.
- '''
+ """Get how many items are queued up for worker threads."""
total = 0
for queues in self.updated_output:
@@ -228,10 +270,11 @@ def worker_queue_size(self):
return total
def worker_downloads(self):
-
- ''' Get items from jellyfin and place them in the appropriate queues.
- '''
- for queue in ((self.updated_queue, self.updated_output), (self.userdata_queue, self.userdata_output)):
+ """Get items from jellyfin and place them in the appropriate queues."""
+ for queue in (
+ (self.updated_queue, self.updated_output),
+ (self.userdata_queue, self.userdata_output),
+ ):
if queue[0].qsize() and len(self.download_threads) < DTHREADS:
new_thread = GetItemWorker(self.server, queue[0], queue[1])
@@ -240,9 +283,7 @@ def worker_downloads(self):
self.download_threads.append(new_thread)
def worker_sort(self):
-
- ''' Get items based on the local jellyfin database and place item in appropriate queues.
- '''
+ """Get items based on the local jellyfin database and place item in appropriate queues."""
if self.removed_queue.qsize() and len(self.jellyfin_threads) < 2:
new_thread = SortWorker(self.removed_queue, self.removed_output)
@@ -250,66 +291,96 @@ def worker_sort(self):
LOG.info("-->[ q:sort/%s ]", id(new_thread))
def worker_updates(self):
-
- ''' Update items in the Kodi database.
- '''
+ """Update items in the Kodi database."""
for queues in self.updated_output:
queue = self.updated_output[queues]
- if queue.qsize() and not len(self.writer_threads['updated']):
-
- if queues in ('Audio', 'MusicArtist', 'AlbumArtist', 'MusicAlbum'):
- new_thread = UpdateWorker(queue, self.notify_output, self.music_database_lock, "music", self.server, self.direct_path)
+ if queue.qsize() and not len(self.writer_threads["updated"]):
+
+ if queues in ("Audio", "MusicArtist", "AlbumArtist", "MusicAlbum"):
+ new_thread = UpdateWorker(
+ queue,
+ self.notify_output,
+ self.music_database_lock,
+ "music",
+ self.server,
+ self.direct_path,
+ )
else:
- new_thread = UpdateWorker(queue, self.notify_output, self.database_lock, "video", self.server, self.direct_path)
+ new_thread = UpdateWorker(
+ queue,
+ self.notify_output,
+ self.database_lock,
+ "video",
+ self.server,
+ self.direct_path,
+ )
new_thread.start()
LOG.info("-->[ q:updated/%s/%s ]", queues, id(new_thread))
- self.writer_threads['updated'].append(new_thread)
+ self.writer_threads["updated"].append(new_thread)
self.enable_pending_refresh()
def worker_userdata(self):
-
- ''' Update userdata in the Kodi database.
- '''
+ """Update userdata in the Kodi database."""
for queues in self.userdata_output:
queue = self.userdata_output[queues]
- if queue.qsize() and not len(self.writer_threads['userdata']):
+ if queue.qsize() and not len(self.writer_threads["userdata"]):
- if queues in ('Audio', 'MusicArtist', 'AlbumArtist', 'MusicAlbum'):
- new_thread = UserDataWorker(queue, self.music_database_lock, "music", self.server, self.direct_path)
+ if queues in ("Audio", "MusicArtist", "AlbumArtist", "MusicAlbum"):
+ new_thread = UserDataWorker(
+ queue,
+ self.music_database_lock,
+ "music",
+ self.server,
+ self.direct_path,
+ )
else:
- new_thread = UserDataWorker(queue, self.database_lock, "video", self.server, self.direct_path)
+ new_thread = UserDataWorker(
+ queue,
+ self.database_lock,
+ "video",
+ self.server,
+ self.direct_path,
+ )
new_thread.start()
LOG.info("-->[ q:userdata/%s/%s ]", queues, id(new_thread))
- self.writer_threads['userdata'].append(new_thread)
+ self.writer_threads["userdata"].append(new_thread)
self.enable_pending_refresh()
def worker_remove(self):
-
- ''' Remove items from the Kodi database.
- '''
+ """Remove items from the Kodi database."""
for queues in self.removed_output:
queue = self.removed_output[queues]
- if queue.qsize() and not len(self.writer_threads['removed']):
+ if queue.qsize() and not len(self.writer_threads["removed"]):
- if queues in ('Audio', 'MusicArtist', 'AlbumArtist', 'MusicAlbum'):
- new_thread = RemovedWorker(queue, self.music_database_lock, "music", self.server, self.direct_path)
+ if queues in ("Audio", "MusicArtist", "AlbumArtist", "MusicAlbum"):
+ new_thread = RemovedWorker(
+ queue,
+ self.music_database_lock,
+ "music",
+ self.server,
+ self.direct_path,
+ )
else:
- new_thread = RemovedWorker(queue, self.database_lock, "video", self.server, self.direct_path)
+ new_thread = RemovedWorker(
+ queue,
+ self.database_lock,
+ "video",
+ self.server,
+ self.direct_path,
+ )
new_thread.start()
LOG.info("-->[ q:removed/%s/%s ]", queues, id(new_thread))
- self.writer_threads['removed'].append(new_thread)
+ self.writer_threads["removed"].append(new_thread)
self.enable_pending_refresh()
def worker_notify(self):
-
- ''' Notify the user of new additions.
- '''
+ """Notify the user of new additions."""
if self.notify_output.qsize() and not len(self.notify_threads):
new_thread = NotifyWorker(self.notify_output, self.player)
@@ -318,11 +389,10 @@ def worker_notify(self):
self.notify_threads.append(new_thread)
def startup(self):
-
- ''' Run at startup.
- Check databases.
- Check for the server plugin.
- '''
+ """Run at startup.
+ Check databases.
+ Check for the server plugin.
+ """
self.test_databases()
self.check_version()
@@ -330,7 +400,7 @@ def startup(self):
Views().get_nodes()
try:
- if get_sync()['Libraries']:
+ if get_sync()["Libraries"]:
try:
with FullSync(self, self.server) as sync:
@@ -340,7 +410,7 @@ def startup(self):
except Exception as error:
LOG.exception(error)
- elif not settings('SyncInstallRunDone.bool'):
+ elif not settings("SyncInstallRunDone.bool"):
with FullSync(self, self.server) as sync:
sync.libraries()
@@ -349,9 +419,7 @@ def startup(self):
return True
- if settings('SyncInstallRunDone.bool') and settings(
- 'kodiCompanion.bool'
- ):
+ if settings("SyncInstallRunDone.bool") and settings("kodiCompanion.bool"):
# None == Unknown
if self.server.jellyfin.check_companion_enabled() is not False:
@@ -372,12 +440,12 @@ def startup(self):
except LibraryException as error:
LOG.error(error.status)
- if error.status in 'SyncLibraryLater':
+ if error.status in "SyncLibraryLater":
dialog("ok", "{jellyfin}", translate(33129))
- settings('SyncInstallRunDone.bool', True)
+ settings("SyncInstallRunDone.bool", True)
sync = get_sync()
- sync['Libraries'] = []
+ sync["Libraries"] = []
save_sync(sync)
return True
@@ -388,33 +456,33 @@ def startup(self):
return False
def fast_sync(self):
-
- ''' Movie and userdata not provided by server yet.
- '''
- last_sync = settings('LastIncrementalSync')
+ """Movie and userdata not provided by server yet."""
+ last_sync = settings("LastIncrementalSync")
include = []
filters = ["tvshows", "boxsets", "musicvideos", "music", "movies"]
sync = get_sync()
- whitelist = [x.replace('Mixed:', "") for x in sync['Whitelist']]
+ whitelist = [x.replace("Mixed:", "") for x in sync["Whitelist"]]
LOG.info("--[ retrieve changes ] %s", last_sync)
# Get the item type of each synced library and build list of types to request
for item_id in whitelist:
library = self.server.jellyfin.get_item(item_id)
- library_type = library.get('CollectionType')
+ library_type = library.get("CollectionType")
if library_type in filters:
include.append(library_type)
# Include boxsets if movies are synced
- if 'movies' in include:
- include.append('boxsets')
+ if "movies" in include:
+ include.append("boxsets")
# Filter down to the list of library types we want to exclude
query_filter = list(set(filters) - set(include))
try:
# Get list of updates from server for synced library types and populate work queues
- result = self.server.jellyfin.get_sync_queue(last_sync, ",".join([x for x in query_filter]))
+ result = self.server.jellyfin.get_sync_queue(
+ last_sync, ",".join([x for x in query_filter])
+ )
if result is None:
return True
@@ -423,18 +491,23 @@ def fast_sync(self):
userdata = []
removed = []
- updated.extend(result['ItemsAdded'])
- updated.extend(result['ItemsUpdated'])
- userdata.extend(result['UserDataChanged'])
- removed.extend(result['ItemsRemoved'])
+ updated.extend(result["ItemsAdded"])
+ updated.extend(result["ItemsUpdated"])
+ userdata.extend(result["UserDataChanged"])
+ removed.extend(result["ItemsRemoved"])
total = len(updated) + len(userdata)
- if total > int(settings('syncIndicator') or 99):
+ if total > int(settings("syncIndicator") or 99):
- ''' Inverse yes no, in case the dialog is forced closed by Kodi.
- '''
- if dialog("yesno", "{jellyfin}", translate(33172).replace('{number}', str(total)), nolabel=translate(107), yeslabel=translate(106)):
+ """Inverse yes no, in case the dialog is forced closed by Kodi."""
+ if dialog(
+ "yesno",
+ "{jellyfin}",
+ translate(33172).replace("{number}", str(total)),
+ nolabel=translate(107),
+ yeslabel=translate(106),
+ ):
LOG.warning("Large updates skipped.")
return True
@@ -453,56 +526,68 @@ def fast_sync(self):
def save_last_sync(self):
try:
- time_now = datetime.strptime(self.server.config.data['server-time'].split(', ', 1)[1], '%d %b %Y %H:%M:%S GMT') - timedelta(minutes=2)
+ time_now = datetime.strptime(
+ self.server.config.data["server-time"].split(", ", 1)[1],
+ "%d %b %Y %H:%M:%S GMT",
+ ) - timedelta(minutes=2)
except Exception as error:
LOG.exception(error)
time_now = datetime.utcnow() - timedelta(minutes=2)
- last_sync = time_now.strftime('%Y-%m-%dT%H:%M:%Sz')
- settings('LastIncrementalSync', value=last_sync)
+ last_sync = time_now.strftime("%Y-%m-%dT%H:%M:%Sz")
+ settings("LastIncrementalSync", value=last_sync)
LOG.info("--[ sync/%s ]", last_sync)
def select_libraries(self, mode=None):
-
- ''' Select from libraries synced. Either update or repair libraries.
- Send event back to service.py
- '''
+ """Select from libraries synced. Either update or repair libraries.
+ Send event back to service.py
+ """
modes = {
- 'SyncLibrarySelection': 'SyncLibrary',
- 'RepairLibrarySelection': 'RepairLibrary',
- 'AddLibrarySelection': 'SyncLibrary',
- 'RemoveLibrarySelection': 'RemoveLibrary'
+ "SyncLibrarySelection": "SyncLibrary",
+ "RepairLibrarySelection": "RepairLibrary",
+ "AddLibrarySelection": "SyncLibrary",
+ "RemoveLibrarySelection": "RemoveLibrary",
}
sync = get_sync()
- whitelist = [x.replace('Mixed:', "") for x in sync['Whitelist']]
+ whitelist = [x.replace("Mixed:", "") for x in sync["Whitelist"]]
libraries = []
- with Database('jellyfin') as jellyfindb:
+ with Database("jellyfin") as jellyfindb:
db = jellyfin_db.JellyfinDatabase(jellyfindb.cursor)
- if mode in ('SyncLibrarySelection', 'RepairLibrarySelection', 'RemoveLibrarySelection'):
- for library in sync['Whitelist']:
+ if mode in (
+ "SyncLibrarySelection",
+ "RepairLibrarySelection",
+ "RemoveLibrarySelection",
+ ):
+ for library in sync["Whitelist"]:
- name = db.get_view_name(library.replace('Mixed:', ""))
- libraries.append({'Id': library, 'Name': name})
+ name = db.get_view_name(library.replace("Mixed:", ""))
+ libraries.append({"Id": library, "Name": name})
else:
- available = [x for x in sync['SortedViews'] if x not in whitelist]
+ available = [x for x in sync["SortedViews"] if x not in whitelist]
for library in available:
view = db.get_view(library)
- if view.media_type in ('movies', 'tvshows', 'musicvideos', 'mixed', 'music'):
- libraries.append({'Id': view.view_id, 'Name': view.view_name})
+ if view.media_type in (
+ "movies",
+ "tvshows",
+ "musicvideos",
+ "mixed",
+ "music",
+ ):
+ libraries.append({"Id": view.view_id, "Name": view.view_name})
- choices = [x['Name'] for x in libraries]
+ choices = [x["Name"] for x in libraries]
choices.insert(0, translate(33121))
titles = {
"RepairLibrarySelection": 33199,
"SyncLibrarySelection": 33198,
"RemoveLibrarySelection": 33200,
- "AddLibrarySelection": 33120
+ "AddLibrarySelection": 33120,
}
title = titles.get(mode, "Failed to get title {}".format(mode))
@@ -519,9 +604,15 @@ def select_libraries(self, mode=None):
for x in selection:
library = libraries[x - 1]
- selected_libraries.append(library['Id'])
+ selected_libraries.append(library["Id"])
- event(modes[mode], {'Id': ','.join([libraries[x - 1]['Id'] for x in selection]), 'Update': mode == 'SyncLibrarySelection'})
+ event(
+ modes[mode],
+ {
+ "Id": ",".join([libraries[x - 1]["Id"] for x in selection]),
+ "Update": mode == "SyncLibrarySelection",
+ },
+ )
def add_library(self, library_id, update=False):
@@ -555,13 +646,11 @@ def remove_library(self, library_id):
return True
def userdata(self, data):
-
- ''' Add item_id to userdata queue.
- '''
+ """Add item_id to userdata queue."""
if not data:
return
- items = [x['ItemId'] for x in data]
+ items = [x["ItemId"] for x in data]
for item in split_list(items, LIMIT):
self.userdata_queue.put(item)
@@ -570,9 +659,7 @@ def userdata(self, data):
LOG.info("---[ userdata:%s ]", len(items))
def updated(self, data):
-
- ''' Add item_id to updated queue.
- '''
+ """Add item_id to updated queue."""
if not data:
return
@@ -583,9 +670,7 @@ def updated(self, data):
LOG.info("---[ updated:%s ]", len(data))
def removed(self, data):
-
- ''' Add item_id to removed queue.
- '''
+ """Add item_id to removed queue."""
if not data:
return
@@ -604,10 +689,12 @@ class UpdateWorker(threading.Thread):
is_done = False
- def __init__(self, queue, notify, lock, database, server=None, direct_path=None, *args):
+ def __init__(
+ self, queue, notify, lock, database, server=None, direct_path=None, *args
+ ):
self.queue = queue
self.notify_output = notify
- self.notify = settings('newContent.bool')
+ self.notify = settings("newContent.bool")
self.lock = lock
self.database = Database(database)
self.args = args
@@ -616,7 +703,7 @@ def __init__(self, queue, notify, lock, database, server=None, direct_path=None,
threading.Thread.__init__(self)
def run(self):
- with self.lock, self.database as kodidb, Database('jellyfin') as jellyfindb:
+ with self.lock, self.database as kodidb, Database("jellyfin") as jellyfindb:
default_args = (self.server, jellyfindb, kodidb, self.direct_path)
if kodidb.db_file == "video":
movies = Movies(*default_args)
@@ -626,7 +713,9 @@ def run(self):
music = Music(*default_args)
else:
# this should not happen
- LOG.error('"{}" is not a valid Kodi library type.'.format(kodidb.db_file))
+ LOG.error(
+ '"{}" is not a valid Kodi library type.'.format(kodidb.db_file)
+ )
return
while True:
@@ -637,39 +726,41 @@ def run(self):
break
try:
- LOG.debug('{} - {}'.format(item['Type'], item['Name']))
- if item['Type'] == 'Movie':
+ LOG.debug("{} - {}".format(item["Type"], item["Name"]))
+ if item["Type"] == "Movie":
movies.movie(item)
- elif item['Type'] == 'BoxSet':
+ elif item["Type"] == "BoxSet":
movies.boxset(item)
- elif item['Type'] == 'Series':
+ elif item["Type"] == "Series":
tvshows.tvshow(item)
- elif item['Type'] == 'Season':
+ elif item["Type"] == "Season":
tvshows.season(item)
- elif item['Type'] == 'Episode':
+ elif item["Type"] == "Episode":
tvshows.episode(item)
- elif item['Type'] == 'MusicVideo':
+ elif item["Type"] == "MusicVideo":
musicvideos.musicvideo(item)
- elif item['Type'] == 'MusicAlbum':
+ elif item["Type"] == "MusicAlbum":
music.album(item)
- elif item['Type'] == 'MusicArtist':
+ elif item["Type"] == "MusicArtist":
music.artist(item)
- elif item['Type'] == 'AlbumArtist':
+ elif item["Type"] == "AlbumArtist":
music.albumartist(item)
- elif item['Type'] == 'Audio':
+ elif item["Type"] == "Audio":
music.song(item)
if self.notify:
- self.notify_output.put((item['Type'], api.API(item).get_naming()))
+ self.notify_output.put(
+ (item["Type"], api.API(item).get_naming())
+ )
except LibraryException as error:
- if error.status == 'StopCalled':
+ if error.status == "StopCalled":
break
except Exception as error:
LOG.exception(error)
self.queue.task_done()
- if window('jellyfin_should_stop.bool'):
+ if window("jellyfin_should_stop.bool"):
break
LOG.info("--<[ q:updated/%s ]", id(self))
@@ -692,7 +783,7 @@ def __init__(self, queue, lock, database, server, direct_path):
def run(self):
- with self.lock, self.database as kodidb, Database('jellyfin') as jellyfindb:
+ with self.lock, self.database as kodidb, Database("jellyfin") as jellyfindb:
default_args = (self.server, jellyfindb, kodidb, self.direct_path)
if kodidb.db_file == "video":
movies = Movies(*default_args)
@@ -701,7 +792,9 @@ def run(self):
music = Music(*default_args)
else:
# this should not happen
- LOG.error('"{}" is not a valid Kodi library type.'.format(kodidb.db_file))
+ LOG.error(
+ '"{}" is not a valid Kodi library type.'.format(kodidb.db_file)
+ )
return
while True:
@@ -712,27 +805,27 @@ def run(self):
break
try:
- if item['Type'] == 'Movie':
+ if item["Type"] == "Movie":
movies.userdata(item)
- elif item['Type'] in ['Series', 'Season', 'Episode']:
+ elif item["Type"] in ["Series", "Season", "Episode"]:
tvshows.userdata(item)
- elif item['Type'] == 'MusicAlbum':
+ elif item["Type"] == "MusicAlbum":
music.album(item)
- elif item['Type'] == 'MusicArtist':
+ elif item["Type"] == "MusicArtist":
music.artist(item)
- elif item['Type'] == 'AlbumArtist':
+ elif item["Type"] == "AlbumArtist":
music.albumartist(item)
- elif item['Type'] == 'Audio':
+ elif item["Type"] == "Audio":
music.userdata(item)
except LibraryException as error:
- if error.status == 'StopCalled':
+ if error.status == "StopCalled":
break
except Exception as error:
LOG.exception(error)
self.queue.task_done()
- if window('jellyfin_should_stop.bool'):
+ if window("jellyfin_should_stop.bool"):
break
LOG.info("--<[ q:userdata/%s ]", id(self))
@@ -752,7 +845,7 @@ def __init__(self, queue, output, *args):
def run(self):
- with Database('jellyfin') as jellyfindb:
+ with Database("jellyfin") as jellyfindb:
database = jellyfin_db.JellyfinDatabase(jellyfindb.cursor)
while True:
@@ -765,21 +858,26 @@ def run(self):
try:
media = database.get_media_by_id(item_id)
if media:
- self.output[media].put({'Id': item_id, 'Type': media})
+ self.output[media].put({"Id": item_id, "Type": media})
else:
items = database.get_media_by_parent_id(item_id)
if not items:
- LOG.info("Could not find media %s in the jellyfin database.", item_id)
+ LOG.info(
+ "Could not find media %s in the jellyfin database.",
+ item_id,
+ )
else:
for item in items:
- self.output[item[1]].put({'Id': item[0], 'Type': item[1]})
+ self.output[item[1]].put(
+ {"Id": item[0], "Type": item[1]}
+ )
except Exception as error:
LOG.exception(error)
self.queue.task_done()
- if window('jellyfin_should_stop.bool'):
+ if window("jellyfin_should_stop.bool"):
break
LOG.info("--<[ q:sort/%s ]", id(self))
@@ -801,7 +899,7 @@ def __init__(self, queue, lock, database, server, direct_path):
def run(self):
- with self.lock, self.database as kodidb, Database('jellyfin') as jellyfindb:
+ with self.lock, self.database as kodidb, Database("jellyfin") as jellyfindb:
default_args = (self.server, jellyfindb, kodidb, self.direct_path)
if kodidb.db_file == "video":
movies = Movies(*default_args)
@@ -811,7 +909,9 @@ def run(self):
music = Music(*default_args)
else:
# this should not happen
- LOG.error('"{}" is not a valid Kodi library type.'.format(kodidb.db_file))
+ LOG.error(
+ '"{}" is not a valid Kodi library type.'.format(kodidb.db_file)
+ )
return
while True:
@@ -821,26 +921,31 @@ def run(self):
except Queue.Empty:
break
- if item['Type'] == 'Movie':
+ if item["Type"] == "Movie":
obj = movies.remove
- elif item['Type'] in ['Series', 'Season', 'Episode']:
+ elif item["Type"] in ["Series", "Season", "Episode"]:
obj = tvshows.remove
- elif item['Type'] in ['MusicAlbum', 'MusicArtist', 'AlbumArtist', 'Audio']:
+ elif item["Type"] in [
+ "MusicAlbum",
+ "MusicArtist",
+ "AlbumArtist",
+ "Audio",
+ ]:
obj = music.remove
- elif item['Type'] == 'MusicVideo':
+ elif item["Type"] == "MusicVideo":
obj = musicvideos.remove
try:
- obj(item['Id'])
+ obj(item["Id"])
except LibraryException as error:
- if error.status == 'StopCalled':
+ if error.status == "StopCalled":
break
except Exception as error:
LOG.exception(error)
finally:
self.queue.task_done()
- if window('jellyfin_should_stop.bool'):
+ if window("jellyfin_should_stop.bool"):
break
LOG.info("--<[ q:removed/%s ]", id(self))
@@ -854,8 +959,8 @@ class NotifyWorker(threading.Thread):
def __init__(self, queue, player):
self.queue = queue
- self.video_time = int(settings('newvideotime')) * 1000
- self.music_time = int(settings('newmusictime')) * 1000
+ self.video_time = int(settings("newvideotime")) * 1000
+ self.music_time = int(settings("newmusictime")) * 1000
self.player = player
threading.Thread.__init__(self)
@@ -868,15 +973,24 @@ def run(self):
except Queue.Empty:
break
- time = self.music_time if item[0] == 'Audio' else self.video_time
+ time = self.music_time if item[0] == "Audio" else self.video_time
- if time and (not self.player.isPlayingVideo() or xbmc.getCondVisibility('VideoPlayer.Content(livetv)')):
- dialog("notification", heading="%s %s" % (translate(33049), item[0]), message=item[1],
- icon="{jellyfin}", time=time, sound=False)
+ if time and (
+ not self.player.isPlayingVideo()
+ or xbmc.getCondVisibility("VideoPlayer.Content(livetv)")
+ ):
+ dialog(
+ "notification",
+ heading="%s %s" % (translate(33049), item[0]),
+ message=item[1],
+ icon="{jellyfin}",
+ time=time,
+ sound=False,
+ )
self.queue.task_done()
- if window('jellyfin_should_stop.bool'):
+ if window("jellyfin_should_stop.bool"):
break
LOG.info("--<[ q:notify/%s ]", id(self))
diff --git a/jellyfin_kodi/monitor.py b/jellyfin_kodi/monitor.py
index 31b9d67cd..4f02226bb 100644
--- a/jellyfin_kodi/monitor.py
+++ b/jellyfin_kodi/monitor.py
@@ -46,23 +46,35 @@ def onScanFinished(self, library):
def onNotification(self, sender, method, data):
- if sender.lower() not in ('plugin.video.jellyfin', 'xbmc', 'upnextprovider.signal'):
+ if sender.lower() not in (
+ "plugin.video.jellyfin",
+ "xbmc",
+ "upnextprovider.signal",
+ ):
return
- if sender == 'plugin.video.jellyfin':
- method = method.split('.')[1]
-
- if method not in ('ReportProgressRequested', 'LoadServer', 'AddUser', 'PlayPlaylist', 'Play', 'Playstate', 'GeneralCommand'):
+ if sender == "plugin.video.jellyfin":
+ method = method.split(".")[1]
+
+ if method not in (
+ "ReportProgressRequested",
+ "LoadServer",
+ "AddUser",
+ "PlayPlaylist",
+ "Play",
+ "Playstate",
+ "GeneralCommand",
+ ):
return
data = json.loads(data)[0]
- elif sender.startswith('upnextprovider'):
- LOG.info('Attempting to play the next episode via upnext')
- method = method.split('.', 1)[1]
+ elif sender.startswith("upnextprovider"):
+ LOG.info("Attempting to play the next episode via upnext")
+ method = method.split(".", 1)[1]
- if method not in ('plugin.video.jellyfin_play_action',):
- LOG.info('Received invalid upnext method: %s', method)
+ if method not in ("plugin.video.jellyfin_play_action",):
+ LOG.info("Received invalid upnext method: %s", method)
return
data = json.loads(data)
@@ -71,15 +83,23 @@ def onNotification(self, sender, method, data):
if data:
data = json.loads(binascii.unhexlify(data[0]))
else:
- if method not in ('Player.OnPlay', 'VideoLibrary.OnUpdate', 'Player.OnAVChange'):
-
- ''' We have to clear the playlist if it was stopped before it has been played completely.
- Otherwise the next played item will be added the previous queue.
- '''
+ if method not in (
+ "Player.OnPlay",
+ "VideoLibrary.OnUpdate",
+ "Player.OnAVChange",
+ ):
+
+ """We have to clear the playlist if it was stopped before it has been played completely.
+ Otherwise the next played item will be added the previous queue.
+ """
if method == "Player.OnStop":
- xbmc.sleep(3000) # let's wait for the player, so we don't clear the canceled playlist by mistake.
+ xbmc.sleep(
+ 3000
+ ) # let's wait for the player, so we don't clear the canceled playlist by mistake.
- if xbmc.getCondVisibility("!Player.HasMedia + !Window.IsVisible(busydialog)"):
+ if xbmc.getCondVisibility(
+ "!Player.HasMedia + !Window.IsVisible(busydialog)"
+ ):
xbmc.executebuiltin("Playlist.Clear")
LOG.info("[ playlist ] cleared")
@@ -96,14 +116,14 @@ def onNotification(self, sender, method, data):
return
try:
- if not data.get('ServerId'):
+ if not data.get("ServerId"):
server = Jellyfin()
else:
- if method != 'LoadServer' and data['ServerId'] not in self.servers:
+ if method != "LoadServer" and data["ServerId"] not in self.servers:
try:
- connect.Connect().register(data['ServerId'])
- self.server_instance(data['ServerId'])
+ connect.Connect().register(data["ServerId"])
+ self.server_instance(data["ServerId"])
except Exception as error:
LOG.exception(error)
@@ -111,80 +131,90 @@ def onNotification(self, sender, method, data):
return
- server = Jellyfin(data['ServerId'])
+ server = Jellyfin(data["ServerId"])
except Exception as error:
LOG.exception(error)
server = Jellyfin()
server = server.get_client()
- if method == 'Play':
+ if method == "Play":
- items = server.jellyfin.get_items(data['ItemIds'])
+ items = server.jellyfin.get_items(data["ItemIds"])
- PlaylistWorker(data.get('ServerId'), items, data['PlayCommand'] == 'PlayNow',
- data.get('StartPositionTicks', 0), data.get('AudioStreamIndex'),
- data.get('SubtitleStreamIndex')).start()
+ PlaylistWorker(
+ data.get("ServerId"),
+ items,
+ data["PlayCommand"] == "PlayNow",
+ data.get("StartPositionTicks", 0),
+ data.get("AudioStreamIndex"),
+ data.get("SubtitleStreamIndex"),
+ ).start()
# TODO no clue if this is called by anything
- elif method == 'PlayPlaylist':
-
- server.jellyfin.post_session(server.config.data['app.session'], "Playing", {
- 'PlayCommand': "PlayNow",
- 'ItemIds': data['Id'],
- 'StartPositionTicks': 0
- })
-
- elif method in ('ReportProgressRequested', 'Player.OnAVChange'):
- self.player.report_playback(data.get('Report', True))
-
- elif method == 'Playstate':
+ elif method == "PlayPlaylist":
+
+ server.jellyfin.post_session(
+ server.config.data["app.session"],
+ "Playing",
+ {
+ "PlayCommand": "PlayNow",
+ "ItemIds": data["Id"],
+ "StartPositionTicks": 0,
+ },
+ )
+
+ elif method in ("ReportProgressRequested", "Player.OnAVChange"):
+ self.player.report_playback(data.get("Report", True))
+
+ elif method == "Playstate":
self.playstate(data)
- elif method == 'GeneralCommand':
+ elif method == "GeneralCommand":
self.general_commands(data)
- elif method == 'LoadServer':
- self.server_instance(data['ServerId'])
+ elif method == "LoadServer":
+ self.server_instance(data["ServerId"])
- elif method == 'AddUser':
- server.jellyfin.session_add_user(server.config.data['app.session'], data['Id'], data['Add'])
+ elif method == "AddUser":
+ server.jellyfin.session_add_user(
+ server.config.data["app.session"], data["Id"], data["Add"]
+ )
self.additional_users(server)
- elif method == 'Player.OnPlay':
+ elif method == "Player.OnPlay":
on_play(data, server)
- elif method == 'VideoLibrary.OnUpdate':
+ elif method == "VideoLibrary.OnUpdate":
on_update(data, server)
def server_instance(self, server_id=None):
server = Jellyfin(server_id).get_client()
session = server.jellyfin.get_device(self.device_id)
- server.config.data['app.session'] = session[0]['Id']
+ server.config.data["app.session"] = session[0]["Id"]
if server_id is not None:
self.servers.append(server_id)
- elif settings('additionalUsers'):
+ elif settings("additionalUsers"):
- users = settings('additionalUsers').split(',')
+ users = settings("additionalUsers").split(",")
all_users = server.jellyfin.get_users()
for additional in users:
for user in all_users:
- if user['Name'].lower() in additional.lower():
- server.jellyfin.session_add_user(server.config.data['app.session'], user['Id'], True)
+ if user["Name"].lower() in additional.lower():
+ server.jellyfin.session_add_user(
+ server.config.data["app.session"], user["Id"], True
+ )
self.additional_users(server)
-
def additional_users(self, server):
-
- ''' Setup additional users images.
- '''
+ """Setup additional users images."""
for i in range(10):
- window('JellyfinAdditionalUserImage.%s' % i, clear=True)
+ window("JellyfinAdditionalUserImage.%s" % i, clear=True)
try:
session = server.jellyfin.get_device(self.device_id)
@@ -193,31 +223,31 @@ def additional_users(self, server):
return
- for index, user in enumerate(session[0]['AdditionalUsers']):
+ for index, user in enumerate(session[0]["AdditionalUsers"]):
- info = server.jellyfin.get_user(user['UserId'])
- image = api.API(info, server.config.data['auth.server']).get_user_artwork(user['UserId'])
- window('JellyfinAdditionalUserImage.%s' % index, image)
- window('JellyfinAdditionalUserPosition.%s' % user['UserId'], str(index))
+ info = server.jellyfin.get_user(user["UserId"])
+ image = api.API(info, server.config.data["auth.server"]).get_user_artwork(
+ user["UserId"]
+ )
+ window("JellyfinAdditionalUserImage.%s" % index, image)
+ window("JellyfinAdditionalUserPosition.%s" % user["UserId"], str(index))
def playstate(self, data):
-
- ''' Jellyfin playstate updates.
- '''
- command = data['Command']
+ """Jellyfin playstate updates."""
+ command = data["Command"]
actions = {
- 'Stop': self.player.stop,
- 'Unpause': self.player.pause,
- 'Pause': self.player.pause,
- 'PlayPause': self.player.pause,
- 'NextTrack': self.player.playnext,
- 'PreviousTrack': self.player.playprevious
+ "Stop": self.player.stop,
+ "Unpause": self.player.pause,
+ "Pause": self.player.pause,
+ "PlayPause": self.player.pause,
+ "NextTrack": self.player.playnext,
+ "PreviousTrack": self.player.playprevious,
}
- if command == 'Seek':
+ if command == "Seek":
if self.player.isPlaying():
- seektime = data['SeekPositionTicks'] / 10000000.0
+ seektime = data["SeekPositionTicks"] / 10000000.0
self.player.seekTime(seektime)
LOG.info("[ seek/%s ]", seektime)
@@ -227,69 +257,78 @@ def playstate(self, data):
LOG.info("[ command/%s ]", command)
def general_commands(self, data):
-
- ''' General commands from Jellyfin to control the Kodi interface.
- '''
- command = data['Name']
- args = data['Arguments']
-
- if command in ('Mute', 'Unmute', 'SetVolume',
- 'SetSubtitleStreamIndex', 'SetAudioStreamIndex', 'SetRepeatMode'):
-
- if command in ['Mute', 'Unmute']:
- xbmc.executebuiltin('Mute')
- elif command == 'SetAudioStreamIndex':
- self.player.set_audio_subs(args['Index'])
- elif command == 'SetRepeatMode':
- xbmc.executebuiltin('xbmc.PlayerControl(%s)' % args['RepeatMode'])
- elif command == 'SetSubtitleStreamIndex':
- self.player.set_audio_subs(None, args['Index'])
-
- elif command == 'SetVolume':
- xbmc.executebuiltin('SetVolume(%s[,showvolumebar])' % args['Volume'])
+ """General commands from Jellyfin to control the Kodi interface."""
+ command = data["Name"]
+ args = data["Arguments"]
+
+ if command in (
+ "Mute",
+ "Unmute",
+ "SetVolume",
+ "SetSubtitleStreamIndex",
+ "SetAudioStreamIndex",
+ "SetRepeatMode",
+ ):
+
+ if command in ["Mute", "Unmute"]:
+ xbmc.executebuiltin("Mute")
+ elif command == "SetAudioStreamIndex":
+ self.player.set_audio_subs(args["Index"])
+ elif command == "SetRepeatMode":
+ xbmc.executebuiltin("xbmc.PlayerControl(%s)" % args["RepeatMode"])
+ elif command == "SetSubtitleStreamIndex":
+ self.player.set_audio_subs(None, args["Index"])
+
+ elif command == "SetVolume":
+ xbmc.executebuiltin("SetVolume(%s[,showvolumebar])" % args["Volume"])
# Kodi needs a bit of time to update its current status
xbmc.sleep(500)
self.player.report_playback()
- elif command == 'DisplayMessage':
- dialog("notification", heading=args['Header'], message=args['Text'],
- icon="{jellyfin}", time=int(settings('displayMessage')) * 1000)
+ elif command == "DisplayMessage":
+ dialog(
+ "notification",
+ heading=args["Header"],
+ message=args["Text"],
+ icon="{jellyfin}",
+ time=int(settings("displayMessage")) * 1000,
+ )
- elif command == 'SendString':
- JSONRPC('Input.SendText').execute({'text': args['String'], 'done': False})
+ elif command == "SendString":
+ JSONRPC("Input.SendText").execute({"text": args["String"], "done": False})
- elif command == 'GoHome':
- JSONRPC('GUI.ActivateWindow').execute({'window': "home"})
+ elif command == "GoHome":
+ JSONRPC("GUI.ActivateWindow").execute({"window": "home"})
- elif command == 'Guide':
- JSONRPC('GUI.ActivateWindow').execute({'window': "tvguide"})
+ elif command == "Guide":
+ JSONRPC("GUI.ActivateWindow").execute({"window": "tvguide"})
- elif command in ('MoveUp', 'MoveDown', 'MoveRight', 'MoveLeft'):
+ elif command in ("MoveUp", "MoveDown", "MoveRight", "MoveLeft"):
actions = {
- 'MoveUp': "Input.Up",
- 'MoveDown': "Input.Down",
- 'MoveRight': "Input.Right",
- 'MoveLeft': "Input.Left"
+ "MoveUp": "Input.Up",
+ "MoveDown": "Input.Down",
+ "MoveRight": "Input.Right",
+ "MoveLeft": "Input.Left",
}
JSONRPC(actions[command]).execute()
else:
builtin = {
- 'ToggleFullscreen': 'Action(FullScreen)',
- 'ToggleOsdMenu': 'Action(OSD)',
- 'ToggleContextMenu': 'Action(ContextMenu)',
- 'Select': 'Action(Select)',
- 'Back': 'Action(back)',
- 'PageUp': 'Action(PageUp)',
- 'NextLetter': 'Action(NextLetter)',
- 'GoToSearch': 'VideoLibrary.Search',
- 'GoToSettings': 'ActivateWindow(Settings)',
- 'PageDown': 'Action(PageDown)',
- 'PreviousLetter': 'Action(PrevLetter)',
- 'TakeScreenshot': 'TakeScreenshot',
- 'ToggleMute': 'Mute',
- 'VolumeUp': 'Action(VolumeUp)',
- 'VolumeDown': 'Action(VolumeDown)',
+ "ToggleFullscreen": "Action(FullScreen)",
+ "ToggleOsdMenu": "Action(OSD)",
+ "ToggleContextMenu": "Action(ContextMenu)",
+ "Select": "Action(Select)",
+ "Back": "Action(back)",
+ "PageUp": "Action(PageUp)",
+ "NextLetter": "Action(NextLetter)",
+ "GoToSearch": "VideoLibrary.Search",
+ "GoToSettings": "ActivateWindow(Settings)",
+ "PageDown": "Action(PageDown)",
+ "PreviousLetter": "Action(PrevLetter)",
+ "TakeScreenshot": "TakeScreenshot",
+ "ToggleMute": "Mute",
+ "VolumeUp": "Action(VolumeUp)",
+ "VolumeDown": "Action(VolumeDown)",
}
if command in builtin:
xbmc.executebuiltin(builtin[command])
@@ -305,10 +344,9 @@ def __init__(self, monitor):
threading.Thread.__init__(self)
def run(self):
-
- ''' Detect the resume dialog for widgets.
- Detect external players.
- '''
+ """Detect the resume dialog for widgets.
+ Detect external players.
+ """
LOG.info("--->[ listener ]")
while not self.stop_thread:
diff --git a/jellyfin_kodi/objects/actions.py b/jellyfin_kodi/objects/actions.py
index dbac009e0..4711e1e11 100644
--- a/jellyfin_kodi/objects/actions.py
+++ b/jellyfin_kodi/objects/actions.py
@@ -31,43 +31,47 @@ def __init__(self, server_id=None, api_client=None):
self.server_id = server_id or None
if not api_client:
- LOG.debug('No api client provided, attempting to use config file')
+ LOG.debug("No api client provided, attempting to use config file")
jellyfin_client = Jellyfin(server_id).get_client()
api_client = jellyfin_client.jellyfin
- addon_data = translate_path("special://profile/addon_data/plugin.video.jellyfin/data.json")
+ addon_data = translate_path(
+ "special://profile/addon_data/plugin.video.jellyfin/data.json"
+ )
try:
- with open(addon_data, 'rb') as infile:
+ with open(addon_data, "rb") as infile:
data = json.load(infile)
- server_data = data['Servers'][0]
- api_client.config.data['auth.server'] = server_data.get('address')
- api_client.config.data['auth.server-name'] = server_data.get('Name')
- api_client.config.data['auth.user_id'] = server_data.get('UserId')
- api_client.config.data['auth.token'] = server_data.get('AccessToken')
+ server_data = data["Servers"][0]
+ api_client.config.data["auth.server"] = server_data.get("address")
+ api_client.config.data["auth.server-name"] = server_data.get("Name")
+ api_client.config.data["auth.user_id"] = server_data.get("UserId")
+ api_client.config.data["auth.token"] = server_data.get(
+ "AccessToken"
+ )
except Exception as e:
- LOG.warning('Addon appears to not be configured yet: {}'.format(e))
+ LOG.warning("Addon appears to not be configured yet: {}".format(e))
self.api_client = api_client
- self.server = self.api_client.config.data['auth.server']
+ self.server = self.api_client.config.data["auth.server"]
self.stack = []
def get_playlist(self, item):
- if item['Type'] == 'Audio':
+ if item["Type"] == "Audio":
return xbmc.PlayList(xbmc.PLAYLIST_MUSIC)
return xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
def play(self, item, db_id=None, transcode=False, playlist=False):
-
- ''' Play requested item
- '''
+ """Play requested item"""
listitem = xbmcgui.ListItem()
- LOG.info("[ play/%s ] %s", item['Id'], item['Name'])
+ LOG.info("[ play/%s ] %s", item["Id"], item["Name"])
- transcode = transcode or settings('playFromTranscode.bool')
- play = playutils.PlayUtils(item, transcode, self.server_id, self.server, self.api_client)
+ transcode = transcode or settings("playFromTranscode.bool")
+ play = playutils.PlayUtils(
+ item, transcode, self.server_id, self.server, self.api_client
+ )
source = play.select_source(play.get_sources())
play.set_external_subs(source, listitem)
@@ -79,43 +83,42 @@ def play(self, item, db_id=None, transcode=False, playlist=False):
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, self.stack[0][1])
def set_playlist(self, item, listitem, db_id=None, transcode=False):
+ """Verify seektime, set intros, set main item and set additional parts.
+ Detect the seektime for video type content.
+ Verify the default video action set in Kodi for accurate resume behavior.
+ """
- ''' Verify seektime, set intros, set main item and set additional parts.
- Detect the seektime for video type content.
- Verify the default video action set in Kodi for accurate resume behavior.
- '''
-
- if item['MediaType'] in ('Video', 'Audio'):
- resume = item['UserData'].get('PlaybackPositionTicks')
+ if item["MediaType"] in ("Video", "Audio"):
+ resume = item["UserData"].get("PlaybackPositionTicks")
if resume and transcode:
- choice = self.resume_dialog(api.API(item, self.server).adjust_resume((resume or 0) / 10000000.0))
+ choice = self.resume_dialog(
+ api.API(item, self.server).adjust_resume((resume or 0) / 10000000.0)
+ )
if choice is None:
raise Exception("User backed out of resume dialog.")
item["resumePlayback"] = bool(choice)
- if settings('enableCinema.bool') and not item["resumePlayback"]:
+ if settings("enableCinema.bool") and not item["resumePlayback"]:
self._set_intros(item)
self.set_listitem(item, listitem, db_id, None)
- playutils.set_properties(item, item['PlaybackInfo']['Method'], self.server_id)
- self.stack.append([item['PlaybackInfo']['Path'], listitem])
+ playutils.set_properties(item, item["PlaybackInfo"]["Method"], self.server_id)
+ self.stack.append([item["PlaybackInfo"]["Path"], listitem])
- if item.get('PartCount'):
- self._set_additional_parts(item['Id'])
+ if item.get("PartCount"):
+ self._set_additional_parts(item["Id"])
def _set_intros(self, item):
+ """if we have any play them when the movie/show is not being resumed."""
+ intros = self.api_client.get_intros(item["Id"])
- ''' if we have any play them when the movie/show is not being resumed.
- '''
- intros = self.api_client.get_intros(item['Id'])
-
- if intros['Items']:
+ if intros["Items"]:
enabled = True
- if settings('askCinema') == "true":
+ if settings("askCinema") == "true":
resp = dialog("yesno", "{jellyfin}", translate(33016))
if not resp:
@@ -124,45 +127,51 @@ def _set_intros(self, item):
LOG.info("Skip trailers.")
if enabled:
- for intro in intros['Items']:
+ for intro in intros["Items"]:
listitem = xbmcgui.ListItem()
- LOG.info("[ intro/%s ] %s", intro['Id'], intro['Name'])
+ LOG.info("[ intro/%s ] %s", intro["Id"], intro["Name"])
- play = playutils.PlayUtils(intro, False, self.server_id, self.server, self.api_client)
+ play = playutils.PlayUtils(
+ intro, False, self.server_id, self.server, self.api_client
+ )
play.select_source(play.get_sources())
self.set_listitem(intro, listitem, intro=True)
- listitem.setPath(intro['PlaybackInfo']['Path'])
- playutils.set_properties(intro, intro['PlaybackInfo']['Method'], self.server_id)
+ listitem.setPath(intro["PlaybackInfo"]["Path"])
+ playutils.set_properties(
+ intro, intro["PlaybackInfo"]["Method"], self.server_id
+ )
- self.stack.append([intro['PlaybackInfo']['Path'], listitem])
+ self.stack.append([intro["PlaybackInfo"]["Path"], listitem])
- window('jellyfin.skip.%s' % intro['Id'], value="true")
+ window("jellyfin.skip.%s" % intro["Id"], value="true")
def _set_additional_parts(self, item_id):
-
- ''' Create listitems and add them to the stack of playlist.
- '''
+ """Create listitems and add them to the stack of playlist."""
parts = self.api_client.get_additional_parts(item_id)
- for part in parts['Items']:
+ for part in parts["Items"]:
listitem = xbmcgui.ListItem()
- LOG.info("[ part/%s ] %s", part['Id'], part['Name'])
+ LOG.info("[ part/%s ] %s", part["Id"], part["Name"])
- play = playutils.PlayUtils(part, False, self.server_id, self.server, self.api_client)
+ play = playutils.PlayUtils(
+ part, False, self.server_id, self.server, self.api_client
+ )
source = play.select_source(play.get_sources())
play.set_external_subs(source, listitem)
self.set_listitem(part, listitem)
- listitem.setPath(part['PlaybackInfo']['Path'])
- playutils.set_properties(part, part['PlaybackInfo']['Method'], self.server_id)
-
- self.stack.append([part['PlaybackInfo']['Path'], listitem])
-
- def play_playlist(self, items, clear=True, seektime=None, audio=None, subtitle=None):
-
- ''' Play a list of items. Creates a new playlist. Add additional items as plugin listing.
- '''
- item = items['Items'][0]
+ listitem.setPath(part["PlaybackInfo"]["Path"])
+ playutils.set_properties(
+ part, part["PlaybackInfo"]["Method"], self.server_id
+ )
+
+ self.stack.append([part["PlaybackInfo"]["Path"], listitem])
+
+ def play_playlist(
+ self, items, clear=True, seektime=None, audio=None, subtitle=None
+ ):
+ """Play a list of items. Creates a new playlist. Add additional items as plugin listing."""
+ item = items["Items"][0]
playlist = self.get_playlist(item)
player = xbmc.Player()
@@ -170,55 +179,66 @@ def play_playlist(self, items, clear=True, seektime=None, audio=None, subtitle=N
if player.isPlaying():
player.stop()
- xbmc.executebuiltin('ActivateWindow(busydialognocancel)')
+ xbmc.executebuiltin("ActivateWindow(busydialognocancel)")
playlist.clear()
index = 0
else:
index = max(playlist.getposition(), 0) + 1 # Can return -1
listitem = xbmcgui.ListItem()
- LOG.info("[ playlist/%s ] %s", item['Id'], item['Name'])
+ LOG.info("[ playlist/%s ] %s", item["Id"], item["Name"])
# Automatically resume if the item is in progress (casting from server)
- resume = item['UserData'].get('PlaybackPositionTicks')
+ resume = item["UserData"].get("PlaybackPositionTicks")
item["resumePlayback"] = bool(resume)
- play = playutils.PlayUtils(item, False, self.server_id, self.server, self.api_client)
+ play = playutils.PlayUtils(
+ item, False, self.server_id, self.server, self.api_client
+ )
source = play.select_source(play.get_sources())
play.set_external_subs(source, listitem)
- item['PlaybackInfo']['AudioStreamIndex'] = audio or item['PlaybackInfo']['AudioStreamIndex']
- item['PlaybackInfo']['SubtitleStreamIndex'] = subtitle or item['PlaybackInfo'].get('SubtitleStreamIndex')
+ item["PlaybackInfo"]["AudioStreamIndex"] = (
+ audio or item["PlaybackInfo"]["AudioStreamIndex"]
+ )
+ item["PlaybackInfo"]["SubtitleStreamIndex"] = subtitle or item[
+ "PlaybackInfo"
+ ].get("SubtitleStreamIndex")
self.set_listitem(item, listitem, None, True if seektime else False)
- listitem.setPath(item['PlaybackInfo']['Path'])
- playutils.set_properties(item, item['PlaybackInfo']['Method'], self.server_id)
+ listitem.setPath(item["PlaybackInfo"]["Path"])
+ playutils.set_properties(item, item["PlaybackInfo"]["Method"], self.server_id)
- playlist.add(item['PlaybackInfo']['Path'], listitem, index)
+ playlist.add(item["PlaybackInfo"]["Path"], listitem, index)
if clear:
- xbmc.executebuiltin('Dialog.Close(busydialognocancel)')
+ xbmc.executebuiltin("Dialog.Close(busydialognocancel)")
player.play(playlist, startpos=index)
index += 1
- server_address = item['PlaybackInfo']['ServerAddress']
- token = item['PlaybackInfo']['Token']
+ server_address = item["PlaybackInfo"]["ServerAddress"]
+ token = item["PlaybackInfo"]["Token"]
- for item in items['Items'][1:]:
+ for item in items["Items"][1:]:
listitem = xbmcgui.ListItem()
- LOG.info("[ playlist/%s ] %s", item['Id'], item['Name'])
+ LOG.info("[ playlist/%s ] %s", item["Id"], item["Name"])
self.set_listitem(item, listitem, None, False)
- path = '{}/Audio/{}/stream.mp3?static=true&api_key={}'.format(
- server_address, item['Id'], token)
+ path = "{}/Audio/{}/stream.mp3?static=true&api_key={}".format(
+ server_address, item["Id"], token
+ )
listitem.setPath(path)
- play = playutils.PlayUtils(item, False, self.server_id, self.server, self.api_client)
+ play = playutils.PlayUtils(
+ item, False, self.server_id, self.server, self.api_client
+ )
source = play.select_source(play.get_sources())
play.set_external_subs(source, listitem)
- playutils.set_properties(item, item['PlaybackInfo']['Method'], self.server_id)
+ playutils.set_properties(
+ item, item["PlaybackInfo"]["Method"], self.server_id
+ )
playlist.add(path, listitem, index)
index += 1
@@ -228,463 +248,540 @@ def set_listitem(self, item, listitem, db_id=None, seektime=None, intro=False):
objects = Objects()
API = api.API(item, self.server)
- if item['Type'] in ('MusicArtist', 'MusicAlbum', 'Audio'):
+ if item["Type"] in ("MusicArtist", "MusicAlbum", "Audio"):
- obj = objects.map(item, 'BrowseAudio')
- obj['DbId'] = db_id
- obj['Artwork'] = API.get_all_artwork(objects.map(item, 'ArtworkMusic'), True)
+ obj = objects.map(item, "BrowseAudio")
+ obj["DbId"] = db_id
+ obj["Artwork"] = API.get_all_artwork(
+ objects.map(item, "ArtworkMusic"), True
+ )
self.listitem_music(obj, listitem, item)
- elif item['Type'] in ('Photo', 'PhotoAlbum'):
+ elif item["Type"] in ("Photo", "PhotoAlbum"):
- obj = objects.map(item, 'BrowsePhoto')
- obj['Artwork'] = API.get_all_artwork(objects.map(item, 'Artwork'))
+ obj = objects.map(item, "BrowsePhoto")
+ obj["Artwork"] = API.get_all_artwork(objects.map(item, "Artwork"))
self.listitem_photo(obj, listitem, item)
- elif item['Type'] in ('TvChannel',):
+ elif item["Type"] in ("TvChannel",):
- obj = objects.map(item, 'BrowseChannel')
- obj['Artwork'] = API.get_all_artwork(objects.map(item, 'Artwork'))
+ obj = objects.map(item, "BrowseChannel")
+ obj["Artwork"] = API.get_all_artwork(objects.map(item, "Artwork"))
self.listitem_channel(obj, listitem, item)
else:
- obj = objects.map(item, 'BrowseVideo')
- obj['DbId'] = db_id
- obj['Artwork'] = API.get_all_artwork(objects.map(item, 'ArtworkParent'), True)
+ obj = objects.map(item, "BrowseVideo")
+ obj["DbId"] = db_id
+ obj["Artwork"] = API.get_all_artwork(
+ objects.map(item, "ArtworkParent"), True
+ )
if intro:
- obj['Artwork']['Primary'] = "&KodiCinemaMode=true"
+ obj["Artwork"]["Primary"] = "&KodiCinemaMode=true"
self.listitem_video(obj, listitem, item, seektime, intro)
- if 'PlaybackInfo' in item:
+ if "PlaybackInfo" in item:
if seektime:
- item['PlaybackInfo']['CurrentPosition'] = obj['Resume']
+ item["PlaybackInfo"]["CurrentPosition"] = obj["Resume"]
- if 'SubtitleUrl' in item['PlaybackInfo']:
+ if "SubtitleUrl" in item["PlaybackInfo"]:
- LOG.info("[ subtitles ] %s", item['PlaybackInfo']['SubtitleUrl'])
- listitem.setSubtitles([item['PlaybackInfo']['SubtitleUrl']])
+ LOG.info("[ subtitles ] %s", item["PlaybackInfo"]["SubtitleUrl"])
+ listitem.setSubtitles([item["PlaybackInfo"]["SubtitleUrl"]])
- if item['Type'] == 'Episode':
+ if item["Type"] == "Episode":
- item['PlaybackInfo']['CurrentEpisode'] = objects.map(item, "UpNext")
- item['PlaybackInfo']['CurrentEpisode']['art'] = {
- 'tvshow.poster': obj['Artwork'].get('Series.Primary'),
- 'thumb': obj['Artwork'].get('Primary'),
- 'tvshow.fanart': None
+ item["PlaybackInfo"]["CurrentEpisode"] = objects.map(item, "UpNext")
+ item["PlaybackInfo"]["CurrentEpisode"]["art"] = {
+ "tvshow.poster": obj["Artwork"].get("Series.Primary"),
+ "thumb": obj["Artwork"].get("Primary"),
+ "tvshow.fanart": None,
}
- if obj['Artwork']['Backdrop']:
- item['PlaybackInfo']['CurrentEpisode']['art']['tvshow.fanart'] = obj['Artwork']['Backdrop'][0]
+ if obj["Artwork"]["Backdrop"]:
+ item["PlaybackInfo"]["CurrentEpisode"]["art"][
+ "tvshow.fanart"
+ ] = obj["Artwork"]["Backdrop"][0]
listitem.setContentLookup(False)
def listitem_video(self, obj, listitem, item, seektime=None, intro=False):
-
- ''' Set listitem for video content. That also include streams.
- '''
+ """Set listitem for video content. That also include streams."""
API = api.API(item, self.server)
- is_video = obj['MediaType'] in ('Video', 'Audio') # audiobook
-
- obj['Genres'] = " / ".join(obj['Genres'] or [])
- obj['Studios'] = [API.validate_studio(studio) for studio in (obj['Studios'] or [])]
- obj['Studios'] = " / ".join(obj['Studios'])
- obj['Mpaa'] = API.get_mpaa(obj['Mpaa'])
- obj['People'] = obj['People'] or []
- obj['Countries'] = " / ".join(obj['Countries'] or [])
- obj['Directors'] = " / ".join(obj['Directors'] or [])
- obj['Writers'] = " / ".join(obj['Writers'] or [])
- obj['Plot'] = API.get_overview(obj['Plot'])
- obj['ShortPlot'] = API.get_overview(obj['ShortPlot'])
- obj['DateAdded'] = obj['DateAdded'].split('.')[0].replace('T', " ")
- obj['Rating'] = obj['Rating'] or 0
- obj['FileDate'] = "%s.%s.%s" % tuple(reversed(obj['DateAdded'].split('T')[0].split('-')))
- obj['Runtime'] = round(float((obj['Runtime'] or 0) / 10000000.0), 6)
- obj['Resume'] = API.adjust_resume((obj['Resume'] or 0) / 10000000.0)
- obj['PlayCount'] = API.get_playcount(obj['Played'], obj['PlayCount']) or 0
- obj['Overlay'] = 7 if obj['Played'] else 6
- obj['Video'] = API.video_streams(obj['Video'] or [], obj['Container'])
- obj['Audio'] = API.audio_streams(obj['Audio'] or [])
- obj['Streams'] = API.media_streams(obj['Video'], obj['Audio'], obj['Subtitles'])
- obj['ChildCount'] = obj['ChildCount'] or 0
- obj['RecursiveCount'] = obj['RecursiveCount'] or 0
- obj['Unwatched'] = obj['Unwatched'] or 0
- obj['Artwork']['Backdrop'] = obj['Artwork']['Backdrop'] or []
- obj['Artwork']['Thumb'] = obj['Artwork']['Thumb'] or ""
-
- if not intro and obj['Type'] != 'Trailer':
- obj['Artwork']['Primary'] = obj['Artwork']['Primary'] \
+ is_video = obj["MediaType"] in ("Video", "Audio") # audiobook
+
+ obj["Genres"] = " / ".join(obj["Genres"] or [])
+ obj["Studios"] = [
+ API.validate_studio(studio) for studio in (obj["Studios"] or [])
+ ]
+ obj["Studios"] = " / ".join(obj["Studios"])
+ obj["Mpaa"] = API.get_mpaa(obj["Mpaa"])
+ obj["People"] = obj["People"] or []
+ obj["Countries"] = " / ".join(obj["Countries"] or [])
+ obj["Directors"] = " / ".join(obj["Directors"] or [])
+ obj["Writers"] = " / ".join(obj["Writers"] or [])
+ obj["Plot"] = API.get_overview(obj["Plot"])
+ obj["ShortPlot"] = API.get_overview(obj["ShortPlot"])
+ obj["DateAdded"] = obj["DateAdded"].split(".")[0].replace("T", " ")
+ obj["Rating"] = obj["Rating"] or 0
+ obj["FileDate"] = "%s.%s.%s" % tuple(
+ reversed(obj["DateAdded"].split("T")[0].split("-"))
+ )
+ obj["Runtime"] = round(float((obj["Runtime"] or 0) / 10000000.0), 6)
+ obj["Resume"] = API.adjust_resume((obj["Resume"] or 0) / 10000000.0)
+ obj["PlayCount"] = API.get_playcount(obj["Played"], obj["PlayCount"]) or 0
+ obj["Overlay"] = 7 if obj["Played"] else 6
+ obj["Video"] = API.video_streams(obj["Video"] or [], obj["Container"])
+ obj["Audio"] = API.audio_streams(obj["Audio"] or [])
+ obj["Streams"] = API.media_streams(obj["Video"], obj["Audio"], obj["Subtitles"])
+ obj["ChildCount"] = obj["ChildCount"] or 0
+ obj["RecursiveCount"] = obj["RecursiveCount"] or 0
+ obj["Unwatched"] = obj["Unwatched"] or 0
+ obj["Artwork"]["Backdrop"] = obj["Artwork"]["Backdrop"] or []
+ obj["Artwork"]["Thumb"] = obj["Artwork"]["Thumb"] or ""
+
+ if not intro and obj["Type"] != "Trailer":
+ obj["Artwork"]["Primary"] = (
+ obj["Artwork"]["Primary"]
or "special://home/addons/plugin.video.jellyfin/resources/icon.png"
+ )
else:
- obj['Artwork']['Primary'] = obj['Artwork']['Primary'] \
- or obj['Artwork']['Thumb'] \
- or (obj['Artwork']['Backdrop'][0]
- if len(obj['Artwork']['Backdrop'])
- else "special://home/addons/plugin.video.jellyfin/resources/fanart.png")
- obj['Artwork']['Primary'] += "&KodiTrailer=true" \
- if obj['Type'] == 'Trailer' else "&KodiCinemaMode=true"
- obj['Artwork']['Backdrop'] = [obj['Artwork']['Primary']]
-
- self.set_artwork(obj['Artwork'], listitem, obj['Type'])
-
- if intro or obj['Type'] == 'Trailer':
- listitem.setArt({'poster': ""}) # Clear the poster value for intros / trailers to prevent issues in skins
-
- listitem.setArt({
- 'icon': 'DefaultVideo.png',
- 'thumb': obj['Artwork']['Primary'],
- })
+ obj["Artwork"]["Primary"] = (
+ obj["Artwork"]["Primary"]
+ or obj["Artwork"]["Thumb"]
+ or (
+ obj["Artwork"]["Backdrop"][0]
+ if len(obj["Artwork"]["Backdrop"])
+ else "special://home/addons/plugin.video.jellyfin/resources/fanart.png"
+ )
+ )
+ obj["Artwork"]["Primary"] += (
+ "&KodiTrailer=true"
+ if obj["Type"] == "Trailer"
+ else "&KodiCinemaMode=true"
+ )
+ obj["Artwork"]["Backdrop"] = [obj["Artwork"]["Primary"]]
+
+ self.set_artwork(obj["Artwork"], listitem, obj["Type"])
+
+ if intro or obj["Type"] == "Trailer":
+ listitem.setArt(
+ {"poster": ""}
+ ) # Clear the poster value for intros / trailers to prevent issues in skins
+
+ listitem.setArt(
+ {
+ "icon": "DefaultVideo.png",
+ "thumb": obj["Artwork"]["Primary"],
+ }
+ )
- if obj['Premiere']:
- obj['Premiere'] = obj['Premiere'].split('T')[0]
+ if obj["Premiere"]:
+ obj["Premiere"] = obj["Premiere"].split("T")[0]
- if obj['DatePlayed']:
- obj['DatePlayed'] = obj['DatePlayed'].split('.')[0].replace('T', " ")
+ if obj["DatePlayed"]:
+ obj["DatePlayed"] = obj["DatePlayed"].split(".")[0].replace("T", " ")
metadata = {
- 'title': obj['Title'],
- 'originaltitle': obj['Title'],
- 'sorttitle': obj['SortTitle'],
- 'country': obj['Countries'],
- 'genre': obj['Genres'],
- 'year': obj['Year'],
- 'rating': obj['Rating'],
- 'playcount': obj['PlayCount'],
- 'overlay': obj['Overlay'],
- 'director': obj['Directors'],
- 'mpaa': obj['Mpaa'],
- 'plot': obj['Plot'],
- 'plotoutline': obj['ShortPlot'],
- 'studio': obj['Studios'],
- 'tagline': obj['Tagline'],
- 'writer': obj['Writers'],
- 'premiered': obj['Premiere'],
- 'votes': obj['Votes'],
- 'dateadded': obj['DateAdded'],
- 'aired': obj['Year'],
- 'date': obj['FileDate'],
- 'dbid': obj['DbId']
+ "title": obj["Title"],
+ "originaltitle": obj["Title"],
+ "sorttitle": obj["SortTitle"],
+ "country": obj["Countries"],
+ "genre": obj["Genres"],
+ "year": obj["Year"],
+ "rating": obj["Rating"],
+ "playcount": obj["PlayCount"],
+ "overlay": obj["Overlay"],
+ "director": obj["Directors"],
+ "mpaa": obj["Mpaa"],
+ "plot": obj["Plot"],
+ "plotoutline": obj["ShortPlot"],
+ "studio": obj["Studios"],
+ "tagline": obj["Tagline"],
+ "writer": obj["Writers"],
+ "premiered": obj["Premiere"],
+ "votes": obj["Votes"],
+ "dateadded": obj["DateAdded"],
+ "aired": obj["Year"],
+ "date": obj["FileDate"],
+ "dbid": obj["DbId"],
}
listitem.setCast(API.get_actors())
- if obj['Premiere']:
- metadata['date'] = obj['Premiere']
-
- if obj['Type'] == 'Episode':
- metadata.update({
- 'mediatype': "episode",
- 'tvshowtitle': obj['SeriesName'],
- 'season': obj['Season'] or 0,
- 'sortseason': obj['Season'] or 0,
- 'episode': obj['Index'] or 0,
- 'sortepisode': obj['Index'] or 0,
- 'lastplayed': obj['DatePlayed'],
- 'duration': obj['Runtime'],
- 'aired': obj['Premiere'],
- })
-
- elif obj['Type'] == 'Season':
- metadata.update({
- 'mediatype': "season",
- 'tvshowtitle': obj['SeriesName'],
- 'season': obj['Index'] or 0,
- 'sortseason': obj['Index'] or 0
- })
- listitem.setProperty('NumEpisodes', str(obj['RecursiveCount']))
- listitem.setProperty('WatchedEpisodes', str(obj['RecursiveCount'] - obj['Unwatched']))
- listitem.setProperty('UnWatchedEpisodes', str(obj['Unwatched']))
- listitem.setProperty('IsFolder', 'true')
-
- elif obj['Type'] == 'Series':
-
- if obj['Status'] != 'Ended':
- obj['Status'] = None
-
- metadata.update({
- 'mediatype': "tvshow",
- 'tvshowtitle': obj['Title'],
- 'status': obj['Status']
- })
- listitem.setProperty('TotalSeasons', str(obj['ChildCount']))
- listitem.setProperty('TotalEpisodes', str(obj['RecursiveCount']))
- listitem.setProperty('WatchedEpisodes', str(obj['RecursiveCount'] - obj['Unwatched']))
- listitem.setProperty('UnWatchedEpisodes', str(obj['Unwatched']))
- listitem.setProperty('IsFolder', 'true')
-
- elif obj['Type'] == 'Movie':
- metadata.update({
- 'mediatype': "movie",
- 'imdbnumber': obj['UniqueId'],
- 'lastplayed': obj['DatePlayed'],
- 'duration': obj['Runtime'],
- })
-
- elif obj['Type'] == 'MusicVideo':
- metadata.update({
- 'mediatype': "musicvideo",
- 'album': obj['Album'],
- 'artist': obj['Artists'] or [],
- 'lastplayed': obj['DatePlayed'],
- 'duration': obj['Runtime']
- })
-
- elif obj['Type'] == 'BoxSet':
- metadata['mediatype'] = "set"
- listitem.setProperty('IsFolder', 'true')
+ if obj["Premiere"]:
+ metadata["date"] = obj["Premiere"]
+
+ if obj["Type"] == "Episode":
+ metadata.update(
+ {
+ "mediatype": "episode",
+ "tvshowtitle": obj["SeriesName"],
+ "season": obj["Season"] or 0,
+ "sortseason": obj["Season"] or 0,
+ "episode": obj["Index"] or 0,
+ "sortepisode": obj["Index"] or 0,
+ "lastplayed": obj["DatePlayed"],
+ "duration": obj["Runtime"],
+ "aired": obj["Premiere"],
+ }
+ )
+
+ elif obj["Type"] == "Season":
+ metadata.update(
+ {
+ "mediatype": "season",
+ "tvshowtitle": obj["SeriesName"],
+ "season": obj["Index"] or 0,
+ "sortseason": obj["Index"] or 0,
+ }
+ )
+ listitem.setProperty("NumEpisodes", str(obj["RecursiveCount"]))
+ listitem.setProperty(
+ "WatchedEpisodes", str(obj["RecursiveCount"] - obj["Unwatched"])
+ )
+ listitem.setProperty("UnWatchedEpisodes", str(obj["Unwatched"]))
+ listitem.setProperty("IsFolder", "true")
+
+ elif obj["Type"] == "Series":
+
+ if obj["Status"] != "Ended":
+ obj["Status"] = None
+
+ metadata.update(
+ {
+ "mediatype": "tvshow",
+ "tvshowtitle": obj["Title"],
+ "status": obj["Status"],
+ }
+ )
+ listitem.setProperty("TotalSeasons", str(obj["ChildCount"]))
+ listitem.setProperty("TotalEpisodes", str(obj["RecursiveCount"]))
+ listitem.setProperty(
+ "WatchedEpisodes", str(obj["RecursiveCount"] - obj["Unwatched"])
+ )
+ listitem.setProperty("UnWatchedEpisodes", str(obj["Unwatched"]))
+ listitem.setProperty("IsFolder", "true")
+
+ elif obj["Type"] == "Movie":
+ metadata.update(
+ {
+ "mediatype": "movie",
+ "imdbnumber": obj["UniqueId"],
+ "lastplayed": obj["DatePlayed"],
+ "duration": obj["Runtime"],
+ }
+ )
+
+ elif obj["Type"] == "MusicVideo":
+ metadata.update(
+ {
+ "mediatype": "musicvideo",
+ "album": obj["Album"],
+ "artist": obj["Artists"] or [],
+ "lastplayed": obj["DatePlayed"],
+ "duration": obj["Runtime"],
+ }
+ )
+
+ elif obj["Type"] == "BoxSet":
+ metadata["mediatype"] = "set"
+ listitem.setProperty("IsFolder", "true")
else:
- metadata.update({
- 'mediatype': "video",
- 'lastplayed': obj['DatePlayed'],
- 'year': obj['Year'],
- 'duration': obj['Runtime']
- })
+ metadata.update(
+ {
+ "mediatype": "video",
+ "lastplayed": obj["DatePlayed"],
+ "year": obj["Year"],
+ "duration": obj["Runtime"],
+ }
+ )
if is_video:
- listitem.setProperty('totaltime', str(obj['Runtime']))
- listitem.setProperty('IsPlayable', 'true')
- listitem.setProperty('IsFolder', 'false')
+ listitem.setProperty("totaltime", str(obj["Runtime"]))
+ listitem.setProperty("IsPlayable", "true")
+ listitem.setProperty("IsFolder", "false")
- if obj['Resume'] and item.get("resumePlayback"):
- listitem.setProperty('resumetime', str(obj['Resume']))
- listitem.setProperty('StartPercent', str(((obj['Resume'] / obj['Runtime']) * 100) - 0.40))
+ if obj["Resume"] and item.get("resumePlayback"):
+ listitem.setProperty("resumetime", str(obj["Resume"]))
+ listitem.setProperty(
+ "StartPercent", str(((obj["Resume"] / obj["Runtime"]) * 100) - 0.40)
+ )
else:
- listitem.setProperty('resumetime', '0')
- listitem.setProperty('StartPercent', '0')
-
- for track in obj['Streams']['video']:
- listitem.addStreamInfo('video', {
- 'hdrtype': track['hdrtype'],
- 'duration': obj['Runtime'],
- 'aspect': track['aspect'],
- 'codec': track['codec'],
- 'width': track['width'],
- 'height': track['height']
- })
-
- for track in obj['Streams']['audio']:
- listitem.addStreamInfo('audio', {'codec': track['codec'], 'channels': track['channels']})
-
- for track in obj['Streams']['subtitle']:
- listitem.addStreamInfo('subtitle', {'language': track})
-
- listitem.setLabel(obj['Title'])
- listitem.setInfo('video', metadata)
+ listitem.setProperty("resumetime", "0")
+ listitem.setProperty("StartPercent", "0")
+
+ for track in obj["Streams"]["video"]:
+ listitem.addStreamInfo(
+ "video",
+ {
+ "hdrtype": track["hdrtype"],
+ "duration": obj["Runtime"],
+ "aspect": track["aspect"],
+ "codec": track["codec"],
+ "width": track["width"],
+ "height": track["height"],
+ },
+ )
+
+ for track in obj["Streams"]["audio"]:
+ listitem.addStreamInfo(
+ "audio", {"codec": track["codec"], "channels": track["channels"]}
+ )
+
+ for track in obj["Streams"]["subtitle"]:
+ listitem.addStreamInfo("subtitle", {"language": track})
+
+ listitem.setLabel(obj["Title"])
+ listitem.setInfo("video", metadata)
listitem.setContentLookup(False)
def listitem_channel(self, obj, listitem, item):
-
- ''' Set listitem for channel content.
- '''
+ """Set listitem for channel content."""
API = api.API(item, self.server)
- obj['Title'] = "%s - %s" % (obj['Title'], obj['ProgramName'])
- obj['Runtime'] = round(float((obj['Runtime'] or 0) / 10000000.0), 6)
- obj['PlayCount'] = API.get_playcount(obj['Played'], obj['PlayCount']) or 0
- obj['Overlay'] = 7 if obj['Played'] else 6
- obj['Artwork']['Primary'] = obj['Artwork']['Primary'] \
+ obj["Title"] = "%s - %s" % (obj["Title"], obj["ProgramName"])
+ obj["Runtime"] = round(float((obj["Runtime"] or 0) / 10000000.0), 6)
+ obj["PlayCount"] = API.get_playcount(obj["Played"], obj["PlayCount"]) or 0
+ obj["Overlay"] = 7 if obj["Played"] else 6
+ obj["Artwork"]["Primary"] = (
+ obj["Artwork"]["Primary"]
or "special://home/addons/plugin.video.jellyfin/resources/icon.png"
- obj['Artwork']['Thumb'] = obj['Artwork']['Thumb'] \
+ )
+ obj["Artwork"]["Thumb"] = (
+ obj["Artwork"]["Thumb"]
or "special://home/addons/plugin.video.jellyfin/resources/fanart.png"
- obj['Artwork']['Backdrop'] = obj['Artwork']['Backdrop'] \
- or ["special://home/addons/plugin.video.jellyfin/resources/fanart.png"]
+ )
+ obj["Artwork"]["Backdrop"] = obj["Artwork"]["Backdrop"] or [
+ "special://home/addons/plugin.video.jellyfin/resources/fanart.png"
+ ]
metadata = {
- 'title': obj['Title'],
- 'originaltitle': obj['Title'],
- 'playcount': obj['PlayCount'],
- 'overlay': obj['Overlay']
+ "title": obj["Title"],
+ "originaltitle": obj["Title"],
+ "playcount": obj["PlayCount"],
+ "overlay": obj["Overlay"],
}
- listitem.setArt({
- 'icon': obj['Artwork']['Thumb'],
- 'thumb': obj['Artwork']['Primary'],
- })
- self.set_artwork(obj['Artwork'], listitem, obj['Type'])
+ listitem.setArt(
+ {
+ "icon": obj["Artwork"]["Thumb"],
+ "thumb": obj["Artwork"]["Primary"],
+ }
+ )
+ self.set_artwork(obj["Artwork"], listitem, obj["Type"])
- if obj['Artwork']['Primary']:
- listitem.setArt({
- 'thumb': obj['Artwork']['Primary'],
- })
+ if obj["Artwork"]["Primary"]:
+ listitem.setArt(
+ {
+ "thumb": obj["Artwork"]["Primary"],
+ }
+ )
- if not obj['Artwork']['Backdrop']:
- listitem.setArt({'fanart': obj['Artwork']['Primary']})
+ if not obj["Artwork"]["Backdrop"]:
+ listitem.setArt({"fanart": obj["Artwork"]["Primary"]})
- listitem.setProperty('totaltime', str(obj['Runtime']))
- listitem.setProperty('IsPlayable', 'true')
- listitem.setProperty('IsFolder', 'false')
+ listitem.setProperty("totaltime", str(obj["Runtime"]))
+ listitem.setProperty("IsPlayable", "true")
+ listitem.setProperty("IsFolder", "false")
- listitem.setLabel(obj['Title'])
- listitem.setInfo('video', metadata)
+ listitem.setLabel(obj["Title"])
+ listitem.setInfo("video", metadata)
listitem.setContentLookup(False)
def listitem_music(self, obj, listitem, item):
API = api.API(item, self.server)
- obj['Runtime'] = round(float((obj['Runtime'] or 0) / 10000000.0), 6)
- obj['PlayCount'] = API.get_playcount(obj['Played'], obj['PlayCount']) or 0
- obj['Rating'] = obj['Rating'] or 0
+ obj["Runtime"] = round(float((obj["Runtime"] or 0) / 10000000.0), 6)
+ obj["PlayCount"] = API.get_playcount(obj["Played"], obj["PlayCount"]) or 0
+ obj["Rating"] = obj["Rating"] or 0
- if not obj['Played']:
- obj['DatePlayed'] = None
- elif obj['FileDate'] or obj['DatePlayed']:
- obj['DatePlayed'] = (obj['DatePlayed'] or obj['FileDate']).split('.')[0].replace('T', " ")
+ if not obj["Played"]:
+ obj["DatePlayed"] = None
+ elif obj["FileDate"] or obj["DatePlayed"]:
+ obj["DatePlayed"] = (
+ (obj["DatePlayed"] or obj["FileDate"]).split(".")[0].replace("T", " ")
+ )
- obj['FileDate'] = "%s.%s.%s" % tuple(reversed(obj['FileDate'].split('T')[0].split('-')))
+ obj["FileDate"] = "%s.%s.%s" % tuple(
+ reversed(obj["FileDate"].split("T")[0].split("-"))
+ )
metadata = {
- 'title': obj['Title'],
- 'genre': obj['Genre'],
- 'year': obj['Year'],
- 'album': obj['Album'],
- 'artist': obj['Artists'],
- 'rating': obj['Rating'],
- 'comment': obj['Comment'],
- 'date': obj['FileDate']
+ "title": obj["Title"],
+ "genre": obj["Genre"],
+ "year": obj["Year"],
+ "album": obj["Album"],
+ "artist": obj["Artists"],
+ "rating": obj["Rating"],
+ "comment": obj["Comment"],
+ "date": obj["FileDate"],
}
- self.set_artwork(obj['Artwork'], listitem, obj['Type'])
-
- if obj['Type'] == 'Audio':
- metadata.update({
- 'mediatype': "song",
- 'tracknumber': obj['Index'],
- 'discnumber': obj['Disc'],
- 'duration': obj['Runtime'],
- 'playcount': obj['PlayCount'],
- 'lastplayed': obj['DatePlayed'],
- 'musicbrainztrackid': obj['UniqueId']
- })
- listitem.setProperty('IsPlayable', 'true')
- listitem.setProperty('IsFolder', 'false')
-
- elif obj['Type'] == 'Album':
- metadata.update({
- 'mediatype': "album",
- 'musicbrainzalbumid': obj['UniqueId']
- })
-
- elif obj['Type'] in ('Artist', 'MusicArtist'):
- metadata.update({
- 'mediatype': "artist",
- 'musicbrainzartistid': obj['UniqueId']
- })
+ self.set_artwork(obj["Artwork"], listitem, obj["Type"])
+
+ if obj["Type"] == "Audio":
+ metadata.update(
+ {
+ "mediatype": "song",
+ "tracknumber": obj["Index"],
+ "discnumber": obj["Disc"],
+ "duration": obj["Runtime"],
+ "playcount": obj["PlayCount"],
+ "lastplayed": obj["DatePlayed"],
+ "musicbrainztrackid": obj["UniqueId"],
+ }
+ )
+ listitem.setProperty("IsPlayable", "true")
+ listitem.setProperty("IsFolder", "false")
+
+ elif obj["Type"] == "Album":
+ metadata.update(
+ {"mediatype": "album", "musicbrainzalbumid": obj["UniqueId"]}
+ )
+
+ elif obj["Type"] in ("Artist", "MusicArtist"):
+ metadata.update(
+ {"mediatype": "artist", "musicbrainzartistid": obj["UniqueId"]}
+ )
else:
- metadata['mediatype'] = "music"
+ metadata["mediatype"] = "music"
- listitem.setLabel(obj['Title'])
- listitem.setInfo('music', metadata)
+ listitem.setLabel(obj["Title"])
+ listitem.setInfo("music", metadata)
listitem.setContentLookup(False)
def listitem_photo(self, obj, listitem, item):
API = api.API(item, self.server)
- obj['Overview'] = API.get_overview(obj['Overview'])
- obj['FileDate'] = "%s.%s.%s" % tuple(reversed(obj['FileDate'].split('T')[0].split('-')))
+ obj["Overview"] = API.get_overview(obj["Overview"])
+ obj["FileDate"] = "%s.%s.%s" % tuple(
+ reversed(obj["FileDate"].split("T")[0].split("-"))
+ )
- metadata = {
- 'title': obj['Title']
- }
- listitem.setProperty('path', obj['Artwork']['Primary'])
- listitem.setArt({
- 'thumb': obj['Artwork']['Primary'],
- })
-
- if obj['Type'] == 'Photo':
- metadata.update({
- 'picturepath': obj['Artwork']['Primary'],
- 'date': obj['FileDate'],
- 'exif:width': str(obj.get('Width', 0)),
- 'exif:height': str(obj.get('Height', 0)),
- 'size': obj['Size'],
- 'exif:cameramake': obj['CameraMake'],
- 'exif:cameramodel': obj['CameraModel'],
- 'exif:exposuretime': str(obj['ExposureTime']),
- 'exif:focallength': str(obj['FocalLength'])
- })
- listitem.setProperty('plot', obj['Overview'])
- listitem.setProperty('IsFolder', 'false')
- listitem.setArt({
- 'icon': 'DefaultPicture.png',
- })
+ metadata = {"title": obj["Title"]}
+ listitem.setProperty("path", obj["Artwork"]["Primary"])
+ listitem.setArt(
+ {
+ "thumb": obj["Artwork"]["Primary"],
+ }
+ )
+
+ if obj["Type"] == "Photo":
+ metadata.update(
+ {
+ "picturepath": obj["Artwork"]["Primary"],
+ "date": obj["FileDate"],
+ "exif:width": str(obj.get("Width", 0)),
+ "exif:height": str(obj.get("Height", 0)),
+ "size": obj["Size"],
+ "exif:cameramake": obj["CameraMake"],
+ "exif:cameramodel": obj["CameraModel"],
+ "exif:exposuretime": str(obj["ExposureTime"]),
+ "exif:focallength": str(obj["FocalLength"]),
+ }
+ )
+ listitem.setProperty("plot", obj["Overview"])
+ listitem.setProperty("IsFolder", "false")
+ listitem.setArt(
+ {
+ "icon": "DefaultPicture.png",
+ }
+ )
else:
- listitem.setProperty('IsFolder', 'true')
- listitem.setArt({
- 'icon': 'DefaultFolder.png',
- })
-
- listitem.setProperty('IsPlayable', 'false')
- listitem.setLabel(obj['Title'])
- listitem.setInfo('pictures', metadata)
+ listitem.setProperty("IsFolder", "true")
+ listitem.setArt(
+ {
+ "icon": "DefaultFolder.png",
+ }
+ )
+
+ listitem.setProperty("IsPlayable", "false")
+ listitem.setLabel(obj["Title"])
+ listitem.setInfo("pictures", metadata)
listitem.setContentLookup(False)
def set_artwork(self, artwork, listitem, media):
- if media == 'Episode':
+ if media == "Episode":
art = {
- 'poster': "Series.Primary",
- 'tvshow.poster': "Series.Primary",
- 'clearart': "Art",
- 'tvshow.clearart': "Art",
- 'clearlogo': "Logo",
- 'tvshow.clearlogo': "Logo",
- 'discart': "Disc",
- 'fanart_image': "Backdrop",
- 'landscape': "Thumb",
- 'tvshow.landscape': "Thumb",
- 'thumb': "Primary",
- 'fanart': "Backdrop"
+ "poster": "Series.Primary",
+ "tvshow.poster": "Series.Primary",
+ "clearart": "Art",
+ "tvshow.clearart": "Art",
+ "clearlogo": "Logo",
+ "tvshow.clearlogo": "Logo",
+ "discart": "Disc",
+ "fanart_image": "Backdrop",
+ "landscape": "Thumb",
+ "tvshow.landscape": "Thumb",
+ "thumb": "Primary",
+ "fanart": "Backdrop",
}
- elif media in ('Artist', 'Audio', 'MusicAlbum'):
+ elif media in ("Artist", "Audio", "MusicAlbum"):
art = {
- 'clearlogo': "Logo",
- 'discart': "Disc",
- 'fanart': "Backdrop",
- 'fanart_image': "Backdrop", # in case
- 'thumb': "Primary"
+ "clearlogo": "Logo",
+ "discart": "Disc",
+ "fanart": "Backdrop",
+ "fanart_image": "Backdrop", # in case
+ "thumb": "Primary",
}
else:
art = {
- 'poster': "Primary",
- 'clearart': "Art",
- 'clearlogo': "Logo",
- 'discart': "Disc",
- 'fanart_image': "Backdrop",
- 'landscape': "Thumb",
- 'thumb': "Primary",
- 'fanart': "Backdrop"
+ "poster": "Primary",
+ "clearart": "Art",
+ "clearlogo": "Logo",
+ "discart": "Disc",
+ "fanart_image": "Backdrop",
+ "landscape": "Thumb",
+ "thumb": "Primary",
+ "fanart": "Backdrop",
}
for k_art, e_art in art.items():
if e_art == "Backdrop":
- self._set_art(listitem, k_art, artwork[e_art][0] if artwork[e_art] else " ")
+ self._set_art(
+ listitem, k_art, artwork[e_art][0] if artwork[e_art] else " "
+ )
else:
self._set_art(listitem, k_art, artwork.get(e_art, " "))
def _set_art(self, listitem, art, path):
LOG.debug(" [ art/%s ] %s", art, path)
- if art in ('fanart_image', 'small_poster', 'tiny_poster',
- 'medium_landscape', 'medium_poster', 'small_fanartimage',
- 'medium_fanartimage', 'fanart_noindicators', 'discart',
- 'tvshow.poster'):
+ if art in (
+ "fanart_image",
+ "small_poster",
+ "tiny_poster",
+ "medium_landscape",
+ "medium_poster",
+ "small_fanartimage",
+ "medium_fanartimage",
+ "fanart_noindicators",
+ "discart",
+ "tvshow.poster",
+ ):
listitem.setProperty(art, path)
else:
listitem.setArt({art: path})
def resume_dialog(self, seektime):
-
- ''' Base resume dialog based on Kodi settings.
- '''
+ """Base resume dialog based on Kodi settings."""
LOG.info("Resume dialog called.")
- XML_PATH = (xbmcaddon.Addon('plugin.video.jellyfin').getAddonInfo('path'), "default", "1080i")
+ XML_PATH = (
+ xbmcaddon.Addon("plugin.video.jellyfin").getAddonInfo("path"),
+ "default",
+ "1080i",
+ )
dialog = resume.ResumeDialog("script-jellyfin-resume.xml", *XML_PATH)
- dialog.set_resume_point("Resume from %s" % str(timedelta(seconds=seektime)).split(".")[0])
+ dialog.set_resume_point(
+ "Resume from %s" % str(timedelta(seconds=seektime)).split(".")[0]
+ )
dialog.doModal()
if dialog.is_selected():
@@ -711,13 +808,11 @@ def run(self):
def on_update(data, server):
-
- ''' Only for manually marking as watched/unwatched
- '''
+ """Only for manually marking as watched/unwatched"""
try:
- kodi_id = data['item']['id']
- media = data['item']['type']
- playcount = int(data['playcount'])
+ kodi_id = data["item"]["id"]
+ media = data["item"]["type"]
+ playcount = int(data["playcount"])
LOG.info(" [ update/%s ] kodi_id: %s media: %s", playcount, kodi_id, media)
except (KeyError, TypeError):
LOG.debug("Invalid playstate update")
@@ -725,20 +820,19 @@ def on_update(data, server):
return
from .. import database
+
item = database.get_item(kodi_id, media)
if item:
- if not window('jellyfin.skip.%s.bool' % item[0]):
+ if not window("jellyfin.skip.%s.bool" % item[0]):
server.jellyfin.item_played(item[0], playcount)
- window('jellyfin.skip.%s' % item[0], clear=True)
+ window("jellyfin.skip.%s" % item[0], clear=True)
def on_play(data, server):
-
- ''' Setup progress for jellyfin playback.
- '''
+ """Setup progress for jellyfin playback."""
player = xbmc.Player()
try:
@@ -746,19 +840,25 @@ def on_play(data, server):
if player.isPlayingVideo():
- ''' Seems to misbehave when playback is not terminated prior to playing new content.
- The kodi id remains that of the previous title. Maybe onPlay happens before
- this information is updated. Added a failsafe further below.
- '''
+ """Seems to misbehave when playback is not terminated prior to playing new content.
+ The kodi id remains that of the previous title. Maybe onPlay happens before
+ this information is updated. Added a failsafe further below.
+ """
item = player.getVideoInfoTag()
kodi_id = item.getDbId()
media = item.getMediaType()
- if kodi_id is None or int(kodi_id) == -1 or 'item' in data and 'id' in data['item'] and data['item']['id'] != kodi_id:
+ if (
+ kodi_id is None
+ or int(kodi_id) == -1
+ or "item" in data
+ and "id" in data["item"]
+ and data["item"]["id"] != kodi_id
+ ):
- item = data['item']
- kodi_id = item['id']
- media = item['type']
+ item = data["item"]
+ kodi_id = item["id"]
+ media = item["type"]
LOG.info(" [ play ] kodi_id: %s media: %s", kodi_id, media)
@@ -767,8 +867,9 @@ def on_play(data, server):
return
- if settings('useDirectPaths') == '1' or media == 'song':
+ if settings("useDirectPaths") == "1" or media == "song":
from .. import database
+
item = database.get_item(kodi_id, media)
if item:
@@ -781,32 +882,34 @@ def on_play(data, server):
return
item = server.jellyfin.get_item(item[0])
- item['PlaybackInfo'] = {'Path': file}
- playutils.set_properties(item, 'DirectStream' if settings('useDirectPaths') == '0' else 'DirectPlay')
+ item["PlaybackInfo"] = {"Path": file}
+ playutils.set_properties(
+ item,
+ "DirectStream" if settings("useDirectPaths") == "0" else "DirectPlay",
+ )
def special_listener():
-
- ''' Corner cases that needs to be listened to.
- This is run in a loop within monitor.py
- '''
+ """Corner cases that needs to be listened to.
+ This is run in a loop within monitor.py
+ """
player = xbmc.Player()
is_playing = player.isPlaying()
- count = int(window('jellyfin.external_count') or 0)
+ count = int(window("jellyfin.external_count") or 0)
- if is_playing and not window('jellyfin.external_check'):
+ if is_playing and not window("jellyfin.external_check"):
time = player.getTime()
if time > 1: # Not external player.
- window('jellyfin.external_check', value="true")
- window('jellyfin.external_count', value="0")
+ window("jellyfin.external_check", value="true")
+ window("jellyfin.external_count", value="0")
elif count == 120:
LOG.info("External player detected.")
- window('jellyfin.external.bool', True)
- window('jellyfin.external_check.bool', True)
- window('jellyfin.external_count', value="0")
+ window("jellyfin.external.bool", True)
+ window("jellyfin.external_check.bool", True)
+ window("jellyfin.external_count", value="0")
elif time == 0:
- window('jellyfin.external_count', value=str(count + 1))
+ window("jellyfin.external_count", value=str(count + 1))
diff --git a/jellyfin_kodi/objects/kodi/artwork.py b/jellyfin_kodi/objects/kodi/artwork.py
index a37320a86..7162c0502 100644
--- a/jellyfin_kodi/objects/kodi/artwork.py
+++ b/jellyfin_kodi/objects/kodi/artwork.py
@@ -21,15 +21,21 @@ def __init__(self, cursor):
self.cursor = cursor
def update(self, image_url, kodi_id, media, image):
-
- ''' Update artwork in the video database.
- Delete current entry before updating with the new one.
- '''
- if not image_url or image == 'poster' and media in ('song', 'artist', 'album'):
+ """Update artwork in the video database.
+ Delete current entry before updating with the new one.
+ """
+ if not image_url or image == "poster" and media in ("song", "artist", "album"):
return
try:
- self.cursor.execute(QU.get_art, (kodi_id, media, image,))
+ self.cursor.execute(
+ QU.get_art,
+ (
+ kodi_id,
+ media,
+ image,
+ ),
+ )
url = self.cursor.fetchone()[0]
except TypeError:
@@ -41,43 +47,39 @@ def update(self, image_url, kodi_id, media, image):
self.cursor.execute(QU.update_art, (image_url, kodi_id, media, image))
def add(self, artwork, *args):
-
- ''' Add all artworks.
- '''
+ """Add all artworks."""
KODI = {
- 'Primary': ['thumb', 'poster'],
- 'Banner': "banner",
- 'Logo': "clearlogo",
- 'Art': "clearart",
- 'Thumb': "landscape",
- 'Disc': "discart",
- 'Backdrop': "fanart"
+ "Primary": ["thumb", "poster"],
+ "Banner": "banner",
+ "Logo": "clearlogo",
+ "Art": "clearart",
+ "Thumb": "landscape",
+ "Disc": "discart",
+ "Backdrop": "fanart",
}
for art in KODI:
- if art == 'Backdrop':
+ if art == "Backdrop":
self.cursor.execute(QU.get_backdrops, args + ("fanart%",))
- if len(self.cursor.fetchall()) > len(artwork['Backdrop']):
+ if len(self.cursor.fetchall()) > len(artwork["Backdrop"]):
self.cursor.execute(QU.delete_backdrops, args + ("fanart_",))
- for index, backdrop in enumerate(artwork['Backdrop']):
+ for index, backdrop in enumerate(artwork["Backdrop"]):
if index:
self.update(*(backdrop,) + args + ("%s%s" % ("fanart", index),))
else:
self.update(*(backdrop,) + args + ("fanart",))
- elif art == 'Primary':
- for kodi_image in KODI['Primary']:
- self.update(*(artwork['Primary'],) + args + (kodi_image,))
+ elif art == "Primary":
+ for kodi_image in KODI["Primary"]:
+ self.update(*(artwork["Primary"],) + args + (kodi_image,))
elif artwork.get(art):
self.update(*(artwork[art],) + args + (KODI[art],))
def delete(self, *args):
-
- ''' Delete artwork from kodi database
- '''
+ """Delete artwork from kodi database"""
self.cursor.execute(QU.delete_art, args)
diff --git a/jellyfin_kodi/objects/kodi/kodi.py b/jellyfin_kodi/objects/kodi/kodi.py
index 74b93e338..2edb53b84 100644
--- a/jellyfin_kodi/objects/kodi/kodi.py
+++ b/jellyfin_kodi/objects/kodi/kodi.py
@@ -89,7 +89,13 @@ def remove_path(self, *args):
def add_file(self, filename, path_id):
try:
- self.cursor.execute(QU.get_file, (filename, path_id,))
+ self.cursor.execute(
+ QU.get_file,
+ (
+ filename,
+ path_id,
+ ),
+ )
file_id = self.cursor.fetchone()[0]
except TypeError:
@@ -120,40 +126,49 @@ def add_people(self, people, *args):
def add_thumbnail(person_id, person, person_type):
- if person['imageurl']:
+ if person["imageurl"]:
art = person_type.lower()
if "writing" in art:
art = "writer"
- self.artwork.update(person['imageurl'], person_id, art, "thumb")
+ self.artwork.update(person["imageurl"], person_id, art, "thumb")
cast_order = 1
bulk_updates = {}
for person in people:
- person_id = self.get_person(person['Name'])
+ person_id = self.get_person(person["Name"])
- if person['Type'] == 'Actor':
+ if person["Type"] == "Actor":
sql = QU.update_actor
- role = person.get('Role')
- bulk_updates.setdefault(sql, []).append((person_id,) + args + (role, cast_order,))
+ role = person.get("Role")
+ bulk_updates.setdefault(sql, []).append(
+ (person_id,)
+ + args
+ + (
+ role,
+ cast_order,
+ )
+ )
cast_order += 1
- elif person['Type'] == 'Director':
- sql = QU.update_link.replace("{LinkType}", 'director_link')
+ elif person["Type"] == "Director":
+ sql = QU.update_link.replace("{LinkType}", "director_link")
bulk_updates.setdefault(sql, []).append((person_id,) + args)
- elif person['Type'] == 'Writer':
- sql = QU.update_link.replace("{LinkType}", 'writer_link')
+ elif person["Type"] == "Writer":
+ sql = QU.update_link.replace("{LinkType}", "writer_link")
bulk_updates.setdefault(sql, []).append((person_id,) + args)
- elif person['Type'] == 'Artist':
- sql = QU.insert_link_if_not_exists.replace("{LinkType}", 'actor_link')
- bulk_updates.setdefault(sql, []).append((person_id,) + args + (person_id,) + args)
+ elif person["Type"] == "Artist":
+ sql = QU.insert_link_if_not_exists.replace("{LinkType}", "actor_link")
+ bulk_updates.setdefault(sql, []).append(
+ (person_id,) + args + (person_id,) + args
+ )
- add_thumbnail(person_id, person, person['Type'])
+ add_thumbnail(person_id, person, person["Type"])
for sql, parameters in bulk_updates.items():
self.cursor.executemany(sql, parameters)
@@ -163,8 +178,7 @@ def add_person(self, *args):
return self.cursor.lastrowid
def _get_person(self, name):
- '''Retrieve person from the database, or add them if they don't exist
- '''
+ """Retrieve person from the database, or add them if they don't exist"""
resp = self.cursor.execute(QU.get_person, (name,)).fetchone()
if resp is not None:
return resp[0]
@@ -172,8 +186,7 @@ def _get_person(self, name):
return self.add_person(name)
def get_person(self, name):
- '''Retrieve person from cache, else forward to db query
- '''
+ """Retrieve person from cache, else forward to db query"""
if name in self._people_cache:
return self._people_cache[name]
else:
@@ -182,9 +195,7 @@ def get_person(self, name):
return person_id
def add_genres(self, genres, *args):
-
- ''' Delete current genres first for clean slate.
- '''
+ """Delete current genres first for clean slate."""
self.cursor.execute(QU.delete_genres, args)
for genre in genres:
@@ -230,29 +241,32 @@ def get_studio(self, *args):
return self.add_studio(*args)
def add_streams(self, file_id, streams, runtime):
-
- ''' First remove any existing entries
- Then re-add video, audio and subtitles.
- '''
+ """First remove any existing entries
+ Then re-add video, audio and subtitles.
+ """
self.cursor.execute(QU.delete_streams, (file_id,))
if streams:
- for track in streams['video']:
+ for track in streams["video"]:
- track['FileId'] = file_id
- track['Runtime'] = runtime
+ track["FileId"] = file_id
+ track["Runtime"] = runtime
if kodi_version() < 20:
self.add_stream_video(*values(track, QU.add_stream_video_obj_19))
else:
self.add_stream_video(*values(track, QU.add_stream_video_obj))
- for track in streams['audio']:
+ for track in streams["audio"]:
- track['FileId'] = file_id
+ track["FileId"] = file_id
self.add_stream_audio(*values(track, QU.add_stream_audio_obj))
- for track in streams['subtitle']:
- self.add_stream_sub(*values({'language': track, 'FileId': file_id}, QU.add_stream_sub_obj))
+ for track in streams["subtitle"]:
+ self.add_stream_sub(
+ *values(
+ {"language": track, "FileId": file_id}, QU.add_stream_sub_obj
+ )
+ )
def add_stream_video(self, *args):
if kodi_version() < 20:
@@ -267,17 +281,24 @@ def add_stream_sub(self, *args):
self.cursor.execute(QU.add_stream_sub, args)
def add_playstate(self, file_id, playcount, date_played, resume, *args):
-
- ''' Delete the existing resume point.
- Set the watched count.
- '''
+ """Delete the existing resume point.
+ Set the watched count.
+ """
self.cursor.execute(QU.delete_bookmark, (file_id,))
self.set_playcount(playcount, date_played, file_id)
if resume:
bookmark_id = self.create_entry_bookmark()
- self.cursor.execute(QU.add_bookmark, (bookmark_id, file_id, resume,) + args)
+ self.cursor.execute(
+ QU.add_bookmark,
+ (
+ bookmark_id,
+ file_id,
+ resume,
+ )
+ + args,
+ )
def set_playcount(self, *args):
self.cursor.execute(QU.update_playcount, args)
diff --git a/jellyfin_kodi/objects/kodi/movies.py b/jellyfin_kodi/objects/kodi/movies.py
index 10172c02f..14fba8434 100644
--- a/jellyfin_kodi/objects/kodi/movies.py
+++ b/jellyfin_kodi/objects/kodi/movies.py
@@ -74,15 +74,11 @@ def get_rating_id(self, *args):
return None
def add_ratings(self, *args):
-
- ''' Add ratings, rating type and votes.
- '''
+ """Add ratings, rating type and votes."""
self.cursor.execute(QU.add_rating, args)
def update_ratings(self, *args):
-
- ''' Update rating by rating_id.
- '''
+ """Update rating by rating_id."""
self.cursor.execute(QU.update_rating, args)
def get_unique_id(self, *args):
@@ -95,15 +91,11 @@ def get_unique_id(self, *args):
return
def add_unique_id(self, *args):
-
- ''' Add the provider id, imdb, tvdb.
- '''
+ """Add the provider id, imdb, tvdb."""
self.cursor.execute(QU.add_unique_id, args)
def update_unique_id(self, *args):
-
- ''' Update the provider id, imdb, tvdb.
- '''
+ """Update the provider id, imdb, tvdb."""
self.cursor.execute(QU.update_unique_id, args)
def add_countries(self, countries, *args):
@@ -141,9 +133,9 @@ def delete_boxset(self, *args):
self.cursor.execute(QU.delete_set, args)
def migrations(self):
- '''
+ """
Used to trigger required database migrations for new versions
- '''
+ """
self.cursor.execute(QU.get_version)
version_id = self.cursor.fetchone()[0]
changes = False
@@ -156,10 +148,10 @@ def migrations(self):
return changes
def omega_migration(self):
- '''
+ """
Adds a video version for all existing movies
- '''
- LOG.info('Starting migration for Omega database changes')
+ """
+ LOG.info("Starting migration for Omega database changes")
# Tracks if this migration made any changes
changes = False
self.cursor.execute(QU.get_missing_versions)
@@ -169,5 +161,5 @@ def omega_migration(self):
self.add_videoversion(entry[0], entry[1], "movie", "0", 40400)
changes = True
- LOG.info('Omega database migration is complete')
+ LOG.info("Omega database migration is complete")
return changes
diff --git a/jellyfin_kodi/objects/kodi/music.py b/jellyfin_kodi/objects/kodi/music.py
index 84d7a1a47..cad294a1b 100644
--- a/jellyfin_kodi/objects/kodi/music.py
+++ b/jellyfin_kodi/objects/kodi/music.py
@@ -25,10 +25,9 @@ def __init__(self, cursor):
Kodi.__init__(self)
def create_entry(self):
-
- ''' Krypton has a dummy first entry
- idArtist: 1 strArtist: [Missing Tag] strMusicBrainzArtistID: Artist Tag Missing
- '''
+ """Krypton has a dummy first entry
+ idArtist: 1 strArtist: [Missing Tag] strMusicBrainzArtistID: Artist Tag Missing
+ """
self.cursor.execute(QU.create_artist)
return self.cursor.fetchone()[0] + 1
@@ -55,9 +54,7 @@ def add_role(self, *args):
self.cursor.execute(QU.update_role, args)
def get(self, artist_id, name, musicbrainz):
-
- ''' Get artist or create the entry.
- '''
+ """Get artist or create the entry."""
try:
self.cursor.execute(QU.get_artist, (musicbrainz,))
result = self.cursor.fetchone()
@@ -72,15 +69,20 @@ def get(self, artist_id, name, musicbrainz):
return artist_id_res
def add_artist(self, artist_id, name, *args):
-
- ''' Safety check, when musicbrainz does not exist
- '''
+ """Safety check, when musicbrainz does not exist"""
try:
self.cursor.execute(QU.get_artist_by_name, (name,))
artist_id_res = self.cursor.fetchone()[0]
except TypeError:
artist_id_res = artist_id or self.create_entry()
- self.cursor.execute(QU.add_artist, (artist_id, name,) + args)
+ self.cursor.execute(
+ QU.add_artist,
+ (
+ artist_id,
+ name,
+ )
+ + args,
+ )
return artist_id_res
@@ -141,7 +143,7 @@ def get_album(self, album_id, name, musicbrainz, artists=None, *args):
self.cursor.execute(QU.get_album_by_name72, (name,))
album = self.cursor.fetchone()
- if album[1] and album[1].split(' / ')[0] not in artists.split(' / '):
+ if album[1] and album[1].split(" / ")[0] not in artists.split(" / "):
LOG.info("Album found, but artist doesn't match?")
LOG.info("Album [ %s/%s ] %s", name, album[1], artists)
@@ -149,7 +151,14 @@ def get_album(self, album_id, name, musicbrainz, artists=None, *args):
album_id = (album or self.cursor.fetchone())[0]
except TypeError:
- album_id = self.add_album(*(album_id, name, musicbrainz,) + args)
+ album_id = self.add_album(
+ *(
+ album_id,
+ name,
+ musicbrainz,
+ )
+ + args
+ )
return album_id
@@ -225,11 +234,10 @@ def rate_song(self, *args):
self.cursor.execute(QU.update_song_rating, args)
def add_genres(self, kodi_id, genres, media):
-
- ''' Add genres, but delete current genres first.
- Album_genres was removed in kodi 18
- '''
- if media == 'album' and self.version_id < 72:
+ """Add genres, but delete current genres first.
+ Album_genres was removed in kodi 18
+ """
+ if media == "album" and self.version_id < 72:
self.cursor.execute(QU.delete_genres_album, (kodi_id,))
for genre in genres:
@@ -237,7 +245,7 @@ def add_genres(self, kodi_id, genres, media):
genre_id = self.get_genre(genre)
self.cursor.execute(QU.update_genre_album, (genre_id, kodi_id))
- if media == 'song':
+ if media == "song":
self.cursor.execute(QU.delete_genres_song, (kodi_id,))
for genre in genres:
diff --git a/jellyfin_kodi/objects/kodi/queries.py b/jellyfin_kodi/objects/kodi/queries.py
index 5bca095d1..d02ef832a 100644
--- a/jellyfin_kodi/objects/kodi/queries.py
+++ b/jellyfin_kodi/objects/kodi/queries.py
@@ -1,9 +1,9 @@
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
the Kodi database query values.
-'''
+"""
create_path = """
SELECT coalesce(max(idPath), 0)
FROM path
@@ -254,21 +254,48 @@
INSERT INTO bookmark(idBookmark, idFile, timeInSeconds, totalTimeInSeconds, player, type)
VALUES (?, ?, ?, ?, ?, ?)
"""
-add_bookmark_obj = ["{FileId}", "{PlayCount}", "{DatePlayed}", "{Resume}", "{Runtime}", "DVDPlayer", 1]
+add_bookmark_obj = [
+ "{FileId}",
+ "{PlayCount}",
+ "{DatePlayed}",
+ "{Resume}",
+ "{Runtime}",
+ "DVDPlayer",
+ 1,
+]
add_streams_obj = ["{FileId}", "{Streams}", "{Runtime}"]
add_stream_video = """
INSERT INTO streamdetails(idFile, iStreamType, strVideoCodec, fVideoAspect, iVideoWidth,
iVideoHeight, iVideoDuration, strStereoMode, strHdrType)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
"""
-add_stream_video_obj = ["{FileId}", 0, "{codec}", "{aspect}", "{width}", "{height}", "{Runtime}", "{3d}", "{hdrtype}"]
+add_stream_video_obj = [
+ "{FileId}",
+ 0,
+ "{codec}",
+ "{aspect}",
+ "{width}",
+ "{height}",
+ "{Runtime}",
+ "{3d}",
+ "{hdrtype}",
+]
# strHdrType is new to Kodi 20
add_stream_video_19 = """
INSERT INTO streamdetails(idFile, iStreamType, strVideoCodec, fVideoAspect, iVideoWidth,
iVideoHeight, iVideoDuration, strStereoMode)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
"""
-add_stream_video_obj_19 = ["{FileId}", 0, "{codec}", "{aspect}", "{width}", "{height}", "{Runtime}", "{3d}"]
+add_stream_video_obj_19 = [
+ "{FileId}",
+ 0,
+ "{codec}",
+ "{aspect}",
+ "{width}",
+ "{height}",
+ "{Runtime}",
+ "{3d}",
+]
add_stream_audio = """
INSERT INTO streamdetails(idFile, iStreamType, strAudioCodec, iAudioChannels, strAudioLanguage)
VALUES (?, ?, ?, ?, ?)
@@ -295,24 +322,82 @@
c09, c10, c11, c12, c14, c15, c16, c18, c19, c21, premiered)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
"""
-add_movie_obj = ["{MovieId}", "{FileId}", "{Title}", "{Plot}", "{ShortPlot}", "{Tagline}",
- "{Votes}", "{RatingId}", "{Writers}", "{Year}", "{Unique}", "{SortTitle}",
- "{Runtime}", "{Mpaa}", "{Genre}", "{Directors}", "{Title}", "{Studio}",
- "{Trailer}", "{Country}", "{Premiere}"]
+add_movie_obj = [
+ "{MovieId}",
+ "{FileId}",
+ "{Title}",
+ "{Plot}",
+ "{ShortPlot}",
+ "{Tagline}",
+ "{Votes}",
+ "{RatingId}",
+ "{Writers}",
+ "{Year}",
+ "{Unique}",
+ "{SortTitle}",
+ "{Runtime}",
+ "{Mpaa}",
+ "{Genre}",
+ "{Directors}",
+ "{Title}",
+ "{Studio}",
+ "{Trailer}",
+ "{Country}",
+ "{Premiere}",
+]
add_rating = """
INSERT INTO rating(rating_id, media_id, media_type, rating_type, rating, votes)
VALUES (?, ?, ?, ?, ?, ?)
"""
-add_rating_movie_obj = ["{RatingId}", "{MovieId}", "movie", "default", "{Rating}", "{Votes}"]
-add_rating_tvshow_obj = ["{RatingId}", "{ShowId}", "tvshow", "default", "{Rating}", "{Votes}"]
-add_rating_episode_obj = ["{RatingId}", "{EpisodeId}", "episode", "default", "{Rating}", "{Votes}"]
+add_rating_movie_obj = [
+ "{RatingId}",
+ "{MovieId}",
+ "movie",
+ "default",
+ "{Rating}",
+ "{Votes}",
+]
+add_rating_tvshow_obj = [
+ "{RatingId}",
+ "{ShowId}",
+ "tvshow",
+ "default",
+ "{Rating}",
+ "{Votes}",
+]
+add_rating_episode_obj = [
+ "{RatingId}",
+ "{EpisodeId}",
+ "episode",
+ "default",
+ "{Rating}",
+ "{Votes}",
+]
add_unique_id = """
INSERT INTO uniqueid(uniqueid_id, media_id, media_type, value, type)
VALUES (?, ?, ?, ?, ?)
"""
-add_unique_id_movie_obj = ["{Unique}", "{MovieId}", "movie", "{UniqueId}", "{ProviderName}"]
-add_unique_id_tvshow_obj = ["{Unique}", "{ShowId}", "tvshow", "{UniqueId}", "{ProviderName}"]
-add_unique_id_episode_obj = ["{Unique}", "{EpisodeId}", "episode", "{UniqueId}", "{ProviderName}"]
+add_unique_id_movie_obj = [
+ "{Unique}",
+ "{MovieId}",
+ "movie",
+ "{UniqueId}",
+ "{ProviderName}",
+]
+add_unique_id_tvshow_obj = [
+ "{Unique}",
+ "{ShowId}",
+ "tvshow",
+ "{UniqueId}",
+ "{ProviderName}",
+]
+add_unique_id_episode_obj = [
+ "{Unique}",
+ "{EpisodeId}",
+ "episode",
+ "{UniqueId}",
+ "{ProviderName}",
+]
add_country = """
INSERT INTO country(name)
VALUES (?)
@@ -335,14 +420,40 @@
c11, c12, premiered)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
"""
-add_musicvideo_obj = ["{MvideoId}", "{FileId}", "{Title}", "{Runtime}", "{Directors}", "{Studio}", "{Year}",
- "{Plot}", "{Album}", "{Artists}", "{Genre}", "{Index}", "{Premiere}"]
+add_musicvideo_obj = [
+ "{MvideoId}",
+ "{FileId}",
+ "{Title}",
+ "{Runtime}",
+ "{Directors}",
+ "{Studio}",
+ "{Year}",
+ "{Plot}",
+ "{Album}",
+ "{Artists}",
+ "{Genre}",
+ "{Index}",
+ "{Premiere}",
+]
add_tvshow = """
INSERT INTO tvshow(idShow, c00, c01, c02, c04, c05, c08, c09, c10, c12, c13, c14, c15)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
"""
-add_tvshow_obj = ["{ShowId}", "{Title}", "{Plot}", "{Status}", "{RatingId}", "{Premiere}", "{Genre}", "{Title}",
- "disintegrate browse bug", "{Unique}", "{Mpaa}", "{Studio}", "{SortTitle}"]
+add_tvshow_obj = [
+ "{ShowId}",
+ "{Title}",
+ "{Plot}",
+ "{Status}",
+ "{RatingId}",
+ "{Premiere}",
+ "{Genre}",
+ "{Title}",
+ "disintegrate browse bug",
+ "{Unique}",
+ "{Mpaa}",
+ "{Studio}",
+ "{SortTitle}",
+]
add_season = """
INSERT INTO seasons(idSeason, idShow, season)
VALUES (?, ?, ?)
@@ -352,9 +463,27 @@
idShow, c15, c16, idSeason, c18, c19, c20)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
"""
-add_episode_obj = ["{EpisodeId}", "{FileId}", "{Title}", "{Plot}", "{RatingId}", "{Writers}", "{Premiere}", "{Runtime}",
- "{Directors}", "{Season}", "{Index}", "{Title}", "{ShowId}", "{AirsBeforeSeason}",
- "{AirsBeforeEpisode}", "{SeasonId}", "{FullFilePath}", "{PathId}", "{Unique}"]
+add_episode_obj = [
+ "{EpisodeId}",
+ "{FileId}",
+ "{Title}",
+ "{Plot}",
+ "{RatingId}",
+ "{Writers}",
+ "{Premiere}",
+ "{Runtime}",
+ "{Directors}",
+ "{Season}",
+ "{Index}",
+ "{Title}",
+ "{ShowId}",
+ "{AirsBeforeSeason}",
+ "{AirsBeforeEpisode}",
+ "{SeasonId}",
+ "{FullFilePath}",
+ "{PathId}",
+ "{Unique}",
+]
add_art = """
INSERT INTO art(media_id, media_type, type, url)
VALUES (?, ?, ?, ?)
@@ -372,7 +501,13 @@
WHERE idPath = ?
"""
update_path_movie_obj = ["{Path}", "movies", "metadata.local", 1, "{PathId}"]
-update_path_toptvshow_obj = ["{TopLevel}", "tvshows", "metadata.local", 1, "{TopPathId}"]
+update_path_toptvshow_obj = [
+ "{TopLevel}",
+ "tvshows",
+ "metadata.local",
+ 1,
+ "{TopPathId}",
+]
update_path_toptvshow_addon_obj = ["{TopLevel}", None, None, 1, "{TopPathId}"]
update_path_tvshow_obj = ["{Path}", None, None, 1, "{PathId}"]
update_path_episode_obj = ["{Path}", None, None, 1, "{PathId}"]
@@ -431,26 +566,83 @@
c16 = ?, c18 = ?, c19 = ?, c21 = ?, premiered = ?
WHERE idMovie = ?
"""
-update_movie_obj = ["{Title}", "{Plot}", "{ShortPlot}", "{Tagline}", "{Votes}", "{RatingId}",
- "{Writers}", "{Year}", "{Unique}", "{SortTitle}", "{Runtime}",
- "{Mpaa}", "{Genre}", "{Directors}", "{Title}", "{Studio}", "{Trailer}",
- "{Country}", "{Premiere}", "{MovieId}"]
+update_movie_obj = [
+ "{Title}",
+ "{Plot}",
+ "{ShortPlot}",
+ "{Tagline}",
+ "{Votes}",
+ "{RatingId}",
+ "{Writers}",
+ "{Year}",
+ "{Unique}",
+ "{SortTitle}",
+ "{Runtime}",
+ "{Mpaa}",
+ "{Genre}",
+ "{Directors}",
+ "{Title}",
+ "{Studio}",
+ "{Trailer}",
+ "{Country}",
+ "{Premiere}",
+ "{MovieId}",
+]
update_rating = """
UPDATE rating
SET media_id = ?, media_type = ?, rating_type = ?, rating = ?, votes = ?
WHERE rating_id = ?
"""
-update_rating_movie_obj = ["{MovieId}", "movie", "default", "{Rating}", "{Votes}", "{RatingId}"]
-update_rating_tvshow_obj = ["{ShowId}", "tvshow", "default", "{Rating}", "{Votes}", "{RatingId}"]
-update_rating_episode_obj = ["{EpisodeId}", "episode", "default", "{Rating}", "{Votes}", "{RatingId}"]
+update_rating_movie_obj = [
+ "{MovieId}",
+ "movie",
+ "default",
+ "{Rating}",
+ "{Votes}",
+ "{RatingId}",
+]
+update_rating_tvshow_obj = [
+ "{ShowId}",
+ "tvshow",
+ "default",
+ "{Rating}",
+ "{Votes}",
+ "{RatingId}",
+]
+update_rating_episode_obj = [
+ "{EpisodeId}",
+ "episode",
+ "default",
+ "{Rating}",
+ "{Votes}",
+ "{RatingId}",
+]
update_unique_id = """
UPDATE uniqueid
SET media_id = ?, media_type = ?, value = ?, type = ?
WHERE uniqueid_id = ?
"""
-update_unique_id_movie_obj = ["{MovieId}", "movie", "{UniqueId}", "{ProviderName}", "{Unique}"]
-update_unique_id_tvshow_obj = ["{ShowId}", "tvshow", "{UniqueId}", "{ProviderName}", "{Unique}"]
-update_unique_id_episode_obj = ["{EpisodeId}", "episode", "{UniqueId}", "{ProviderName}", "{Unique}"]
+update_unique_id_movie_obj = [
+ "{MovieId}",
+ "movie",
+ "{UniqueId}",
+ "{ProviderName}",
+ "{Unique}",
+]
+update_unique_id_tvshow_obj = [
+ "{ShowId}",
+ "tvshow",
+ "{UniqueId}",
+ "{ProviderName}",
+ "{Unique}",
+]
+update_unique_id_episode_obj = [
+ "{EpisodeId}",
+ "episode",
+ "{UniqueId}",
+ "{ProviderName}",
+ "{Unique}",
+]
update_country = """
INSERT OR REPLACE INTO country_link(country_id, media_id, media_type)
VALUES (?, ?, ?)
@@ -474,16 +666,41 @@
c11 = ?, c12 = ?, premiered = ?
WHERE idMVideo = ?
"""
-update_musicvideo_obj = ["{Title}", "{Runtime}", "{Directors}", "{Studio}", "{Year}", "{Plot}", "{Album}",
- "{Artists}", "{Genre}", "{Index}", "{Premiere}", "{MvideoId}"]
+update_musicvideo_obj = [
+ "{Title}",
+ "{Runtime}",
+ "{Directors}",
+ "{Studio}",
+ "{Year}",
+ "{Plot}",
+ "{Album}",
+ "{Artists}",
+ "{Genre}",
+ "{Index}",
+ "{Premiere}",
+ "{MvideoId}",
+]
update_tvshow = """
UPDATE tvshow
SET c00 = ?, c01 = ?, c02 = ?, c04 = ?, c05 = ?, c08 = ?, c09 = ?, c10 = ?,
c12 = ?, c13 = ?, c14 = ?, c15 = ?
WHERE idShow = ?
"""
-update_tvshow_obj = ["{Title}", "{Plot}", "{Status}", "{RatingId}", "{Premiere}", "{Genre}", "{Title}",
- "disintegrate browse bug", "{Unique}", "{Mpaa}", "{Studio}", "{SortTitle}", "{ShowId}"]
+update_tvshow_obj = [
+ "{Title}",
+ "{Plot}",
+ "{Status}",
+ "{RatingId}",
+ "{Premiere}",
+ "{Genre}",
+ "{Title}",
+ "disintegrate browse bug",
+ "{Unique}",
+ "{Mpaa}",
+ "{Studio}",
+ "{SortTitle}",
+ "{ShowId}",
+]
update_tvshow_link = """
INSERT OR REPLACE INTO tvshowlinkpath(idShow, idPath)
VALUES (?, ?)
@@ -501,9 +718,26 @@
c18 = ?, c19 = ?, c20 = ?
WHERE idEpisode = ?
"""
-update_episode_obj = ["{Title}", "{Plot}", "{RatingId}", "{Writers}", "{Premiere}", "{Runtime}", "{Directors}",
- "{Season}", "{Index}", "{Title}", "{AirsBeforeSeason}", "{AirsBeforeEpisode}", "{SeasonId}",
- "{ShowId}", "{FullFilePath}", "{PathId}", "{Unique}", "{EpisodeId}"]
+update_episode_obj = [
+ "{Title}",
+ "{Plot}",
+ "{RatingId}",
+ "{Writers}",
+ "{Premiere}",
+ "{Runtime}",
+ "{Directors}",
+ "{Season}",
+ "{Index}",
+ "{Title}",
+ "{AirsBeforeSeason}",
+ "{AirsBeforeEpisode}",
+ "{SeasonId}",
+ "{ShowId}",
+ "{FullFilePath}",
+ "{PathId}",
+ "{Unique}",
+ "{EpisodeId}",
+]
delete_path = """
diff --git a/jellyfin_kodi/objects/kodi/queries_music.py b/jellyfin_kodi/objects/kodi/queries_music.py
index 88131dc3f..ca6c7fa4c 100644
--- a/jellyfin_kodi/objects/kodi/queries_music.py
+++ b/jellyfin_kodi/objects/kodi/queries_music.py
@@ -54,7 +54,14 @@
WHERE strMusicBrainzAlbumID = ?
"""
get_album_obj = ["{AlbumId}", "{Title}", "{UniqueId}", "{Artists}", "album"]
-get_album_obj82 = ["{AlbumId}", "{Title}", "{UniqueId}", "{Artists}", "album", "{DateAdded}"]
+get_album_obj82 = [
+ "{AlbumId}",
+ "{Title}",
+ "{UniqueId}",
+ "{Artists}",
+ "album",
+ "{DateAdded}",
+]
get_album_by_name = """
SELECT idAlbum, strArtists
FROM album
@@ -132,9 +139,24 @@
rating, comment, dateAdded)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
"""
-add_song_obj = ["{SongId}", "{AlbumId}", "{PathId}", "{Artists}", "{Genre}", "{Title}", "{Index}",
- "{Runtime}", "{Year}", "{Filename}", "{UniqueId}", "{PlayCount}", "{DatePlayed}", "{Rating}",
- "{Comment}", "{DateAdded}"]
+add_song_obj = [
+ "{SongId}",
+ "{AlbumId}",
+ "{PathId}",
+ "{Artists}",
+ "{Genre}",
+ "{Title}",
+ "{Index}",
+ "{Runtime}",
+ "{Year}",
+ "{Filename}",
+ "{UniqueId}",
+ "{PlayCount}",
+ "{DatePlayed}",
+ "{Rating}",
+ "{Comment}",
+ "{DateAdded}",
+]
add_genre = """
INSERT INTO genre(idGenre, strGenre)
VALUES (?, ?)
@@ -197,7 +219,17 @@
iUserrating = ?, lastScraped = ?, bScrapedMBID = 1, strReleaseType = ?
WHERE idAlbum = ?
"""
-update_album_obj = ["{Artists}", "{Year}", "{Genre}", "{Bio}", "{Thumb}", "{Rating}", "{LastScraped}", "album", "{AlbumId}"]
+update_album_obj = [
+ "{Artists}",
+ "{Year}",
+ "{Genre}",
+ "{Bio}",
+ "{Thumb}",
+ "{Rating}",
+ "{LastScraped}",
+ "album",
+ "{AlbumId}",
+]
update_album_artist = """
UPDATE album
SET strArtists = ?
@@ -229,9 +261,22 @@
rating = ?, comment = ?, dateAdded = ?
WHERE idSong = ?
"""
-update_song_obj = ["{AlbumId}", "{Artists}", "{Genre}", "{Title}", "{Index}", "{Runtime}", "{Year}",
- "{Filename}", "{PlayCount}", "{DatePlayed}", "{Rating}", "{Comment}",
- "{DateAdded}", "{SongId}"]
+update_song_obj = [
+ "{AlbumId}",
+ "{Artists}",
+ "{Genre}",
+ "{Title}",
+ "{Index}",
+ "{Runtime}",
+ "{Year}",
+ "{Filename}",
+ "{PlayCount}",
+ "{DatePlayed}",
+ "{Rating}",
+ "{Comment}",
+ "{DateAdded}",
+ "{SongId}",
+]
update_song_artist = """
INSERT OR REPLACE INTO song_artist(idArtist, idSong, idRole, iOrder, strArtist)
VALUES (?, ?, ?, ?, ?)
diff --git a/jellyfin_kodi/objects/movies.py b/jellyfin_kodi/objects/movies.py
index 0971c578b..44927b69c 100644
--- a/jellyfin_kodi/objects/movies.py
+++ b/jellyfin_kodi/objects/movies.py
@@ -8,7 +8,16 @@
from .. import downloader as server
from ..database import jellyfin_db, queries as QUEM
-from ..helper import api, stop, validate, validate_bluray_dir, validate_dvd_dir, jellyfin_item, values, Local
+from ..helper import (
+ api,
+ stop,
+ validate,
+ validate_bluray_dir,
+ validate_dvd_dir,
+ jellyfin_item,
+ values,
+ Local,
+)
from ..helper import LazyLogger
from ..helper.utils import find_library
from ..helper.exceptions import PathValidationException
@@ -42,74 +51,83 @@ def __init__(self, server, jellyfindb, videodb, direct_path, library=None):
@stop
@jellyfin_item
def movie(self, item, e_item):
-
- ''' If item does not exist, entry will be added.
- If item exists, entry will be updated.
- '''
- server_address = self.server.auth.get_server_info(self.server.auth.server_id)['address']
+ """If item does not exist, entry will be added.
+ If item exists, entry will be updated.
+ """
+ server_address = self.server.auth.get_server_info(self.server.auth.server_id)[
+ "address"
+ ]
API = api.API(item, server_address)
- obj = self.objects.map(item, 'Movie')
+ obj = self.objects.map(item, "Movie")
update = True
try:
- obj['MovieId'] = e_item[0]
- obj['FileId'] = e_item[1]
- obj['PathId'] = e_item[2]
- obj['LibraryId'] = e_item[6]
- obj['LibraryName'] = self.jellyfin_db.get_view_name(obj['LibraryId'])
+ obj["MovieId"] = e_item[0]
+ obj["FileId"] = e_item[1]
+ obj["PathId"] = e_item[2]
+ obj["LibraryId"] = e_item[6]
+ obj["LibraryName"] = self.jellyfin_db.get_view_name(obj["LibraryId"])
except TypeError:
update = False
- LOG.debug("MovieId %s not found", obj['Id'])
+ LOG.debug("MovieId %s not found", obj["Id"])
library = self.library or find_library(self.server, item)
if not library:
# This item doesn't belong to a whitelisted library
return
- obj['MovieId'] = self.create_entry()
- obj['LibraryId'] = library['Id']
- obj['LibraryName'] = library['Name']
+ obj["MovieId"] = self.create_entry()
+ obj["LibraryId"] = library["Id"]
+ obj["LibraryName"] = library["Name"]
else:
if self.get(*values(obj, QU.get_movie_obj)) is None:
update = False
- LOG.info("MovieId %s missing from kodi. repairing the entry.", obj['MovieId'])
-
- obj['Path'] = API.get_file_path(obj['Path'])
- obj['Genres'] = obj['Genres'] or []
- obj['Studios'] = [API.validate_studio(studio) for studio in (obj['Studios'] or [])]
- obj['People'] = obj['People'] or []
- obj['Genre'] = " / ".join(obj['Genres'])
- obj['Writers'] = " / ".join(obj['Writers'] or [])
- obj['Directors'] = " / ".join(obj['Directors'] or [])
- obj['Plot'] = API.get_overview(obj['Plot'])
- obj['Mpaa'] = API.get_mpaa(obj['Mpaa'])
- obj['Resume'] = API.adjust_resume((obj['Resume'] or 0) / 10000000.0)
- obj['Runtime'] = round(float((obj['Runtime'] or 0) / 10000000.0), 6)
- obj['People'] = API.get_people_artwork(obj['People'])
- obj['DateAdded'] = Local(obj['DateAdded']).split('.')[0].replace('T', " ")
- obj['DatePlayed'] = None if not obj['DatePlayed'] else Local(obj['DatePlayed']).split('.')[0].replace('T', " ")
- obj['PlayCount'] = API.get_playcount(obj['Played'], obj['PlayCount'])
- obj['Artwork'] = API.get_all_artwork(self.objects.map(item, 'Artwork'))
- obj['Video'] = API.video_streams(obj['Video'] or [], obj['Container'])
- obj['Audio'] = API.audio_streams(obj['Audio'] or [])
- obj['Streams'] = API.media_streams(obj['Video'], obj['Audio'], obj['Subtitles'])
- if obj['Premiere'] is not None:
- obj['Premiere'] = str(obj['Premiere']).split('T')[0]
+ LOG.info(
+ "MovieId %s missing from kodi. repairing the entry.", obj["MovieId"]
+ )
+
+ obj["Path"] = API.get_file_path(obj["Path"])
+ obj["Genres"] = obj["Genres"] or []
+ obj["Studios"] = [
+ API.validate_studio(studio) for studio in (obj["Studios"] or [])
+ ]
+ obj["People"] = obj["People"] or []
+ obj["Genre"] = " / ".join(obj["Genres"])
+ obj["Writers"] = " / ".join(obj["Writers"] or [])
+ obj["Directors"] = " / ".join(obj["Directors"] or [])
+ obj["Plot"] = API.get_overview(obj["Plot"])
+ obj["Mpaa"] = API.get_mpaa(obj["Mpaa"])
+ obj["Resume"] = API.adjust_resume((obj["Resume"] or 0) / 10000000.0)
+ obj["Runtime"] = round(float((obj["Runtime"] or 0) / 10000000.0), 6)
+ obj["People"] = API.get_people_artwork(obj["People"])
+ obj["DateAdded"] = Local(obj["DateAdded"]).split(".")[0].replace("T", " ")
+ obj["DatePlayed"] = (
+ None
+ if not obj["DatePlayed"]
+ else Local(obj["DatePlayed"]).split(".")[0].replace("T", " ")
+ )
+ obj["PlayCount"] = API.get_playcount(obj["Played"], obj["PlayCount"])
+ obj["Artwork"] = API.get_all_artwork(self.objects.map(item, "Artwork"))
+ obj["Video"] = API.video_streams(obj["Video"] or [], obj["Container"])
+ obj["Audio"] = API.audio_streams(obj["Audio"] or [])
+ obj["Streams"] = API.media_streams(obj["Video"], obj["Audio"], obj["Subtitles"])
+ if obj["Premiere"] is not None:
+ obj["Premiere"] = str(obj["Premiere"]).split("T")[0]
self.get_path_filename(obj)
self.trailer(obj)
- if obj['Countries']:
+ if obj["Countries"]:
self.add_countries(*values(obj, QU.update_country_obj))
- tags = list(obj['Tags'] or [])
- tags.append(obj['LibraryName'])
+ tags = list(obj["Tags"] or [])
+ tags.append(obj["LibraryName"])
- if obj['Favorite']:
- tags.append('Favorite movies')
+ if obj["Favorite"]:
+ tags.append("Favorite movies")
- obj['Tags'] = tags
+ obj["Tags"] = tags
if update:
self.movie_update(obj)
@@ -124,239 +142,289 @@ def movie(self, item, e_item):
self.add_playstate(*values(obj, QU.add_bookmark_obj))
self.add_people(*values(obj, QU.add_people_movie_obj))
self.add_streams(*values(obj, QU.add_streams_obj))
- self.artwork.add(obj['Artwork'], obj['MovieId'], "movie")
- self.item_ids.append(obj['Id'])
+ self.artwork.add(obj["Artwork"], obj["MovieId"], "movie")
+ self.item_ids.append(obj["Id"])
return not update
def movie_add(self, obj):
-
- ''' Add object to kodi.
- '''
- obj['RatingId'] = self.create_entry_rating()
+ """Add object to kodi."""
+ obj["RatingId"] = self.create_entry_rating()
self.add_ratings(*values(obj, QU.add_rating_movie_obj))
- obj['Unique'] = self.create_entry_unique_id()
+ obj["Unique"] = self.create_entry_unique_id()
self.add_unique_id(*values(obj, QU.add_unique_id_movie_obj))
- obj['PathId'] = self.add_path(*values(obj, QU.add_path_obj))
- obj['FileId'] = self.add_file(*values(obj, QU.add_file_obj))
+ obj["PathId"] = self.add_path(*values(obj, QU.add_path_obj))
+ obj["FileId"] = self.add_file(*values(obj, QU.add_file_obj))
self.add(*values(obj, QU.add_movie_obj))
self.add_videoversion(*values(obj, QU.add_video_version_obj))
self.jellyfin_db.add_reference(*values(obj, QUEM.add_reference_movie_obj))
- LOG.debug("ADD movie [%s/%s/%s] %s: %s", obj['PathId'], obj['FileId'], obj['MovieId'], obj['Id'], obj['Title'])
+ LOG.debug(
+ "ADD movie [%s/%s/%s] %s: %s",
+ obj["PathId"],
+ obj["FileId"],
+ obj["MovieId"],
+ obj["Id"],
+ obj["Title"],
+ )
def movie_update(self, obj):
-
- ''' Update object to kodi.
- '''
- obj['RatingId'] = self.get_rating_id(*values(obj, QU.get_rating_movie_obj))
+ """Update object to kodi."""
+ obj["RatingId"] = self.get_rating_id(*values(obj, QU.get_rating_movie_obj))
self.update_ratings(*values(obj, QU.update_rating_movie_obj))
- obj['Unique'] = self.get_unique_id(*values(obj, QU.get_unique_id_movie_obj))
+ obj["Unique"] = self.get_unique_id(*values(obj, QU.get_unique_id_movie_obj))
self.update_unique_id(*values(obj, QU.update_unique_id_movie_obj))
self.update(*values(obj, QU.update_movie_obj))
self.jellyfin_db.update_reference(*values(obj, QUEM.update_reference_obj))
- LOG.debug("UPDATE movie [%s/%s/%s] %s: %s", obj['PathId'], obj['FileId'], obj['MovieId'], obj['Id'], obj['Title'])
+ LOG.debug(
+ "UPDATE movie [%s/%s/%s] %s: %s",
+ obj["PathId"],
+ obj["FileId"],
+ obj["MovieId"],
+ obj["Id"],
+ obj["Title"],
+ )
def trailer(self, obj):
try:
- if obj['LocalTrailer']:
-
- trailer = self.server.jellyfin.get_local_trailers(obj['Id'])
- obj['Trailer'] = "plugin://plugin.video.jellyfin/trailer?id=%s&mode=play" % trailer[0]['Id']
-
- elif obj['Trailer']:
- obj['Trailer'] = "plugin://plugin.video.youtube/play/?video_id=%s" % obj['Trailer'].rsplit('=', 1)[1]
+ if obj["LocalTrailer"]:
+
+ trailer = self.server.jellyfin.get_local_trailers(obj["Id"])
+ obj["Trailer"] = (
+ "plugin://plugin.video.jellyfin/trailer?id=%s&mode=play"
+ % trailer[0]["Id"]
+ )
+
+ elif obj["Trailer"]:
+ obj["Trailer"] = (
+ "plugin://plugin.video.youtube/play/?video_id=%s"
+ % obj["Trailer"].rsplit("=", 1)[1]
+ )
except Exception as error:
LOG.exception("Failed to get trailer: %s", error)
- obj['Trailer'] = None
+ obj["Trailer"] = None
def get_path_filename(self, obj):
-
- ''' Get the path and filename and build it into protocol://path
- '''
- obj['Filename'] = obj['Path'].rsplit('\\', 1)[1] if '\\' in obj['Path'] else obj['Path'].rsplit('/', 1)[1]
+ """Get the path and filename and build it into protocol://path"""
+ obj["Filename"] = (
+ obj["Path"].rsplit("\\", 1)[1]
+ if "\\" in obj["Path"]
+ else obj["Path"].rsplit("/", 1)[1]
+ )
if self.direct_path:
- if not validate(obj['Path']):
+ if not validate(obj["Path"]):
raise PathValidationException("Failed to validate path. User stopped.")
- obj['Path'] = obj['Path'].replace(obj['Filename'], "")
+ obj["Path"] = obj["Path"].replace(obj["Filename"], "")
- '''check dvd directories and point it to ./VIDEO_TS/VIDEO_TS.IFO'''
- if validate_dvd_dir(obj['Path'] + obj['Filename']):
- obj['Path'] = obj['Path'] + obj['Filename'] + '/VIDEO_TS/'
- obj['Filename'] = 'VIDEO_TS.IFO'
- LOG.debug("DVD directory %s", obj['Path'])
+ """check dvd directories and point it to ./VIDEO_TS/VIDEO_TS.IFO"""
+ if validate_dvd_dir(obj["Path"] + obj["Filename"]):
+ obj["Path"] = obj["Path"] + obj["Filename"] + "/VIDEO_TS/"
+ obj["Filename"] = "VIDEO_TS.IFO"
+ LOG.debug("DVD directory %s", obj["Path"])
- '''check bluray directories and point it to ./BDMV/index.bdmv'''
- if validate_bluray_dir(obj['Path'] + obj['Filename']):
- obj['Path'] = obj['Path'] + obj['Filename'] + '/BDMV/'
- obj['Filename'] = 'index.bdmv'
- LOG.debug("Bluray directory %s", obj['Path'])
+ """check bluray directories and point it to ./BDMV/index.bdmv"""
+ if validate_bluray_dir(obj["Path"] + obj["Filename"]):
+ obj["Path"] = obj["Path"] + obj["Filename"] + "/BDMV/"
+ obj["Filename"] = "index.bdmv"
+ LOG.debug("Bluray directory %s", obj["Path"])
else:
- obj['Path'] = "plugin://plugin.video.jellyfin/%s/" % obj['LibraryId']
+ obj["Path"] = "plugin://plugin.video.jellyfin/%s/" % obj["LibraryId"]
params = {
- 'filename': py2_encode(obj['Filename'], 'utf-8'),
- 'id': obj['Id'],
- 'dbid': obj['MovieId'],
- 'mode': "play"
+ "filename": py2_encode(obj["Filename"], "utf-8"),
+ "id": obj["Id"],
+ "dbid": obj["MovieId"],
+ "mode": "play",
}
- obj['Filename'] = "%s?%s" % (obj['Path'], urlencode(params))
+ obj["Filename"] = "%s?%s" % (obj["Path"], urlencode(params))
@stop
@jellyfin_item
def boxset(self, item, e_item):
-
- ''' If item does not exist, entry will be added.
- If item exists, entry will be updated.
-
- Process movies inside boxset.
- Process removals from boxset.
- '''
- server_address = self.server.auth.get_server_info(self.server.auth.server_id)['address']
+ """If item does not exist, entry will be added.
+ If item exists, entry will be updated.
+
+ Process movies inside boxset.
+ Process removals from boxset.
+ """
+ server_address = self.server.auth.get_server_info(self.server.auth.server_id)[
+ "address"
+ ]
API = api.API(item, server_address)
- obj = self.objects.map(item, 'Boxset')
+ obj = self.objects.map(item, "Boxset")
- obj['Overview'] = API.get_overview(obj['Overview'])
+ obj["Overview"] = API.get_overview(obj["Overview"])
try:
- obj['SetId'] = e_item[0]
+ obj["SetId"] = e_item[0]
self.update_boxset(*values(obj, QU.update_set_obj))
except TypeError:
- LOG.debug("SetId %s not found", obj['Id'])
- obj['SetId'] = self.add_boxset(*values(obj, QU.add_set_obj))
+ LOG.debug("SetId %s not found", obj["Id"])
+ obj["SetId"] = self.add_boxset(*values(obj, QU.add_set_obj))
self.boxset_current(obj)
- obj['Artwork'] = API.get_all_artwork(self.objects.map(item, 'Artwork'))
+ obj["Artwork"] = API.get_all_artwork(self.objects.map(item, "Artwork"))
- for movie in obj['Current']:
+ for movie in obj["Current"]:
temp_obj = dict(obj)
- temp_obj['Movie'] = movie
- temp_obj['MovieId'] = obj['Current'][temp_obj['Movie']]
+ temp_obj["Movie"] = movie
+ temp_obj["MovieId"] = obj["Current"][temp_obj["Movie"]]
self.remove_from_boxset(*values(temp_obj, QU.delete_movie_set_obj))
- self.jellyfin_db.update_parent_id(*values(temp_obj, QUEM.delete_parent_boxset_obj))
- LOG.debug("DELETE from boxset [%s] %s: %s", temp_obj['SetId'], temp_obj['Title'], temp_obj['MovieId'])
-
- self.artwork.add(obj['Artwork'], obj['SetId'], "set")
+ self.jellyfin_db.update_parent_id(
+ *values(temp_obj, QUEM.delete_parent_boxset_obj)
+ )
+ LOG.debug(
+ "DELETE from boxset [%s] %s: %s",
+ temp_obj["SetId"],
+ temp_obj["Title"],
+ temp_obj["MovieId"],
+ )
+
+ self.artwork.add(obj["Artwork"], obj["SetId"], "set")
self.jellyfin_db.add_reference(*values(obj, QUEM.add_reference_boxset_obj))
- LOG.debug("UPDATE boxset [%s] %s", obj['SetId'], obj['Title'])
+ LOG.debug("UPDATE boxset [%s] %s", obj["SetId"], obj["Title"])
def boxset_current(self, obj):
-
- ''' Add or removes movies based on the current movies found in the boxset.
- '''
+ """Add or removes movies based on the current movies found in the boxset."""
try:
- current = self.jellyfin_db.get_item_id_by_parent_id(*values(obj, QUEM.get_item_id_by_parent_boxset_obj))
+ current = self.jellyfin_db.get_item_id_by_parent_id(
+ *values(obj, QUEM.get_item_id_by_parent_boxset_obj)
+ )
movies = dict(current)
except ValueError:
movies = {}
- obj['Current'] = movies
+ obj["Current"] = movies
- for all_movies in server.get_movies_by_boxset(obj['Id']):
- for movie in all_movies['Items']:
+ for all_movies in server.get_movies_by_boxset(obj["Id"]):
+ for movie in all_movies["Items"]:
temp_obj = dict(obj)
- temp_obj['Title'] = movie['Name']
- temp_obj['Id'] = movie['Id']
+ temp_obj["Title"] = movie["Name"]
+ temp_obj["Id"] = movie["Id"]
try:
- temp_obj['MovieId'] = self.jellyfin_db.get_item_by_id(*values(temp_obj, QUEM.get_item_obj))[0]
+ temp_obj["MovieId"] = self.jellyfin_db.get_item_by_id(
+ *values(temp_obj, QUEM.get_item_obj)
+ )[0]
except TypeError:
- LOG.info("Failed to process %s to boxset.", temp_obj['Title'])
+ LOG.info("Failed to process %s to boxset.", temp_obj["Title"])
continue
- if temp_obj['Id'] not in obj['Current']:
+ if temp_obj["Id"] not in obj["Current"]:
self.set_boxset(*values(temp_obj, QU.update_movie_set_obj))
- self.jellyfin_db.update_parent_id(*values(temp_obj, QUEM.update_parent_movie_obj))
- LOG.debug("ADD to boxset [%s/%s] %s: %s to boxset", temp_obj['SetId'], temp_obj['MovieId'], temp_obj['Title'], temp_obj['Id'])
+ self.jellyfin_db.update_parent_id(
+ *values(temp_obj, QUEM.update_parent_movie_obj)
+ )
+ LOG.debug(
+ "ADD to boxset [%s/%s] %s: %s to boxset",
+ temp_obj["SetId"],
+ temp_obj["MovieId"],
+ temp_obj["Title"],
+ temp_obj["Id"],
+ )
else:
- obj['Current'].pop(temp_obj['Id'])
+ obj["Current"].pop(temp_obj["Id"])
def boxsets_reset(self):
-
- ''' Special function to remove all existing boxsets.
- '''
- boxsets = self.jellyfin_db.get_items_by_media('set')
+ """Special function to remove all existing boxsets."""
+ boxsets = self.jellyfin_db.get_items_by_media("set")
for boxset in boxsets:
self.remove(boxset[0])
@stop
@jellyfin_item
def userdata(self, item, e_item):
-
- ''' This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks
- Poster with progress bar
- '''
- server_address = self.server.auth.get_server_info(self.server.auth.server_id)['address']
+ """This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks
+ Poster with progress bar
+ """
+ server_address = self.server.auth.get_server_info(self.server.auth.server_id)[
+ "address"
+ ]
API = api.API(item, server_address)
- obj = self.objects.map(item, 'MovieUserData')
+ obj = self.objects.map(item, "MovieUserData")
try:
- obj['MovieId'] = e_item[0]
- obj['FileId'] = e_item[1]
+ obj["MovieId"] = e_item[0]
+ obj["FileId"] = e_item[1]
except TypeError:
return
- obj['Resume'] = API.adjust_resume((obj['Resume'] or 0) / 10000000.0)
- obj['Runtime'] = round(float((obj['Runtime'] or 0) / 10000000.0), 6)
- obj['PlayCount'] = API.get_playcount(obj['Played'], obj['PlayCount'])
+ obj["Resume"] = API.adjust_resume((obj["Resume"] or 0) / 10000000.0)
+ obj["Runtime"] = round(float((obj["Runtime"] or 0) / 10000000.0), 6)
+ obj["PlayCount"] = API.get_playcount(obj["Played"], obj["PlayCount"])
- if obj['DatePlayed']:
- obj['DatePlayed'] = Local(obj['DatePlayed']).split('.')[0].replace('T', " ")
+ if obj["DatePlayed"]:
+ obj["DatePlayed"] = Local(obj["DatePlayed"]).split(".")[0].replace("T", " ")
- if obj['Favorite']:
+ if obj["Favorite"]:
self.get_tag(*values(obj, QU.get_tag_movie_obj))
else:
self.remove_tag(*values(obj, QU.delete_tag_movie_obj))
- LOG.debug("New resume point %s: %s", obj['Id'], obj['Resume'])
+ LOG.debug("New resume point %s: %s", obj["Id"], obj["Resume"])
self.add_playstate(*values(obj, QU.add_bookmark_obj))
self.jellyfin_db.update_reference(*values(obj, QUEM.update_reference_obj))
- LOG.debug("USERDATA movie [%s/%s] %s: %s", obj['FileId'], obj['MovieId'], obj['Id'], obj['Title'])
+ LOG.debug(
+ "USERDATA movie [%s/%s] %s: %s",
+ obj["FileId"],
+ obj["MovieId"],
+ obj["Id"],
+ obj["Title"],
+ )
@stop
@jellyfin_item
def remove(self, item_id, e_item):
-
- ''' Remove movieid, fileid, jellyfin reference.
- Remove artwork, boxset
- '''
- obj = {'Id': item_id}
+ """Remove movieid, fileid, jellyfin reference.
+ Remove artwork, boxset
+ """
+ obj = {"Id": item_id}
try:
- obj['KodiId'] = e_item[0]
- obj['FileId'] = e_item[1]
- obj['Media'] = e_item[4]
+ obj["KodiId"] = e_item[0]
+ obj["FileId"] = e_item[1]
+ obj["Media"] = e_item[4]
except TypeError:
return
- self.artwork.delete(obj['KodiId'], obj['Media'])
+ self.artwork.delete(obj["KodiId"], obj["Media"])
- if obj['Media'] == 'movie':
+ if obj["Media"] == "movie":
self.delete(*values(obj, QU.delete_movie_obj))
- elif obj['Media'] == 'set':
+ elif obj["Media"] == "set":
- for movie in self.jellyfin_db.get_item_by_parent_id(*values(obj, QUEM.get_item_by_parent_movie_obj)):
+ for movie in self.jellyfin_db.get_item_by_parent_id(
+ *values(obj, QUEM.get_item_by_parent_movie_obj)
+ ):
temp_obj = dict(obj)
- temp_obj['MovieId'] = movie[1]
- temp_obj['Movie'] = movie[0]
+ temp_obj["MovieId"] = movie[1]
+ temp_obj["Movie"] = movie[0]
self.remove_from_boxset(*values(temp_obj, QU.delete_movie_set_obj))
- self.jellyfin_db.update_parent_id(*values(temp_obj, QUEM.delete_parent_boxset_obj))
+ self.jellyfin_db.update_parent_id(
+ *values(temp_obj, QUEM.delete_parent_boxset_obj)
+ )
self.delete_boxset(*values(obj, QU.delete_set_obj))
self.jellyfin_db.remove_item(*values(obj, QUEM.delete_item_obj))
- LOG.debug("DELETE %s [%s/%s] %s", obj['Media'], obj['FileId'], obj['KodiId'], obj['Id'])
+ LOG.debug(
+ "DELETE %s [%s/%s] %s",
+ obj["Media"],
+ obj["FileId"],
+ obj["KodiId"],
+ obj["Id"],
+ )
diff --git a/jellyfin_kodi/objects/music.py b/jellyfin_kodi/objects/music.py
index 1ecf68779..310c2052b 100644
--- a/jellyfin_kodi/objects/music.py
+++ b/jellyfin_kodi/objects/music.py
@@ -39,19 +39,20 @@ def __init__(self, server, jellyfindb, musicdb, direct_path, library=None):
@stop
@jellyfin_item
def artist(self, item, e_item):
-
- ''' If item does not exist, entry will be added.
- If item exists, entry will be updated.
- '''
- server_address = self.server.auth.get_server_info(self.server.auth.server_id)['address']
+ """If item does not exist, entry will be added.
+ If item exists, entry will be updated.
+ """
+ server_address = self.server.auth.get_server_info(self.server.auth.server_id)[
+ "address"
+ ]
API = api.API(item, server_address)
- obj = self.objects.map(item, 'Artist')
+ obj = self.objects.map(item, "Artist")
update = True
try:
- obj['ArtistId'] = e_item[0]
- obj['LibraryId'] = e_item[6]
- obj['LibraryName'] = self.jellyfin_db.get_view_name(obj['LibraryId'])
+ obj["ArtistId"] = e_item[0]
+ obj["LibraryId"] = e_item[6]
+ obj["LibraryName"] = self.jellyfin_db.get_view_name(obj["LibraryId"])
except TypeError:
update = False
@@ -60,72 +61,81 @@ def artist(self, item, e_item):
# This item doesn't belong to a whitelisted library
return
- obj['ArtistId'] = None
- obj['LibraryId'] = library['Id']
- obj['LibraryName'] = library['Name']
- LOG.debug("ArtistId %s not found", obj['Id'])
+ obj["ArtistId"] = None
+ obj["LibraryId"] = library["Id"]
+ obj["LibraryName"] = library["Name"]
+ LOG.debug("ArtistId %s not found", obj["Id"])
else:
if self.validate_artist(*values(obj, QU.get_artist_by_id_obj)) is None:
update = False
- LOG.info("ArtistId %s missing from kodi. repairing the entry.", obj['ArtistId'])
-
- obj['LastScraped'] = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
- obj['ArtistType'] = "MusicArtist"
- obj['Genre'] = " / ".join(obj['Genres'] or [])
- obj['Bio'] = API.get_overview(obj['Bio'])
- obj['Artwork'] = API.get_all_artwork(self.objects.map(item, 'ArtworkMusic'), True)
- obj['Thumb'] = obj['Artwork']['Primary']
- obj['Backdrops'] = obj['Artwork']['Backdrop'] or ""
-
- if obj['Thumb']:
- obj['Thumb'] = "%s" % obj['Thumb']
-
- if obj['Backdrops']:
- obj['Backdrops'] = "%s" % obj['Backdrops'][0]
+ LOG.info(
+ "ArtistId %s missing from kodi. repairing the entry.",
+ obj["ArtistId"],
+ )
+
+ obj["LastScraped"] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+ obj["ArtistType"] = "MusicArtist"
+ obj["Genre"] = " / ".join(obj["Genres"] or [])
+ obj["Bio"] = API.get_overview(obj["Bio"])
+ obj["Artwork"] = API.get_all_artwork(
+ self.objects.map(item, "ArtworkMusic"), True
+ )
+ obj["Thumb"] = obj["Artwork"]["Primary"]
+ obj["Backdrops"] = obj["Artwork"]["Backdrop"] or ""
+
+ if obj["Thumb"]:
+ obj["Thumb"] = "%s" % obj["Thumb"]
+
+ if obj["Backdrops"]:
+ obj["Backdrops"] = "%s" % obj["Backdrops"][0]
if update:
self.artist_update(obj)
else:
self.artist_add(obj)
- self.update(obj['Genre'], obj['Bio'], obj['Thumb'], obj['Backdrops'], obj['LastScraped'], obj['ArtistId'])
- self.artwork.add(obj['Artwork'], obj['ArtistId'], "artist")
- self.item_ids.append(obj['Id'])
+ self.update(
+ obj["Genre"],
+ obj["Bio"],
+ obj["Thumb"],
+ obj["Backdrops"],
+ obj["LastScraped"],
+ obj["ArtistId"],
+ )
+ self.artwork.add(obj["Artwork"], obj["ArtistId"], "artist")
+ self.item_ids.append(obj["Id"])
def artist_add(self, obj):
+ """Add object to kodi.
- ''' Add object to kodi.
-
- safety checks: It looks like Jellyfin supports the same artist multiple times.
- Kodi doesn't allow that. In case that happens we just merge the artist entries.
- '''
- obj['ArtistId'] = self.get(*values(obj, QU.get_artist_obj))
+ safety checks: It looks like Jellyfin supports the same artist multiple times.
+ Kodi doesn't allow that. In case that happens we just merge the artist entries.
+ """
+ obj["ArtistId"] = self.get(*values(obj, QU.get_artist_obj))
self.jellyfin_db.add_reference(*values(obj, QUEM.add_reference_artist_obj))
- LOG.debug("ADD artist [%s] %s: %s", obj['ArtistId'], obj['Name'], obj['Id'])
+ LOG.debug("ADD artist [%s] %s: %s", obj["ArtistId"], obj["Name"], obj["Id"])
def artist_update(self, obj):
-
- ''' Update object to kodi.
- '''
+ """Update object to kodi."""
self.jellyfin_db.update_reference(*values(obj, QUEM.update_reference_obj))
- LOG.debug("UPDATE artist [%s] %s: %s", obj['ArtistId'], obj['Name'], obj['Id'])
+ LOG.debug("UPDATE artist [%s] %s: %s", obj["ArtistId"], obj["Name"], obj["Id"])
@stop
@jellyfin_item
def album(self, item, e_item):
-
- ''' Update object to kodi.
- '''
- server_address = self.server.auth.get_server_info(self.server.auth.server_id)['address']
+ """Update object to kodi."""
+ server_address = self.server.auth.get_server_info(self.server.auth.server_id)[
+ "address"
+ ]
API = api.API(item, server_address)
- obj = self.objects.map(item, 'Album')
+ obj = self.objects.map(item, "Album")
update = True
try:
- obj['AlbumId'] = e_item[0]
- obj['LibraryId'] = e_item[6]
- obj['LibraryName'] = self.jellyfin_db.get_view_name(obj['LibraryId'])
+ obj["AlbumId"] = e_item[0]
+ obj["LibraryId"] = e_item[6]
+ obj["LibraryName"] = self.jellyfin_db.get_view_name(obj["LibraryId"])
except TypeError:
update = False
@@ -134,31 +144,35 @@ def album(self, item, e_item):
# This item doesn't belong to a whitelisted library
return
- obj['AlbumId'] = None
- obj['LibraryId'] = library['Id']
- obj['LibraryName'] = library['Name']
- LOG.debug("AlbumId %s not found", obj['Id'])
+ obj["AlbumId"] = None
+ obj["LibraryId"] = library["Id"]
+ obj["LibraryName"] = library["Name"]
+ LOG.debug("AlbumId %s not found", obj["Id"])
else:
if self.validate_album(*values(obj, QU.get_album_by_id_obj)) is None:
update = False
- LOG.info("AlbumId %s missing from kodi. repairing the entry.", obj['AlbumId'])
-
- obj['Rating'] = 0
- obj['LastScraped'] = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
- obj['Genres'] = obj['Genres'] or []
- obj['Genre'] = " / ".join(obj['Genres'])
- obj['Bio'] = API.get_overview(obj['Bio'])
- obj['Artists'] = " / ".join(obj['Artists'] or [])
- obj['Artwork'] = API.get_all_artwork(self.objects.map(item, 'ArtworkMusic'), True)
- obj['Thumb'] = obj['Artwork']['Primary']
- obj['DateAdded'] = item.get('DateCreated')
-
- if obj['DateAdded']:
- obj['DateAdded'] = Local(obj['DateAdded']).split('.')[0].replace('T', " ")
-
- if obj['Thumb']:
- obj['Thumb'] = "%s" % obj['Thumb']
+ LOG.info(
+ "AlbumId %s missing from kodi. repairing the entry.", obj["AlbumId"]
+ )
+
+ obj["Rating"] = 0
+ obj["LastScraped"] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+ obj["Genres"] = obj["Genres"] or []
+ obj["Genre"] = " / ".join(obj["Genres"])
+ obj["Bio"] = API.get_overview(obj["Bio"])
+ obj["Artists"] = " / ".join(obj["Artists"] or [])
+ obj["Artwork"] = API.get_all_artwork(
+ self.objects.map(item, "ArtworkMusic"), True
+ )
+ obj["Thumb"] = obj["Artwork"]["Primary"]
+ obj["DateAdded"] = item.get("DateCreated")
+
+ if obj["DateAdded"]:
+ obj["DateAdded"] = Local(obj["DateAdded"]).split(".")[0].replace("T", " ")
+
+ if obj["Thumb"]:
+ obj["Thumb"] = "%s" % obj["Thumb"]
if update:
self.album_update(obj)
@@ -169,89 +183,90 @@ def album(self, item, e_item):
self.artist_discography(obj)
self.update_album(*values(obj, QU.update_album_obj))
self.add_genres(*values(obj, QU.add_genres_obj))
- self.artwork.add(obj['Artwork'], obj['AlbumId'], "album")
- self.item_ids.append(obj['Id'])
+ self.artwork.add(obj["Artwork"], obj["AlbumId"], "album")
+ self.item_ids.append(obj["Id"])
def album_add(self, obj):
-
- ''' Add object to kodi.
- '''
+ """Add object to kodi."""
if self.version_id >= 82:
obj_values = values(obj, QU.get_album_obj82)
else:
obj_values = values(obj, QU.get_album_obj)
- obj['AlbumId'] = self.get_album(*obj_values)
+ obj["AlbumId"] = self.get_album(*obj_values)
self.jellyfin_db.add_reference(*values(obj, QUEM.add_reference_album_obj))
- LOG.debug("ADD album [%s] %s: %s", obj['AlbumId'], obj['Title'], obj['Id'])
+ LOG.debug("ADD album [%s] %s: %s", obj["AlbumId"], obj["Title"], obj["Id"])
def album_update(self, obj):
-
- ''' Update object to kodi.
- '''
+ """Update object to kodi."""
self.jellyfin_db.update_reference(*values(obj, QUEM.update_reference_obj))
- LOG.debug("UPDATE album [%s] %s: %s", obj['AlbumId'], obj['Title'], obj['Id'])
+ LOG.debug("UPDATE album [%s] %s: %s", obj["AlbumId"], obj["Title"], obj["Id"])
def artist_discography(self, obj):
-
- ''' Update the artist's discography.
- '''
- for artist in (obj['ArtistItems'] or []):
+ """Update the artist's discography."""
+ for artist in obj["ArtistItems"] or []:
temp_obj = dict(obj)
- temp_obj['Id'] = artist['Id']
- temp_obj['AlbumId'] = obj['Id']
+ temp_obj["Id"] = artist["Id"]
+ temp_obj["AlbumId"] = obj["Id"]
try:
- temp_obj['ArtistId'] = self.jellyfin_db.get_item_by_id(*values(temp_obj, QUEM.get_item_obj))[0]
+ temp_obj["ArtistId"] = self.jellyfin_db.get_item_by_id(
+ *values(temp_obj, QUEM.get_item_obj)
+ )[0]
except TypeError:
continue
self.add_discography(*values(temp_obj, QU.update_discography_obj))
- self.jellyfin_db.update_parent_id(*values(temp_obj, QUEM.update_parent_album_obj))
+ self.jellyfin_db.update_parent_id(
+ *values(temp_obj, QUEM.update_parent_album_obj)
+ )
def artist_link(self, obj):
-
- ''' Assign main artists to album.
- Artist does not exist in jellyfin database, create the reference.
- '''
- for artist in (obj['AlbumArtists'] or []):
+ """Assign main artists to album.
+ Artist does not exist in jellyfin database, create the reference.
+ """
+ for artist in obj["AlbumArtists"] or []:
temp_obj = dict(obj)
- temp_obj['Name'] = artist['Name']
- temp_obj['Id'] = artist['Id']
+ temp_obj["Name"] = artist["Name"]
+ temp_obj["Id"] = artist["Id"]
try:
- temp_obj['ArtistId'] = self.jellyfin_db.get_item_by_id(*values(temp_obj, QUEM.get_item_obj))[0]
+ temp_obj["ArtistId"] = self.jellyfin_db.get_item_by_id(
+ *values(temp_obj, QUEM.get_item_obj)
+ )[0]
except TypeError:
try:
- self.artist(self.server.jellyfin.get_item(temp_obj['Id']))
- temp_obj['ArtistId'] = self.jellyfin_db.get_item_by_id(*values(temp_obj, QUEM.get_item_obj))[0]
+ self.artist(self.server.jellyfin.get_item(temp_obj["Id"]))
+ temp_obj["ArtistId"] = self.jellyfin_db.get_item_by_id(
+ *values(temp_obj, QUEM.get_item_obj)
+ )[0]
except Exception as error:
LOG.exception(error)
continue
self.update_artist_name(*values(temp_obj, QU.update_artist_name_obj))
self.link(*values(temp_obj, QU.update_link_obj))
- self.item_ids.append(temp_obj['Id'])
+ self.item_ids.append(temp_obj["Id"])
@stop
@jellyfin_item
def song(self, item, e_item):
-
- ''' Update object to kodi.
- '''
- server_address = self.server.auth.get_server_info(self.server.auth.server_id)['address']
+ """Update object to kodi."""
+ server_address = self.server.auth.get_server_info(self.server.auth.server_id)[
+ "address"
+ ]
API = api.API(item, server_address)
- obj = self.objects.map(item, 'Song')
+ obj = self.objects.map(item, "Song")
update = True
try:
- obj['SongId'] = e_item[0]
- obj['PathId'] = e_item[2]
- obj['AlbumId'] = e_item[3]
- obj['LibraryId'] = e_item[6]
- obj['LibraryName'] = self.jellyfin_db.get_view_name(obj['LibraryId'])
+ obj["SongId"] = e_item[0]
+ obj["PathId"] = e_item[2]
+ obj["AlbumId"] = e_item[3]
+ obj["LibraryId"] = e_item[6]
+ obj["LibraryName"] = self.jellyfin_db.get_view_name(obj["LibraryId"])
except TypeError:
update = False
@@ -260,38 +275,42 @@ def song(self, item, e_item):
# This item doesn't belong to a whitelisted library
return
- obj['SongId'] = self.create_entry_song()
- obj['LibraryId'] = library['Id']
- obj['LibraryName'] = library['Name']
- LOG.debug("SongId %s not found", obj['Id'])
+ obj["SongId"] = self.create_entry_song()
+ obj["LibraryId"] = library["Id"]
+ obj["LibraryName"] = library["Name"]
+ LOG.debug("SongId %s not found", obj["Id"])
else:
if self.validate_song(*values(obj, QU.get_song_by_id_obj)) is None:
update = False
- LOG.info("SongId %s missing from kodi. repairing the entry.", obj['SongId'])
+ LOG.info(
+ "SongId %s missing from kodi. repairing the entry.", obj["SongId"]
+ )
self.get_song_path_filename(obj, API)
- obj['Rating'] = 0
- obj['Genres'] = obj['Genres'] or []
- obj['PlayCount'] = API.get_playcount(obj['Played'], obj['PlayCount'])
- obj['Runtime'] = (obj['Runtime'] or 0) / 10000000.0
- obj['Genre'] = " / ".join(obj['Genres'])
- obj['Artists'] = " / ".join(obj['Artists'] or [])
- obj['AlbumArtists'] = obj['AlbumArtists'] or []
- obj['Index'] = obj['Index'] or 0
- obj['Disc'] = obj['Disc'] or 1
- obj['EmbedCover'] = False
- obj['Comment'] = API.get_overview(obj['Comment'])
- obj['Artwork'] = API.get_all_artwork(self.objects.map(item, 'ArtworkMusic'), True)
-
- if obj['DateAdded']:
- obj['DateAdded'] = Local(obj['DateAdded']).split('.')[0].replace('T', " ")
-
- if obj['DatePlayed']:
- obj['DatePlayed'] = Local(obj['DatePlayed']).split('.')[0].replace('T', " ")
-
- obj['Index'] = obj['Disc'] * 2 ** 16 + obj['Index']
+ obj["Rating"] = 0
+ obj["Genres"] = obj["Genres"] or []
+ obj["PlayCount"] = API.get_playcount(obj["Played"], obj["PlayCount"])
+ obj["Runtime"] = (obj["Runtime"] or 0) / 10000000.0
+ obj["Genre"] = " / ".join(obj["Genres"])
+ obj["Artists"] = " / ".join(obj["Artists"] or [])
+ obj["AlbumArtists"] = obj["AlbumArtists"] or []
+ obj["Index"] = obj["Index"] or 0
+ obj["Disc"] = obj["Disc"] or 1
+ obj["EmbedCover"] = False
+ obj["Comment"] = API.get_overview(obj["Comment"])
+ obj["Artwork"] = API.get_all_artwork(
+ self.objects.map(item, "ArtworkMusic"), True
+ )
+
+ if obj["DateAdded"]:
+ obj["DateAdded"] = Local(obj["DateAdded"]).split(".")[0].replace("T", " ")
+
+ if obj["DatePlayed"]:
+ obj["DatePlayed"] = Local(obj["DatePlayed"]).split(".")[0].replace("T", " ")
+
+ obj["Index"] = obj["Disc"] * 2**16 + obj["Index"]
if update:
self.song_update(obj)
@@ -303,227 +322,275 @@ def song(self, item, e_item):
self.song_artist_link(obj)
self.song_artist_discography(obj)
- obj['strAlbumArtists'] = " / ".join(obj['AlbumArtists'])
+ obj["strAlbumArtists"] = " / ".join(obj["AlbumArtists"])
self.get_album_artist(*values(obj, QU.get_album_artist_obj))
self.add_genres(*values(obj, QU.update_genre_song_obj))
- self.artwork.add(obj['Artwork'], obj['SongId'], "song")
- self.item_ids.append(obj['Id'])
+ self.artwork.add(obj["Artwork"], obj["SongId"], "song")
+ self.item_ids.append(obj["Id"])
- if obj['SongAlbumId'] is None:
- self.artwork.add(obj['Artwork'], obj['AlbumId'], "album")
+ if obj["SongAlbumId"] is None:
+ self.artwork.add(obj["Artwork"], obj["AlbumId"], "album")
return not update
def song_add(self, obj):
+ """Add object to kodi.
- ''' Add object to kodi.
-
- Verify if there's an album associated.
- If no album found, create a single's album
- '''
- obj['PathId'] = self.add_path(obj['Path'])
+ Verify if there's an album associated.
+ If no album found, create a single's album
+ """
+ obj["PathId"] = self.add_path(obj["Path"])
try:
- obj['AlbumId'] = self.jellyfin_db.get_item_by_id(*values(obj, QUEM.get_item_song_obj))[0]
+ obj["AlbumId"] = self.jellyfin_db.get_item_by_id(
+ *values(obj, QUEM.get_item_song_obj)
+ )[0]
except TypeError:
try:
- if obj['SongAlbumId'] is None:
+ if obj["SongAlbumId"] is None:
raise TypeError("No album id found associated?")
- self.album(self.server.jellyfin.get_item(obj['SongAlbumId']))
- obj['AlbumId'] = self.jellyfin_db.get_item_by_id(*values(obj, QUEM.get_item_song_obj))[0]
+ self.album(self.server.jellyfin.get_item(obj["SongAlbumId"]))
+ obj["AlbumId"] = self.jellyfin_db.get_item_by_id(
+ *values(obj, QUEM.get_item_song_obj)
+ )[0]
except TypeError:
self.single(obj)
self.add_song(*values(obj, QU.add_song_obj))
self.jellyfin_db.add_reference(*values(obj, QUEM.add_reference_song_obj))
- LOG.debug("ADD song [%s/%s/%s] %s: %s", obj['PathId'], obj['AlbumId'], obj['SongId'], obj['Id'], obj['Title'])
+ LOG.debug(
+ "ADD song [%s/%s/%s] %s: %s",
+ obj["PathId"],
+ obj["AlbumId"],
+ obj["SongId"],
+ obj["Id"],
+ obj["Title"],
+ )
def song_update(self, obj):
-
- ''' Update object to kodi.
- '''
+ """Update object to kodi."""
self.update_path(*values(obj, QU.update_path_obj))
self.update_song(*values(obj, QU.update_song_obj))
self.jellyfin_db.update_reference(*values(obj, QUEM.update_reference_obj))
- LOG.debug("UPDATE song [%s/%s/%s] %s: %s", obj['PathId'], obj['AlbumId'], obj['SongId'], obj['Id'], obj['Title'])
+ LOG.debug(
+ "UPDATE song [%s/%s/%s] %s: %s",
+ obj["PathId"],
+ obj["AlbumId"],
+ obj["SongId"],
+ obj["Id"],
+ obj["Title"],
+ )
def get_song_path_filename(self, obj, api):
-
- ''' Get the path and filename and build it into protocol://path
- '''
- obj['Path'] = api.get_file_path(obj['Path'])
- obj['Filename'] = obj['Path'].rsplit('\\', 1)[1] if '\\' in obj['Path'] else obj['Path'].rsplit('/', 1)[1]
+ """Get the path and filename and build it into protocol://path"""
+ obj["Path"] = api.get_file_path(obj["Path"])
+ obj["Filename"] = (
+ obj["Path"].rsplit("\\", 1)[1]
+ if "\\" in obj["Path"]
+ else obj["Path"].rsplit("/", 1)[1]
+ )
if self.direct_path:
- if not validate(obj['Path']):
+ if not validate(obj["Path"]):
raise PathValidationException("Failed to validate path. User stopped.")
- obj['Path'] = obj['Path'].replace(obj['Filename'], "")
+ obj["Path"] = obj["Path"].replace(obj["Filename"], "")
else:
- server_address = self.server.auth.get_server_info(self.server.auth.server_id)['address']
- obj['Path'] = "%s/Audio/%s/" % (server_address, obj['Id'])
- obj['Filename'] = "stream.%s?static=true" % obj['Container']
+ server_address = self.server.auth.get_server_info(
+ self.server.auth.server_id
+ )["address"]
+ obj["Path"] = "%s/Audio/%s/" % (server_address, obj["Id"])
+ obj["Filename"] = "stream.%s?static=true" % obj["Container"]
def song_artist_discography(self, obj):
-
- ''' Update the artist's discography.
- '''
+ """Update the artist's discography."""
artists = []
- for artist in (obj['AlbumArtists'] or []):
+ for artist in obj["AlbumArtists"] or []:
temp_obj = dict(obj)
- temp_obj['Name'] = artist['Name']
- temp_obj['Id'] = artist['Id']
+ temp_obj["Name"] = artist["Name"]
+ temp_obj["Id"] = artist["Id"]
- artists.append(temp_obj['Name'])
+ artists.append(temp_obj["Name"])
try:
- temp_obj['ArtistId'] = self.jellyfin_db.get_item_by_id(*values(temp_obj, QUEM.get_item_obj))[0]
+ temp_obj["ArtistId"] = self.jellyfin_db.get_item_by_id(
+ *values(temp_obj, QUEM.get_item_obj)
+ )[0]
except TypeError:
try:
- self.artist(self.server.jellyfin.get_item(temp_obj['Id']))
- temp_obj['ArtistId'] = self.jellyfin_db.get_item_by_id(*values(temp_obj, QUEM.get_item_obj))[0]
+ self.artist(self.server.jellyfin.get_item(temp_obj["Id"]))
+ temp_obj["ArtistId"] = self.jellyfin_db.get_item_by_id(
+ *values(temp_obj, QUEM.get_item_obj)
+ )[0]
except Exception as error:
LOG.exception(error)
continue
self.link(*values(temp_obj, QU.update_link_obj))
- self.item_ids.append(temp_obj['Id'])
+ self.item_ids.append(temp_obj["Id"])
- if obj['Album']:
+ if obj["Album"]:
- temp_obj['Title'] = obj['Album']
- temp_obj['Year'] = 0
+ temp_obj["Title"] = obj["Album"]
+ temp_obj["Year"] = 0
self.add_discography(*values(temp_obj, QU.update_discography_obj))
- obj['AlbumArtists'] = artists
+ obj["AlbumArtists"] = artists
def song_artist_link(self, obj):
-
- ''' Assign main artists to song.
- Artist does not exist in jellyfin database, create the reference.
- '''
- for index, artist in enumerate(obj['ArtistItems'] or []):
+ """Assign main artists to song.
+ Artist does not exist in jellyfin database, create the reference.
+ """
+ for index, artist in enumerate(obj["ArtistItems"] or []):
temp_obj = dict(obj)
- temp_obj['Name'] = artist['Name']
- temp_obj['Id'] = artist['Id']
- temp_obj['Index'] = index
+ temp_obj["Name"] = artist["Name"]
+ temp_obj["Id"] = artist["Id"]
+ temp_obj["Index"] = index
try:
- temp_obj['ArtistId'] = self.jellyfin_db.get_item_by_id(*values(temp_obj, QUEM.get_item_obj))[0]
+ temp_obj["ArtistId"] = self.jellyfin_db.get_item_by_id(
+ *values(temp_obj, QUEM.get_item_obj)
+ )[0]
except TypeError:
try:
- self.artist(self.server.jellyfin.get_item(temp_obj['Id']))
- temp_obj['ArtistId'] = self.jellyfin_db.get_item_by_id(*values(temp_obj, QUEM.get_item_obj))[0]
+ self.artist(self.server.jellyfin.get_item(temp_obj["Id"]))
+ temp_obj["ArtistId"] = self.jellyfin_db.get_item_by_id(
+ *values(temp_obj, QUEM.get_item_obj)
+ )[0]
except Exception as error:
LOG.exception(error)
continue
self.link_song_artist(*values(temp_obj, QU.update_song_artist_obj))
- self.item_ids.append(temp_obj['Id'])
+ self.item_ids.append(temp_obj["Id"])
def single(self, obj):
- obj['AlbumId'] = self.create_entry_album()
+ obj["AlbumId"] = self.create_entry_album()
self.add_single(*values(obj, QU.add_single_obj))
@stop
@jellyfin_item
def userdata(self, item, e_item):
+ """This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks
+ Poster with progress bar
+ """
- ''' This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks
- Poster with progress bar
- '''
-
- obj = self.objects.map(item, 'SongUserData')
+ obj = self.objects.map(item, "SongUserData")
try:
- obj['KodiId'] = e_item[0]
- obj['Media'] = e_item[4]
+ obj["KodiId"] = e_item[0]
+ obj["Media"] = e_item[4]
except TypeError:
return
- obj['Rating'] = 0
+ obj["Rating"] = 0
- if obj['Media'] == 'song':
+ if obj["Media"] == "song":
- if obj['DatePlayed']:
- obj['DatePlayed'] = Local(obj['DatePlayed']).split('.')[0].replace('T', " ")
+ if obj["DatePlayed"]:
+ obj["DatePlayed"] = (
+ Local(obj["DatePlayed"]).split(".")[0].replace("T", " ")
+ )
self.rate_song(*values(obj, QU.update_song_rating_obj))
self.jellyfin_db.update_reference(*values(obj, QUEM.update_reference_obj))
- LOG.debug("USERDATA %s [%s] %s: %s", obj['Media'], obj['KodiId'], obj['Id'], obj['Title'])
+ LOG.debug(
+ "USERDATA %s [%s] %s: %s",
+ obj["Media"],
+ obj["KodiId"],
+ obj["Id"],
+ obj["Title"],
+ )
@stop
@jellyfin_item
def remove(self, item_id, e_item):
+ """This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks
+ Poster with progress bar
- ''' This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks
- Poster with progress bar
-
- This should address single song scenario, where server doesn't actually
- create an album for the song.
- '''
- obj = {'Id': item_id}
+ This should address single song scenario, where server doesn't actually
+ create an album for the song.
+ """
+ obj = {"Id": item_id}
try:
- obj['KodiId'] = e_item[0]
- obj['Media'] = e_item[4]
+ obj["KodiId"] = e_item[0]
+ obj["Media"] = e_item[4]
except TypeError:
return
- if obj['Media'] == 'song':
+ if obj["Media"] == "song":
- self.remove_song(obj['KodiId'], obj['Id'])
- self.jellyfin_db.remove_wild_item(obj['Id'])
+ self.remove_song(obj["KodiId"], obj["Id"])
+ self.jellyfin_db.remove_wild_item(obj["Id"])
- for item in self.jellyfin_db.get_item_by_wild_id(*values(obj, QUEM.get_item_by_wild_obj)):
- if item[1] == 'album':
+ for item in self.jellyfin_db.get_item_by_wild_id(
+ *values(obj, QUEM.get_item_by_wild_obj)
+ ):
+ if item[1] == "album":
temp_obj = dict(obj)
- temp_obj['ParentId'] = item[0]
+ temp_obj["ParentId"] = item[0]
- if not self.jellyfin_db.get_item_by_parent_id(*values(temp_obj, QUEM.get_item_by_parent_song_obj)):
- self.remove_album(temp_obj['ParentId'], obj['Id'])
+ if not self.jellyfin_db.get_item_by_parent_id(
+ *values(temp_obj, QUEM.get_item_by_parent_song_obj)
+ ):
+ self.remove_album(temp_obj["ParentId"], obj["Id"])
- elif obj['Media'] == 'album':
- obj['ParentId'] = obj['KodiId']
+ elif obj["Media"] == "album":
+ obj["ParentId"] = obj["KodiId"]
- for song in self.jellyfin_db.get_item_by_parent_id(*values(obj, QUEM.get_item_by_parent_song_obj)):
- self.remove_song(song[1], obj['Id'])
+ for song in self.jellyfin_db.get_item_by_parent_id(
+ *values(obj, QUEM.get_item_by_parent_song_obj)
+ ):
+ self.remove_song(song[1], obj["Id"])
else:
- self.jellyfin_db.remove_items_by_parent_id(*values(obj, QUEM.delete_item_by_parent_song_obj))
+ self.jellyfin_db.remove_items_by_parent_id(
+ *values(obj, QUEM.delete_item_by_parent_song_obj)
+ )
- self.remove_album(obj['KodiId'], obj['Id'])
+ self.remove_album(obj["KodiId"], obj["Id"])
- elif obj['Media'] == 'artist':
- obj['ParentId'] = obj['KodiId']
+ elif obj["Media"] == "artist":
+ obj["ParentId"] = obj["KodiId"]
- for album in self.jellyfin_db.get_item_by_parent_id(*values(obj, QUEM.get_item_by_parent_album_obj)):
+ for album in self.jellyfin_db.get_item_by_parent_id(
+ *values(obj, QUEM.get_item_by_parent_album_obj)
+ ):
temp_obj = dict(obj)
- temp_obj['ParentId'] = album[1]
+ temp_obj["ParentId"] = album[1]
- for song in self.jellyfin_db.get_item_by_parent_id(*values(temp_obj, QUEM.get_item_by_parent_song_obj)):
- self.remove_song(song[1], obj['Id'])
+ for song in self.jellyfin_db.get_item_by_parent_id(
+ *values(temp_obj, QUEM.get_item_by_parent_song_obj)
+ ):
+ self.remove_song(song[1], obj["Id"])
else:
- self.jellyfin_db.remove_items_by_parent_id(*values(temp_obj, QUEM.delete_item_by_parent_song_obj))
- self.jellyfin_db.remove_items_by_parent_id(*values(temp_obj, QUEM.delete_item_by_parent_artist_obj))
- self.remove_album(temp_obj['ParentId'], obj['Id'])
+ self.jellyfin_db.remove_items_by_parent_id(
+ *values(temp_obj, QUEM.delete_item_by_parent_song_obj)
+ )
+ self.jellyfin_db.remove_items_by_parent_id(
+ *values(temp_obj, QUEM.delete_item_by_parent_artist_obj)
+ )
+ self.remove_album(temp_obj["ParentId"], obj["Id"])
else:
- self.jellyfin_db.remove_items_by_parent_id(*values(obj, QUEM.delete_item_by_parent_album_obj))
+ self.jellyfin_db.remove_items_by_parent_id(
+ *values(obj, QUEM.delete_item_by_parent_album_obj)
+ )
- self.remove_artist(obj['KodiId'], obj['Id'])
+ self.remove_artist(obj["KodiId"], obj["Id"])
self.jellyfin_db.remove_item(*values(obj, QUEM.delete_item_obj))
@@ -547,29 +614,31 @@ def remove_song(self, kodi_id, item_id):
@jellyfin_item
def get_child(self, item_id, e_item):
-
- ''' Get all child elements from tv show jellyfin id.
- '''
- obj = {'Id': item_id}
+ """Get all child elements from tv show jellyfin id."""
+ obj = {"Id": item_id}
child = []
try:
- obj['KodiId'] = e_item[0]
- obj['FileId'] = e_item[1]
- obj['ParentId'] = e_item[3]
- obj['Media'] = e_item[4]
+ obj["KodiId"] = e_item[0]
+ obj["FileId"] = e_item[1]
+ obj["ParentId"] = e_item[3]
+ obj["Media"] = e_item[4]
except TypeError:
return child
- obj['ParentId'] = obj['KodiId']
+ obj["ParentId"] = obj["KodiId"]
- for album in self.jellyfin_db.get_item_by_parent_id(*values(obj, QUEM.get_item_by_parent_album_obj)):
+ for album in self.jellyfin_db.get_item_by_parent_id(
+ *values(obj, QUEM.get_item_by_parent_album_obj)
+ ):
temp_obj = dict(obj)
- temp_obj['ParentId'] = album[1]
+ temp_obj["ParentId"] = album[1]
child.append((album[0],))
- for song in self.jellyfin_db.get_item_by_parent_id(*values(temp_obj, QUEM.get_item_by_parent_song_obj)):
+ for song in self.jellyfin_db.get_item_by_parent_id(
+ *values(temp_obj, QUEM.get_item_by_parent_song_obj)
+ ):
child.append((song[0],))
return child
diff --git a/jellyfin_kodi/objects/musicvideos.py b/jellyfin_kodi/objects/musicvideos.py
index 93cc7b607..c2d280e44 100644
--- a/jellyfin_kodi/objects/musicvideos.py
+++ b/jellyfin_kodi/objects/musicvideos.py
@@ -43,24 +43,25 @@ def __init__(self, server, jellyfindb, videodb, direct_path, library=None):
@stop
@jellyfin_item
def musicvideo(self, item, e_item):
-
- ''' If item does not exist, entry will be added.
- If item exists, entry will be updated.
-
- If we don't get the track number from Jellyfin, see if we can infer it
- from the sortname attribute.
- '''
- server_address = self.server.auth.get_server_info(self.server.auth.server_id)['address']
+ """If item does not exist, entry will be added.
+ If item exists, entry will be updated.
+
+ If we don't get the track number from Jellyfin, see if we can infer it
+ from the sortname attribute.
+ """
+ server_address = self.server.auth.get_server_info(self.server.auth.server_id)[
+ "address"
+ ]
API = api.API(item, server_address)
- obj = self.objects.map(item, 'MusicVideo')
+ obj = self.objects.map(item, "MusicVideo")
update = True
try:
- obj['MvideoId'] = e_item[0]
- obj['FileId'] = e_item[1]
- obj['PathId'] = e_item[2]
- obj['LibraryId'] = e_item[6]
- obj['LibraryName'] = self.jellyfin_db.get_view_name(obj['LibraryId'])
+ obj["MvideoId"] = e_item[0]
+ obj["FileId"] = e_item[1]
+ obj["PathId"] = e_item[2]
+ obj["LibraryId"] = e_item[6]
+ obj["LibraryName"] = self.jellyfin_db.get_view_name(obj["LibraryId"])
except TypeError:
update = False
@@ -69,67 +70,80 @@ def musicvideo(self, item, e_item):
# This item doesn't belong to a whitelisted library
return
- LOG.debug("MvideoId for %s not found", obj['Id'])
- obj['MvideoId'] = self.create_entry()
- obj['LibraryId'] = library['Id']
- obj['LibraryName'] = library['Name']
+ LOG.debug("MvideoId for %s not found", obj["Id"])
+ obj["MvideoId"] = self.create_entry()
+ obj["LibraryId"] = library["Id"]
+ obj["LibraryName"] = library["Name"]
else:
if self.get(*values(obj, QU.get_musicvideo_obj)) is None:
update = False
- LOG.info("MvideoId %s missing from kodi. repairing the entry.", obj['MvideoId'])
-
- if (obj.get('ProductionYear') or 0) > 9999:
- obj['ProductionYear'] = int(str(obj['ProductionYear'])[:4])
-
- if (obj.get('Year') or 0) > 9999:
- obj['Year'] = int(str(obj['Year'])[:4])
-
- obj['Path'] = API.get_file_path(obj['Path'])
- obj['Genres'] = obj['Genres'] or []
- obj['ArtistItems'] = obj['ArtistItems'] or []
- obj['Studios'] = [API.validate_studio(studio) for studio in (obj['Studios'] or [])]
- obj['Plot'] = API.get_overview(obj['Plot'])
- obj['DateAdded'] = Local(obj['DateAdded']).split('.')[0].replace('T', " ")
- obj['DatePlayed'] = None if not obj['DatePlayed'] else Local(obj['DatePlayed']).split('.')[0].replace('T', " ")
- obj['PlayCount'] = API.get_playcount(obj['Played'], obj['PlayCount'])
- obj['Resume'] = API.adjust_resume((obj['Resume'] or 0) / 10000000.0)
- obj['Runtime'] = round(float((obj['Runtime'] or 0) / 10000000.0), 6)
- obj['Premiere'] = Local(obj['Premiere']) if obj['Premiere'] else datetime.date(obj['Year'] or 1970, 1, 1)
- obj['Genre'] = " / ".join(obj['Genres'])
- obj['Studio'] = " / ".join(obj['Studios'])
- obj['Artists'] = " / ".join(obj['Artists'] or [])
- obj['Directors'] = " / ".join(obj['Directors'] or [])
- obj['Video'] = API.video_streams(obj['Video'] or [], obj['Container'])
- obj['Audio'] = API.audio_streams(obj['Audio'] or [])
- obj['Streams'] = API.media_streams(obj['Video'], obj['Audio'], obj['Subtitles'])
- obj['Artwork'] = API.get_all_artwork(self.objects.map(item, 'Artwork'))
+ LOG.info(
+ "MvideoId %s missing from kodi. repairing the entry.",
+ obj["MvideoId"],
+ )
+
+ if (obj.get("ProductionYear") or 0) > 9999:
+ obj["ProductionYear"] = int(str(obj["ProductionYear"])[:4])
+
+ if (obj.get("Year") or 0) > 9999:
+ obj["Year"] = int(str(obj["Year"])[:4])
+
+ obj["Path"] = API.get_file_path(obj["Path"])
+ obj["Genres"] = obj["Genres"] or []
+ obj["ArtistItems"] = obj["ArtistItems"] or []
+ obj["Studios"] = [
+ API.validate_studio(studio) for studio in (obj["Studios"] or [])
+ ]
+ obj["Plot"] = API.get_overview(obj["Plot"])
+ obj["DateAdded"] = Local(obj["DateAdded"]).split(".")[0].replace("T", " ")
+ obj["DatePlayed"] = (
+ None
+ if not obj["DatePlayed"]
+ else Local(obj["DatePlayed"]).split(".")[0].replace("T", " ")
+ )
+ obj["PlayCount"] = API.get_playcount(obj["Played"], obj["PlayCount"])
+ obj["Resume"] = API.adjust_resume((obj["Resume"] or 0) / 10000000.0)
+ obj["Runtime"] = round(float((obj["Runtime"] or 0) / 10000000.0), 6)
+ obj["Premiere"] = (
+ Local(obj["Premiere"])
+ if obj["Premiere"]
+ else datetime.date(obj["Year"] or 1970, 1, 1)
+ )
+ obj["Genre"] = " / ".join(obj["Genres"])
+ obj["Studio"] = " / ".join(obj["Studios"])
+ obj["Artists"] = " / ".join(obj["Artists"] or [])
+ obj["Directors"] = " / ".join(obj["Directors"] or [])
+ obj["Video"] = API.video_streams(obj["Video"] or [], obj["Container"])
+ obj["Audio"] = API.audio_streams(obj["Audio"] or [])
+ obj["Streams"] = API.media_streams(obj["Video"], obj["Audio"], obj["Subtitles"])
+ obj["Artwork"] = API.get_all_artwork(self.objects.map(item, "Artwork"))
self.get_path_filename(obj)
- if obj['Premiere']:
- obj['Premiere'] = str(obj['Premiere']).split('.')[0].replace('T', " ")
+ if obj["Premiere"]:
+ obj["Premiere"] = str(obj["Premiere"]).split(".")[0].replace("T", " ")
- for artist in obj['ArtistItems']:
- artist['Type'] = "Artist"
+ for artist in obj["ArtistItems"]:
+ artist["Type"] = "Artist"
- obj['People'] = obj['People'] or [] + obj['ArtistItems']
- obj['People'] = API.get_people_artwork(obj['People'])
+ obj["People"] = obj["People"] or [] + obj["ArtistItems"]
+ obj["People"] = API.get_people_artwork(obj["People"])
- if obj['Index'] is None and obj['SortTitle'] is not None:
- search = re.search(r'^\d+\s?', obj['SortTitle'])
+ if obj["Index"] is None and obj["SortTitle"] is not None:
+ search = re.search(r"^\d+\s?", obj["SortTitle"])
if search:
- obj['Index'] = search.group()
+ obj["Index"] = search.group()
tags = []
- tags.extend(obj['Tags'] or [])
- tags.append(obj['LibraryName'])
+ tags.extend(obj["Tags"] or [])
+ tags.append(obj["LibraryName"])
- if obj['Favorite']:
- tags.append('Favorite musicvideos')
+ if obj["Favorite"]:
+ tags.append("Favorite musicvideos")
- obj['Tags'] = tags
+ obj["Tags"] = tags
if update:
self.musicvideo_update(obj)
@@ -144,106 +158,129 @@ def musicvideo(self, item, e_item):
self.add_playstate(*values(obj, QU.add_bookmark_obj))
self.add_people(*values(obj, QU.add_people_mvideo_obj))
self.add_streams(*values(obj, QU.add_streams_obj))
- self.artwork.add(obj['Artwork'], obj['MvideoId'], "musicvideo")
- self.item_ids.append(obj['Id'])
+ self.artwork.add(obj["Artwork"], obj["MvideoId"], "musicvideo")
+ self.item_ids.append(obj["Id"])
return not update
def musicvideo_add(self, obj):
-
- ''' Add object to kodi.
- '''
- obj['PathId'] = self.add_path(*values(obj, QU.add_path_obj))
- obj['FileId'] = self.add_file(*values(obj, QU.add_file_obj))
+ """Add object to kodi."""
+ obj["PathId"] = self.add_path(*values(obj, QU.add_path_obj))
+ obj["FileId"] = self.add_file(*values(obj, QU.add_file_obj))
self.add(*values(obj, QU.add_musicvideo_obj))
self.jellyfin_db.add_reference(*values(obj, QUEM.add_reference_mvideo_obj))
- LOG.debug("ADD mvideo [%s/%s/%s] %s: %s", obj['PathId'], obj['FileId'], obj['MvideoId'], obj['Id'], obj['Title'])
+ LOG.debug(
+ "ADD mvideo [%s/%s/%s] %s: %s",
+ obj["PathId"],
+ obj["FileId"],
+ obj["MvideoId"],
+ obj["Id"],
+ obj["Title"],
+ )
def musicvideo_update(self, obj):
-
- ''' Update object to kodi.
- '''
+ """Update object to kodi."""
self.update(*values(obj, QU.update_musicvideo_obj))
self.jellyfin_db.update_reference(*values(obj, QUEM.update_reference_obj))
- LOG.debug("UPDATE mvideo [%s/%s/%s] %s: %s", obj['PathId'], obj['FileId'], obj['MvideoId'], obj['Id'], obj['Title'])
+ LOG.debug(
+ "UPDATE mvideo [%s/%s/%s] %s: %s",
+ obj["PathId"],
+ obj["FileId"],
+ obj["MvideoId"],
+ obj["Id"],
+ obj["Title"],
+ )
def get_path_filename(self, obj):
-
- ''' Get the path and filename and build it into protocol://path
- '''
- obj['Filename'] = obj['Path'].rsplit('\\', 1)[1] if '\\' in obj['Path'] else obj['Path'].rsplit('/', 1)[1]
+ """Get the path and filename and build it into protocol://path"""
+ obj["Filename"] = (
+ obj["Path"].rsplit("\\", 1)[1]
+ if "\\" in obj["Path"]
+ else obj["Path"].rsplit("/", 1)[1]
+ )
if self.direct_path:
- if not validate(obj['Path']):
+ if not validate(obj["Path"]):
raise PathValidationException("Failed to validate path. User stopped.")
- obj['Path'] = obj['Path'].replace(obj['Filename'], "")
+ obj["Path"] = obj["Path"].replace(obj["Filename"], "")
else:
- obj['Path'] = "plugin://plugin.video.jellyfin/%s/" % obj['LibraryId']
+ obj["Path"] = "plugin://plugin.video.jellyfin/%s/" % obj["LibraryId"]
params = {
- 'filename': py2_encode(obj['Filename'], 'utf-8'),
- 'id': obj['Id'],
- 'dbid': obj['MvideoId'],
- 'mode': "play"
+ "filename": py2_encode(obj["Filename"], "utf-8"),
+ "id": obj["Id"],
+ "dbid": obj["MvideoId"],
+ "mode": "play",
}
- obj['Filename'] = "%s?%s" % (obj['Path'], urlencode(params))
+ obj["Filename"] = "%s?%s" % (obj["Path"], urlencode(params))
@stop
@jellyfin_item
def userdata(self, item, e_item):
-
- ''' This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks
- Poster with progress bar
- '''
- server_address = self.server.auth.get_server_info(self.server.auth.server_id)['address']
+ """This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks
+ Poster with progress bar
+ """
+ server_address = self.server.auth.get_server_info(self.server.auth.server_id)[
+ "address"
+ ]
API = api.API(item, server_address)
- obj = self.objects.map(item, 'MusicVideoUserData')
+ obj = self.objects.map(item, "MusicVideoUserData")
try:
- obj['MvideoId'] = e_item[0]
- obj['FileId'] = e_item[1]
+ obj["MvideoId"] = e_item[0]
+ obj["FileId"] = e_item[1]
except TypeError:
return
- obj['Resume'] = API.adjust_resume((obj['Resume'] or 0) / 10000000.0)
- obj['Runtime'] = round(float((obj['Runtime'] or 0) / 10000000.0), 6)
- obj['PlayCount'] = API.get_playcount(obj['Played'], obj['PlayCount'])
+ obj["Resume"] = API.adjust_resume((obj["Resume"] or 0) / 10000000.0)
+ obj["Runtime"] = round(float((obj["Runtime"] or 0) / 10000000.0), 6)
+ obj["PlayCount"] = API.get_playcount(obj["Played"], obj["PlayCount"])
- if obj['DatePlayed']:
- obj['DatePlayed'] = Local(obj['DatePlayed']).split('.')[0].replace('T', " ")
+ if obj["DatePlayed"]:
+ obj["DatePlayed"] = Local(obj["DatePlayed"]).split(".")[0].replace("T", " ")
- if obj['Favorite']:
+ if obj["Favorite"]:
self.get_tag(*values(obj, QU.get_tag_mvideo_obj))
else:
self.remove_tag(*values(obj, QU.delete_tag_mvideo_obj))
self.add_playstate(*values(obj, QU.add_bookmark_obj))
self.jellyfin_db.update_reference(*values(obj, QUEM.update_reference_obj))
- LOG.debug("USERDATA mvideo [%s/%s] %s: %s", obj['FileId'], obj['MvideoId'], obj['Id'], obj['Title'])
+ LOG.debug(
+ "USERDATA mvideo [%s/%s] %s: %s",
+ obj["FileId"],
+ obj["MvideoId"],
+ obj["Id"],
+ obj["Title"],
+ )
@stop
@jellyfin_item
def remove(self, item_id, e_item):
-
- ''' Remove mvideoid, fileid, pathid, jellyfin reference.
- '''
- obj = {'Id': item_id}
+ """Remove mvideoid, fileid, pathid, jellyfin reference."""
+ obj = {"Id": item_id}
try:
- obj['MvideoId'] = e_item[0]
- obj['FileId'] = e_item[1]
- obj['PathId'] = e_item[2]
+ obj["MvideoId"] = e_item[0]
+ obj["FileId"] = e_item[1]
+ obj["PathId"] = e_item[2]
except TypeError:
return
- self.artwork.delete(obj['MvideoId'], "musicvideo")
+ self.artwork.delete(obj["MvideoId"], "musicvideo")
self.delete(*values(obj, QU.delete_musicvideo_obj))
if self.direct_path:
self.remove_path(*values(obj, QU.delete_path_obj))
self.jellyfin_db.remove_item(*values(obj, QUEM.delete_item_obj))
- LOG.debug("DELETE musicvideo %s [%s/%s] %s", obj['MvideoId'], obj['PathId'], obj['FileId'], obj['Id'])
+ LOG.debug(
+ "DELETE musicvideo %s [%s/%s] %s",
+ obj["MvideoId"],
+ obj["PathId"],
+ obj["FileId"],
+ obj["Id"],
+ )
diff --git a/jellyfin_kodi/objects/obj.py b/jellyfin_kodi/objects/obj.py
index f9015791f..ace80e12f 100644
--- a/jellyfin_kodi/objects/obj.py
+++ b/jellyfin_kodi/objects/obj.py
@@ -23,35 +23,30 @@ class Objects(object):
_shared_state = {}
def __init__(self):
-
- ''' Hold all persistent data here.
- '''
+ """Hold all persistent data here."""
self.__dict__ = self._shared_state
def mapping(self):
-
- ''' Load objects mapping.
- '''
+ """Load objects mapping."""
file_dir = os.path.dirname(ensure_text(__file__, get_filesystem_encoding()))
- with open(os.path.join(file_dir, 'obj_map.json')) as infile:
+ with open(os.path.join(file_dir, "obj_map.json")) as infile:
self.objects = json.load(infile)
def map(self, item, mapping_name):
-
- ''' Syntax to traverse the item dictionary.
- This of the query almost as a url.
-
- Item is the Jellyfin item json object structure
-
- ",": each element will be used as a fallback until a value is found.
- "?": split filters and key name from the query part, i.e. MediaSources/0?$Name
- "$": lead the key name with $. Only one key value can be requested per element.
- ":": indicates it's a list of elements [], i.e. MediaSources/0/MediaStreams:?$Name
- MediaStreams is a list.
- "/": indicates where to go directly
- '''
+ """Syntax to traverse the item dictionary.
+ This of the query almost as a url.
+
+ Item is the Jellyfin item json object structure
+
+ ",": each element will be used as a fallback until a value is found.
+ "?": split filters and key name from the query part, i.e. MediaSources/0?$Name
+ "$": lead the key name with $. Only one key value can be requested per element.
+ ":": indicates it's a list of elements [], i.e. MediaSources/0/MediaStreams:?$Name
+ MediaStreams is a list.
+ "/": indicates where to go directly
+ """
self.mapped_item = {}
if not mapping_name:
@@ -62,7 +57,7 @@ def map(self, item, mapping_name):
for key, value in iteritems(mapping):
self.mapped_item[key] = None
- params = value.split(',')
+ params = value.split(",")
for param in params:
@@ -71,19 +66,19 @@ def map(self, item, mapping_name):
obj_key = ""
obj_filters = {}
- if '?' in obj_param:
+ if "?" in obj_param:
- if '$' in obj_param:
- obj_param, obj_key = obj_param.rsplit('$', 1)
+ if "$" in obj_param:
+ obj_param, obj_key = obj_param.rsplit("$", 1)
- obj_param, filters = obj_param.rsplit('?', 1)
+ obj_param, filters = obj_param.rsplit("?", 1)
if filters:
- for filter in filters.split('&'):
- filter_key, filter_value = filter.split('=')
+ for filter in filters.split("&"):
+ filter_key, filter_value = filter.split("=")
obj_filters[filter_key] = filter_value
- if ':' in obj_param:
+ if ":" in obj_param:
result = []
for d in self.__recursiveloop__(obj, obj_param):
@@ -94,7 +89,7 @@ def map(self, item, mapping_name):
obj = result
obj_filters = {}
- elif '/' in obj_param:
+ elif "/" in obj_param:
obj = self.__recursive__(obj, obj_param)
elif obj is item and obj is not None:
@@ -107,21 +102,31 @@ def map(self, item, mapping_name):
continue
if obj_key:
- obj = [d[obj_key] for d in obj if d.get(obj_key)] if type(obj) == list else obj.get(obj_key)
+ obj = (
+ [d[obj_key] for d in obj if d.get(obj_key)]
+ if type(obj) == list
+ else obj.get(obj_key)
+ )
self.mapped_item[key] = obj
break
- if not mapping_name.startswith('Browse') and not mapping_name.startswith('Artwork') and not mapping_name.startswith('UpNext'):
+ if (
+ not mapping_name.startswith("Browse")
+ and not mapping_name.startswith("Artwork")
+ and not mapping_name.startswith("UpNext")
+ ):
- self.mapped_item['ProviderName'] = self.objects.get('%sProviderName' % mapping_name)
- self.mapped_item['Checksum'] = json.dumps(item['UserData'])
+ self.mapped_item["ProviderName"] = self.objects.get(
+ "%sProviderName" % mapping_name
+ )
+ self.mapped_item["Checksum"] = json.dumps(item["UserData"])
return self.mapped_item
def __recursiveloop__(self, obj, keys):
- first, rest = keys.split(':', 1)
+ first, rest = keys.split(":", 1)
obj = self.__recursive__(obj, first)
if obj:
@@ -133,7 +138,7 @@ def __recursiveloop__(self, obj, keys):
def __recursive__(self, obj, keys):
- for string in keys.split('/'):
+ for string in keys.split("/"):
if not obj:
return
@@ -150,10 +155,10 @@ def __filters__(self, obj, filters):
inverse = False
- if value.startswith('!'):
+ if value.startswith("!"):
inverse = True
- value = value.split('!', 1)[1]
+ value = value.split("!", 1)[1]
if value.lower() == "null":
value = None
diff --git a/jellyfin_kodi/objects/tvshows.py b/jellyfin_kodi/objects/tvshows.py
index 31b89ba47..09485509e 100644
--- a/jellyfin_kodi/objects/tvshows.py
+++ b/jellyfin_kodi/objects/tvshows.py
@@ -11,7 +11,16 @@
from .. import downloader as server
from ..database import jellyfin_db, queries as QUEM
-from ..helper import api, stop, validate, validate_bluray_dir, validate_dvd_dir, jellyfin_item, values, Local
+from ..helper import (
+ api,
+ stop,
+ validate,
+ validate_bluray_dir,
+ validate_dvd_dir,
+ jellyfin_item,
+ values,
+ Local,
+)
from ..helper import LazyLogger
from ..helper.utils import find_library
from ..helper.exceptions import PathValidationException
@@ -28,7 +37,15 @@
class TVShows(KodiDb):
- def __init__(self, server, jellyfindb, videodb, direct_path, library=None, update_library=False):
+ def __init__(
+ self,
+ server,
+ jellyfindb,
+ videodb,
+ direct_path,
+ library=None,
+ update_library=False,
+ ):
self.server = server
self.jellyfin = jellyfindb
@@ -46,69 +63,76 @@ def __init__(self, server, jellyfindb, videodb, direct_path, library=None, updat
@stop
@jellyfin_item
def tvshow(self, item, e_item):
-
- ''' If item does not exist, entry will be added.
- If item exists, entry will be updated.
-
- If the show is empty, try to remove it.
- Process seasons.
- Apply series pooling.
- '''
- server_address = self.server.auth.get_server_info(self.server.auth.server_id)['address']
+ """If item does not exist, entry will be added.
+ If item exists, entry will be updated.
+
+ If the show is empty, try to remove it.
+ Process seasons.
+ Apply series pooling.
+ """
+ server_address = self.server.auth.get_server_info(self.server.auth.server_id)[
+ "address"
+ ]
API = api.API(item, server_address)
- obj = self.objects.map(item, 'Series')
+ obj = self.objects.map(item, "Series")
update = True
try:
- obj['ShowId'] = e_item[0]
- obj['PathId'] = e_item[2]
- obj['LibraryId'] = e_item[6]
- obj['LibraryName'] = self.jellyfin_db.get_view_name(obj['LibraryId'])
+ obj["ShowId"] = e_item[0]
+ obj["PathId"] = e_item[2]
+ obj["LibraryId"] = e_item[6]
+ obj["LibraryName"] = self.jellyfin_db.get_view_name(obj["LibraryId"])
except TypeError:
update = False
- LOG.debug("ShowId %s not found", obj['Id'])
+ LOG.debug("ShowId %s not found", obj["Id"])
library = self.library or find_library(self.server, item)
if not library:
# This item doesn't belong to a whitelisted library
return
- obj['ShowId'] = self.create_entry()
- obj['LibraryId'] = library['Id']
- obj['LibraryName'] = library['Name']
+ obj["ShowId"] = self.create_entry()
+ obj["LibraryId"] = library["Id"]
+ obj["LibraryName"] = library["Name"]
else:
if self.get(*values(obj, QU.get_tvshow_obj)) is None:
update = False
- LOG.info("ShowId %s missing from kodi. repairing the entry.", obj['ShowId'])
-
- obj['Path'] = API.get_file_path(obj['Path'])
- obj['Genres'] = obj['Genres'] or []
- obj['People'] = obj['People'] or []
- obj['Mpaa'] = API.get_mpaa(obj['Mpaa'])
- obj['Studios'] = [API.validate_studio(studio) for studio in (obj['Studios'] or [])]
- obj['Genre'] = " / ".join(obj['Genres'])
- obj['People'] = API.get_people_artwork(obj['People'])
- obj['Plot'] = API.get_overview(obj['Plot'])
- obj['Studio'] = " / ".join(obj['Studios'])
- obj['Artwork'] = API.get_all_artwork(self.objects.map(item, 'Artwork'))
-
- if obj['Status'] != 'Ended':
- obj['Status'] = None
+ LOG.info(
+ "ShowId %s missing from kodi. repairing the entry.", obj["ShowId"]
+ )
+
+ obj["Path"] = API.get_file_path(obj["Path"])
+ obj["Genres"] = obj["Genres"] or []
+ obj["People"] = obj["People"] or []
+ obj["Mpaa"] = API.get_mpaa(obj["Mpaa"])
+ obj["Studios"] = [
+ API.validate_studio(studio) for studio in (obj["Studios"] or [])
+ ]
+ obj["Genre"] = " / ".join(obj["Genres"])
+ obj["People"] = API.get_people_artwork(obj["People"])
+ obj["Plot"] = API.get_overview(obj["Plot"])
+ obj["Studio"] = " / ".join(obj["Studios"])
+ obj["Artwork"] = API.get_all_artwork(self.objects.map(item, "Artwork"))
+
+ if obj["Status"] != "Ended":
+ obj["Status"] = None
self.get_path_filename(obj)
- if obj['Premiere']:
- obj['Premiere'] = str(Local(obj['Premiere'])).split('.')[0].replace('T', " ")
+ if obj["Premiere"]:
+ obj["Premiere"] = (
+ str(Local(obj["Premiere"])).split(".")[0].replace("T", " ")
+ )
tags = []
- tags.extend(obj['Tags'] or [])
- tags.append(obj['LibraryName'])
+ tags.extend(obj["Tags"] or [])
+ tags.append(obj["LibraryName"])
- if obj['Favorite']:
- tags.append('Favorite tvshows')
+ if obj["Favorite"]:
+ tags.append("Favorite tvshows")
- obj['Tags'] = tags
+ obj["Tags"] = tags
if update:
self.tvshow_update(obj)
@@ -121,54 +145,60 @@ def tvshow(self, item, e_item):
self.add_people(*values(obj, QU.add_people_tvshow_obj))
self.add_genres(*values(obj, QU.add_genres_tvshow_obj))
self.add_studios(*values(obj, QU.add_studios_tvshow_obj))
- self.artwork.add(obj['Artwork'], obj['ShowId'], "tvshow")
- self.item_ids.append(obj['Id'])
+ self.artwork.add(obj["Artwork"], obj["ShowId"], "tvshow")
+ self.item_ids.append(obj["Id"])
season_episodes = {}
- for season in self.server.jellyfin.get_seasons(obj['Id'])['Items']:
+ for season in self.server.jellyfin.get_seasons(obj["Id"])["Items"]:
- if season['SeriesId'] != obj['Id']:
- obj['SeriesId'] = season['SeriesId']
- self.item_ids.append(season['SeriesId'])
+ if season["SeriesId"] != obj["Id"]:
+ obj["SeriesId"] = season["SeriesId"]
+ self.item_ids.append(season["SeriesId"])
try:
- self.jellyfin_db.get_item_by_id(*values(obj, QUEM.get_item_series_obj))[0]
+ self.jellyfin_db.get_item_by_id(
+ *values(obj, QUEM.get_item_series_obj)
+ )[0]
if self.update_library:
- season_episodes[season['Id']] = season['SeriesId']
+ season_episodes[season["Id"]] = season["SeriesId"]
except TypeError:
- self.jellyfin_db.add_reference(*values(obj, QUEM.add_reference_pool_obj))
- LOG.info("POOL %s [%s/%s]", obj['Title'], obj['Id'], obj['SeriesId'])
- season_episodes[season['Id']] = season['SeriesId']
+ self.jellyfin_db.add_reference(
+ *values(obj, QUEM.add_reference_pool_obj)
+ )
+ LOG.info(
+ "POOL %s [%s/%s]", obj["Title"], obj["Id"], obj["SeriesId"]
+ )
+ season_episodes[season["Id"]] = season["SeriesId"]
try:
- self.jellyfin_db.get_item_by_id(season['Id'])[0]
- self.item_ids.append(season['Id'])
+ self.jellyfin_db.get_item_by_id(season["Id"])[0]
+ self.item_ids.append(season["Id"])
except TypeError:
- self.season(season, obj['ShowId'])
+ self.season(season, obj["ShowId"])
else:
season_id = self.get_season(*values(obj, QU.get_season_special_obj))
- self.artwork.add(obj['Artwork'], season_id, "season")
+ self.artwork.add(obj["Artwork"], season_id, "season")
for season in season_episodes:
- for episodes in server.get_episode_by_season(season_episodes[season], season):
+ for episodes in server.get_episode_by_season(
+ season_episodes[season], season
+ ):
- for episode in episodes['Items']:
+ for episode in episodes["Items"]:
self.episode(episode)
def tvshow_add(self, obj):
-
- ''' Add object to kodi.
- '''
- obj['RatingId'] = self.create_entry_rating()
+ """Add object to kodi."""
+ obj["RatingId"] = self.create_entry_rating()
self.add_ratings(*values(obj, QU.add_rating_tvshow_obj))
- obj['Unique'] = self.create_entry_unique_id()
+ obj["Unique"] = self.create_entry_unique_id()
self.add_unique_id(*values(obj, QU.add_unique_id_tvshow_obj))
- obj['TopPathId'] = self.add_path(obj['TopLevel'])
+ obj["TopPathId"] = self.add_path(obj["TopLevel"])
if self.direct_path:
# Normal way, we use the actual top path
@@ -178,179 +208,207 @@ def tvshow_add(self, obj):
# We create a path on top of all others that holds mediaType and scrapper
self.update_path(*values(obj, QU.update_path_toptvshow_addon_obj))
temp_obj = dict()
- temp_obj['TopLevel'] = 'plugin://plugin.video.jellyfin/'
- temp_obj['TopPathId'] = self.add_path(temp_obj['TopLevel'])
+ temp_obj["TopLevel"] = "plugin://plugin.video.jellyfin/"
+ temp_obj["TopPathId"] = self.add_path(temp_obj["TopLevel"])
self.update_path(*values(temp_obj, QU.update_path_toptvshow_obj))
- self.update_path_parent_id(obj['TopPathId'], temp_obj['TopPathId'])
+ self.update_path_parent_id(obj["TopPathId"], temp_obj["TopPathId"])
- obj['PathId'] = self.add_path(*values(obj, QU.get_path_obj))
+ obj["PathId"] = self.add_path(*values(obj, QU.get_path_obj))
self.add(*values(obj, QU.add_tvshow_obj))
self.jellyfin_db.add_reference(*values(obj, QUEM.add_reference_tvshow_obj))
- LOG.debug("ADD tvshow [%s/%s/%s] %s: %s", obj['TopPathId'], obj['PathId'], obj['ShowId'], obj['Title'], obj['Id'])
+ LOG.debug(
+ "ADD tvshow [%s/%s/%s] %s: %s",
+ obj["TopPathId"],
+ obj["PathId"],
+ obj["ShowId"],
+ obj["Title"],
+ obj["Id"],
+ )
- self.update_path_parent_id(obj['PathId'], obj['TopPathId'])
+ self.update_path_parent_id(obj["PathId"], obj["TopPathId"])
def tvshow_update(self, obj):
-
- ''' Update object to kodi.
- '''
- obj['RatingId'] = self.get_rating_id(*values(obj, QU.get_unique_id_tvshow_obj))
+ """Update object to kodi."""
+ obj["RatingId"] = self.get_rating_id(*values(obj, QU.get_unique_id_tvshow_obj))
self.update_ratings(*values(obj, QU.update_rating_tvshow_obj))
- obj['Unique'] = self.get_unique_id(*values(obj, QU.get_unique_id_tvshow_obj))
+ obj["Unique"] = self.get_unique_id(*values(obj, QU.get_unique_id_tvshow_obj))
self.update_unique_id(*values(obj, QU.update_unique_id_tvshow_obj))
- obj['TopPathId'] = self.get_path(obj['TopLevel'])
+ obj["TopPathId"] = self.get_path(obj["TopLevel"])
self.update(*values(obj, QU.update_tvshow_obj))
self.jellyfin_db.update_reference(*values(obj, QUEM.update_reference_obj))
- LOG.debug("UPDATE tvshow [%s/%s] %s: %s", obj['PathId'], obj['ShowId'], obj['Title'], obj['Id'])
+ LOG.debug(
+ "UPDATE tvshow [%s/%s] %s: %s",
+ obj["PathId"],
+ obj["ShowId"],
+ obj["Title"],
+ obj["Id"],
+ )
- self.update_path_parent_id(obj['PathId'], obj['TopPathId'])
+ self.update_path_parent_id(obj["PathId"], obj["TopPathId"])
def get_path_filename(self, obj):
-
- ''' Get the path and build it into protocol://path
- '''
+ """Get the path and build it into protocol://path"""
if self.direct_path:
- if '\\' in obj['Path']:
- obj['Path'] = "%s\\" % obj['Path']
- obj['TopLevel'] = "%s\\" % dirname(dirname(obj['Path']))
- elif 'smb://' in obj['Path'] or 'nfs://' in obj['Path']:
- obj['Path'] = "%s/" % obj['Path']
- obj['TopLevel'] = "%s/" % dirname(dirname(obj['Path']))
+ if "\\" in obj["Path"]:
+ obj["Path"] = "%s\\" % obj["Path"]
+ obj["TopLevel"] = "%s\\" % dirname(dirname(obj["Path"]))
+ elif "smb://" in obj["Path"] or "nfs://" in obj["Path"]:
+ obj["Path"] = "%s/" % obj["Path"]
+ obj["TopLevel"] = "%s/" % dirname(dirname(obj["Path"]))
else:
- obj['Path'] = "%s/" % obj['Path']
- obj['TopLevel'] = "plugin://plugin.video.jellyfin/"
+ obj["Path"] = "%s/" % obj["Path"]
+ obj["TopLevel"] = "plugin://plugin.video.jellyfin/"
- if not validate(obj['Path']):
+ if not validate(obj["Path"]):
raise PathValidationException("Failed to validate path. User stopped.")
else:
- obj['TopLevel'] = "plugin://plugin.video.jellyfin/%s/" % obj['LibraryId']
- obj['Path'] = "%s%s/" % (obj['TopLevel'], obj['Id'])
+ obj["TopLevel"] = "plugin://plugin.video.jellyfin/%s/" % obj["LibraryId"]
+ obj["Path"] = "%s%s/" % (obj["TopLevel"], obj["Id"])
@stop
def season(self, item, show_id=None):
-
- ''' If item does not exist, entry will be added.
- If item exists, entry will be updated.
-
- If the show is empty, try to remove it.
- '''
- server_address = self.server.auth.get_server_info(self.server.auth.server_id)['address']
+ """If item does not exist, entry will be added.
+ If item exists, entry will be updated.
+
+ If the show is empty, try to remove it.
+ """
+ server_address = self.server.auth.get_server_info(self.server.auth.server_id)[
+ "address"
+ ]
API = api.API(item, server_address)
- obj = self.objects.map(item, 'Season')
+ obj = self.objects.map(item, "Season")
- obj['ShowId'] = show_id
+ obj["ShowId"] = show_id
- if obj['ShowId'] is None:
+ if obj["ShowId"] is None:
try:
- obj['ShowId'] = self.jellyfin_db.get_item_by_id(*values(obj, QUEM.get_item_series_obj))[0]
+ obj["ShowId"] = self.jellyfin_db.get_item_by_id(
+ *values(obj, QUEM.get_item_series_obj)
+ )[0]
except (KeyError, TypeError) as error:
- LOG.error("Unable to add series %s", obj['SeriesId'])
+ LOG.error("Unable to add series %s", obj["SeriesId"])
LOG.exception(error)
return False
- obj['SeasonId'] = self.get_season(*values(obj, QU.get_season_obj))
- obj['Artwork'] = API.get_all_artwork(self.objects.map(item, 'Artwork'))
+ obj["SeasonId"] = self.get_season(*values(obj, QU.get_season_obj))
+ obj["Artwork"] = API.get_all_artwork(self.objects.map(item, "Artwork"))
- if obj['Location'] != "Virtual":
+ if obj["Location"] != "Virtual":
self.jellyfin_db.add_reference(*values(obj, QUEM.add_reference_season_obj))
- self.item_ids.append(obj['Id'])
+ self.item_ids.append(obj["Id"])
- self.artwork.add(obj['Artwork'], obj['SeasonId'], "season")
- LOG.debug("UPDATE season [%s/%s] %s: %s", obj['ShowId'], obj['SeasonId'], obj['Title'] or obj['Index'], obj['Id'])
+ self.artwork.add(obj["Artwork"], obj["SeasonId"], "season")
+ LOG.debug(
+ "UPDATE season [%s/%s] %s: %s",
+ obj["ShowId"],
+ obj["SeasonId"],
+ obj["Title"] or obj["Index"],
+ obj["Id"],
+ )
@stop
@jellyfin_item
def episode(self, item, e_item):
-
- ''' If item does not exist, entry will be added.
- If item exists, entry will be updated.
-
- Create additional entry for widgets.
- This is only required for plugin/episode.
- '''
- server_address = self.server.auth.get_server_info(self.server.auth.server_id)['address']
+ """If item does not exist, entry will be added.
+ If item exists, entry will be updated.
+
+ Create additional entry for widgets.
+ This is only required for plugin/episode.
+ """
+ server_address = self.server.auth.get_server_info(self.server.auth.server_id)[
+ "address"
+ ]
API = api.API(item, server_address)
- obj = self.objects.map(item, 'Episode')
+ obj = self.objects.map(item, "Episode")
update = True
- if obj['Location'] == "Virtual":
- LOG.info("Skipping virtual episode %s: %s", obj['Title'], obj['Id'])
+ if obj["Location"] == "Virtual":
+ LOG.info("Skipping virtual episode %s: %s", obj["Title"], obj["Id"])
return
- elif obj['SeriesId'] is None:
- LOG.info("Skipping episode %s with missing SeriesId", obj['Id'])
+ elif obj["SeriesId"] is None:
+ LOG.info("Skipping episode %s with missing SeriesId", obj["Id"])
return
try:
- obj['EpisodeId'] = e_item[0]
- obj['FileId'] = e_item[1]
- obj['PathId'] = e_item[2]
+ obj["EpisodeId"] = e_item[0]
+ obj["FileId"] = e_item[1]
+ obj["PathId"] = e_item[2]
except TypeError:
update = False
- LOG.debug("EpisodeId %s not found", obj['Id'])
+ LOG.debug("EpisodeId %s not found", obj["Id"])
library = self.library or find_library(self.server, item)
if not library:
# This item doesn't belong to a whitelisted library
return
- obj['EpisodeId'] = self.create_entry_episode()
+ obj["EpisodeId"] = self.create_entry_episode()
else:
if self.get_episode(*values(obj, QU.get_episode_obj)) is None:
update = False
- LOG.info("EpisodeId %s missing from kodi. repairing the entry.", obj['EpisodeId'])
-
- obj['Path'] = API.get_file_path(obj['Path'])
- obj['Index'] = obj['Index'] or -1
- obj['Writers'] = " / ".join(obj['Writers'] or [])
- obj['Directors'] = " / ".join(obj['Directors'] or [])
- obj['Plot'] = API.get_overview(obj['Plot'])
- obj['Resume'] = API.adjust_resume((obj['Resume'] or 0) / 10000000.0)
- obj['Runtime'] = round(float((obj['Runtime'] or 0) / 10000000.0), 6)
- obj['People'] = API.get_people_artwork(obj['People'] or [])
- obj['DateAdded'] = Local(obj['DateAdded']).split('.')[0].replace('T', " ")
- obj['DatePlayed'] = None if not obj['DatePlayed'] else Local(obj['DatePlayed']).split('.')[0].replace('T', " ")
- obj['PlayCount'] = API.get_playcount(obj['Played'], obj['PlayCount'])
- obj['Artwork'] = API.get_all_artwork(self.objects.map(item, 'Artwork'))
- obj['Video'] = API.video_streams(obj['Video'] or [], obj['Container'])
- obj['Audio'] = API.audio_streams(obj['Audio'] or [])
- obj['Streams'] = API.media_streams(obj['Video'], obj['Audio'], obj['Subtitles'])
+ LOG.info(
+ "EpisodeId %s missing from kodi. repairing the entry.",
+ obj["EpisodeId"],
+ )
+
+ obj["Path"] = API.get_file_path(obj["Path"])
+ obj["Index"] = obj["Index"] or -1
+ obj["Writers"] = " / ".join(obj["Writers"] or [])
+ obj["Directors"] = " / ".join(obj["Directors"] or [])
+ obj["Plot"] = API.get_overview(obj["Plot"])
+ obj["Resume"] = API.adjust_resume((obj["Resume"] or 0) / 10000000.0)
+ obj["Runtime"] = round(float((obj["Runtime"] or 0) / 10000000.0), 6)
+ obj["People"] = API.get_people_artwork(obj["People"] or [])
+ obj["DateAdded"] = Local(obj["DateAdded"]).split(".")[0].replace("T", " ")
+ obj["DatePlayed"] = (
+ None
+ if not obj["DatePlayed"]
+ else Local(obj["DatePlayed"]).split(".")[0].replace("T", " ")
+ )
+ obj["PlayCount"] = API.get_playcount(obj["Played"], obj["PlayCount"])
+ obj["Artwork"] = API.get_all_artwork(self.objects.map(item, "Artwork"))
+ obj["Video"] = API.video_streams(obj["Video"] or [], obj["Container"])
+ obj["Audio"] = API.audio_streams(obj["Audio"] or [])
+ obj["Streams"] = API.media_streams(obj["Video"], obj["Audio"], obj["Subtitles"])
self.get_episode_path_filename(obj)
- if obj['Premiere']:
- obj['Premiere'] = Local(obj['Premiere']).split('.')[0].replace('T', " ")
+ if obj["Premiere"]:
+ obj["Premiere"] = Local(obj["Premiere"]).split(".")[0].replace("T", " ")
- if obj['Season'] is None:
- if obj['AbsoluteNumber']:
+ if obj["Season"] is None:
+ if obj["AbsoluteNumber"]:
- obj['Season'] = 1
- obj['Index'] = obj['AbsoluteNumber']
+ obj["Season"] = 1
+ obj["Index"] = obj["AbsoluteNumber"]
else:
- obj['Season'] = 0
+ obj["Season"] = 0
- if obj['AirsAfterSeason']:
+ if obj["AirsAfterSeason"]:
- obj['AirsBeforeSeason'] = obj['AirsAfterSeason']
- obj['AirsBeforeEpisode'] = 4096 # Kodi default number for afterseason ordering
+ obj["AirsBeforeSeason"] = obj["AirsAfterSeason"]
+ obj["AirsBeforeEpisode"] = (
+ 4096 # Kodi default number for afterseason ordering
+ )
- if obj['MultiEpisode']:
- obj['Title'] = "| %02d | %s" % (obj['MultiEpisode'], obj['Title'])
+ if obj["MultiEpisode"]:
+ obj["Title"] = "| %02d | %s" % (obj["MultiEpisode"], obj["Title"])
if not self.get_show_id(obj):
return False
- obj['SeasonId'] = self.get_season(*values(obj, QU.get_season_episode_obj))
+ obj["SeasonId"] = self.get_season(*values(obj, QU.get_season_episode_obj))
if update:
self.episode_update(obj)
@@ -362,273 +420,339 @@ def episode(self, item, e_item):
self.add_people(*values(obj, QU.add_people_episode_obj))
self.add_streams(*values(obj, QU.add_streams_obj))
self.add_playstate(*values(obj, QU.add_bookmark_obj))
- self.artwork.update(obj['Artwork']['Primary'], obj['EpisodeId'], "episode", "thumb")
- self.item_ids.append(obj['Id'])
+ self.artwork.update(
+ obj["Artwork"]["Primary"], obj["EpisodeId"], "episode", "thumb"
+ )
+ self.item_ids.append(obj["Id"])
- if not self.direct_path and obj['Resume']:
+ if not self.direct_path and obj["Resume"]:
temp_obj = dict(obj)
- temp_obj['Path'] = "plugin://plugin.video.jellyfin/"
- temp_obj['PathId'] = self.get_path(*values(temp_obj, QU.get_path_obj))
- temp_obj['FileId'] = self.add_file(*values(temp_obj, QU.add_file_obj))
+ temp_obj["Path"] = "plugin://plugin.video.jellyfin/"
+ temp_obj["PathId"] = self.get_path(*values(temp_obj, QU.get_path_obj))
+ temp_obj["FileId"] = self.add_file(*values(temp_obj, QU.add_file_obj))
self.update_file(*values(temp_obj, QU.update_file_obj))
self.add_playstate(*values(temp_obj, QU.add_bookmark_obj))
return not update
def episode_add(self, obj):
-
- ''' Add object to kodi.
- '''
- obj['RatingId'] = self.create_entry_rating()
+ """Add object to kodi."""
+ obj["RatingId"] = self.create_entry_rating()
self.add_ratings(*values(obj, QU.add_rating_episode_obj))
- obj['Unique'] = self.create_entry_unique_id()
+ obj["Unique"] = self.create_entry_unique_id()
self.add_unique_id(*values(obj, QU.add_unique_id_episode_obj))
- obj['PathId'] = self.add_path(*values(obj, QU.add_path_obj))
- obj['FileId'] = self.add_file(*values(obj, QU.add_file_obj))
+ obj["PathId"] = self.add_path(*values(obj, QU.add_path_obj))
+ obj["FileId"] = self.add_file(*values(obj, QU.add_file_obj))
try:
self.add_episode(*values(obj, QU.add_episode_obj))
except sqlite3.IntegrityError:
LOG.error("IntegrityError for %s", obj)
- obj['EpisodeId'] = self.create_entry_episode()
+ obj["EpisodeId"] = self.create_entry_episode()
return self.episode_add(obj)
self.jellyfin_db.add_reference(*values(obj, QUEM.add_reference_episode_obj))
- parentPathId = self.jellyfin_db.get_episode_kodi_parent_path_id(*values(obj, QUEM.get_episode_kodi_parent_path_id_obj))
- if obj['PathId'] != parentPathId:
- LOG.debug("Setting episode pathParentId, episode %s, title %s, pathId %s, pathParentId %s", obj['Id'], obj['Title'], obj['PathId'], parentPathId)
- self.update_path_parent_id(obj['PathId'], parentPathId)
-
- LOG.debug("ADD episode [%s/%s] %s: %s", obj['PathId'], obj['FileId'], obj['Id'], obj['Title'])
+ parentPathId = self.jellyfin_db.get_episode_kodi_parent_path_id(
+ *values(obj, QUEM.get_episode_kodi_parent_path_id_obj)
+ )
+ if obj["PathId"] != parentPathId:
+ LOG.debug(
+ "Setting episode pathParentId, episode %s, title %s, pathId %s, pathParentId %s",
+ obj["Id"],
+ obj["Title"],
+ obj["PathId"],
+ parentPathId,
+ )
+ self.update_path_parent_id(obj["PathId"], parentPathId)
+
+ LOG.debug(
+ "ADD episode [%s/%s] %s: %s",
+ obj["PathId"],
+ obj["FileId"],
+ obj["Id"],
+ obj["Title"],
+ )
def episode_update(self, obj):
-
- ''' Update object to kodi.
- '''
- obj['RatingId'] = self.get_rating_id(*values(obj, QU.get_rating_episode_obj))
+ """Update object to kodi."""
+ obj["RatingId"] = self.get_rating_id(*values(obj, QU.get_rating_episode_obj))
self.update_ratings(*values(obj, QU.update_rating_episode_obj))
- obj['Unique'] = self.get_unique_id(*values(obj, QU.get_unique_id_episode_obj))
+ obj["Unique"] = self.get_unique_id(*values(obj, QU.get_unique_id_episode_obj))
self.update_unique_id(*values(obj, QU.update_unique_id_episode_obj))
self.update_episode(*values(obj, QU.update_episode_obj))
self.jellyfin_db.update_reference(*values(obj, QUEM.update_reference_obj))
self.jellyfin_db.update_parent_id(*values(obj, QUEM.update_parent_episode_obj))
- LOG.debug("UPDATE episode [%s/%s] %s: %s", obj['PathId'], obj['FileId'], obj['Id'], obj['Title'])
+ LOG.debug(
+ "UPDATE episode [%s/%s] %s: %s",
+ obj["PathId"],
+ obj["FileId"],
+ obj["Id"],
+ obj["Title"],
+ )
def get_episode_path_filename(self, obj):
-
- ''' Get the path and build it into protocol://path
- '''
- if '\\' in obj['Path']:
- obj['Filename'] = obj['Path'].rsplit('\\', 1)[1]
+ """Get the path and build it into protocol://path"""
+ if "\\" in obj["Path"]:
+ obj["Filename"] = obj["Path"].rsplit("\\", 1)[1]
else:
- obj['Filename'] = obj['Path'].rsplit('/', 1)[1]
+ obj["Filename"] = obj["Path"].rsplit("/", 1)[1]
if self.direct_path:
- if not validate(obj['Path']):
+ if not validate(obj["Path"]):
raise PathValidationException("Failed to validate path. User stopped.")
- obj['Path'] = obj['Path'].replace(obj['Filename'], "")
+ obj["Path"] = obj["Path"].replace(obj["Filename"], "")
- '''check dvd directories and point it to ./VIDEO_TS/VIDEO_TS.IFO'''
- if validate_dvd_dir(obj['Path'] + obj['Filename']):
- obj['Path'] = obj['Path'] + obj['Filename'] + '/VIDEO_TS/'
- obj['Filename'] = 'VIDEO_TS.IFO'
- LOG.debug("DVD directory %s", obj['Path'])
+ """check dvd directories and point it to ./VIDEO_TS/VIDEO_TS.IFO"""
+ if validate_dvd_dir(obj["Path"] + obj["Filename"]):
+ obj["Path"] = obj["Path"] + obj["Filename"] + "/VIDEO_TS/"
+ obj["Filename"] = "VIDEO_TS.IFO"
+ LOG.debug("DVD directory %s", obj["Path"])
- '''check bluray directories and point it to ./BDMV/index.bdmv'''
- if validate_bluray_dir(obj['Path'] + obj['Filename']):
- obj['Path'] = obj['Path'] + obj['Filename'] + '/BDMV/'
- obj['Filename'] = 'index.bdmv'
- LOG.debug("Bluray directory %s", obj['Path'])
+ """check bluray directories and point it to ./BDMV/index.bdmv"""
+ if validate_bluray_dir(obj["Path"] + obj["Filename"]):
+ obj["Path"] = obj["Path"] + obj["Filename"] + "/BDMV/"
+ obj["Filename"] = "index.bdmv"
+ LOG.debug("Bluray directory %s", obj["Path"])
- obj['FullFilePath'] = obj['Path'] + obj['Filename']
+ obj["FullFilePath"] = obj["Path"] + obj["Filename"]
else:
# We need LibraryId
library = self.library or find_library(self.server, obj)
- obj['LibraryId'] = library['Id']
- obj['Path'] = "plugin://plugin.video.jellyfin/%s/%s/" % (obj['LibraryId'], obj['SeriesId'])
+ obj["LibraryId"] = library["Id"]
+ obj["Path"] = "plugin://plugin.video.jellyfin/%s/%s/" % (
+ obj["LibraryId"],
+ obj["SeriesId"],
+ )
params = {
- 'filename': py2_encode(obj['Filename'], 'utf-8'),
- 'id': obj['Id'],
- 'dbid': obj['EpisodeId'],
- 'mode': "play"
+ "filename": py2_encode(obj["Filename"], "utf-8"),
+ "id": obj["Id"],
+ "dbid": obj["EpisodeId"],
+ "mode": "play",
}
- obj['Filename'] = "%s?%s" % (obj['Path'], urlencode(params))
- obj['FullFilePath'] = obj['Filename']
+ obj["Filename"] = "%s?%s" % (obj["Path"], urlencode(params))
+ obj["FullFilePath"] = obj["Filename"]
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)
+ )
- if obj['ShowId'] is None:
+ if obj["ShowId"] is None:
try:
- self.tvshow(self.server.jellyfin.get_item(obj['SeriesId']))
- obj['ShowId'] = self.jellyfin_db.get_item_by_id(*values(obj, QUEM.get_item_series_obj))[0]
+ self.tvshow(self.server.jellyfin.get_item(obj["SeriesId"]))
+ obj["ShowId"] = self.jellyfin_db.get_item_by_id(
+ *values(obj, QUEM.get_item_series_obj)
+ )[0]
except (TypeError, KeyError) as error:
- LOG.error("Unable to add series %s", obj['SeriesId'])
+ LOG.error("Unable to add series %s", obj["SeriesId"])
LOG.exception(error)
return False
else:
- obj['ShowId'] = obj['ShowId'][0]
+ obj["ShowId"] = obj["ShowId"][0]
- self.item_ids.append(obj['SeriesId'])
+ self.item_ids.append(obj["SeriesId"])
return True
@stop
@jellyfin_item
def userdata(self, item, e_item):
-
- ''' This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks
- Poster with progress bar
-
- Make sure there's no other bookmarks created by widget.
- Create additional entry for widgets. This is only required for plugin/episode.
- '''
- server_address = self.server.auth.get_server_info(self.server.auth.server_id)['address']
+ """This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks
+ Poster with progress bar
+
+ Make sure there's no other bookmarks created by widget.
+ Create additional entry for widgets. This is only required for plugin/episode.
+ """
+ server_address = self.server.auth.get_server_info(self.server.auth.server_id)[
+ "address"
+ ]
API = api.API(item, server_address)
- obj = self.objects.map(item, 'EpisodeUserData')
+ obj = self.objects.map(item, "EpisodeUserData")
try:
- obj['KodiId'] = e_item[0]
- obj['FileId'] = e_item[1]
- obj['Media'] = e_item[4]
+ obj["KodiId"] = e_item[0]
+ obj["FileId"] = e_item[1]
+ obj["Media"] = e_item[4]
except TypeError:
return
- if obj['Media'] == "tvshow":
+ if obj["Media"] == "tvshow":
- if obj['Favorite']:
+ if obj["Favorite"]:
self.get_tag(*values(obj, QU.get_tag_episode_obj))
else:
self.remove_tag(*values(obj, QU.delete_tag_episode_obj))
- elif obj['Media'] == "episode":
+ elif obj["Media"] == "episode":
- obj['Resume'] = API.adjust_resume((obj['Resume'] or 0) / 10000000.0)
- obj['Runtime'] = round(float((obj['Runtime'] or 0) / 10000000.0), 6)
- obj['PlayCount'] = API.get_playcount(obj['Played'], obj['PlayCount'])
+ obj["Resume"] = API.adjust_resume((obj["Resume"] or 0) / 10000000.0)
+ obj["Runtime"] = round(float((obj["Runtime"] or 0) / 10000000.0), 6)
+ obj["PlayCount"] = API.get_playcount(obj["Played"], obj["PlayCount"])
- if obj['DatePlayed']:
- obj['DatePlayed'] = Local(obj['DatePlayed']).split('.')[0].replace('T', " ")
+ if obj["DatePlayed"]:
+ obj["DatePlayed"] = (
+ Local(obj["DatePlayed"]).split(".")[0].replace("T", " ")
+ )
- if obj['DateAdded']:
- obj['DateAdded'] = Local(obj['DateAdded']).split('.')[0].replace('T', " ")
+ if obj["DateAdded"]:
+ obj["DateAdded"] = (
+ Local(obj["DateAdded"]).split(".")[0].replace("T", " ")
+ )
self.add_playstate(*values(obj, QU.add_bookmark_obj))
- if not self.direct_path and not obj['Resume']:
+ if not self.direct_path and not obj["Resume"]:
temp_obj = dict(obj)
- temp_obj['Filename'] = self.get_filename(*values(temp_obj, QU.get_file_obj))
- temp_obj['Path'] = "plugin://plugin.video.jellyfin/"
+ temp_obj["Filename"] = self.get_filename(
+ *values(temp_obj, QU.get_file_obj)
+ )
+ temp_obj["Path"] = "plugin://plugin.video.jellyfin/"
self.remove_file(*values(temp_obj, QU.delete_file_obj))
- elif not self.direct_path and obj['Resume']:
+ elif not self.direct_path and obj["Resume"]:
temp_obj = dict(obj)
- temp_obj['Filename'] = self.get_filename(*values(temp_obj, QU.get_file_obj))
- temp_obj['PathId'] = self.get_path("plugin://plugin.video.jellyfin/")
- temp_obj['FileId'] = self.add_file(*values(temp_obj, QU.add_file_obj))
+ temp_obj["Filename"] = self.get_filename(
+ *values(temp_obj, QU.get_file_obj)
+ )
+ temp_obj["PathId"] = self.get_path("plugin://plugin.video.jellyfin/")
+ temp_obj["FileId"] = self.add_file(*values(temp_obj, QU.add_file_obj))
self.update_file(*values(temp_obj, QU.update_file_obj))
self.add_playstate(*values(temp_obj, QU.add_bookmark_obj))
self.jellyfin_db.update_reference(*values(obj, QUEM.update_reference_obj))
- LOG.debug("USERDATA %s [%s/%s] %s: %s", obj['Media'], obj['FileId'], obj['KodiId'], obj['Id'], obj['Title'])
+ LOG.debug(
+ "USERDATA %s [%s/%s] %s: %s",
+ obj["Media"],
+ obj["FileId"],
+ obj["KodiId"],
+ obj["Id"],
+ obj["Title"],
+ )
@stop
@jellyfin_item
def remove(self, item_id, e_item):
-
- ''' Remove showid, fileid, pathid, jellyfin reference.
- There's no episodes left, delete show and any possible remaining seasons
- '''
- obj = {'Id': item_id}
+ """Remove showid, fileid, pathid, jellyfin reference.
+ There's no episodes left, delete show and any possible remaining seasons
+ """
+ obj = {"Id": item_id}
try:
- obj['KodiId'] = e_item[0]
- obj['FileId'] = e_item[1]
- obj['ParentId'] = e_item[3]
- obj['Media'] = e_item[4]
+ obj["KodiId"] = e_item[0]
+ obj["FileId"] = e_item[1]
+ obj["ParentId"] = e_item[3]
+ obj["Media"] = e_item[4]
except TypeError:
return
- if obj['Media'] == 'episode':
+ if obj["Media"] == "episode":
temp_obj = dict(obj)
- self.remove_episode(obj['KodiId'], obj['FileId'], obj['Id'])
- season = self.jellyfin_db.get_full_item_by_kodi_id(*values(obj, QUEM.delete_item_by_parent_season_obj))
+ self.remove_episode(obj["KodiId"], obj["FileId"], obj["Id"])
+ season = self.jellyfin_db.get_full_item_by_kodi_id(
+ *values(obj, QUEM.delete_item_by_parent_season_obj)
+ )
try:
- temp_obj['Id'] = season[0]
- temp_obj['ParentId'] = season[1]
+ temp_obj["Id"] = season[0]
+ temp_obj["ParentId"] = season[1]
except TypeError:
return
- if not self.jellyfin_db.get_item_by_parent_id(*values(obj, QUEM.get_item_by_parent_episode_obj)):
+ if not self.jellyfin_db.get_item_by_parent_id(
+ *values(obj, QUEM.get_item_by_parent_episode_obj)
+ ):
- self.remove_season(obj['ParentId'], obj['Id'])
+ self.remove_season(obj["ParentId"], obj["Id"])
self.jellyfin_db.remove_item(*values(temp_obj, QUEM.delete_item_obj))
- temp_obj['Id'] = self.jellyfin_db.get_item_by_kodi_id(*values(temp_obj, QUEM.get_item_by_parent_tvshow_obj))
+ temp_obj["Id"] = self.jellyfin_db.get_item_by_kodi_id(
+ *values(temp_obj, QUEM.get_item_by_parent_tvshow_obj)
+ )
- if not self.get_total_episodes(*values(temp_obj, QU.get_total_episodes_obj)):
+ if not self.get_total_episodes(
+ *values(temp_obj, QU.get_total_episodes_obj)
+ ):
- for season in self.jellyfin_db.get_item_by_parent_id(*values(temp_obj, QUEM.get_item_by_parent_season_obj)):
- self.remove_season(season[1], obj['Id'])
+ for season in self.jellyfin_db.get_item_by_parent_id(
+ *values(temp_obj, QUEM.get_item_by_parent_season_obj)
+ ):
+ self.remove_season(season[1], obj["Id"])
else:
- self.jellyfin_db.remove_items_by_parent_id(*values(temp_obj, QUEM.delete_item_by_parent_season_obj))
+ self.jellyfin_db.remove_items_by_parent_id(
+ *values(temp_obj, QUEM.delete_item_by_parent_season_obj)
+ )
- self.remove_tvshow(temp_obj['ParentId'], obj['Id'])
+ self.remove_tvshow(temp_obj["ParentId"], obj["Id"])
self.jellyfin_db.remove_item(*values(temp_obj, QUEM.delete_item_obj))
- elif obj['Media'] == 'tvshow':
- obj['ParentId'] = obj['KodiId']
+ elif obj["Media"] == "tvshow":
+ obj["ParentId"] = obj["KodiId"]
- for season in self.jellyfin_db.get_item_by_parent_id(*values(obj, QUEM.get_item_by_parent_season_obj)):
+ for season in self.jellyfin_db.get_item_by_parent_id(
+ *values(obj, QUEM.get_item_by_parent_season_obj)
+ ):
temp_obj = dict(obj)
- temp_obj['ParentId'] = season[1]
+ temp_obj["ParentId"] = season[1]
- for episode in self.jellyfin_db.get_item_by_parent_id(*values(temp_obj, QUEM.get_item_by_parent_episode_obj)):
- self.remove_episode(episode[1], episode[2], obj['Id'])
+ for episode in self.jellyfin_db.get_item_by_parent_id(
+ *values(temp_obj, QUEM.get_item_by_parent_episode_obj)
+ ):
+ self.remove_episode(episode[1], episode[2], obj["Id"])
else:
- self.jellyfin_db.remove_items_by_parent_id(*values(temp_obj, QUEM.delete_item_by_parent_episode_obj))
+ self.jellyfin_db.remove_items_by_parent_id(
+ *values(temp_obj, QUEM.delete_item_by_parent_episode_obj)
+ )
else:
- self.jellyfin_db.remove_items_by_parent_id(*values(obj, QUEM.delete_item_by_parent_season_obj))
+ self.jellyfin_db.remove_items_by_parent_id(
+ *values(obj, QUEM.delete_item_by_parent_season_obj)
+ )
- self.remove_tvshow(obj['KodiId'], obj['Id'])
+ self.remove_tvshow(obj["KodiId"], obj["Id"])
- elif obj['Media'] == 'season':
+ elif obj["Media"] == "season":
- for episode in self.jellyfin_db.get_item_by_parent_id(*values(obj, QUEM.get_item_by_parent_episode_obj)):
- self.remove_episode(episode[1], episode[2], obj['Id'])
+ for episode in self.jellyfin_db.get_item_by_parent_id(
+ *values(obj, QUEM.get_item_by_parent_episode_obj)
+ ):
+ self.remove_episode(episode[1], episode[2], obj["Id"])
else:
- self.jellyfin_db.remove_items_by_parent_id(*values(obj, QUEM.delete_item_by_parent_episode_obj))
+ self.jellyfin_db.remove_items_by_parent_id(
+ *values(obj, QUEM.delete_item_by_parent_episode_obj)
+ )
- self.remove_season(obj['KodiId'], obj['Id'])
+ self.remove_season(obj["KodiId"], obj["Id"])
- if not self.jellyfin_db.get_item_by_parent_id(*values(obj, QUEM.delete_item_by_parent_season_obj)):
+ if not self.jellyfin_db.get_item_by_parent_id(
+ *values(obj, QUEM.delete_item_by_parent_season_obj)
+ ):
- self.remove_tvshow(obj['ParentId'], obj['Id'])
- self.jellyfin_db.remove_item_by_kodi_id(*values(obj, QUEM.delete_item_by_parent_tvshow_obj))
+ self.remove_tvshow(obj["ParentId"], obj["Id"])
+ self.jellyfin_db.remove_item_by_kodi_id(
+ *values(obj, QUEM.delete_item_by_parent_tvshow_obj)
+ )
# Remove any series pooling episodes
- for episode in self.jellyfin_db.get_media_by_parent_id(obj['Id']):
- self.remove_episode(episode[2], episode[3], obj['Id'])
+ for episode in self.jellyfin_db.get_media_by_parent_id(obj["Id"]):
+ self.remove_episode(episode[2], episode[3], obj["Id"])
else:
- self.jellyfin_db.remove_media_by_parent_id(obj['Id'])
+ self.jellyfin_db.remove_media_by_parent_id(obj["Id"])
self.jellyfin_db.remove_item(*values(obj, QUEM.delete_item_obj))
@@ -652,32 +776,34 @@ def remove_episode(self, kodi_id, file_id, item_id):
@jellyfin_item
def get_child(self, item_id, e_item):
-
- ''' Get all child elements from tv show jellyfin id.
- '''
- obj = {'Id': item_id}
+ """Get all child elements from tv show jellyfin id."""
+ obj = {"Id": item_id}
child = []
try:
- obj['KodiId'] = e_item[0]
- obj['FileId'] = e_item[1]
- obj['ParentId'] = e_item[3]
- obj['Media'] = e_item[4]
+ obj["KodiId"] = e_item[0]
+ obj["FileId"] = e_item[1]
+ obj["ParentId"] = e_item[3]
+ obj["Media"] = e_item[4]
except TypeError:
return child
- obj['ParentId'] = obj['KodiId']
+ obj["ParentId"] = obj["KodiId"]
- for season in self.jellyfin_db.get_item_by_parent_id(*values(obj, QUEM.get_item_by_parent_season_obj)):
+ for season in self.jellyfin_db.get_item_by_parent_id(
+ *values(obj, QUEM.get_item_by_parent_season_obj)
+ ):
temp_obj = dict(obj)
- temp_obj['ParentId'] = season[1]
+ temp_obj["ParentId"] = season[1]
child.append(season[0])
- for episode in self.jellyfin_db.get_item_by_parent_id(*values(temp_obj, QUEM.get_item_by_parent_episode_obj)):
+ for episode in self.jellyfin_db.get_item_by_parent_id(
+ *values(temp_obj, QUEM.get_item_by_parent_episode_obj)
+ ):
child.append(episode[0])
- for episode in self.jellyfin_db.get_media_by_parent_id(obj['Id']):
+ for episode in self.jellyfin_db.get_media_by_parent_id(obj["Id"]):
child.append(episode[0])
return child
diff --git a/jellyfin_kodi/objects/utils.py b/jellyfin_kodi/objects/utils.py
index 31bbf7d12..1aec01412 100644
--- a/jellyfin_kodi/objects/utils.py
+++ b/jellyfin_kodi/objects/utils.py
@@ -14,8 +14,8 @@
def get_grouped_set():
-
- ''' Get if boxsets should be grouped
- '''
- result = JSONRPC('Settings.GetSettingValue').execute({'setting': "videolibrary.groupmoviesets"})
- return result.get('result', {}).get('value', False)
+ """Get if boxsets should be grouped"""
+ result = JSONRPC("Settings.GetSettingValue").execute(
+ {"setting": "videolibrary.groupmoviesets"}
+ )
+ return result.get("result", {}).get("value", False)
diff --git a/jellyfin_kodi/player.py b/jellyfin_kodi/player.py
index bb6a8bea7..ac7092082 100644
--- a/jellyfin_kodi/player.py
+++ b/jellyfin_kodi/player.py
@@ -44,11 +44,10 @@ def is_playing_file(self, file):
return file in self.played
def onPlayBackStarted(self):
-
- ''' We may need to wait for info to be set in kodi monitor.
- Accounts for scenario where Kodi starts playback and exits immediately.
- First, ensure previous playback terminated correctly in Jellyfin.
- '''
+ """We may need to wait for info to be set in kodi monitor.
+ Accounts for scenario where Kodi starts playback and exits immediately.
+ First, ensure previous playback terminated correctly in Jellyfin.
+ """
self.stop_playback()
self.up_next = False
count = 0
@@ -69,11 +68,11 @@ def onPlayBackStarted(self):
if monitor.waitForAbort(1):
return
else:
- LOG.info('Cancel playback report')
+ LOG.info("Cancel playback report")
return
- items = window('jellyfin_play.json')
+ items = window("jellyfin_play.json")
item = None
while not items:
@@ -81,7 +80,7 @@ def onPlayBackStarted(self):
if monitor.waitForAbort(2):
return
- items = window('jellyfin_play.json')
+ items = window("jellyfin_play.json")
count += 1
if count == 20:
@@ -90,51 +89,49 @@ def onPlayBackStarted(self):
return
for item in items:
- if item['Path'] == current_file:
+ if item["Path"] == current_file:
items.pop(items.index(item))
break
else:
item = items.pop(0)
- window('jellyfin_play.json', items)
+ window("jellyfin_play.json", items)
self.set_item(current_file, item)
data = {
- 'QueueableMediaTypes': "Video,Audio",
- 'CanSeek': True,
- 'ItemId': item['Id'],
- 'MediaSourceId': item['MediaSourceId'],
- 'PlayMethod': item['PlayMethod'],
- 'VolumeLevel': item['Volume'],
- 'PositionTicks': int(item['CurrentPosition'] * 10000000),
- 'IsPaused': item['Paused'],
- 'IsMuted': item['Muted'],
- 'PlaySessionId': item['PlaySessionId'],
- 'AudioStreamIndex': item['AudioStreamIndex'],
- 'SubtitleStreamIndex': item['SubtitleStreamIndex']
+ "QueueableMediaTypes": "Video,Audio",
+ "CanSeek": True,
+ "ItemId": item["Id"],
+ "MediaSourceId": item["MediaSourceId"],
+ "PlayMethod": item["PlayMethod"],
+ "VolumeLevel": item["Volume"],
+ "PositionTicks": int(item["CurrentPosition"] * 10000000),
+ "IsPaused": item["Paused"],
+ "IsMuted": item["Muted"],
+ "PlaySessionId": item["PlaySessionId"],
+ "AudioStreamIndex": item["AudioStreamIndex"],
+ "SubtitleStreamIndex": item["SubtitleStreamIndex"],
}
- item['Server'].jellyfin.session_playing(data)
- window('jellyfin.skip.%s.bool' % item['Id'], True)
+ item["Server"].jellyfin.session_playing(data)
+ window("jellyfin.skip.%s.bool" % item["Id"], True)
if monitor.waitForAbort(2):
return
- if item['PlayOption'] == 'Addon':
- self.set_audio_subs(item['AudioStreamIndex'], item['SubtitleStreamIndex'])
+ if item["PlayOption"] == "Addon":
+ self.set_audio_subs(item["AudioStreamIndex"], item["SubtitleStreamIndex"])
def set_item(self, file, item):
-
- ''' Set playback information.
- '''
+ """Set playback information."""
try:
- item['Runtime'] = int(item['Runtime'])
+ item["Runtime"] = int(item["Runtime"])
except (TypeError, ValueError):
try:
- item['Runtime'] = int(self.getTotalTime())
- LOG.info("Runtime is missing, Kodi runtime: %s" % item['Runtime'])
+ item["Runtime"] = int(self.getTotalTime())
+ LOG.info("Runtime is missing, Kodi runtime: %s" % item["Runtime"])
except Exception:
- item['Runtime'] = 0
+ item["Runtime"] = 0
LOG.info("Runtime is missing, Using Zero")
try:
@@ -142,22 +139,26 @@ def set_item(self, file, item):
except Exception: # at this point we should be playing and if not then bail out
return
- result = JSONRPC('Application.GetProperties').execute({'properties': ["volume", "muted"]})
- result = result.get('result', {})
- volume = result.get('volume')
- muted = result.get('muted')
-
- item.update({
- 'File': file,
- 'CurrentPosition': item.get('CurrentPosition') or int(seektime),
- 'Muted': muted,
- 'Volume': volume,
- 'Server': Jellyfin(item['ServerId']).get_client(),
- 'Paused': False
- })
+ result = JSONRPC("Application.GetProperties").execute(
+ {"properties": ["volume", "muted"]}
+ )
+ result = result.get("result", {})
+ volume = result.get("volume")
+ muted = result.get("muted")
+
+ item.update(
+ {
+ "File": file,
+ "CurrentPosition": item.get("CurrentPosition") or int(seektime),
+ "Muted": muted,
+ "Volume": volume,
+ "Server": Jellyfin(item["ServerId"]).get_client(),
+ "Paused": False,
+ }
+ )
self.played[file] = item
- LOG.info("-->[ play/%s ] %s", item['Id'], item)
+ LOG.info("-->[ play/%s ] %s", item["Id"], item)
def set_audio_subs(self, audio=None, subtitle=None):
if audio:
@@ -165,15 +166,15 @@ def set_audio_subs(self, audio=None, subtitle=None):
if subtitle:
subtitle = int(subtitle)
- ''' Only for after playback started
- '''
+ """ Only for after playback started
+ """
LOG.info("Setting audio: %s subs: %s", audio, subtitle)
current_file = self.get_playing_file()
if self.is_playing_file(current_file):
item = self.get_file_info(current_file)
- mapping = item['SubsMapping']
+ mapping = item["SubsMapping"]
if audio and len(self.getAvailableAudioStreams()) > 1:
self.setAudioStream(audio - 1)
@@ -200,82 +201,90 @@ def set_audio_subs(self, audio=None, subtitle=None):
def detect_audio_subs(self, item):
params = {
- 'playerid': 1,
- 'properties': ["currentsubtitle", "currentaudiostream", "subtitleenabled"]
+ "playerid": 1,
+ "properties": ["currentsubtitle", "currentaudiostream", "subtitleenabled"],
}
- result = JSONRPC('Player.GetProperties').execute(params)
- result = result.get('result')
+ result = JSONRPC("Player.GetProperties").execute(params)
+ result = result.get("result")
try: # Audio tracks
- audio = result['currentaudiostream']['index']
+ audio = result["currentaudiostream"]["index"]
except (KeyError, TypeError):
audio = 0
try: # Subtitles tracks
- subs = result['currentsubtitle']['index']
+ subs = result["currentsubtitle"]["index"]
except (KeyError, TypeError):
subs = 0
try: # If subtitles are enabled
- subs_enabled = result['subtitleenabled']
+ subs_enabled = result["subtitleenabled"]
except (KeyError, TypeError):
subs_enabled = False
- item['AudioStreamIndex'] = audio + 1
+ item["AudioStreamIndex"] = audio + 1
if not subs_enabled or not len(self.getAvailableSubtitleStreams()):
- item['SubtitleStreamIndex'] = None
+ item["SubtitleStreamIndex"] = None
return
- mapping = item['SubsMapping']
+ mapping = item["SubsMapping"]
tracks = len(self.getAvailableAudioStreams())
if mapping:
if str(subs) in mapping:
- item['SubtitleStreamIndex'] = mapping[str(subs)]
+ item["SubtitleStreamIndex"] = mapping[str(subs)]
else:
- item['SubtitleStreamIndex'] = subs - len(mapping) + tracks + 1
+ item["SubtitleStreamIndex"] = subs - len(mapping) + tracks + 1
else:
- item['SubtitleStreamIndex'] = subs + tracks + 1
+ item["SubtitleStreamIndex"] = subs + tracks + 1
def next_up(self):
item = self.get_file_info(self.get_playing_file())
objects = Objects()
- if item['Type'] != 'Episode' or not item.get('CurrentEpisode'):
+ if item["Type"] != "Episode" or not item.get("CurrentEpisode"):
return
- next_items = item['Server'].jellyfin.get_adjacent_episodes(item['CurrentEpisode']['tvshowid'], item['Id'])
+ next_items = item["Server"].jellyfin.get_adjacent_episodes(
+ item["CurrentEpisode"]["tvshowid"], item["Id"]
+ )
- for index, next_item in enumerate(next_items['Items']):
- if next_item['Id'] == item['Id']:
+ for index, next_item in enumerate(next_items["Items"]):
+ if next_item["Id"] == item["Id"]:
try:
- next_item = next_items['Items'][index + 1]
+ next_item = next_items["Items"][index + 1]
except IndexError:
LOG.warning("No next up episode.")
return
break
- server_address = item['Server'].auth.get_server_info(item['Server'].auth.server_id)['address']
+ server_address = item["Server"].auth.get_server_info(
+ item["Server"].auth.server_id
+ )["address"]
API = api.API(next_item, server_address)
data = objects.map(next_item, "UpNext")
- artwork = API.get_all_artwork(objects.map(next_item, 'ArtworkParent'), True)
- data['art'] = {
- 'tvshow.poster': artwork.get('Series.Primary'),
- 'tvshow.fanart': None,
- 'thumb': artwork.get('Primary')
+ artwork = API.get_all_artwork(objects.map(next_item, "ArtworkParent"), True)
+ data["art"] = {
+ "tvshow.poster": artwork.get("Series.Primary"),
+ "tvshow.fanart": None,
+ "thumb": artwork.get("Primary"),
}
- if artwork['Backdrop']:
- data['art']['tvshow.fanart'] = artwork['Backdrop'][0]
+ if artwork["Backdrop"]:
+ data["art"]["tvshow.fanart"] = artwork["Backdrop"][0]
next_info = {
- 'play_info': {'ItemIds': [data['episodeid']], 'ServerId': item['ServerId'], 'PlayCommand': 'PlayNow'},
- 'current_episode': item['CurrentEpisode'],
- 'next_episode': data
+ "play_info": {
+ "ItemIds": [data["episodeid"]],
+ "ServerId": item["ServerId"],
+ "PlayCommand": "PlayNow",
+ },
+ "current_episode": item["CurrentEpisode"],
+ "next_episode": data,
}
LOG.info("--[ next up ] %s", next_info)
@@ -286,7 +295,7 @@ def onPlayBackPaused(self):
if self.is_playing_file(current_file):
- self.get_file_info(current_file)['Paused'] = True
+ self.get_file_info(current_file)["Paused"] = True
self.report_playback()
LOG.debug("-->[ paused ]")
@@ -295,24 +304,21 @@ def onPlayBackResumed(self):
if self.is_playing_file(current_file):
- self.get_file_info(current_file)['Paused'] = False
+ self.get_file_info(current_file)["Paused"] = False
self.report_playback()
LOG.debug("--<[ paused ]")
def onPlayBackSeek(self, time, seek_offset):
-
- ''' Does not seem to work in Leia??
- '''
+ """Does not seem to work in Leia??"""
if self.is_playing_file(self.get_playing_file()):
self.report_playback()
LOG.info("--[ seek ]")
def report_playback(self, report=True):
-
- ''' Report playback progress to jellyfin server.
- Check if the user seek.
- '''
+ """Report playback progress to jellyfin server.
+ Check if the user seek.
+ """
current_file = self.get_playing_file()
if not self.is_playing_file(current_file):
@@ -320,25 +326,29 @@ def report_playback(self, report=True):
item = self.get_file_info(current_file)
- if window('jellyfin.external.bool'):
+ if window("jellyfin.external.bool"):
return
if not report:
- previous = item['CurrentPosition']
+ previous = item["CurrentPosition"]
try:
- item['CurrentPosition'] = int(self.getTime())
+ item["CurrentPosition"] = int(self.getTime())
except Exception as e:
# getTime() raises RuntimeError if nothing is playing
LOG.debug("Failed to get playback position: %s", e)
return
- if int(item['CurrentPosition']) == 1:
+ if int(item["CurrentPosition"]) == 1:
return
try:
- played = float(item['CurrentPosition'] * 10000000) / int(item['Runtime']) * 100
+ played = (
+ float(item["CurrentPosition"] * 10000000)
+ / int(item["Runtime"])
+ * 100
+ )
except ZeroDivisionError: # Runtime is 0.
played = 0
@@ -347,52 +357,48 @@ def report_playback(self, report=True):
self.up_next = True
self.next_up()
- if (item['CurrentPosition'] - previous) < 30:
+ if (item["CurrentPosition"] - previous) < 30:
return
- result = JSONRPC('Application.GetProperties').execute({'properties': ["volume", "muted"]})
- result = result.get('result', {})
- item['Volume'] = result.get('volume')
- item['Muted'] = result.get('muted')
- item['CurrentPosition'] = int(self.getTime())
+ result = JSONRPC("Application.GetProperties").execute(
+ {"properties": ["volume", "muted"]}
+ )
+ result = result.get("result", {})
+ item["Volume"] = result.get("volume")
+ item["Muted"] = result.get("muted")
+ item["CurrentPosition"] = int(self.getTime())
self.detect_audio_subs(item)
data = {
- 'QueueableMediaTypes': "Video,Audio",
- 'CanSeek': True,
- 'ItemId': item['Id'],
- 'MediaSourceId': item['MediaSourceId'],
- 'PlayMethod': item['PlayMethod'],
- 'VolumeLevel': item['Volume'],
- 'PositionTicks': int(item['CurrentPosition'] * 10000000),
- 'IsPaused': item['Paused'],
- 'IsMuted': item['Muted'],
- 'PlaySessionId': item['PlaySessionId'],
- 'AudioStreamIndex': item['AudioStreamIndex'],
- 'SubtitleStreamIndex': item['SubtitleStreamIndex']
+ "QueueableMediaTypes": "Video,Audio",
+ "CanSeek": True,
+ "ItemId": item["Id"],
+ "MediaSourceId": item["MediaSourceId"],
+ "PlayMethod": item["PlayMethod"],
+ "VolumeLevel": item["Volume"],
+ "PositionTicks": int(item["CurrentPosition"] * 10000000),
+ "IsPaused": item["Paused"],
+ "IsMuted": item["Muted"],
+ "PlaySessionId": item["PlaySessionId"],
+ "AudioStreamIndex": item["AudioStreamIndex"],
+ "SubtitleStreamIndex": item["SubtitleStreamIndex"],
}
- item['Server'].jellyfin.session_progress(data)
+ item["Server"].jellyfin.session_progress(data)
def onPlayBackStopped(self):
-
- ''' Will be called when user stops playing a file.
- '''
- window('jellyfin_play', clear=True)
+ """Will be called when user stops playing a file."""
+ window("jellyfin_play", clear=True)
self.stop_playback()
LOG.info("--<[ playback ]")
def onPlayBackEnded(self):
-
- ''' Will be called when kodi stops playing a file.
- '''
+ """Will be called when kodi stops playing a file."""
self.stop_playback()
LOG.info("--<<[ playback ]")
def stop_playback(self):
-
- ''' Stop all playback. Check for external player for positionticks.
- '''
+ """Stop all playback. Check for external player for positionticks."""
if not self.played:
return
@@ -401,61 +407,67 @@ def stop_playback(self):
for file in self.played:
item = self.get_file_info(file)
- window('jellyfin.skip.%s.bool' % item['Id'], True)
+ window("jellyfin.skip.%s.bool" % item["Id"], True)
- if window('jellyfin.external.bool'):
- window('jellyfin.external', clear=True)
+ if window("jellyfin.external.bool"):
+ window("jellyfin.external", clear=True)
- if int(item['CurrentPosition']) == 1:
- item['CurrentPosition'] = int(item['Runtime'])
+ if int(item["CurrentPosition"]) == 1:
+ item["CurrentPosition"] = int(item["Runtime"])
data = {
- 'ItemId': item['Id'],
- 'MediaSourceId': item['MediaSourceId'],
- 'PositionTicks': int(item['CurrentPosition'] * 10000000),
- 'PlaySessionId': item['PlaySessionId']
+ "ItemId": item["Id"],
+ "MediaSourceId": item["MediaSourceId"],
+ "PositionTicks": int(item["CurrentPosition"] * 10000000),
+ "PlaySessionId": item["PlaySessionId"],
}
- item['Server'].jellyfin.session_stop(data)
+ item["Server"].jellyfin.session_stop(data)
- if item.get('LiveStreamId'):
+ if item.get("LiveStreamId"):
- LOG.info("<[ livestream/%s ]", item['LiveStreamId'])
- item['Server'].jellyfin.close_live_stream(item['LiveStreamId'])
+ LOG.info("<[ livestream/%s ]", item["LiveStreamId"])
+ item["Server"].jellyfin.close_live_stream(item["LiveStreamId"])
- elif item['PlayMethod'] == 'Transcode':
+ elif item["PlayMethod"] == "Transcode":
- LOG.info("<[ transcode/%s ]", item['Id'])
- item['Server'].jellyfin.close_transcode(item['DeviceId'], item['PlaySessionId'])
+ LOG.info("<[ transcode/%s ]", item["Id"])
+ item["Server"].jellyfin.close_transcode(
+ item["DeviceId"], item["PlaySessionId"]
+ )
- path = translate_path("special://profile/addon_data/plugin.video.jellyfin/temp/")
+ path = translate_path(
+ "special://profile/addon_data/plugin.video.jellyfin/temp/"
+ )
if xbmcvfs.exists(path):
dirs, files = xbmcvfs.listdir(path)
for file in files:
# Only delete the cached files for the previous play session
- if item['Id'] in file:
+ if item["Id"] in file:
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 {}
- if 'UserData' in result and result['UserData']['Played']:
+ if "UserData" in result and result["UserData"]["Played"]:
delete = False
- if result['Type'] == 'Episode' and settings('deleteTV.bool'):
+ if result["Type"] == "Episode" and settings("deleteTV.bool"):
delete = True
- elif result['Type'] == 'Movie' and settings('deleteMovies.bool'):
+ elif result["Type"] == "Movie" and settings("deleteMovies.bool"):
delete = True
- if not settings('offerDelete.bool'):
+ if not settings("offerDelete.bool"):
delete = False
if delete:
LOG.info("Offer delete option")
- if dialog("yesno", translate(30091), translate(33015), autoclose=120000):
- item['Server'].jellyfin.delete_item(item['Id'])
+ if dialog(
+ "yesno", translate(30091), translate(33015), autoclose=120000
+ ):
+ item["Server"].jellyfin.delete_item(item["Id"])
- window('jellyfin.external_check', clear=True)
+ window("jellyfin.external_check", clear=True)
self.played.clear()
diff --git a/jellyfin_kodi/views.py b/jellyfin_kodi/views.py
index 8569cb1ed..00eea84f8 100644
--- a/jellyfin_kodi/views.py
+++ b/jellyfin_kodi/views.py
@@ -19,86 +19,86 @@
LOG = LazyLogger(__name__)
NODES = {
- 'tvshows': [
- ('all', None),
- ('recent', translate(30170)),
- ('recentepisodes', translate(30175)),
- ('inprogress', translate(30171)),
- ('inprogressepisodes', translate(30178)),
- ('nextepisodes', translate(30179)),
- ('genres', 135),
- ('random', translate(30229)),
- ('recommended', translate(30230))
+ "tvshows": [
+ ("all", None),
+ ("recent", translate(30170)),
+ ("recentepisodes", translate(30175)),
+ ("inprogress", translate(30171)),
+ ("inprogressepisodes", translate(30178)),
+ ("nextepisodes", translate(30179)),
+ ("genres", 135),
+ ("random", translate(30229)),
+ ("recommended", translate(30230)),
],
- 'movies': [
- ('all', None),
- ('recent', translate(30174)),
- ('inprogress', translate(30177)),
- ('unwatched', translate(30189)),
- ('sets', 20434),
- ('genres', 135),
- ('random', translate(30229)),
- ('recommended', translate(30230))
+ "movies": [
+ ("all", None),
+ ("recent", translate(30174)),
+ ("inprogress", translate(30177)),
+ ("unwatched", translate(30189)),
+ ("sets", 20434),
+ ("genres", 135),
+ ("random", translate(30229)),
+ ("recommended", translate(30230)),
+ ],
+ "musicvideos": [
+ ("all", None),
+ ("recent", translate(30256)),
+ ("inprogress", translate(30257)),
+ ("unwatched", translate(30258)),
],
- 'musicvideos': [
- ('all', None),
- ('recent', translate(30256)),
- ('inprogress', translate(30257)),
- ('unwatched', translate(30258))
- ]
}
DYNNODES = {
- 'tvshows': [
- ('all', None),
- ('RecentlyAdded', translate(30170)),
- ('recentepisodes', translate(30175)),
- ('InProgress', translate(30171)),
- ('inprogressepisodes', translate(30178)),
- ('nextepisodes', translate(30179)),
- ('Genres', translate(135)),
- ('Random', translate(30229)),
- ('recommended', translate(30230))
+ "tvshows": [
+ ("all", None),
+ ("RecentlyAdded", translate(30170)),
+ ("recentepisodes", translate(30175)),
+ ("InProgress", translate(30171)),
+ ("inprogressepisodes", translate(30178)),
+ ("nextepisodes", translate(30179)),
+ ("Genres", translate(135)),
+ ("Random", translate(30229)),
+ ("recommended", translate(30230)),
],
- 'movies': [
- ('all', None),
- ('RecentlyAdded', translate(30174)),
- ('InProgress', translate(30177)),
- ('Boxsets', translate(20434)),
- ('Favorite', translate(33168)),
- ('FirstLetter', translate(33171)),
- ('Genres', translate(135)),
- ('Random', translate(30229)),
+ "movies": [
+ ("all", None),
+ ("RecentlyAdded", translate(30174)),
+ ("InProgress", translate(30177)),
+ ("Boxsets", translate(20434)),
+ ("Favorite", translate(33168)),
+ ("FirstLetter", translate(33171)),
+ ("Genres", translate(135)),
+ ("Random", translate(30229)),
# ('Recommended', translate(30230))
],
- 'musicvideos': [
- ('all', None),
- ('RecentlyAdded', translate(30256)),
- ('InProgress', translate(30257)),
- ('Unwatched', translate(30258))
+ "musicvideos": [
+ ("all", None),
+ ("RecentlyAdded", translate(30256)),
+ ("InProgress", translate(30257)),
+ ("Unwatched", translate(30258)),
+ ],
+ "homevideos": [
+ ("all", None),
+ ("RecentlyAdded", translate(33167)),
+ ("InProgress", translate(33169)),
+ ("Favorite", translate(33168)),
],
- 'homevideos': [
- ('all', None),
- ('RecentlyAdded', translate(33167)),
- ('InProgress', translate(33169)),
- ('Favorite', translate(33168))
+ "books": [
+ ("all", None),
+ ("RecentlyAdded", translate(33167)),
+ ("InProgress", translate(33169)),
+ ("Favorite", translate(33168)),
],
- 'books': [
- ('all', None),
- ('RecentlyAdded', translate(33167)),
- ('InProgress', translate(33169)),
- ('Favorite', translate(33168))
+ "audiobooks": [
+ ("all", None),
+ ("RecentlyAdded", translate(33167)),
+ ("InProgress", translate(33169)),
+ ("Favorite", translate(33168)),
],
- 'audiobooks': [
- ('all', None),
- ('RecentlyAdded', translate(33167)),
- ('InProgress', translate(33169)),
- ('Favorite', translate(33168))
+ "music": [
+ ("all", None),
+ ("RecentlyAdded", translate(33167)),
+ ("Favorite", translate(33168)),
],
- 'music': [
- ('all', None),
- ('RecentlyAdded', translate(33167)),
- ('Favorite', translate(33168))
- ]
}
#################################################################################################
@@ -116,17 +116,15 @@ def __init__(self):
self.server = Jellyfin()
def add_library(self, view):
-
- ''' Add entry to view table in jellyfin database.
- '''
- with Database('jellyfin') as jellyfindb:
- jellyfin_db.JellyfinDatabase(jellyfindb.cursor).add_view(view['Id'], view['Name'], view['Media'])
+ """Add entry to view table in jellyfin database."""
+ with Database("jellyfin") as jellyfindb:
+ jellyfin_db.JellyfinDatabase(jellyfindb.cursor).add_view(
+ view["Id"], view["Name"], view["Media"]
+ )
def remove_library(self, view_id):
-
- ''' Remove entry from view table in jellyfin database.
- '''
- with Database('jellyfin') as jellyfindb:
+ """Remove entry from view table in jellyfin database."""
+ with Database("jellyfin") as jellyfindb:
jellyfin_db.JellyfinDatabase(jellyfindb.cursor).remove_view(view_id)
self.delete_playlist_by_id(view_id)
@@ -135,10 +133,10 @@ def remove_library(self, view_id):
def get_libraries(self):
try:
- libraries = self.server.jellyfin.get_media_folders()['Items']
- library_ids = [x['Id'] for x in libraries]
- for view in self.server.jellyfin.get_views()['Items']:
- if view['Id'] not in library_ids:
+ libraries = self.server.jellyfin.get_media_folders()["Items"]
+ library_ids = [x["Id"] for x in libraries]
+ for view in self.server.jellyfin.get_views()["Items"]:
+ if view["Id"] not in library_ids:
libraries.append(view)
except Exception as error:
@@ -148,9 +146,7 @@ def get_libraries(self):
return libraries
def get_views(self):
-
- ''' Get the media folders. Add or remove them. Do not proceed if issue getting libraries.
- '''
+ """Get the media folders. Add or remove them. Do not proceed if issue getting libraries."""
try:
libraries = self.get_libraries()
except IndexError as error:
@@ -158,35 +154,35 @@ def get_views(self):
return
- self.sync['SortedViews'] = [x['Id'] for x in libraries]
+ self.sync["SortedViews"] = [x["Id"] for x in libraries]
for library in libraries:
- if library['Type'] == 'Channel':
- library['Media'] = "channels"
+ if library["Type"] == "Channel":
+ library["Media"] = "channels"
else:
- library['Media'] = library.get('OriginalCollectionType', library.get('CollectionType', "mixed"))
+ library["Media"] = library.get(
+ "OriginalCollectionType", library.get("CollectionType", "mixed")
+ )
self.add_library(library)
- with Database('jellyfin') as jellyfindb:
+ with Database("jellyfin") as jellyfindb:
views = jellyfin_db.JellyfinDatabase(jellyfindb.cursor).get_views()
removed = []
for view in views:
- if view.view_id not in self.sync['SortedViews']:
+ if view.view_id not in self.sync["SortedViews"]:
removed.append(view.view_id)
if removed:
- event('RemoveLibrary', {'Id': ','.join(removed)})
+ event("RemoveLibrary", {"Id": ",".join(removed)})
save_sync(self.sync)
def get_nodes(self):
-
- ''' Set up playlists, video nodes, window prop.
- '''
+ """Set up playlists, video nodes, window prop."""
node_path = translate_path("special://profile/library/video")
playlist_path = translate_path("special://profile/playlists/video")
index = 0
@@ -195,38 +191,57 @@ def get_nodes(self):
if not os.path.isdir(node_path):
os.makedirs(node_path)
- with Database('jellyfin') as jellyfindb:
+ with Database("jellyfin") as jellyfindb:
db = jellyfin_db.JellyfinDatabase(jellyfindb.cursor)
- for library in self.sync['Whitelist']:
+ for library in self.sync["Whitelist"]:
- library = library.replace('Mixed:', "")
+ library = library.replace("Mixed:", "")
view = db.get_view(library)
if view:
- view = {'Id': library, 'Name': view.view_name, 'Tag': view.view_name, 'Media': view.media_type}
+ view = {
+ "Id": library,
+ "Name": view.view_name,
+ "Tag": view.view_name,
+ "Media": view.media_type,
+ }
- if view['Media'] == 'mixed':
- for media in ('movies', 'tvshows'):
+ if view["Media"] == "mixed":
+ for media in ("movies", "tvshows"):
temp_view = dict(view)
- temp_view['Media'] = media
+ temp_view["Media"] = media
self.add_playlist(playlist_path, temp_view, True)
self.add_nodes(node_path, temp_view, True)
index += 1 # Compensate for the duplicate.
else:
- if view['Media'] in ('movies', 'tvshows', 'musicvideos'):
+ if view["Media"] in ("movies", "tvshows", "musicvideos"):
self.add_playlist(playlist_path, view)
- if view['Media'] not in ('music',):
+ if view["Media"] not in ("music",):
self.add_nodes(node_path, view)
index += 1
- for single in [{'Name': translate('fav_movies'), 'Tag': "Favorite movies", 'Media': "movies"},
- {'Name': translate('fav_tvshows'), 'Tag': "Favorite tvshows", 'Media': "tvshows"},
- {'Name': translate('fav_episodes'), 'Tag': "Favorite episodes", 'Media': "episodes"}]:
+ for single in [
+ {
+ "Name": translate("fav_movies"),
+ "Tag": "Favorite movies",
+ "Media": "movies",
+ },
+ {
+ "Name": translate("fav_tvshows"),
+ "Tag": "Favorite tvshows",
+ "Media": "tvshows",
+ },
+ {
+ "Name": translate("fav_episodes"),
+ "Tag": "Favorite episodes",
+ "Media": "episodes",
+ },
+ ]:
self.add_single_node(node_path, index, "favorites", single)
index += 1
@@ -234,95 +249,107 @@ def get_nodes(self):
self.window_nodes()
def add_playlist(self, path, view, mixed=False):
-
- ''' Create or update the xps file.
- '''
- file = os.path.join(path, "jellyfin%s%s.xsp" % (view['Media'], view['Id']))
+ """Create or update the xps file."""
+ file = os.path.join(path, "jellyfin%s%s.xsp" % (view["Media"], view["Id"]))
try:
if os.path.isfile(file):
xml = etree.parse(file).getroot()
else:
- xml = etree.Element('smartplaylist', {'type': view['Media']})
- etree.SubElement(xml, 'name')
- etree.SubElement(xml, 'match')
+ xml = etree.Element("smartplaylist", {"type": view["Media"]})
+ etree.SubElement(xml, "name")
+ etree.SubElement(xml, "match")
except Exception:
LOG.warning("Unable to parse file '%s'", file)
- xml = etree.Element('smartplaylist', {'type': view['Media']})
- etree.SubElement(xml, 'name')
- etree.SubElement(xml, 'match')
+ xml = etree.Element("smartplaylist", {"type": view["Media"]})
+ etree.SubElement(xml, "name")
+ etree.SubElement(xml, "match")
- name = xml.find('name')
- name.text = view['Name'] if not mixed else "%s (%s)" % (view['Name'], view['Media'])
+ name = xml.find("name")
+ name.text = (
+ view["Name"] if not mixed else "%s (%s)" % (view["Name"], view["Media"])
+ )
- match = xml.find('match')
+ match = xml.find("match")
match.text = "all"
- for rule in xml.findall('.//value'):
- if rule.text == view['Tag']:
+ for rule in xml.findall(".//value"):
+ if rule.text == view["Tag"]:
break
else:
- rule = etree.SubElement(xml, 'rule', {'field': "tag", 'operator': "is"})
- etree.SubElement(rule, 'value').text = view['Tag']
+ rule = etree.SubElement(xml, "rule", {"field": "tag", "operator": "is"})
+ etree.SubElement(rule, "value").text = view["Tag"]
tree = etree.ElementTree(xml)
tree.write(file)
def add_nodes(self, path, view, mixed=False):
-
- ''' Create or update the video node file.
- '''
- folder = os.path.join(path, "jellyfin%s%s" % (view['Media'], view['Id']))
+ """Create or update the video node file."""
+ folder = os.path.join(path, "jellyfin%s%s" % (view["Media"], view["Id"]))
if not xbmcvfs.exists(folder):
xbmcvfs.mkdir(folder)
self.node_index(folder, view, mixed)
- if view['Media'] == 'tvshows':
+ if view["Media"] == "tvshows":
self.node_tvshow(folder, view)
else:
self.node(folder, view)
def add_single_node(self, path, index, item_type, view):
- file = os.path.join(path, "jellyfin_%s.xml" % view['Tag'].replace(" ", ""))
+ file = os.path.join(path, "jellyfin_%s.xml" % view["Tag"].replace(" ", ""))
try:
if os.path.isfile(file):
xml = etree.parse(file).getroot()
else:
- xml = self.node_root('folder' if item_type == 'favorites' and view['Media'] == 'episodes' else 'filter', index)
- etree.SubElement(xml, 'label')
- etree.SubElement(xml, 'match')
- etree.SubElement(xml, 'content')
+ xml = self.node_root(
+ (
+ "folder"
+ if item_type == "favorites" and view["Media"] == "episodes"
+ else "filter"
+ ),
+ index,
+ )
+ etree.SubElement(xml, "label")
+ etree.SubElement(xml, "match")
+ etree.SubElement(xml, "content")
except Exception:
LOG.warning("Unable to parse file '%s'", file)
- xml = self.node_root('folder' if item_type == 'favorites' and view['Media'] == 'episodes' else 'filter', index)
- etree.SubElement(xml, 'label')
- etree.SubElement(xml, 'match')
- etree.SubElement(xml, 'content')
-
- label = xml.find('label')
- label.text = view['Name']
-
- content = xml.find('content')
- content.text = view['Media']
-
- match = xml.find('match')
+ xml = self.node_root(
+ (
+ "folder"
+ if item_type == "favorites" and view["Media"] == "episodes"
+ else "filter"
+ ),
+ index,
+ )
+ etree.SubElement(xml, "label")
+ etree.SubElement(xml, "match")
+ etree.SubElement(xml, "content")
+
+ label = xml.find("label")
+ label.text = view["Name"]
+
+ content = xml.find("content")
+ content.text = view["Media"]
+
+ match = xml.find("match")
match.text = "all"
- if view['Media'] != 'episodes':
+ if view["Media"] != "episodes":
- for rule in xml.findall('.//value'):
- if rule.text == view['Tag']:
+ for rule in xml.findall(".//value"):
+ if rule.text == view["Tag"]:
break
else:
- rule = etree.SubElement(xml, 'rule', {'field': "tag", 'operator': "is"})
- etree.SubElement(rule, 'value').text = view['Tag']
+ rule = etree.SubElement(xml, "rule", {"field": "tag", "operator": "is"})
+ etree.SubElement(rule, "value").text = view["Tag"]
- if item_type == 'favorites' and view['Media'] == 'episodes':
- path = self.window_browse(view, 'FavEpisodes')
+ if item_type == "favorites" and view["Media"] == "episodes":
+ path = self.window_browse(view, "FavEpisodes")
self.node_favepisodes(xml, path)
else:
self.node_all(xml)
@@ -331,62 +358,68 @@ def add_single_node(self, path, index, item_type, view):
tree.write(file)
def node_root(self, root, index):
-
- ''' Create the root element
- '''
- if root == 'main':
- element = etree.Element('node', {'order': str(index)})
- elif root == 'filter':
- element = etree.Element('node', {'order': str(index), 'type': "filter"})
+ """Create the root element"""
+ if root == "main":
+ element = etree.Element("node", {"order": str(index)})
+ elif root == "filter":
+ element = etree.Element("node", {"order": str(index), "type": "filter"})
else:
- element = etree.Element('node', {'order': str(index), 'type': "folder"})
+ element = etree.Element("node", {"order": str(index), "type": "folder"})
- etree.SubElement(element, 'icon').text = "special://home/addons/plugin.video.jellyfin/resources/icon.png"
+ etree.SubElement(element, "icon").text = (
+ "special://home/addons/plugin.video.jellyfin/resources/icon.png"
+ )
return element
def node_index(self, folder, view, mixed=False):
file = os.path.join(folder, "index.xml")
- index = self.sync['SortedViews'].index(view['Id'])
+ index = self.sync["SortedViews"].index(view["Id"])
try:
if os.path.isfile(file):
xml = etree.parse(file).getroot()
- xml.set('order', str(index))
+ xml.set("order", str(index))
else:
- xml = self.node_root('main', index)
- etree.SubElement(xml, 'label')
+ xml = self.node_root("main", index)
+ etree.SubElement(xml, "label")
except Exception as error:
LOG.exception(error)
- xml = self.node_root('main', index)
- etree.SubElement(xml, 'label')
+ xml = self.node_root("main", index)
+ etree.SubElement(xml, "label")
- label = xml.find('label')
- label.text = view['Name'] if not mixed else "%s (%s)" % (view['Name'], translate(view['Media']))
+ label = xml.find("label")
+ label.text = (
+ view["Name"]
+ if not mixed
+ else "%s (%s)" % (view["Name"], translate(view["Media"]))
+ )
tree = etree.ElementTree(xml)
tree.write(file)
def node(self, folder, view):
- for node in NODES[view['Media']]:
+ for node in NODES[view["Media"]]:
xml_name = node[0]
- xml_label = node[1] or view['Name']
+ xml_label = node[1] or view["Name"]
file = os.path.join(folder, "%s.xml" % xml_name)
- self.add_node(NODES[view['Media']].index(node), file, view, xml_name, xml_label)
+ self.add_node(
+ NODES[view["Media"]].index(node), file, view, xml_name, xml_label
+ )
def node_tvshow(self, folder, view):
- for node in NODES[view['Media']]:
+ for node in NODES[view["Media"]]:
xml_name = node[0]
- xml_label = node[1] or view['Name']
- xml_index = NODES[view['Media']].index(node)
+ xml_label = node[1] or view["Name"]
+ xml_index = NODES[view["Media"]].index(node)
file = os.path.join(folder, "%s.xml" % xml_name)
- if xml_name == 'nextepisodes':
+ if xml_name == "nextepisodes":
path = self.window_nextepisodes(view)
self.add_dynamic_node(xml_index, file, view, xml_name, xml_label, path)
else:
@@ -398,35 +431,35 @@ def add_node(self, index, file, view, node, name):
if os.path.isfile(file):
xml = etree.parse(file).getroot()
else:
- xml = self.node_root('filter', index)
- etree.SubElement(xml, 'label')
- etree.SubElement(xml, 'match')
- etree.SubElement(xml, 'content')
+ xml = self.node_root("filter", index)
+ etree.SubElement(xml, "label")
+ etree.SubElement(xml, "match")
+ etree.SubElement(xml, "content")
except Exception:
LOG.warning("Unable to parse file '%s'", file)
- xml = self.node_root('filter', index)
- etree.SubElement(xml, 'label')
- etree.SubElement(xml, 'match')
- etree.SubElement(xml, 'content')
+ xml = self.node_root("filter", index)
+ etree.SubElement(xml, "label")
+ etree.SubElement(xml, "match")
+ etree.SubElement(xml, "content")
- label = xml.find('label')
+ label = xml.find("label")
label.text = str(name) if type(name) == int else name
- content = xml.find('content')
- content.text = view['Media']
+ content = xml.find("content")
+ content.text = view["Media"]
- match = xml.find('match')
+ match = xml.find("match")
match.text = "all"
- for rule in xml.findall('.//value'):
- if rule.text == view['Tag']:
+ for rule in xml.findall(".//value"):
+ if rule.text == view["Tag"]:
break
else:
- rule = etree.SubElement(xml, 'rule', {'field': "tag", 'operator': "is"})
- etree.SubElement(rule, 'value').text = view['Tag']
+ rule = etree.SubElement(xml, "rule", {"field": "tag", "operator": "is"})
+ etree.SubElement(rule, "value").text = view["Tag"]
- getattr(self, 'node_' + node)(xml) # get node function based on node type
+ getattr(self, "node_" + node)(xml) # get node function based on node type
tree = etree.ElementTree(xml)
tree.write(file)
@@ -436,237 +469,258 @@ def add_dynamic_node(self, index, file, view, node, name, path):
if os.path.isfile(file):
xml = etree.parse(file).getroot()
else:
- xml = self.node_root('folder', index)
- etree.SubElement(xml, 'label')
- etree.SubElement(xml, 'content')
+ xml = self.node_root("folder", index)
+ etree.SubElement(xml, "label")
+ etree.SubElement(xml, "content")
except Exception:
LOG.warning("Unable to parse file '%s'", file)
- xml = self.node_root('folder', index)
- etree.SubElement(xml, 'label')
- etree.SubElement(xml, 'content')
+ xml = self.node_root("folder", index)
+ etree.SubElement(xml, "label")
+ etree.SubElement(xml, "content")
# Migration for https://github.com/jellyfin/jellyfin-kodi/issues/239
- if xml.attrib.get('type') == 'filter':
- xml.attrib = {'type': 'folder', 'order': '5'}
+ if xml.attrib.get("type") == "filter":
+ xml.attrib = {"type": "folder", "order": "5"}
- label = xml.find('label')
+ label = xml.find("label")
label.text = name
- getattr(self, 'node_' + node)(xml, path)
+ getattr(self, "node_" + node)(xml, path)
tree = etree.ElementTree(xml)
tree.write(file)
def node_all(self, root):
- for rule in root.findall('.//order'):
+ for rule in root.findall(".//order"):
if rule.text == "sorttitle":
break
else:
- etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
+ etree.SubElement(root, "order", {"direction": "ascending"}).text = (
+ "sorttitle"
+ )
def node_nextepisodes(self, root, path):
- for rule in root.findall('.//path'):
+ for rule in root.findall(".//path"):
rule.text = path
break
else:
- etree.SubElement(root, 'path').text = path
+ etree.SubElement(root, "path").text = path
- for rule in root.findall('.//content'):
+ for rule in root.findall(".//content"):
rule.text = "episodes"
break
else:
- etree.SubElement(root, 'content').text = "episodes"
+ etree.SubElement(root, "content").text = "episodes"
def node_recent(self, root):
- for rule in root.findall('.//order'):
+ for rule in root.findall(".//order"):
if rule.text == "dateadded":
break
else:
- etree.SubElement(root, 'order', {'direction': "descending"}).text = "dateadded"
+ etree.SubElement(root, "order", {"direction": "descending"}).text = (
+ "dateadded"
+ )
- for rule in root.findall('.//limit'):
+ for rule in root.findall(".//limit"):
rule.text = str(self.limit)
break
else:
- etree.SubElement(root, 'limit').text = str(self.limit)
+ etree.SubElement(root, "limit").text = str(self.limit)
- for rule in root.findall('.//rule'):
- if rule.attrib['field'] == 'playcount':
- rule.find('value').text = "0"
+ for rule in root.findall(".//rule"):
+ if rule.attrib["field"] == "playcount":
+ rule.find("value").text = "0"
break
else:
- rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"})
- etree.SubElement(rule, 'value').text = "0"
+ rule = etree.SubElement(
+ root, "rule", {"field": "playcount", "operator": "is"}
+ )
+ etree.SubElement(rule, "value").text = "0"
def node_inprogress(self, root):
- for rule in root.findall('.//rule'):
- if rule.attrib['field'] == 'inprogress':
+ for rule in root.findall(".//rule"):
+ if rule.attrib["field"] == "inprogress":
break
else:
- etree.SubElement(root, 'rule', {'field': "inprogress", 'operator': "true"})
+ etree.SubElement(root, "rule", {"field": "inprogress", "operator": "true"})
- for rule in root.findall('.//limit'):
+ for rule in root.findall(".//limit"):
rule.text = str(self.limit)
break
else:
- etree.SubElement(root, 'limit').text = str(self.limit)
+ etree.SubElement(root, "limit").text = str(self.limit)
def node_genres(self, root):
- for rule in root.findall('.//order'):
+ for rule in root.findall(".//order"):
if rule.text == "sorttitle":
break
else:
- etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
+ etree.SubElement(root, "order", {"direction": "ascending"}).text = (
+ "sorttitle"
+ )
- for rule in root.findall('.//group'):
+ for rule in root.findall(".//group"):
rule.text = "genres"
break
else:
- etree.SubElement(root, 'group').text = "genres"
+ etree.SubElement(root, "group").text = "genres"
def node_unwatched(self, root):
- for rule in root.findall('.//order'):
+ for rule in root.findall(".//order"):
if rule.text == "sorttitle":
break
else:
- etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
+ etree.SubElement(root, "order", {"direction": "ascending"}).text = (
+ "sorttitle"
+ )
- for rule in root.findall('.//rule'):
- if rule.attrib['field'] == 'playcount':
- rule.find('value').text = "0"
+ for rule in root.findall(".//rule"):
+ if rule.attrib["field"] == "playcount":
+ rule.find("value").text = "0"
break
else:
- rule = etree.SubElement(root, "rule", {'field': "playcount", 'operator': "is"})
- etree.SubElement(rule, 'value').text = "0"
+ rule = etree.SubElement(
+ root, "rule", {"field": "playcount", "operator": "is"}
+ )
+ etree.SubElement(rule, "value").text = "0"
def node_sets(self, root):
- for rule in root.findall('.//order'):
+ for rule in root.findall(".//order"):
if rule.text == "sorttitle":
break
else:
- etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
+ etree.SubElement(root, "order", {"direction": "ascending"}).text = (
+ "sorttitle"
+ )
- for rule in root.findall('.//group'):
+ for rule in root.findall(".//group"):
rule.text = "sets"
break
else:
- etree.SubElement(root, 'group').text = "sets"
+ etree.SubElement(root, "group").text = "sets"
def node_random(self, root):
- for rule in root.findall('.//order'):
+ for rule in root.findall(".//order"):
if rule.text == "random":
break
else:
- etree.SubElement(root, 'order', {'direction': "ascending"}).text = "random"
+ etree.SubElement(root, "order", {"direction": "ascending"}).text = "random"
- for rule in root.findall('.//limit'):
+ for rule in root.findall(".//limit"):
rule.text = str(self.limit)
break
else:
- etree.SubElement(root, 'limit').text = str(self.limit)
+ etree.SubElement(root, "limit").text = str(self.limit)
def node_recommended(self, root):
- for rule in root.findall('.//order'):
+ for rule in root.findall(".//order"):
if rule.text == "rating":
break
else:
- etree.SubElement(root, 'order', {'direction': "descending"}).text = "rating"
+ etree.SubElement(root, "order", {"direction": "descending"}).text = "rating"
- for rule in root.findall('.//limit'):
+ for rule in root.findall(".//limit"):
rule.text = str(self.limit)
break
else:
- etree.SubElement(root, 'limit').text = str(self.limit)
+ etree.SubElement(root, "limit").text = str(self.limit)
- for rule in root.findall('.//rule'):
- if rule.attrib['field'] == 'playcount':
- rule.find('value').text = "0"
+ for rule in root.findall(".//rule"):
+ if rule.attrib["field"] == "playcount":
+ rule.find("value").text = "0"
break
else:
- rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"})
- etree.SubElement(rule, 'value').text = "0"
-
- for rule in root.findall('.//rule'):
- if rule.attrib['field'] == 'rating':
- rule.find('value').text = "7"
+ rule = etree.SubElement(
+ root, "rule", {"field": "playcount", "operator": "is"}
+ )
+ etree.SubElement(rule, "value").text = "0"
+
+ for rule in root.findall(".//rule"):
+ if rule.attrib["field"] == "rating":
+ rule.find("value").text = "7"
break
else:
- rule = etree.SubElement(root, 'rule', {'field': "rating", 'operator': "greaterthan"})
- etree.SubElement(rule, 'value').text = "7"
+ rule = etree.SubElement(
+ root, "rule", {"field": "rating", "operator": "greaterthan"}
+ )
+ etree.SubElement(rule, "value").text = "7"
def node_recentepisodes(self, root):
- for rule in root.findall('.//order'):
+ for rule in root.findall(".//order"):
if rule.text == "dateadded":
break
else:
- etree.SubElement(root, 'order', {'direction': "descending"}).text = "dateadded"
+ etree.SubElement(root, "order", {"direction": "descending"}).text = (
+ "dateadded"
+ )
- for rule in root.findall('.//limit'):
+ for rule in root.findall(".//limit"):
rule.text = str(self.limit)
break
else:
- etree.SubElement(root, 'limit').text = str(self.limit)
+ etree.SubElement(root, "limit").text = str(self.limit)
- for rule in root.findall('.//rule'):
- if rule.attrib['field'] == 'playcount':
- rule.find('value').text = "0"
+ for rule in root.findall(".//rule"):
+ if rule.attrib["field"] == "playcount":
+ rule.find("value").text = "0"
break
else:
- rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"})
- etree.SubElement(rule, 'value').text = "0"
+ rule = etree.SubElement(
+ root, "rule", {"field": "playcount", "operator": "is"}
+ )
+ etree.SubElement(rule, "value").text = "0"
- content = root.find('content')
+ content = root.find("content")
content.text = "episodes"
def node_inprogressepisodes(self, root):
- for rule in root.findall('.//limit'):
+ for rule in root.findall(".//limit"):
rule.text = str(self.limit)
break
else:
- etree.SubElement(root, 'limit').text = str(self.limit)
+ etree.SubElement(root, "limit").text = str(self.limit)
- for rule in root.findall('.//rule'):
- if rule.attrib['field'] == 'inprogress':
+ for rule in root.findall(".//rule"):
+ if rule.attrib["field"] == "inprogress":
break
else:
- etree.SubElement(root, 'rule', {'field': "inprogress", 'operator': "true"})
+ etree.SubElement(root, "rule", {"field": "inprogress", "operator": "true"})
- content = root.find('content')
+ content = root.find("content")
content.text = "episodes"
def node_favepisodes(self, root, path):
- for rule in root.findall('.//path'):
+ for rule in root.findall(".//path"):
rule.text = path
break
else:
- etree.SubElement(root, 'path').text = path
+ etree.SubElement(root, "path").text = path
- for rule in root.findall('.//content'):
+ for rule in root.findall(".//content"):
rule.text = "episodes"
break
else:
- etree.SubElement(root, 'content').text = "episodes"
+ etree.SubElement(root, "content").text = "episodes"
def order_media_folders(self, folders):
-
- ''' Returns a list of sorted media folders based on the Jellyfin views.
- Insert them in SortedViews and remove Views that are not in media folders.
- '''
+ """Returns a list of sorted media folders based on the Jellyfin views.
+ Insert them in SortedViews and remove Views that are not in media folders.
+ """
if not folders:
return folders
- sorted_views = list(self.sync['SortedViews'])
+ sorted_views = list(self.sync["SortedViews"])
unordered = [x[0] for x in folders]
grouped = [x for x in unordered if x not in sorted_views]
@@ -678,14 +732,13 @@ def order_media_folders(self, folders):
return [folders[unordered.index(x)] for x in sorted_folders]
def window_nodes(self):
-
- ''' Just read from the database and populate based on SortedViews
- Set up the window properties that reflect the jellyfin server views and more.
- '''
+ """Just read from the database and populate based on SortedViews
+ Set up the window properties that reflect the jellyfin server views and more.
+ """
self.window_clear()
- self.window_clear('Jellyfin.wnodes')
+ self.window_clear("Jellyfin.wnodes")
- with Database('jellyfin') as jellyfindb:
+ with Database("jellyfin") as jellyfindb:
libraries = jellyfin_db.JellyfinDatabase(jellyfindb.cursor).get_views()
libraries = self.order_media_folders(libraries or [])
@@ -698,20 +751,30 @@ def window_nodes(self):
LOG.exception(error)
for library in libraries:
- view = {'Id': library.view_id, 'Name': library.view_name, 'Tag': library.view_name, 'Media': library.media_type}
+ view = {
+ "Id": library.view_id,
+ "Name": library.view_name,
+ "Tag": library.view_name,
+ "Media": library.media_type,
+ }
- if library.view_id in [x.replace('Mixed:', "") for x in self.sync['Whitelist']]: # Synced libraries
+ if library.view_id in [
+ x.replace("Mixed:", "") for x in self.sync["Whitelist"]
+ ]: # Synced libraries
- if view['Media'] in ('movies', 'tvshows', 'musicvideos', 'mixed'):
+ if view["Media"] in ("movies", "tvshows", "musicvideos", "mixed"):
- if view['Media'] == 'mixed':
- for media in ('movies', 'tvshows'):
+ if view["Media"] == "mixed":
+ for media in ("movies", "tvshows"):
for node in NODES[media]:
temp_view = dict(view)
- temp_view['Media'] = media
- temp_view['Name'] = "%s (%s)" % (view['Name'], translate(media))
+ temp_view["Media"] = media
+ temp_view["Name"] = "%s (%s)" % (
+ view["Name"],
+ translate(media),
+ )
self.window_node(index, temp_view, *node)
self.window_wnode(windex, temp_view, *node)
@@ -719,206 +782,231 @@ def window_nodes(self):
index += 1
windex += 1
else:
- for node in NODES[view['Media']]:
+ for node in NODES[view["Media"]]:
self.window_node(index, view, *node)
- if view['Media'] in ('movies', 'tvshows'):
+ if view["Media"] in ("movies", "tvshows"):
self.window_wnode(windex, view, *node)
- if view['Media'] in ('movies', 'tvshows'):
+ if view["Media"] in ("movies", "tvshows"):
windex += 1
- elif view['Media'] == 'music':
- self.window_node(index, view, 'music')
+ elif view["Media"] == "music":
+ self.window_node(index, view, "music")
else: # Dynamic entry
- if view['Media'] in ('homevideos', 'books', 'playlists'):
- self.window_wnode(windex, view, 'browse')
+ if view["Media"] in ("homevideos", "books", "playlists"):
+ self.window_wnode(windex, view, "browse")
windex += 1
- self.window_node(index, view, 'browse')
+ self.window_node(index, view, "browse")
index += 1
- for single in [{'Name': translate('fav_movies'), 'Tag': "Favorite movies", 'Media': "movies"},
- {'Name': translate('fav_tvshows'), 'Tag': "Favorite tvshows", 'Media': "tvshows"},
- {'Name': translate('fav_episodes'), 'Tag': "Favorite episodes", 'Media': "episodes"}]:
+ for single in [
+ {
+ "Name": translate("fav_movies"),
+ "Tag": "Favorite movies",
+ "Media": "movies",
+ },
+ {
+ "Name": translate("fav_tvshows"),
+ "Tag": "Favorite tvshows",
+ "Media": "tvshows",
+ },
+ {
+ "Name": translate("fav_episodes"),
+ "Tag": "Favorite episodes",
+ "Media": "episodes",
+ },
+ ]:
self.window_single_node(index, "favorites", single)
index += 1
- window('Jellyfin.nodes.total', str(index))
- window('Jellyfin.wnodes.total', str(windex))
+ window("Jellyfin.nodes.total", str(index))
+ window("Jellyfin.wnodes.total", str(windex))
def window_node(self, index, view, node=None, node_label=None):
-
- ''' Leads to another listing of nodes.
- '''
- if view['Media'] in ('homevideos', 'photos'):
- path = self.window_browse(view, None if node in ('all', 'browse') else node)
- elif node == 'nextepisodes':
+ """Leads to another listing of nodes."""
+ if view["Media"] in ("homevideos", "photos"):
+ path = self.window_browse(view, None if node in ("all", "browse") else node)
+ elif node == "nextepisodes":
path = self.window_nextepisodes(view)
- elif node == 'music':
+ elif node == "music":
path = self.window_music(view)
- elif node == 'browse':
+ elif node == "browse":
path = self.window_browse(view)
else:
path = self.window_path(view, node)
- if node == 'music':
+ if node == "music":
window_path = "ActivateWindow(Music,%s,return)" % path
- elif node in ('browse', 'homevideos', 'photos'):
+ elif node in ("browse", "homevideos", "photos"):
window_path = path
else:
window_path = "ActivateWindow(Videos,%s,return)" % path
node_label = translate(node_label) if type(node_label) == int else node_label
- node_label = node_label or view['Name']
+ node_label = node_label or view["Name"]
- if node in ('all', 'music'):
+ if node in ("all", "music"):
window_prop = "Jellyfin.nodes.%s" % index
- window('%s.index' % window_prop, path.replace('all.xml', "")) # dir
- window('%s.title' % window_prop, view['Name'])
- window('%s.content' % window_prop, path)
+ window("%s.index" % window_prop, path.replace("all.xml", "")) # dir
+ window("%s.title" % window_prop, view["Name"])
+ window("%s.content" % window_prop, path)
- elif node == 'browse':
+ elif node == "browse":
window_prop = "Jellyfin.nodes.%s" % index
- window('%s.title' % window_prop, view['Name'])
+ window("%s.title" % window_prop, view["Name"])
else:
window_prop = "Jellyfin.nodes.%s.%s" % (index, node)
- window('%s.title' % window_prop, node_label)
- window('%s.content' % window_prop, path)
+ window("%s.title" % window_prop, node_label)
+ window("%s.content" % window_prop, path)
- window('%s.id' % window_prop, view['Id'])
- window('%s.path' % window_prop, window_path)
- window('%s.type' % window_prop, view['Media'])
- self.window_artwork(window_prop, view['Id'])
+ window("%s.id" % window_prop, view["Id"])
+ window("%s.path" % window_prop, window_path)
+ window("%s.type" % window_prop, view["Media"])
+ self.window_artwork(window_prop, view["Id"])
def window_single_node(self, index, item_type, view):
-
- ''' Single destination node.
- '''
- path = "library://video/jellyfin_%s.xml" % view['Tag'].replace(" ", "")
+ """Single destination node."""
+ path = "library://video/jellyfin_%s.xml" % view["Tag"].replace(" ", "")
window_path = "ActivateWindow(Videos,%s,return)" % path
window_prop = "Jellyfin.nodes.%s" % index
- window('%s.title' % window_prop, view['Name'])
- window('%s.path' % window_prop, window_path)
- window('%s.content' % window_prop, path)
- window('%s.type' % window_prop, item_type)
+ window("%s.title" % window_prop, view["Name"])
+ window("%s.path" % window_prop, window_path)
+ window("%s.content" % window_prop, path)
+ window("%s.type" % window_prop, item_type)
def window_wnode(self, index, view, node=None, node_label=None):
-
- ''' Similar to window_node, but does not contain music, musicvideos.
- Contains books, audiobooks.
- '''
- if view['Media'] in ('homevideos', 'photos', 'books', 'playlists'):
- path = self.window_browse(view, None if node in ('all', 'browse') else node)
+ """Similar to window_node, but does not contain music, musicvideos.
+ Contains books, audiobooks.
+ """
+ if view["Media"] in ("homevideos", "photos", "books", "playlists"):
+ path = self.window_browse(view, None if node in ("all", "browse") else node)
else:
path = self.window_path(view, node)
- if node in ('browse', 'homevideos', 'photos', 'books', 'playlists'):
+ if node in ("browse", "homevideos", "photos", "books", "playlists"):
window_path = path
else:
window_path = "ActivateWindow(Videos,%s,return)" % path
node_label = translate(node_label) if type(node_label) == int else node_label
- node_label = node_label or view['Name']
+ node_label = node_label or view["Name"]
- if node == 'all':
+ if node == "all":
window_prop = "Jellyfin.wnodes.%s" % index
- window('%s.index' % window_prop, path.replace('all.xml', "")) # dir
- window('%s.title' % window_prop, view['Name'])
- elif node == 'browse':
+ window("%s.index" % window_prop, path.replace("all.xml", "")) # dir
+ window("%s.title" % window_prop, view["Name"])
+ elif node == "browse":
window_prop = "Jellyfin.wnodes.%s" % index
- window('%s.title' % window_prop, view['Name'])
+ window("%s.title" % window_prop, view["Name"])
else:
window_prop = "Jellyfin.wnodes.%s.%s" % (index, node)
- window('%s.title' % window_prop, node_label)
- window('%s.content' % window_prop, path)
+ window("%s.title" % window_prop, node_label)
+ window("%s.content" % window_prop, path)
- window('%s.id' % window_prop, view['Id'])
- window('%s.path' % window_prop, window_path)
- window('%s.type' % window_prop, view['Media'])
- self.window_artwork(window_prop, view['Id'])
+ window("%s.id" % window_prop, view["Id"])
+ window("%s.path" % window_prop, window_path)
+ window("%s.type" % window_prop, view["Media"])
+ self.window_artwork(window_prop, view["Id"])
- LOG.debug("--[ wnode/%s/%s ] %s", index, window('%s.title' % window_prop), window('%s.artwork' % window_prop))
+ LOG.debug(
+ "--[ wnode/%s/%s ] %s",
+ index,
+ window("%s.title" % window_prop),
+ window("%s.artwork" % window_prop),
+ )
def window_artwork(self, prop, view_id):
if not self.server.logged_in:
- window('%s.artwork' % prop, clear=True)
+ window("%s.artwork" % prop, clear=True)
elif self.media_folders is not None:
for library in self.media_folders:
- if library['Id'] == view_id and 'Primary' in library.get('ImageTags', {}):
- server_address = self.server.auth.get_server_info(self.server.auth.server_id)['address']
- artwork = api.API(None, server_address).get_artwork(view_id, 'Primary')
- window('%s.artwork' % prop, artwork)
+ if library["Id"] == view_id and "Primary" in library.get(
+ "ImageTags", {}
+ ):
+ server_address = self.server.auth.get_server_info(
+ self.server.auth.server_id
+ )["address"]
+ artwork = api.API(None, server_address).get_artwork(
+ view_id, "Primary"
+ )
+ window("%s.artwork" % prop, artwork)
break
else:
- window('%s.artwork' % prop, clear=True)
+ window("%s.artwork" % prop, clear=True)
def window_path(self, view, node):
- return "library://video/jellyfin%s%s/%s.xml" % (view['Media'], view['Id'], node)
+ return "library://video/jellyfin%s%s/%s.xml" % (view["Media"], view["Id"], node)
def window_music(self, view):
return "library://music/"
def window_nextepisodes(self, view):
- params = {
- 'id': view['Id'],
- 'mode': "nextepisodes",
- 'limit': self.limit
- }
+ params = {"id": view["Id"], "mode": "nextepisodes", "limit": self.limit}
return "%s?%s" % ("plugin://plugin.video.jellyfin/", urlencode(params))
def window_browse(self, view, node=None):
- params = {
- 'mode': "browse",
- 'type': view['Media']
- }
+ params = {"mode": "browse", "type": view["Media"]}
- if view.get('Id'):
- params['id'] = view['Id']
+ if view.get("Id"):
+ params["id"] = view["Id"]
if node:
- params['folder'] = node
+ params["folder"] = node
return "%s?%s" % ("plugin://plugin.video.jellyfin/", urlencode(params))
def window_clear(self, name=None):
-
- ''' Clearing window prop setup for Views.
- '''
- total = int(window((name or 'Jellyfin.nodes') + '.total') or 0)
+ """Clearing window prop setup for Views."""
+ total = int(window((name or "Jellyfin.nodes") + ".total") or 0)
props = [
-
- "index", "id", "path", "artwork", "title", "content", "type"
- "inprogress.content", "inprogress.title",
- "inprogress.content", "inprogress.path",
- "nextepisodes.title", "nextepisodes.content",
- "nextepisodes.path", "unwatched.title",
- "unwatched.content", "unwatched.path",
- "recent.title", "recent.content", "recent.path",
- "recentepisodes.title", "recentepisodes.content",
- "recentepisodes.path", "inprogressepisodes.title",
- "inprogressepisodes.content", "inprogressepisodes.path"
+ "index",
+ "id",
+ "path",
+ "artwork",
+ "title",
+ "content",
+ "type" "inprogress.content",
+ "inprogress.title",
+ "inprogress.content",
+ "inprogress.path",
+ "nextepisodes.title",
+ "nextepisodes.content",
+ "nextepisodes.path",
+ "unwatched.title",
+ "unwatched.content",
+ "unwatched.path",
+ "recent.title",
+ "recent.content",
+ "recent.path",
+ "recentepisodes.title",
+ "recentepisodes.content",
+ "recentepisodes.path",
+ "inprogressepisodes.title",
+ "inprogressepisodes.content",
+ "inprogressepisodes.path",
]
for i in range(total):
for prop in props:
- window('Jellyfin.nodes.%s.%s' % (str(i), prop), clear=True)
+ window("Jellyfin.nodes.%s.%s" % (str(i), prop), clear=True)
for prop in props:
- window('Jellyfin.nodes.%s' % prop, clear=True)
+ window("Jellyfin.nodes.%s" % prop, clear=True)
def delete_playlist(self, path):
@@ -926,25 +1014,21 @@ def delete_playlist(self, path):
LOG.info("DELETE playlist %s", path)
def delete_playlists(self):
-
- ''' Remove all jellyfin playlists.
- '''
+ """Remove all jellyfin playlists."""
path = translate_path("special://profile/playlists/video/")
_, files = xbmcvfs.listdir(path)
for file in files:
- if file.startswith('jellyfin'):
+ if file.startswith("jellyfin"):
self.delete_playlist(os.path.join(path, file))
def delete_playlist_by_id(self, view_id):
-
- ''' Remove playlist based on view_id.
- '''
+ """Remove playlist based on view_id."""
path = translate_path("special://profile/playlists/video/")
_, files = xbmcvfs.listdir(path)
for file in files:
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))
def delete_node(self, path):
@@ -953,20 +1037,18 @@ def delete_node(self, path):
LOG.info("DELETE node %s", path)
def delete_nodes(self):
-
- ''' Remove node and children files.
- '''
+ """Remove node and children files."""
path = translate_path("special://profile/library/video/")
dirs, files = xbmcvfs.listdir(path)
for file in files:
- if file.startswith('jellyfin'):
+ if file.startswith("jellyfin"):
self.delete_node(os.path.join(path, file))
for directory in dirs:
- if directory.startswith('jellyfin'):
+ if directory.startswith("jellyfin"):
_, files = xbmcvfs.listdir(os.path.join(path, directory))
for file in files:
@@ -975,15 +1057,13 @@ def delete_nodes(self):
xbmcvfs.rmdir(os.path.join(path, directory))
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 = translate_path("special://profile/library/video/")
dirs, files = xbmcvfs.listdir(path)
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))
for file in files:
diff --git a/service.py b/service.py
index 9ffa713bd..c19472d5b 100644
--- a/service.py
+++ b/service.py
@@ -14,16 +14,16 @@
#################################################################################################
LOG = LazyLogger(__name__)
-DELAY = int(settings('startupDelay') if settings('SyncInstallRunDone.bool') else 4)
+DELAY = int(settings("startupDelay") if settings("SyncInstallRunDone.bool") else 4)
#################################################################################################
class ServiceManager(threading.Thread):
+ """Service thread.
+ To allow to restart and reload modules internally.
+ """
- ''' Service thread.
- To allow to restart and reload modules internally.
- '''
exception = None
def __init__(self):
@@ -44,10 +44,10 @@ def run(self):
if service is not None:
# TODO: fix this properly as to not match on str()
- if 'ExitService' not in str(error):
+ if "ExitService" not in str(error):
service.shutdown()
- if 'RestartService' in str(error):
+ if "RestartService" in str(error):
service.reload_objects()
self.exception = error
@@ -58,7 +58,7 @@ def run(self):
LOG.info("Delay startup by %s seconds.", DELAY)
while True:
- if not settings('enableAddon.bool'):
+ if not settings("enableAddon.bool"):
LOG.warning("Jellyfin for Kodi is not enabled.")
break
@@ -68,12 +68,11 @@ def run(self):
session.start()
session.join() # Block until the thread exits.
- if 'RestartService' in str(session.exception):
+ if "RestartService" in str(session.exception):
continue
except Exception as error:
- ''' Issue initializing the service.
- '''
+ """Issue initializing the service."""
LOG.exception(error)
break
diff --git a/tests/test_clean_none_dict_values.py b/tests/test_clean_none_dict_values.py
index 4a4f43d8b..982f4515d 100644
--- a/tests/test_clean_none_dict_values.py
+++ b/tests/test_clean_none_dict_values.py
@@ -3,45 +3,51 @@
from jellyfin_kodi.jellyfin.utils import clean_none_dict_values
-@pytest.mark.parametrize("obj,expected", [
- (None, None),
- ([None, 1, 2, 3, None, 4], [None, 1, 2, 3, None, 4]),
- ({'foo': None, 'bar': 123}, {'bar': 123}),
- ({
- 'dict': {
- 'empty': None,
- 'string': "Hello, Woorld!",
- },
- 'number': 123,
- 'list': [
- None,
- 123,
- "foo",
+@pytest.mark.parametrize(
+ "obj,expected",
+ [
+ (None, None),
+ ([None, 1, 2, 3, None, 4], [None, 1, 2, 3, None, 4]),
+ ({"foo": None, "bar": 123}, {"bar": 123}),
+ (
{
- 'empty': None,
- 'number': 123,
- 'string': "foo",
- 'list': [],
- 'dict': {},
- }
- ]
- }, {
- 'dict': {
- 'string': "Hello, Woorld!",
- },
- 'number': 123,
- 'list': [
- None,
- 123,
- "foo",
+ "dict": {
+ "empty": None,
+ "string": "Hello, Woorld!",
+ },
+ "number": 123,
+ "list": [
+ None,
+ 123,
+ "foo",
+ {
+ "empty": None,
+ "number": 123,
+ "string": "foo",
+ "list": [],
+ "dict": {},
+ },
+ ],
+ },
{
- 'number': 123,
- 'string': "foo",
- 'list': [],
- 'dict': {},
- }
- ]
- }),
-])
+ "dict": {
+ "string": "Hello, Woorld!",
+ },
+ "number": 123,
+ "list": [
+ None,
+ 123,
+ "foo",
+ {
+ "number": 123,
+ "string": "foo",
+ "list": [],
+ "dict": {},
+ },
+ ],
+ },
+ ),
+ ],
+)
def test_clean_none_dict_values(obj, expected):
assert clean_none_dict_values(obj) == expected
diff --git a/tests/test_imports.py b/tests/test_imports.py
index 7946604ae..6bf60859c 100644
--- a/tests/test_imports.py
+++ b/tests/test_imports.py
@@ -37,6 +37,7 @@ def test_import_downloader():
def test_import_entrypoint():
import jellyfin_kodi.entrypoint
import jellyfin_kodi.entrypoint.context
+
# import jellyfin_kodi.entrypoint.default # FIXME: Messes with sys.argv
import jellyfin_kodi.entrypoint.service # noqa: F401
diff --git a/typings/jellyfin_kodi/database/jellyfin_db.pyi b/typings/jellyfin_kodi/database/jellyfin_db.pyi
index 2cfc2a3d6..2859419b1 100644
--- a/typings/jellyfin_kodi/database/jellyfin_db.pyi
+++ b/typings/jellyfin_kodi/database/jellyfin_db.pyi
@@ -1,13 +1,11 @@
from sqlite3 import Cursor
from typing import Any, List, Optional, NamedTuple
-
class ViewRow(NamedTuple):
view_id: str
view_name: str
media_type: str
-
class JellyfinDatabase:
cursor: Cursor = ...
def __init__(self, cursor: Cursor) -> None: ...