Merge pull request #104 from oddstr13/pr-flake8-1

Most flake8 warnings corrected
This commit is contained in:
mcarlton00 2019-10-04 16:39:02 -04:00 committed by GitHub
commit 739c7efb45
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
65 changed files with 1377 additions and 1359 deletions

View file

@ -18,7 +18,7 @@ sys.path.insert(0, __base__)
################################################################################################# #################################################################################################
from entrypoint import Context from entrypoint import Context # noqa: F402
################################################################################################# #################################################################################################

View file

@ -18,7 +18,7 @@ sys.path.insert(0, __base__)
################################################################################################# #################################################################################################
from entrypoint import Context from entrypoint import Context # noqa: F402
################################################################################################# #################################################################################################

View file

@ -18,7 +18,7 @@ sys.path.insert(0, __base__)
################################################################################################# #################################################################################################
from entrypoint import Events from entrypoint import Events # noqa: F402
################################################################################################# #################################################################################################

View file

@ -2,7 +2,6 @@
################################################################################################# #################################################################################################
import json
import logging import logging
import os import os
@ -15,19 +14,22 @@ from helper.utils import create_id
################################################################################################## ##################################################################################################
LOG = logging.getLogger("JELLYFIN."+__name__) LOG = logging.getLogger("JELLYFIN." + __name__)
################################################################################################## ##################################################################################################
def get_addon_name(): def get_addon_name():
''' Used for logging. ''' Used for logging.
''' '''
return xbmcaddon.Addon(addon_id()).getAddonInfo('name').upper() return xbmcaddon.Addon(addon_id()).getAddonInfo('name').upper()
def get_version(): def get_version():
return xbmcaddon.Addon(addon_id()).getAddonInfo('version') return xbmcaddon.Addon(addon_id()).getAddonInfo('version')
def get_platform(): def get_platform():
if xbmc.getCondVisibility('system.platform.osx'): if xbmc.getCondVisibility('system.platform.osx'):
@ -53,6 +55,7 @@ def get_platform():
else: else:
return "Unknown" return "Unknown"
def get_device_name(): def get_device_name():
''' Detect the device name. If deviceNameOpt, then ''' Detect the device name. If deviceNameOpt, then
@ -68,6 +71,7 @@ def get_device_name():
return device_name return device_name
def get_device_id(reset=False): def get_device_id(reset=False):
''' Return the device_id if already loaded. ''' Return the device_id if already loaded.
@ -105,6 +109,7 @@ def get_device_id(reset=False):
return client_id return client_id
def reset_device_id(): def reset_device_id():
window('jellyfin_deviceId', clear=True) window('jellyfin_deviceId', clear=True)
@ -112,6 +117,7 @@ def reset_device_id():
dialog("ok", heading="{jellyfin}", line1=_(33033)) dialog("ok", heading="{jellyfin}", line1=_(33033))
xbmc.executebuiltin('RestartApp') xbmc.executebuiltin('RestartApp')
def get_info(): def get_info():
return { return {
'DeviceName': get_device_name(), 'DeviceName': get_device_name(),

View file

@ -2,25 +2,22 @@
################################################################################################## ##################################################################################################
import json
import logging import logging
import os
import xbmc import xbmc
import xbmcaddon import xbmcaddon
import xbmcvfs
import client import client
from database import get_credentials, save_credentials from database import get_credentials, save_credentials
from dialogs import ServerConnect, UsersConnect, LoginManual, ServerManual from dialogs import ServerConnect, UsersConnect, LoginManual, ServerManual
from helper import _, settings, addon_id, event, api, dialog, window from helper import settings, addon_id, event, api, window
from jellyfin import Jellyfin from jellyfin import Jellyfin
from jellyfin.core.connection_manager import get_server_address, CONNECTION_STATE from jellyfin.core.connection_manager import CONNECTION_STATE
from jellyfin.core.exceptions import HTTPException from jellyfin.core.exceptions import HTTPException
################################################################################################## ##################################################################################################
LOG = logging.getLogger("JELLYFIN."+__name__) LOG = logging.getLogger("JELLYFIN." + __name__)
XML_PATH = (xbmcaddon.Addon(addon_id()).getAddonInfo('path'), "default", "1080i") XML_PATH = (xbmcaddon.Addon(addon_id()).getAddonInfo('path'), "default", "1080i")
################################################################################################## ##################################################################################################
@ -42,7 +39,7 @@ class Connect(object):
if server_id is None and credentials['Servers']: if server_id is None and credentials['Servers']:
credentials['Servers'] = [credentials['Servers'][0]] credentials['Servers'] = [credentials['Servers'][0]]
elif credentials['Servers']: elif credentials['Servers']:
for server in credentials['Servers']: for server in credentials['Servers']:
@ -88,7 +85,7 @@ class Connect(object):
return client 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) client = self.get_client(server_id)
self.client = client self.client = client
self.connect_manager = client.auth self.connect_manager = client.auth
@ -102,7 +99,7 @@ class Connect(object):
if state['State'] == CONNECTION_STATE['SignedIn']: if state['State'] == CONNECTION_STATE['SignedIn']:
client.callback_ws = event client.callback_ws = event
if server_id is None: # Only assign for default server if server_id is None: # Only assign for default server
client.callback = event client.callback = event
self.get_user(client) self.get_user(client)
@ -115,8 +112,7 @@ class Connect(object):
return state['Credentials'] return state['Credentials']
elif (server_selection or state['State'] == CONNECTION_STATE['ServerSelection'] or elif (server_selection or state['State'] == CONNECTION_STATE['ServerSelection'] or state['State'] == CONNECTION_STATE['Unavailable'] and not settings('SyncInstallRunDone.bool')):
state['State'] == CONNECTION_STATE['Unavailable'] and not settings('SyncInstallRunDone.bool')):
self.select_servers(state) self.select_servers(state)
@ -143,7 +139,6 @@ class Connect(object):
return client.get_credentials() return client.get_credentials()
def get_user(self, client): def get_user(self, client):
''' Save user info. ''' Save user info.
@ -178,7 +173,8 @@ class Connect(object):
LOG.debug("Adding manual server") LOG.debug("Adding manual server")
try: try:
self.manual_server() self.manual_server()
except RuntimeError: pass except RuntimeError:
pass
else: else:
raise RuntimeError("No server selected") raise RuntimeError("No server selected")
@ -237,14 +233,16 @@ class Connect(object):
LOG.debug("User has password, present manual login") LOG.debug("User has password, present manual login")
try: try:
return self.login_manual(username) return self.login_manual(username)
except RuntimeError: pass except RuntimeError:
pass
else: else:
return self.connect_manager.login(server, username) return self.connect_manager.login(server, username)
elif dialog.is_manual_login(): elif dialog.is_manual_login():
try: try:
return self.login_manual() return self.login_manual()
except RuntimeError: pass except RuntimeError:
pass
else: else:
raise RuntimeError("No user selected") raise RuntimeError("No user selected")
@ -267,7 +265,7 @@ class Connect(object):
save_credentials(credentials) save_credentials(credentials)
def login_manual(self, user=None, manager=None): 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 = LoginManual("script-jellyfin-connect-login-manual.xml", *XML_PATH)
@ -294,4 +292,3 @@ class Connect(object):
save_credentials(credentials) save_credentials(credentials)
LOG.info("[ remove server ] %s", server_id) LOG.info("[ remove server ] %s", server_id)

View file

@ -18,7 +18,7 @@ from objects import obj
################################################################################################# #################################################################################################
LOG = logging.getLogger("JELLYFIN."+__name__) LOG = logging.getLogger("JELLYFIN." + __name__)
################################################################################################# #################################################################################################
@ -51,7 +51,7 @@ class Database(object):
self.cursor = self.conn.cursor() self.cursor = self.conn.cursor()
if self.db_file in ('video', 'music', 'texture', 'jellyfin'): if self.db_file in ('video', 'music', 'texture', 'jellyfin'):
self.conn.execute("PRAGMA journal_mode=WAL") # to avoid writing conflict with kodi self.conn.execute("PRAGMA journal_mode=WAL") # to avoid writing conflict with kodi
LOG.debug("--->[ database: %s ] %s", self.db_file, id(self.conn)) LOG.debug("--->[ database: %s ] %s", self.db_file, id(self.conn))
@ -105,8 +105,7 @@ class Database(object):
for file in reversed(files): for file in reversed(files):
if (file.startswith(database) and not file.endswith('-wal') and if (file.startswith(database) and not file.endswith('-wal') and not file.endswith('-shm') and not file.endswith('db-journal')):
not file.endswith('-shm') and not file.endswith('db-journal')):
st = xbmcvfs.Stat(databases + file.decode('utf-8')) st = xbmcvfs.Stat(databases + file.decode('utf-8'))
modified_int = st.st_mtime() modified_int = st.st_mtime()
@ -147,7 +146,7 @@ class Database(object):
loaded = self._get_database(databases[alt_file]) loaded = self._get_database(databases[alt_file])
break break
except KeyError: # No other db options except KeyError: # No other db options
loaded = None loaded = None
break break
@ -172,7 +171,7 @@ class Database(object):
''' '''
changes = self.conn.total_changes changes = self.conn.total_changes
if exc_type is not None: # errors raised if exc_type is not None: # errors raised
LOG.error("type: %s value: %s", exc_type, exc_val) LOG.error("type: %s value: %s", exc_type, exc_val)
if self.commit_close and changes: if self.commit_close and changes:
@ -184,6 +183,7 @@ class Database(object):
self.cursor.close() self.cursor.close()
self.conn.close() self.conn.close()
def jellyfin_tables(cursor): def jellyfin_tables(cursor):
''' Create the tables for the jellyfin database. ''' Create the tables for the jellyfin database.
@ -205,6 +205,7 @@ def jellyfin_tables(cursor):
LOG.info("Add missing column jellyfin_parent_id") LOG.info("Add missing column jellyfin_parent_id")
cursor.execute("ALTER TABLE jellyfin ADD COLUMN jellyfin_parent_id 'TEXT'") cursor.execute("ALTER TABLE jellyfin ADD COLUMN jellyfin_parent_id 'TEXT'")
def reset(): def reset():
''' Reset both the jellyfin database and the kodi database. ''' Reset both the jellyfin database and the kodi database.
@ -257,6 +258,7 @@ def reset():
dialog("ok", heading="{jellyfin}", line1=_(33088)) dialog("ok", heading="{jellyfin}", line1=_(33088))
xbmc.executebuiltin('RestartApp') xbmc.executebuiltin('RestartApp')
def reset_kodi(): def reset_kodi():
with Database() as videodb: with Database() as videodb:
@ -281,6 +283,7 @@ def reset_kodi():
LOG.info("[ reset kodi ]") LOG.info("[ reset kodi ]")
def reset_jellyfin(): def reset_jellyfin():
with Database('jellyfin') as jellyfindb: with Database('jellyfin') as jellyfindb:
@ -298,6 +301,7 @@ def reset_jellyfin():
LOG.info("[ reset jellyfin ]") LOG.info("[ reset jellyfin ]")
def reset_artwork(): def reset_artwork():
''' Remove all existing texture. ''' Remove all existing texture.
@ -325,6 +329,7 @@ def reset_artwork():
LOG.info("[ reset artwork ]") LOG.info("[ reset artwork ]")
def get_sync(): def get_sync():
path = xbmc.translatePath("special://profile/addon_data/plugin.video.jellyfin/").decode('utf-8') path = xbmc.translatePath("special://profile/addon_data/plugin.video.jellyfin/").decode('utf-8')
@ -345,6 +350,7 @@ def get_sync():
return sync return sync
def save_sync(sync): def save_sync(sync):
path = xbmc.translatePath("special://profile/addon_data/plugin.video.jellyfin/").decode('utf-8') path = xbmc.translatePath("special://profile/addon_data/plugin.video.jellyfin/").decode('utf-8')
@ -358,6 +364,7 @@ def save_sync(sync):
data = json.dumps(sync, sort_keys=True, indent=4, ensure_ascii=False) data = json.dumps(sync, sort_keys=True, indent=4, ensure_ascii=False)
outfile.write(unicode(data)) outfile.write(unicode(data))
def get_credentials(): def get_credentials():
path = xbmc.translatePath("special://profile/addon_data/plugin.video.jellyfin/").decode('utf-8') path = xbmc.translatePath("special://profile/addon_data/plugin.video.jellyfin/").decode('utf-8')
@ -383,19 +390,21 @@ def get_credentials():
return credentials return credentials
def save_credentials(credentials): def save_credentials(credentials):
credentials = credentials or {} credentials = credentials or {}
path = xbmc.translatePath("special://profile/addon_data/plugin.video.jellyfin/").decode('utf-8') path = xbmc.translatePath("special://profile/addon_data/plugin.video.jellyfin/").decode('utf-8')
if not xbmcvfs.exists(path): if not xbmcvfs.exists(path):
xbmcvfs.mkdirs(path) xbmcvfs.mkdirs(path)
try: try:
with open(os.path.join(path, 'data.json'), 'w', encoding='utf8') as outfile: with open(os.path.join(path, 'data.json'), 'w', encoding='utf8') as outfile:
data = json.dumps(credentials, sort_keys=True, indent=4, ensure_ascii=False) data = json.dumps(credentials, sort_keys=True, indent=4, ensure_ascii=False)
outfile.write(unicode(data)) outfile.write(unicode(data))
except Exception as e: except Exception as e:
LOG.error("Failed to save credentials: {}".format(e)) LOG.error("Failed to save credentials: {}".format(e))
def get_item(kodi_id, media): def get_item(kodi_id, media):
''' Get jellyfin item based on kodi id and media. ''' Get jellyfin item based on kodi id and media.
@ -409,4 +418,3 @@ def get_item(kodi_id, media):
return return
return item return item

View file

@ -8,7 +8,7 @@ import queries as QU
################################################################################################## ##################################################################################################
LOG = logging.getLogger("JELLYFIN."+__name__) LOG = logging.getLogger("JELLYFIN." + __name__)
################################################################################################## ##################################################################################################

View file

@ -1,182 +1,169 @@
get_item = """ SELECT kodi_id, kodi_fileid, kodi_pathid, parent_id, media_type, get_item = """
jellyfin_type, media_folder, jellyfin_parent_id SELECT kodi_id, kodi_fileid, kodi_pathid, parent_id, media_type,
FROM jellyfin jellyfin_type, media_folder, jellyfin_parent_id
WHERE jellyfin_id = ? FROM jellyfin
""" WHERE jellyfin_id = ?
get_item_obj = [ "{Id}" """
] get_item_obj = ["{Id}"]
get_item_series_obj = [ "{SeriesId}" get_item_series_obj = ["{SeriesId}"]
] get_item_song_obj = ["{SongAlbumId}"]
get_item_song_obj = [ "{SongAlbumId}" get_item_id_by_parent = """
] SELECT jellyfin_id, kodi_id
get_item_id_by_parent = """ SELECT jellyfin_id, kodi_id FROM jellyfin
FROM jellyfin WHERE parent_id = ?
WHERE parent_id = ? AND media_type = ?
AND media_type = ? """
""" get_item_id_by_parent_boxset_obj = ["{SetId}", "movie"]
get_item_id_by_parent_boxset_obj = [ "{SetId}","movie" get_item_by_parent = """
] SELECT jellyfin_id, kodi_id, kodi_fileid
get_item_by_parent = """ SELECT jellyfin_id, kodi_id, kodi_fileid FROM jellyfin
FROM jellyfin WHERE parent_id = ?
WHERE parent_id = ? AND media_type = ?
AND media_type = ? """
""" get_item_by_media_folder = """
get_item_by_media_folder = """ SELECT jellyfin_id, jellyfin_type SELECT jellyfin_id, jellyfin_type
FROM jellyfin FROM jellyfin
WHERE media_folder = ? WHERE media_folder = ?
""" """
get_item_by_parent_movie_obj = [ "{KodiId}","movie" get_item_by_parent_movie_obj = ["{KodiId}", "movie"]
] get_item_by_parent_tvshow_obj = ["{ParentId}", "tvshow"]
get_item_by_parent_tvshow_obj = [ "{ParentId}","tvshow" get_item_by_parent_season_obj = ["{ParentId}", "season"]
] get_item_by_parent_episode_obj = ["{ParentId}", "episode"]
get_item_by_parent_season_obj = [ "{ParentId}","season" get_item_by_parent_album_obj = ["{ParentId}", "album"]
] get_item_by_parent_song_obj = ["{ParentId}", "song"]
get_item_by_parent_episode_obj = [ "{ParentId}","episode" get_item_by_wild = """
] SELECT kodi_id, media_type
get_item_by_parent_album_obj = [ "{ParentId}","album" FROM jellyfin
] WHERE jellyfin_id LIKE ?
get_item_by_parent_song_obj = [ "{ParentId}","song" """
] get_item_by_wild_obj = ["{Id}"]
get_item_by_wild = """ SELECT kodi_id, media_type get_item_by_kodi = """
FROM jellyfin SELECT jellyfin_id, parent_id, media_folder, jellyfin_type, checksum
WHERE jellyfin_id LIKE ? FROM jellyfin
""" WHERE kodi_id = ?
get_item_by_wild_obj = [ "{Id}" AND media_type = ?
] """
get_item_by_kodi = """ SELECT jellyfin_id, parent_id, media_folder, jellyfin_type, checksum get_checksum = """
FROM jellyfin SELECT jellyfin_id, checksum
WHERE kodi_id = ? FROM jellyfin
AND media_type = ? WHERE jellyfin_type = ?
""" """
get_checksum = """ SELECT jellyfin_id, checksum get_view_name = """
FROM jellyfin SELECT view_name
WHERE jellyfin_type = ? FROM view
""" WHERE view_id = ?
get_view_name = """ SELECT view_name """
FROM view get_media_by_id = """
WHERE view_id = ? SELECT jellyfin_type
""" FROM jellyfin
get_media_by_id = """ SELECT jellyfin_type WHERE jellyfin_id = ?
FROM jellyfin """
WHERE jellyfin_id = ? get_media_by_parent_id = """
""" SELECT jellyfin_id, jellyfin_type, kodi_id, kodi_fileid
get_media_by_parent_id = """ SELECT jellyfin_id, jellyfin_type, kodi_id, kodi_fileid FROM jellyfin
FROM jellyfin WHERE jellyfin_parent_id = ?
WHERE jellyfin_parent_id = ? """
""" get_view = """
get_view = """ SELECT view_name, media_type SELECT view_name, media_type
FROM view FROM view
WHERE view_id = ? WHERE view_id = ?
""" """
get_views = """ SELECT * get_views = """
FROM view SELECT *
""" FROM view
get_views_by_media = """ SELECT * """
FROM view get_views_by_media = """
WHERE media_type = ? SELECT *
""" FROM view
get_items_by_media = """ SELECT jellyfin_id WHERE media_type = ?
FROM jellyfin """
WHERE media_type = ? get_items_by_media = """
""" SELECT jellyfin_id
get_version = """ SELECT idVersion FROM jellyfin
FROM version WHERE media_type = ?
""" """
get_version = """
SELECT idVersion
FROM version
"""
add_reference = """
add_reference = """ INSERT OR REPLACE INTO jellyfin(jellyfin_id, kodi_id, kodi_fileid, kodi_pathid, jellyfin_type, INSERT OR REPLACE INTO jellyfin(jellyfin_id, kodi_id, kodi_fileid, kodi_pathid, jellyfin_type,
media_type, parent_id, checksum, media_folder, jellyfin_parent_id) media_type, parent_id, checksum, media_folder, jellyfin_parent_id)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""" """
add_reference_movie_obj = [ "{Id}","{MovieId}","{FileId}","{PathId}","Movie","movie", None,"{Checksum}","{LibraryId}", add_reference_movie_obj = ["{Id}", "{MovieId}", "{FileId}", "{PathId}", "Movie", "movie", None, "{Checksum}", "{LibraryId}", "{JellyfinParentId}"]
"{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_boxset_obj = [ "{Id}","{SetId}",None,None,"BoxSet","set",None,"{Checksum}",None,None 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_tvshow_obj = [ "{Id}","{ShowId}",None,"{PathId}","Series","tvshow",None,"{Checksum}","{LibraryId}", add_reference_episode_obj = ["{Id}", "{EpisodeId}", "{FileId}", "{PathId}", "Episode", "episode", "{SeasonId}", "{Checksum}", None, "{JellyfinParentId}"]
"{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_season_obj = [ "{Id}","{SeasonId}",None,None,"Season","season","{ShowId}",None,None,None add_reference_album_obj = ["{Id}", "{AlbumId}", None, None, "MusicAlbum", "album", None, "{Checksum}", None, "{JellyfinParentId}"]
] add_reference_song_obj = ["{Id}", "{SongId}", None, "{PathId}", "Audio", "song", "{AlbumId}", "{Checksum}", None, "{JellyfinParentId}"]
add_reference_pool_obj = [ "{SeriesId}","{ShowId}",None,"{PathId}","Series","tvshow",None,"{Checksum}","{LibraryId}",None add_view = """
] INSERT OR REPLACE INTO view(view_id, view_name, media_type)
add_reference_episode_obj = [ "{Id}","{EpisodeId}","{FileId}","{PathId}","Episode","episode","{SeasonId}","{Checksum}", VALUES (?, ?, ?)
None,"{JellyfinParentId}" """
] add_version = """
add_reference_mvideo_obj = [ "{Id}","{MvideoId}","{FileId}","{PathId}","MusicVideo","musicvideo",None,"{Checksum}", INSERT OR REPLACE INTO version(idVersion)
"{LibraryId}","{JellyfinParentId}" VALUES (?)
] """
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}",None,"{JellyfinParentId}"
]
add_reference_song_obj = [ "{Id}","{SongId}",None,"{PathId}","Audio","song","{AlbumId}","{Checksum}",
None,"{JellyfinParentId}"
]
add_view = """ INSERT OR REPLACE INTO view(view_id, view_name, media_type)
VALUES (?, ?, ?)
"""
add_version = """ INSERT OR REPLACE INTO version(idVersion)
VALUES (?)
"""
update_reference = """ UPDATE jellyfin update_reference = """
SET checksum = ? UPDATE jellyfin
WHERE jellyfin_id = ? SET checksum = ?
""" WHERE jellyfin_id = ?
update_reference_obj = [ "{Checksum}", "{Id}" """
] update_reference_obj = ["{Checksum}", "{Id}"]
update_parent = """ UPDATE jellyfin update_parent = """
SET parent_id = ? UPDATE jellyfin
WHERE jellyfin_id = ? SET parent_id = ?
""" WHERE jellyfin_id = ?
update_parent_movie_obj = [ "{SetId}","{Id}" """
] update_parent_movie_obj = ["{SetId}", "{Id}"]
update_parent_episode_obj = [ "{SeasonId}","{Id}" update_parent_episode_obj = ["{SeasonId}", "{Id}"]
] update_parent_album_obj = ["{ArtistId}", "{AlbumId}"]
update_parent_album_obj = [ "{ArtistId}","{AlbumId}"]
delete_item = """
delete_item = """ DELETE FROM jellyfin DELETE FROM jellyfin
WHERE jellyfin_id = ? WHERE jellyfin_id = ?
""" """
delete_item_obj = [ "{Id}" delete_item_obj = ["{Id}"]
] delete_item_by_parent = """
delete_item_by_parent = """ DELETE FROM jellyfin DELETE FROM jellyfin
WHERE parent_id = ? WHERE parent_id = ?
AND media_type = ? AND media_type = ?
""" """
delete_item_by_parent_tvshow_obj = [ "{ParentId}","tvshow" delete_item_by_parent_tvshow_obj = ["{ParentId}", "tvshow"]
] delete_item_by_parent_season_obj = ["{ParentId}", "season"]
delete_item_by_parent_season_obj = [ "{ParentId}","season" delete_item_by_parent_episode_obj = ["{ParentId}", "episode"]
] delete_item_by_parent_song_obj = ["{ParentId}", "song"]
delete_item_by_parent_episode_obj = [ "{ParentId}","episode" delete_item_by_parent_artist_obj = ["{ParentId}", "artist"]
] delete_item_by_parent_album_obj = ["{KodiId}", "album"]
delete_item_by_parent_song_obj = [ "{ParentId}","song" delete_item_by_kodi = """
] DELETE FROM jellyfin
delete_item_by_parent_artist_obj = [ "{ParentId}","artist" WHERE kodi_id = ?
] AND media_type = ?
delete_item_by_parent_album_obj = [ "{KodiId}","album" """
] delete_item_by_wild = """
delete_item_by_kodi = """ DELETE FROM jellyfin DELETE FROM jellyfin
WHERE kodi_id = ? WHERE jellyfin_id LIKE ?
AND media_type = ? """
""" delete_view = """
delete_item_by_wild = """ DELETE FROM jellyfin DELETE FROM view
WHERE jellyfin_id LIKE ? WHERE view_id = ?
""" """
delete_view = """ DELETE FROM view delete_parent_boxset_obj = [None, "{Movie}"]
WHERE view_id = ? delete_media_by_parent_id = """
""" DELETE FROM jellyfin
delete_parent_boxset_obj = [ None, "{Movie}" WHERE jellyfin_parent_id = ?
] """
delete_media_by_parent_id = """ DELETE FROM jellyfin delete_version = """
WHERE jellyfin_parent_id = ? DELETE FROM version
""" """
delete_version = """ DELETE FROM version
"""

View file

@ -12,7 +12,7 @@ from helper import window, addon_id
################################################################################################## ##################################################################################################
LOG = logging.getLogger("JELLYFIN."+__name__) LOG = logging.getLogger("JELLYFIN." + __name__)
ACTION_PARENT_DIR = 9 ACTION_PARENT_DIR = 9
ACTION_PREVIOUS_MENU = 10 ACTION_PREVIOUS_MENU = 10
ACTION_BACK = 92 ACTION_BACK = 92
@ -29,7 +29,6 @@ class ContextMenu(xbmcgui.WindowXMLDialog):
_options = [] _options = []
selected_option = None selected_option = None
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs) xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs)
@ -48,7 +47,6 @@ class ContextMenu(xbmcgui.WindowXMLDialog):
if window('JellyfinUserImage'): if window('JellyfinUserImage'):
self.getControl(USER_IMAGE).setImage(window('JellyfinUserImage')) self.getControl(USER_IMAGE).setImage(window('JellyfinUserImage'))
height = 479 + (len(self._options) * 55)
LOG.info("options: %s", self._options) LOG.info("options: %s", self._options)
self.list_ = self.getControl(LIST) self.list_ = self.getControl(LIST)

View file

@ -12,7 +12,7 @@ from helper import _, addon_id
################################################################################################## ##################################################################################################
LOG = logging.getLogger("JELLYFIN."+__name__) LOG = logging.getLogger("JELLYFIN." + __name__)
ACTION_PARENT_DIR = 9 ACTION_PARENT_DIR = 9
ACTION_PREVIOUS_MENU = 10 ACTION_PREVIOUS_MENU = 10
ACTION_BACK = 92 ACTION_BACK = 92
@ -31,7 +31,6 @@ class LoginManual(xbmcgui.WindowXMLDialog):
error = None error = None
username = None username = None
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs) xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs)
@ -118,7 +117,6 @@ class LoginManual(xbmcgui.WindowXMLDialog):
def _login(self, username, password): def _login(self, username, password):
mode = self.connect_manager.get_server_info(self.connect_manager.server_id)['LastConnectionMode']
server_data = self.connect_manager.get_server_info(self.connect_manager.server_id) server_data = self.connect_manager.get_server_info(self.connect_manager.server_id)
server = self.connect_manager.get_server_address(server_data, server_data['LastConnectionMode']) server = self.connect_manager.get_server_address(server_data, server_data['LastConnectionMode'])
result = self.connect_manager.login(server, username, password) result = self.connect_manager.login(server, username, password)

View file

@ -6,11 +6,10 @@ import logging
import xbmc import xbmc
import xbmcgui import xbmcgui
import xbmcaddon
################################################################################################## ##################################################################################################
LOG = logging.getLogger("JELLYFIN."+__name__) LOG = logging.getLogger("JELLYFIN." + __name__)
ACTION_PARENT_DIR = 9 ACTION_PARENT_DIR = 9
ACTION_PREVIOUS_MENU = 10 ACTION_PREVIOUS_MENU = 10
ACTION_BACK = 92 ACTION_BACK = 92
@ -43,7 +42,7 @@ class ResumeDialog(xbmcgui.WindowXMLDialog):
self.getControl(START_BEGINNING).setLabel(xbmc.getLocalizedString(12021)) self.getControl(START_BEGINNING).setLabel(xbmc.getLocalizedString(12021))
def onAction(self, action): def onAction(self, action):
if action in (ACTION_BACK, ACTION_PARENT_DIR, ACTION_PREVIOUS_MENU): if action in (ACTION_BACK, ACTION_PARENT_DIR, ACTION_PREVIOUS_MENU):
self.close() self.close()
@ -52,7 +51,7 @@ class ResumeDialog(xbmcgui.WindowXMLDialog):
if controlID == RESUME: if controlID == RESUME:
self.selected_option = 1 self.selected_option = 1
self.close() self.close()
if controlID == START_BEGINNING: if controlID == START_BEGINNING:
self.selected_option = 0 self.selected_option = 0
self.close() self.close()

View file

@ -12,7 +12,7 @@ from jellyfin.core.connection_manager import CONNECTION_STATE
################################################################################################## ##################################################################################################
LOG = logging.getLogger("JELLYFIN."+__name__) LOG = logging.getLogger("JELLYFIN." + __name__)
ACTION_PARENT_DIR = 9 ACTION_PARENT_DIR = 9
ACTION_PREVIOUS_MENU = 10 ACTION_PREVIOUS_MENU = 10
ACTION_BACK = 92 ACTION_BACK = 92
@ -38,7 +38,6 @@ class ServerConnect(xbmcgui.WindowXMLDialog):
_connect_login = False _connect_login = False
_manual_server = False _manual_server = False
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs) xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs)

View file

@ -13,7 +13,7 @@ from jellyfin.core.connection_manager import CONNECTION_STATE
################################################################################################## ##################################################################################################
LOG = logging.getLogger("JELLYFIN."+__name__) LOG = logging.getLogger("JELLYFIN." + __name__)
ACTION_PARENT_DIR = 9 ACTION_PARENT_DIR = 9
ACTION_PREVIOUS_MENU = 10 ACTION_PREVIOUS_MENU = 10
ACTION_BACK = 92 ACTION_BACK = 92
@ -34,7 +34,6 @@ class ServerManual(xbmcgui.WindowXMLDialog):
_server = None _server = None
error = None error = None
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs) xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs)

View file

@ -9,7 +9,7 @@ import xbmcgui
################################################################################################## ##################################################################################################
LOG = logging.getLogger("JELLYFIN."+__name__) LOG = logging.getLogger("JELLYFIN." + __name__)
ACTION_PARENT_DIR = 9 ACTION_PARENT_DIR = 9
ACTION_PREVIOUS_MENU = 10 ACTION_PREVIOUS_MENU = 10
ACTION_BACK = 92 ACTION_BACK = 92
@ -27,7 +27,6 @@ class UsersConnect(xbmcgui.WindowXMLDialog):
_user = None _user = None
_manual_login = False _manual_login = False
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.kodi_version = int(xbmc.getInfoLabel('System.BuildVersion')[:2]) self.kodi_version = int(xbmc.getInfoLabel('System.BuildVersion')[:2])

View file

@ -15,7 +15,7 @@ from jellyfin.core.exceptions import HTTPException
################################################################################################# #################################################################################################
LOG = logging.getLogger("JELLYFIN."+__name__) LOG = logging.getLogger("JELLYFIN." + __name__)
LIMIT = min(int(settings('limitIndex') or 50), 50) LIMIT = min(int(settings('limitIndex') or 50), 50)
################################################################################################# #################################################################################################
@ -35,13 +35,14 @@ def browse_info():
return ( return (
"DateCreated,EpisodeCount,SeasonCount,Path,Genres,Studios,Taglines,MediaStreams,Overview,Etag," "DateCreated,EpisodeCount,SeasonCount,Path,Genres,Studios,Taglines,MediaStreams,Overview,Etag,"
"ProductionLocations,Width,Height,RecursiveItemCount,ChildCount" "ProductionLocations,Width,Height,RecursiveItemCount,ChildCount"
) )
def _http(action, url, request={}, server_id=None): def _http(action, url, request={}, server_id=None):
request.update({'url': url, 'type': action}) request.update({'url': url, 'type': action})
return Jellyfin(server_id).http.request(request) return Jellyfin(server_id).http.request(request)
def _get(handler, params=None, server_id=None): 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)
@ -61,10 +62,10 @@ def validate_view(library_id, item_id):
''' '''
try: try:
result = _get("Users/{UserId}/Items", { result = _get("Users/{UserId}/Items", {
'ParentId': library_id, 'ParentId': library_id,
'Recursive': True, 'Recursive': True,
'Ids': item_id 'Ids': item_id
}) })
except Exception as error: except Exception as error:
LOG.exception(error) LOG.exception(error)
return False return False

View file

@ -10,6 +10,10 @@ import xbmcvfs
from helper import loghandler from helper import loghandler
from jellyfin import Jellyfin from jellyfin import Jellyfin
from .default import Events
from .service import Service
from .context import Context
################################################################################################# #################################################################################################
Jellyfin.set_loghandler(loghandler.LogHandler, logging.DEBUG) Jellyfin.set_loghandler(loghandler.LogHandler, logging.DEBUG)
@ -18,7 +22,3 @@ loghandler.config()
LOG = logging.getLogger('JELLYFIN.entrypoint') LOG = logging.getLogger('JELLYFIN.entrypoint')
################################################################################################# #################################################################################################
from default import Events
from service import Service
from context import Context

View file

@ -13,11 +13,10 @@ import database
from dialogs import context from dialogs import context
from helper import _, settings, dialog from helper import _, settings, dialog
from downloader import TheVoid from downloader import TheVoid
from objects import Actions
################################################################################################# #################################################################################################
LOG = logging.getLogger("JELLYFIN."+__name__) LOG = logging.getLogger("JELLYFIN." + __name__)
XML_PATH = (xbmcaddon.Addon('plugin.video.jellyfin').getAddonInfo('path'), "default", "1080i") XML_PATH = (xbmcaddon.Addon('plugin.video.jellyfin').getAddonInfo('path'), "default", "1080i")
OPTIONS = { OPTIONS = {
'Refresh': _(30410), 'Refresh': _(30410),

View file

@ -8,7 +8,6 @@ import sys
import urlparse import urlparse
import urllib import urllib
import os import os
import sys
import xbmc import xbmc
import xbmcvfs import xbmcvfs
@ -24,14 +23,13 @@ from helper import _, event, settings, window, dialog, api, JSONRPC
################################################################################################# #################################################################################################
LOG = logging.getLogger("JELLYFIN."+__name__) LOG = logging.getLogger("JELLYFIN." + __name__)
################################################################################################# #################################################################################################
class Events(object): class Events(object):
def __init__(self): def __init__(self):
''' Parse the parameters. Reroute to our service.py ''' Parse the parameters. Reroute to our service.py
@ -65,7 +63,7 @@ class Events(object):
jellyfin_id = params.get('id') jellyfin_id = params.get('id')
get_video_extras(jellyfin_id, jellyfin_path, server) get_video_extras(jellyfin_id, jellyfin_path, server)
elif mode =='play': elif mode == 'play':
item = TheVoid('GetItem', {'Id': params['id'], 'ServerId': server}).get() item = TheVoid('GetItem', {'Id': params['id'], 'ServerId': server}).get()
Actions(server).play(item, params.get('dbid'), params.get('transcode') == 'true', playlist=params.get('playlist') == 'true') Actions(server).play(item, params.get('dbid'), params.get('transcode') == 'true', playlist=params.get('playlist') == 'true')
@ -180,7 +178,6 @@ def listing():
else: 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(_(33194), "plugin://plugin.video.jellyfin/?mode=managelibs", True) directory(_(33194), "plugin://plugin.video.jellyfin/?mode=managelibs", True)
directory(_(33134), "plugin://plugin.video.jellyfin/?mode=addserver", False) directory(_(33134), "plugin://plugin.video.jellyfin/?mode=addserver", False)
directory(_(33054), "plugin://plugin.video.jellyfin/?mode=adduser", False) directory(_(33054), "plugin://plugin.video.jellyfin/?mode=adduser", False)
@ -194,6 +191,7 @@ def listing():
xbmcplugin.setContent(int(sys.argv[1]), 'files') xbmcplugin.setContent(int(sys.argv[1]), 'files')
xbmcplugin.endOfDirectory(int(sys.argv[1])) xbmcplugin.endOfDirectory(int(sys.argv[1]))
def directory(label, path, folder=True, artwork=None, fanart=None, context=None): 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)*]
@ -207,6 +205,7 @@ def directory(label, path, folder=True, artwork=None, fanart=None, context=None)
return li return li
def dir_listitem(label, path, artwork=None, fanart=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
@ -218,6 +217,7 @@ def dir_listitem(label, path, artwork=None, fanart=None):
return li return li
def manage_libraries(): def manage_libraries():
directory(_(33098), "plugin://plugin.video.jellyfin/?mode=refreshboxsets", False) directory(_(33098), "plugin://plugin.video.jellyfin/?mode=refreshboxsets", False)
@ -230,6 +230,7 @@ def manage_libraries():
xbmcplugin.setContent(int(sys.argv[1]), 'files') xbmcplugin.setContent(int(sys.argv[1]), 'files')
xbmcplugin.endOfDirectory(int(sys.argv[1])) xbmcplugin.endOfDirectory(int(sys.argv[1]))
def browse(media, view_id=None, folder=None, server_id=None): def browse(media, view_id=None, folder=None, server_id=None):
''' Browse content dynamically. ''' Browse content dynamically.
@ -274,7 +275,6 @@ def browse(media, view_id=None, folder=None, server_id=None):
elif media == 'music': elif media == 'music':
content_type = "artists" content_type = "artists"
if folder == 'recentlyadded': if folder == 'recentlyadded':
listing = TheVoid('RecentlyAdded', {'Id': view_id, 'ServerId': server_id}).get() listing = TheVoid('RecentlyAdded', {'Id': view_id, 'ServerId': server_id}).get()
elif folder == 'genres': elif folder == 'genres':
@ -316,7 +316,6 @@ def browse(media, view_id=None, folder=None, server_id=None):
else: else:
listing = TheVoid('Browse', {'Id': folder or view_id, 'ServerId': server_id, 'Recursive': False}).get() listing = TheVoid('Browse', {'Id': folder or view_id, 'ServerId': server_id, 'Recursive': False}).get()
if listing: if listing:
actions = Actions(server_id) actions = Actions(server_id)
@ -339,7 +338,7 @@ def browse(media, view_id=None, folder=None, server_id=None):
'folder': item['Id'], 'folder': item['Id'],
'server': server_id 'server': server_id
} }
path = "%s?%s" % ("plugin://plugin.video.jellyfin/", urllib.urlencode(params)) path = "%s?%s" % ("plugin://plugin.video.jellyfin/", urllib.urlencode(params))
context = [] context = []
if item['Type'] in ('Series', 'Season', 'Playlist'): if item['Type'] in ('Series', 'Season', 'Playlist'):
@ -362,7 +361,7 @@ def browse(media, view_id=None, folder=None, server_id=None):
'folder': 'genres-%s' % item['Id'], 'folder': 'genres-%s' % item['Id'],
'server': server_id 'server': server_id
} }
path = "%s?%s" % ("plugin://plugin.video.jellyfin/", urllib.urlencode(params)) path = "%s?%s" % ("plugin://plugin.video.jellyfin/", urllib.urlencode(params))
list_li.append((path, li, True)) list_li.append((path, li, True))
else: else:
@ -396,6 +395,7 @@ def browse(media, view_id=None, folder=None, server_id=None):
xbmcplugin.setContent(int(sys.argv[1]), content_type) xbmcplugin.setContent(int(sys.argv[1]), content_type)
xbmcplugin.endOfDirectory(int(sys.argv[1])) xbmcplugin.endOfDirectory(int(sys.argv[1]))
def browse_subfolders(media, view_id, server_id=None): def browse_subfolders(media, view_id, server_id=None):
''' Display submenus for jellyfin views. ''' Display submenus for jellyfin views.
@ -415,12 +415,13 @@ def browse_subfolders(media, view_id, server_id=None):
'folder': view_id if node[0] == 'all' else node[0], 'folder': view_id if node[0] == 'all' else node[0],
'server': server_id 'server': server_id
} }
path = "%s?%s" % ("plugin://plugin.video.jellyfin/", urllib.urlencode(params)) path = "%s?%s" % ("plugin://plugin.video.jellyfin/", urllib.urlencode(params))
directory(node[1] or view['Name'], path) directory(node[1] or view['Name'], path)
xbmcplugin.setContent(int(sys.argv[1]), 'files') xbmcplugin.setContent(int(sys.argv[1]), 'files')
xbmcplugin.endOfDirectory(int(sys.argv[1])) xbmcplugin.endOfDirectory(int(sys.argv[1]))
def browse_letters(media, view_id, server_id=None): def browse_letters(media, view_id, server_id=None):
''' Display letters as options. ''' Display letters as options.
@ -439,12 +440,13 @@ def browse_letters(media, view_id, server_id=None):
'folder': 'firstletter-%s' % node, 'folder': 'firstletter-%s' % node,
'server': server_id 'server': server_id
} }
path = "%s?%s" % ("plugin://plugin.video.jellyfin/", urllib.urlencode(params)) path = "%s?%s" % ("plugin://plugin.video.jellyfin/", urllib.urlencode(params))
directory(node, path) directory(node, path)
xbmcplugin.setContent(int(sys.argv[1]), 'files') xbmcplugin.setContent(int(sys.argv[1]), 'files')
xbmcplugin.endOfDirectory(int(sys.argv[1])) xbmcplugin.endOfDirectory(int(sys.argv[1]))
def get_folder_type(item, content_type=None): def get_folder_type(item, content_type=None):
media = item['Type'] media = item['Type']
@ -480,6 +482,7 @@ def get_media_type(media):
elif media == 'music': elif media == 'music':
return "MusicArtist,MusicAlbum,Audio" return "MusicArtist,MusicAlbum,Audio"
def get_fanart(item_id, path, server_id=None): def get_fanart(item_id, path, server_id=None):
''' Get extra fanart for listitems. This is called by skinhelper. ''' Get extra fanart for listitems. This is called by skinhelper.
@ -524,6 +527,7 @@ def get_fanart(item_id, path, server_id=None):
xbmcplugin.addDirectoryItems(int(sys.argv[1]), list_li, len(list_li)) xbmcplugin.addDirectoryItems(int(sys.argv[1]), list_li, len(list_li))
xbmcplugin.endOfDirectory(int(sys.argv[1])) xbmcplugin.endOfDirectory(int(sys.argv[1]))
def get_video_extras(item_id, path, server_id=None): def get_video_extras(item_id, path, server_id=None):
''' Returns the video files for the item as plugin listing, can be used ''' Returns the video files for the item as plugin listing, can be used
@ -565,6 +569,7 @@ def get_video_extras(item_id, path, server_id=None):
#xbmcplugin.endOfDirectory(int(sys.argv[1])) #xbmcplugin.endOfDirectory(int(sys.argv[1]))
""" """
def get_next_episodes(item_id, limit): def get_next_episodes(item_id, limit):
''' Only for synced content. ''' Only for synced content.
@ -578,14 +583,14 @@ def get_next_episodes(item_id, limit):
return return
result = JSONRPC('VideoLibrary.GetTVShows').execute({ result = JSONRPC('VideoLibrary.GetTVShows').execute({
'sort': {'order': "descending", 'method': "lastplayed"}, 'sort': {'order': "descending", 'method': "lastplayed"},
'filter': { 'filter': {
'and': [ 'and': [
{'operator': "true", 'field': "inprogress", 'value': ""}, {'operator': "true", 'field': "inprogress", 'value': ""},
{'operator': "is", 'field': "tag", 'value': "%s" % library} {'operator': "is", 'field': "tag", 'value': "%s" % library}
]}, ]},
'properties': ['title', 'studio', 'mpaa', 'file', 'art'] 'properties': ['title', 'studio', 'mpaa', 'file', 'art']
}) })
try: try:
items = result['result']['tvshows'] items = result['result']['tvshows']
@ -603,7 +608,7 @@ def get_next_episodes(item_id, limit):
'and': [ 'and': [
{'operator': "lessthan", 'field': "playcount", 'value': "1"}, {'operator': "lessthan", 'field': "playcount", 'value': "1"},
{'operator': "greaterthan", 'field': "season", 'value': "0"} {'operator': "greaterthan", 'field': "season", 'value': "0"}
]}, ]},
'properties': [ 'properties': [
"title", "playcount", "season", "episode", "showtitle", "title", "playcount", "season", "episode", "showtitle",
"plot", "file", "rating", "resume", "tvshowid", "art", "plot", "file", "rating", "resume", "tvshowid", "art",
@ -645,6 +650,7 @@ def get_next_episodes(item_id, limit):
xbmcplugin.setContent(int(sys.argv[1]), 'episodes') xbmcplugin.setContent(int(sys.argv[1]), 'episodes')
xbmcplugin.endOfDirectory(int(sys.argv[1])) xbmcplugin.endOfDirectory(int(sys.argv[1]))
def create_listitem(item): def create_listitem(item):
''' Listitem based on jsonrpc items. ''' Listitem based on jsonrpc items.
@ -656,7 +662,7 @@ def create_listitem(item):
metadata = { metadata = {
'Title': title, 'Title': title,
'duration': str(item['runtime']/60), 'duration': str(item['runtime'] / 60),
'Plot': item['plot'], 'Plot': item['plot'],
'Playcount': item['playcount'] 'Playcount': item['playcount']
} }
@ -688,7 +694,7 @@ def create_listitem(item):
metadata['Premiered'] = item['firstaired'] metadata['Premiered'] = item['firstaired']
if "rating" in item: if "rating" in item:
metadata['Rating'] = str(round(float(item['rating']),1)) metadata['Rating'] = str(round(float(item['rating']), 1))
if "director" in item: if "director" in item:
metadata['Director'] = " / ".join(item['director']) metadata['Director'] = " / ".join(item['director'])
@ -711,10 +717,10 @@ def create_listitem(item):
li.setProperty('resumetime', str(item['resume']['position'])) li.setProperty('resumetime', str(item['resume']['position']))
li.setProperty('totaltime', str(item['resume']['total'])) li.setProperty('totaltime', str(item['resume']['total']))
li.setArt(item['art']) li.setArt(item['art'])
li.setThumbnailImage(item['art'].get('thumb','')) li.setThumbnailImage(item['art'].get('thumb', ''))
li.setIconImage('DefaultTVShows.png') li.setIconImage('DefaultTVShows.png')
li.setProperty('dbid', str(item['episodeid'])) li.setProperty('dbid', str(item['episodeid']))
li.setProperty('fanart_image', item['art'].get('tvshow.fanart','')) li.setProperty('fanart_image', item['art'].get('tvshow.fanart', ''))
for key, value in item['streamdetails'].iteritems(): for key, value in item['streamdetails'].iteritems():
for stream in value: for stream in value:
@ -722,6 +728,7 @@ def create_listitem(item):
return li return li
def add_user(): def add_user():
''' Add or remove users from the default server session. ''' Add or remove users from the default server session.
@ -738,7 +745,7 @@ def add_user():
if result < 0: if result < 0:
return return
if not result: # Add user if not result: # Add user
eligible = [x for x in users if x['Id'] not in [current_user['UserId'] for current_user in current]] eligible = [x for x in users if x['Id'] not in [current_user['UserId'] for current_user in current]]
resp = dialog("select", _(33064), [x['Name'] for x in eligible]) resp = dialog("select", _(33064), [x['Name'] for x in eligible])
@ -747,7 +754,7 @@ def add_user():
user = eligible[resp] user = eligible[resp]
event('AddUser', {'Id': user['Id'], 'Add': True}) event('AddUser', {'Id': user['Id'], 'Add': True})
else: # Remove user else: # Remove user
resp = dialog("select", _(33064), [x['UserName'] for x in current]) resp = dialog("select", _(33064), [x['UserName'] for x in current])
if resp < 0: if resp < 0:
@ -756,6 +763,7 @@ def add_user():
user = current[resp] user = current[resp]
event('AddUser', {'Id': user['UserId'], 'Add': False}) event('AddUser', {'Id': user['UserId'], 'Add': False})
def get_themes(): def get_themes():
''' Add theme media locally, via strm. This is only for tv tunes. ''' Add theme media locally, via strm. This is only for tv tunes.
@ -786,7 +794,6 @@ def get_themes():
all_views = jellyfin_db.JellyfinDatabase(jellyfindb.cursor).get_views() all_views = jellyfin_db.JellyfinDatabase(jellyfindb.cursor).get_views()
views = [x[0] for x in all_views if x[2] in ('movies', 'tvshows', 'mixed')] views = [x[0] for x in all_views if x[2] in ('movies', 'tvshows', 'mixed')]
items = {} items = {}
server = TheVoid('GetServerAddress', {'ServerId': None}).get() server = TheVoid('GetServerAddress', {'ServerId': None}).get()
token = TheVoid('GetToken', {'ServerId': None}).get() token = TheVoid('GetToken', {'ServerId': None}).get()
@ -829,6 +836,7 @@ def get_themes():
dialog("notification", heading="{jellyfin}", message=_(33153), icon="{jellyfin}", time=1000, sound=False) dialog("notification", heading="{jellyfin}", message=_(33153), icon="{jellyfin}", time=1000, sound=False)
def delete_item(): def delete_item():
''' Delete keymap action. ''' Delete keymap action.
@ -837,6 +845,7 @@ def delete_item():
context.Context(delete=True) context.Context(delete=True)
def backup(): def backup():
''' Jellyfin backup. ''' Jellyfin backup.

View file

@ -2,12 +2,13 @@
################################################################################################# #################################################################################################
import _strptime # Workaround for threads using datetime: _striptime is locked
import json import json
import logging import logging
import sys import sys
from datetime import datetime from datetime import datetime
# Workaround for threads using datetime: _striptime is locked
import _strptime # noqa:F401
import xbmc import xbmc
import xbmcgui import xbmcgui
@ -24,7 +25,7 @@ from database import Database, jellyfin_db, reset
################################################################################################# #################################################################################################
LOG = logging.getLogger("JELLYFIN."+__name__) LOG = logging.getLogger("JELLYFIN." + __name__)
################################################################################################# #################################################################################################
@ -38,7 +39,6 @@ class Service(xbmc.Monitor):
warn = True 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): def __init__(self):
window('jellyfin_should_stop', clear=True) window('jellyfin_should_stop', clear=True)
@ -209,7 +209,7 @@ class Service(xbmc.Monitor):
users = [user for user in (settings('additionalUsers') or "").decode('utf-8').split(',') if user] users = [user for user in (settings('additionalUsers') or "").decode('utf-8').split(',') if user]
users.insert(0, settings('username').decode('utf-8')) users.insert(0, settings('username').decode('utf-8'))
dialog("notification", heading="{jellyfin}", message="%s %s" % (_(33000), ", ".join(users)), dialog("notification", heading="{jellyfin}", message="%s %s" % (_(33000), ", ".join(users)),
icon="{jellyfin}", time=1500, sound=False) icon="{jellyfin}", time=1500, sound=False)
if self.library_thread is None: if self.library_thread is None:
@ -352,7 +352,7 @@ class Service(xbmc.Monitor):
return return
LOG.info("--<[ sleep ]") LOG.info("--<[ sleep ]")
xbmc.sleep(10000)# Allow network to wake up xbmc.sleep(10000) # Allow network to wake up
self.monitor.sleep = False self.monitor.sleep = False
window('jellyfin_should_stop', clear=True) window('jellyfin_should_stop', clear=True)
@ -444,7 +444,7 @@ class Service(xbmc.Monitor):
LOG.info("---<[ EXITING ]") LOG.info("---<[ EXITING ]")
window('jellyfin_should_stop.bool', True) window('jellyfin_should_stop.bool', True)
properties = [ # TODO: review properties = [ # TODO: review
"jellyfin_state", "jellyfin_serverStatus", "jellyfin_currUser", "jellyfin_state", "jellyfin_serverStatus", "jellyfin_currUser",
"jellyfin_play", "jellyfin_online", "jellyfin.connected", "jellyfin.resume", "jellyfin_startup", "jellyfin_play", "jellyfin_online", "jellyfin.connected", "jellyfin.resume", "jellyfin_startup",

View file

@ -3,23 +3,19 @@
################################################################################################## ##################################################################################################
import datetime import datetime
import json
import logging import logging
import os
import xbmc import xbmc
import xbmcvfs
import downloader as server import downloader as server
import helper.xmls as xmls import helper.xmls as xmls
from database import Database, get_sync, save_sync, jellyfin_db from database import Database, get_sync, save_sync, jellyfin_db
from helper import _, settings, window, progress, dialog, LibraryException from helper import _, settings, window, progress, dialog, LibraryException
from helper.utils import get_screensaver, set_screensaver from helper.utils import get_screensaver, set_screensaver
from views import Views
################################################################################################## ##################################################################################################
LOG = logging.getLogger("JELLYFIN."+__name__) LOG = logging.getLogger("JELLYFIN." + __name__)
################################################################################################## ##################################################################################################
@ -36,7 +32,6 @@ class FullSync(object):
running = False running = False
screensaver = None screensaver = None
def __init__(self, library, server): def __init__(self, library, server):
''' You can call all big syncing methods here. ''' You can call all big syncing methods here.
@ -69,7 +64,6 @@ class FullSync(object):
return self return self
def libraries(self, library_id=None, update=False): def libraries(self, library_id=None, update=False):
''' Map the syncing process and start the sync. Ensure only one sync is running. ''' Map the syncing process and start the sync. Ensure only one sync is running.
@ -179,7 +173,6 @@ class FullSync(object):
return [libraries[x - 1] for x in selection] return [libraries[x - 1] for x in selection]
def start(self): def start(self):
''' Main sync process. ''' Main sync process.
@ -278,7 +271,7 @@ class FullSync(object):
for index, movie in enumerate(items['Items']): for index, movie in enumerate(items['Items']):
dialog.update(int((float(start_index + index) / float(items['TotalRecordCount']))*100), dialog.update(int((float(start_index + index) / float(items['TotalRecordCount'])) * 100),
heading="%s: %s" % (_('addon_name'), library['Name']), heading="%s: %s" % (_('addon_name'), library['Name']),
message=movie['Name']) message=movie['Name'])
obj.movie(movie, library=library) obj.movie(movie, library=library)
@ -318,11 +311,11 @@ class FullSync(object):
for index, show in enumerate(items['Items']): for index, show in enumerate(items['Items']):
percent = int((float(start_index + index) / float(items['TotalRecordCount']))*100) percent = int((float(start_index + index) / float(items['TotalRecordCount'])) * 100)
message = show['Name'] message = show['Name']
dialog.update(percent, heading="%s: %s" % (_('addon_name'), library['Name']), message=message) dialog.update(percent, heading="%s: %s" % (_('addon_name'), library['Name']), message=message)
if obj.tvshow(show, library=library) != False: if obj.tvshow(show, library=library) is not False:
for episodes in server.get_episode_by_show(show['Id']): for episodes in server.get_episode_by_show(show['Id']):
for episode in episodes['Items']: for episode in episodes['Items']:
@ -368,7 +361,7 @@ class FullSync(object):
for index, mvideo in enumerate(items['Items']): for index, mvideo in enumerate(items['Items']):
dialog.update(int((float(start_index + index) / float(items['TotalRecordCount']))*100), dialog.update(int((float(start_index + index) / float(items['TotalRecordCount'])) * 100),
heading="%s: %s" % (_('addon_name'), library['Name']), heading="%s: %s" % (_('addon_name'), library['Name']),
message=mvideo['Name']) message=mvideo['Name'])
obj.musicvideo(mvideo, library=library) obj.musicvideo(mvideo, library=library)
@ -408,7 +401,7 @@ class FullSync(object):
for index, artist in enumerate(items['Items']): for index, artist in enumerate(items['Items']):
percent = int((float(start_index + index) / float(items['TotalRecordCount']))*100) percent = int((float(start_index + index) / float(items['TotalRecordCount'])) * 100)
message = artist['Name'] message = artist['Name']
dialog.update(percent, heading="%s: %s" % (_('addon_name'), library['Name']), message=message) dialog.update(percent, heading="%s: %s" % (_('addon_name'), library['Name']), message=message)
obj.artist(artist, library=library) obj.artist(artist, library=library)
@ -431,7 +424,6 @@ class FullSync(object):
dialog.update(percent, message="%s/%s" % (message, song['Name'])) dialog.update(percent, message="%s/%s" % (message, song['Name']))
obj.song(song) obj.song(song)
if self.update_library: if self.update_library:
self.music_compare(library, obj, jellyfindb) self.music_compare(library, obj, jellyfindb)
@ -470,7 +462,7 @@ class FullSync(object):
for index, boxset in enumerate(items['Items']): for index, boxset in enumerate(items['Items']):
dialog.update(int((float(start_index + index) / float(items['TotalRecordCount']))*100), dialog.update(int((float(start_index + index) / float(items['TotalRecordCount'])) * 100),
heading="%s: %s" % (_('addon_name'), _('boxsets')), heading="%s: %s" % (_('addon_name'), _('boxsets')),
message=boxset['Name']) message=boxset['Name'])
obj.boxset(boxset) obj.boxset(boxset)
@ -524,7 +516,7 @@ class FullSync(object):
for item in movies: for item in movies:
obj(item[0]) obj(item[0])
dialog.update(int((float(count) / float(len(items))*100)), heading="%s: %s" % (_('addon_name'), library[0])) dialog.update(int((float(count) / float(len(items)) * 100)), heading="%s: %s" % (_('addon_name'), library[0]))
count += 1 count += 1
obj = TVShows(self.server, jellyfindb, kodidb, direct_path).remove obj = TVShows(self.server, jellyfindb, kodidb, direct_path).remove
@ -532,7 +524,7 @@ class FullSync(object):
for item in tvshows: for item in tvshows:
obj(item[0]) obj(item[0])
dialog.update(int((float(count) / float(len(items))*100)), heading="%s: %s" % (_('addon_name'), library[0])) dialog.update(int((float(count) / float(len(items)) * 100)), heading="%s: %s" % (_('addon_name'), library[0]))
count += 1 count += 1
else: else:
# from mcarlton: I'm not sure what triggers this. # from mcarlton: I'm not sure what triggers this.
@ -547,7 +539,7 @@ class FullSync(object):
for item in items: for item in items:
obj(item[0]) obj(item[0])
dialog.update(int((float(count) / float(len(items))*100)), heading="%s: %s" % (_('addon_name'), library[0])) dialog.update(int((float(count) / float(len(items)) * 100)), heading="%s: %s" % (_('addon_name'), library[0]))
count += 1 count += 1
self.sync = get_sync() self.sync = get_sync()
@ -560,7 +552,6 @@ class FullSync(object):
save_sync(self.sync) save_sync(self.sync)
def __exit__(self, exc_type, exc_val, exc_tb): def __exit__(self, exc_type, exc_val, exc_tb):
''' Exiting sync ''' Exiting sync

View file

@ -8,14 +8,14 @@ from . import settings
################################################################################################## ##################################################################################################
LOG = logging.getLogger("JELLYFIN."+__name__) LOG = logging.getLogger("JELLYFIN." + __name__)
################################################################################################## ##################################################################################################
class API(object): class API(object):
def __init__(self, item, server=None): def __init__(self, item, server=None):
''' Get item information in special cases. ''' Get item information in special cases.
server is the server address, provide if your functions requires it. server is the server address, provide if your functions requires it.
''' '''
@ -225,7 +225,7 @@ class API(object):
return path return path
def get_user_artwork(self, user_id): def get_user_artwork(self, user_id):
''' Get jellyfin user profile picture. ''' Get jellyfin user profile picture.
''' '''
return "%s/emby/Users/%s/Images/Primary?Format=original" % (self.server, user_id) return "%s/emby/Users/%s/Images/Primary?Format=original" % (self.server, user_id)
@ -286,7 +286,7 @@ class API(object):
if obj.get('SeriesTag'): if obj.get('SeriesTag'):
all_artwork['Series.Primary'] = self.get_artwork(obj['SeriesId'], "Primary", obj['SeriesTag'], query) all_artwork['Series.Primary'] = self.get_artwork(obj['SeriesId'], "Primary", obj['SeriesTag'], query)
if not all_artwork['Primary']: if not all_artwork['Primary']:
all_artwork['Primary'] = all_artwork['Series.Primary'] all_artwork['Primary'] = all_artwork['Series.Primary']

View file

@ -2,9 +2,8 @@
################################################################################################# #################################################################################################
class LibraryException(Exception): class LibraryException(Exception):
# Jellyfin library sync exception # Jellyfin library sync exception
def __init__(self, status): def __init__(self, status):
self.status = status self.status = status

View file

@ -2,25 +2,24 @@
################################################################################################# #################################################################################################
import json
import logging import logging
import os import os
from uuid import uuid4 from uuid import uuid4
import collections
import xbmc import xbmc
import xbmcvfs import xbmcvfs
import api import api
import database
import client import client
import collections
import requests import requests
from . import _, settings, window, dialog
from downloader import TheVoid from downloader import TheVoid
from . import _, settings, window, dialog
################################################################################################# #################################################################################################
LOG = logging.getLogger("JELLYFIN."+__name__) LOG = logging.getLogger("JELLYFIN." + __name__)
################################################################################################# #################################################################################################
@ -52,8 +51,8 @@ def set_properties(item, method, server_id=None):
window('jellyfin_play.json', current) window('jellyfin_play.json', current)
class PlayUtils(object):
class PlayUtils(object):
def __init__(self, item, force_transcode=False, server_id=None, server=None, token=None): def __init__(self, item, force_transcode=False, server_id=None, server=None, token=None):
@ -236,7 +235,7 @@ class PlayUtils(object):
def transcode(self, source, audio=None, subtitle=None): def transcode(self, source, audio=None, subtitle=None):
if not 'TranscodingUrl' in source: if 'TranscodingUrl' not in source:
raise Exception("use get_sources to get transcoding url") raise Exception("use get_sources to get transcoding url")
self.info['Method'] = "Transcode" self.info['Method'] = "Transcode"
@ -248,7 +247,7 @@ class PlayUtils(object):
url_parsed = params.split('&') url_parsed = params.split('&')
for i in url_parsed: for i in url_parsed:
if 'AudioStreamIndex' in i or 'AudioBitrate' in i or 'SubtitleStreamIndex' in i: # handle manually if 'AudioStreamIndex' in i or 'AudioBitrate' in i or 'SubtitleStreamIndex' in i: # handle manually
url_parsed.remove(i) url_parsed.remove(i)
params = "%s%s" % ('&'.join(url_parsed), self.get_audio_subs(source, audio, subtitle)) params = "%s%s" % ('&'.join(url_parsed), self.get_audio_subs(source, audio, subtitle))
@ -275,13 +274,19 @@ class PlayUtils(object):
self.info['Method'] = "DirectStream" self.info['Method'] = "DirectStream"
if self.item['Type'] == "Audio": if self.item['Type'] == "Audio":
self.info['Path'] = ("%s/emby/Audio/%s/stream.%s?static=true&api_key=%s" % self.info['Path'] = "%s/emby/Audio/%s/stream.%s?static=true&api_key=%s" % (
(self.info['ServerAddress'], self.item['Id'], self.info['ServerAddress'],
source.get('Container', "mp4").split(',')[0], self.item['Id'],
self.info['Token'])) source.get('Container', "mp4").split(',')[0],
self.info['Token']
)
else: else:
self.info['Path'] = ("%s/emby/Videos/%s/stream?static=true&MediaSourceId=%s&api_key=%s" % self.info['Path'] = "%s/emby/Videos/%s/stream?static=true&MediaSourceId=%s&api_key=%s" % (
(self.info['ServerAddress'], self.item['Id'], source['Id'], self.info['Token'])) self.info['ServerAddress'],
self.item['Id'],
source['Id'],
self.info['Token']
)
return self.info['Path'] return self.info['Path']
@ -495,7 +500,6 @@ class PlayUtils(object):
listitem.setSubtitles(subs) listitem.setSubtitles(subs)
self.item['PlaybackInfo']['Subtitles'] = mapping self.item['PlaybackInfo']['Subtitles'] = mapping
@classmethod @classmethod
def download_external_subs(cls, src, filename): def download_external_subs(cls, src, filename):
@ -579,7 +583,7 @@ class PlayUtils(object):
selection = list(audio_streams.keys()) selection = list(audio_streams.keys())
resp = dialog("select", _(33013), selection) resp = dialog("select", _(33013), selection)
audio_selected = audio_streams[selection[resp]] if resp else source['DefaultAudioStreamIndex'] audio_selected = audio_streams[selection[resp]] if resp else source['DefaultAudioStreamIndex']
else: # Only one choice else: # Only one choice
audio_selected = audio_streams[next(iter(audio_streams))] audio_selected = audio_streams[next(iter(audio_streams))]
else: else:
audio_selected = source['DefaultAudioStreamIndex'] audio_selected = source['DefaultAudioStreamIndex']
@ -628,7 +632,13 @@ class PlayUtils(object):
if stream['IsTextSubtitleStream'] and 'DeliveryUrl' in stream and stream['DeliveryUrl'].lower().startswith('/videos'): if stream['IsTextSubtitleStream'] and 'DeliveryUrl' in stream and stream['DeliveryUrl'].lower().startswith('/videos'):
url = "%s/emby%s" % (self.info['ServerAddress'], stream['DeliveryUrl']) url = "%s/emby%s" % (self.info['ServerAddress'], stream['DeliveryUrl'])
else: else:
url = ("%s/emby/Videos/%s/%s/Subtitles/%s/Stream.%s?api_key=%s" % url = "%s/emby/Videos/%s/%s/Subtitles/%s/Stream.%s?api_key=%s" % (
(self.info['ServerAddress'], self.item['Id'], source['Id'], index, stream['Codec'], self.info['Token'])) self.info['ServerAddress'],
self.item['Id'],
source['Id'],
index,
stream['Codec'],
self.info['Token']
)
return url return url

View file

@ -2,19 +2,18 @@
################################################################################################## ##################################################################################################
import json
import logging import logging
import os
import xbmc import xbmc
import xbmcaddon import xbmcaddon
################################################################################################## ##################################################################################################
LOG = logging.getLogger('JELLYFIN.'+__name__) LOG = logging.getLogger('JELLYFIN.' + __name__)
################################################################################################## ##################################################################################################
def _(string): def _(string):
''' Get add-on string. Returns in unicode. ''' Get add-on string. Returns in unicode.
@ -26,7 +25,7 @@ def _(string):
if not result: if not result:
result = xbmc.getLocalizedString(string) result = xbmc.getLocalizedString(string)
return result return result

View file

@ -12,26 +12,30 @@ import urllib
from uuid import uuid4 from uuid import uuid4
from distutils.version import LooseVersion from distutils.version import LooseVersion
from dateutil import tz, parser
import xbmc import xbmc
import xbmcaddon import xbmcaddon
import xbmcgui import xbmcgui
import xbmcvfs import xbmcvfs
from . import _ from .translate import _
from dateutil import tz, parser
################################################################################################# #################################################################################################
LOG = logging.getLogger("JELLYFIN."+__name__) LOG = logging.getLogger("JELLYFIN." + __name__)
################################################################################################# #################################################################################################
def addon_id(): def addon_id():
return "plugin.video.jellyfin" return "plugin.video.jellyfin"
def kodi_version(): def kodi_version():
return xbmc.getInfoLabel('System.BuildVersion')[:2] return xbmc.getInfoLabel('System.BuildVersion')[:2]
def window(key, value=None, clear=False, window_id=10000): def window(key, value=None, clear=False, window_id=10000):
''' Get or set window properties. ''' Get or set window properties.
@ -65,6 +69,7 @@ def window(key, value=None, clear=False, window_id=10000):
return result return result
def settings(setting, value=None): def settings(setting, value=None):
''' Get or add add-on settings. ''' Get or add add-on settings.
@ -87,9 +92,11 @@ def settings(setting, value=None):
return result return result
def create_id(): def create_id():
return uuid4() return uuid4()
def compare_version(a, b): def compare_version(a, b):
''' -1 a is smaller ''' -1 a is smaller
@ -107,6 +114,7 @@ def compare_version(a, b):
return 0 return 0
def find(dict, item): def find(dict, item):
''' Find value in dictionary. ''' Find value in dictionary.
@ -114,11 +122,12 @@ def find(dict, item):
if item in dict: if item in dict:
return dict[item] return dict[item]
for key,value in sorted(dict.iteritems(), key=lambda (k,v): (v,k)): for key, value in sorted(dict.iteritems(), key=lambda (k, v): (v, k)):
if re.match(key, item, re.I): if re.match(key, item, re.I):
return dict[key] return dict[key]
def event(method, data=None, sender=None, hexlify=False): def event(method, data=None, sender=None, hexlify=False):
''' Data is a dictionary. ''' Data is a dictionary.
@ -134,13 +143,16 @@ def event(method, data=None, sender=None, hexlify=False):
xbmc.executebuiltin('NotifyAll(%s, %s, %s)' % (sender, method, data)) xbmc.executebuiltin('NotifyAll(%s, %s, %s)' % (sender, method, data))
LOG.debug("---[ event: %s/%s ] %s", sender, method, data) LOG.debug("---[ event: %s/%s ] %s", sender, method, data)
def dialog(dialog_type, *args, **kwargs): def dialog(dialog_type, *args, **kwargs):
d = xbmcgui.Dialog() d = xbmcgui.Dialog()
if "icon" in kwargs: if "icon" in kwargs:
kwargs['icon'] = kwargs['icon'].replace("{jellyfin}", kwargs['icon'] = kwargs['icon'].replace(
"special://home/addons/plugin.video.jellyfin/resources/icon.png") "{jellyfin}",
"special://home/addons/plugin.video.jellyfin/resources/icon.png"
)
if "heading" in kwargs: if "heading" in kwargs:
kwargs['heading'] = kwargs['heading'].replace("{jellyfin}", _('addon_name')) kwargs['heading'] = kwargs['heading'].replace("{jellyfin}", _('addon_name'))
@ -155,6 +167,7 @@ def dialog(dialog_type, *args, **kwargs):
} }
return types[dialog_type](*args, **kwargs) return types[dialog_type](*args, **kwargs)
def should_stop(): def should_stop():
''' Checkpoint during the sync process. ''' Checkpoint during the sync process.
@ -171,6 +184,7 @@ def should_stop():
return False return False
def get_screensaver(): def get_screensaver():
''' Get the current screensaver value. ''' Get the current screensaver value.
@ -181,6 +195,7 @@ def get_screensaver():
except KeyError: except KeyError:
return "" return ""
def set_screensaver(value): def set_screensaver(value):
''' Toggle the screensaver ''' Toggle the screensaver
@ -192,6 +207,7 @@ def set_screensaver(value):
result = JSONRPC('Settings.setSettingValue').execute(params) result = JSONRPC('Settings.setSettingValue').execute(params)
LOG.info("---[ screensaver/%s ] %s", value, result) LOG.info("---[ screensaver/%s ] %s", value, result)
class JSONRPC(object): class JSONRPC(object):
version = 1 version = 1
@ -221,6 +237,7 @@ class JSONRPC(object):
self.params = params self.params = params
return json.loads(xbmc.executeJSONRPC(self._query())) return json.loads(xbmc.executeJSONRPC(self._query()))
def validate(path): def validate(path):
''' Verify if path is accessible. ''' Verify if path is accessible.
@ -241,6 +258,7 @@ def validate(path):
return True return True
def values(item, keys): def values(item, keys):
''' Grab the values in the item for a list of keys {key},{key1}.... ''' Grab the values in the item for a list of keys {key},{key1}....
@ -248,6 +266,7 @@ def values(item, keys):
''' '''
return (item[key.replace('{', "").replace('}', "")] if type(key) == str and key.startswith('{') else key for key in keys) return (item[key.replace('{', "").replace('}', "")] if type(key) == str and key.startswith('{') else key for key in keys)
def indent(elem, level=0): def indent(elem, level=0):
''' Prettify xml docs. ''' Prettify xml docs.
@ -256,20 +275,21 @@ def indent(elem, level=0):
i = "\n" + level * " " i = "\n" + level * " "
if len(elem): if len(elem):
if not elem.text or not elem.text.strip(): if not elem.text or not elem.text.strip():
elem.text = i + " " elem.text = i + " "
if not elem.tail or not elem.tail.strip(): if not elem.tail or not elem.tail.strip():
elem.tail = i elem.tail = i
for elem in elem: for elem in elem:
indent(elem, level + 1) indent(elem, level + 1)
if not elem.tail or not elem.tail.strip(): if not elem.tail or not elem.tail.strip():
elem.tail = i elem.tail = i
else: else:
if level and (not elem.tail or not elem.tail.strip()): if level and (not elem.tail or not elem.tail.strip()):
elem.tail = i elem.tail = i
except Exception as error: except Exception as error:
LOG.exception(error) LOG.exception(error)
return return
def write_xml(content, file): def write_xml(content, file):
with open(file, 'w') as infile: with open(file, 'w') as infile:
@ -332,6 +352,7 @@ def unzip(path, dest, folder=None):
LOG.info("Unzipped %s", path) LOG.info("Unzipped %s", path)
def unzip_recursive(path, dirs, dest): def unzip_recursive(path, dirs, dest):
for directory in dirs: for directory in dirs:
@ -348,6 +369,7 @@ def unzip_recursive(path, dirs, dest):
for file in files: for file in files:
unzip_file(os.path.join(dirs_dir, file.decode('utf-8')), os.path.join(dest_dir, file.decode('utf-8'))) unzip_file(os.path.join(dirs_dir, file.decode('utf-8')), os.path.join(dest_dir, file.decode('utf-8')))
def unzip_file(path, dest): def unzip_file(path, dest):
''' Unzip specific file. Path should start with zip:// ''' Unzip specific file. Path should start with zip://
@ -355,6 +377,7 @@ def unzip_file(path, dest):
xbmcvfs.copy(path, dest) xbmcvfs.copy(path, dest)
LOG.debug("unzip: %s to %s", path, dest) LOG.debug("unzip: %s to %s", path, dest)
def get_zip_directory(path, folder): def get_zip_directory(path, folder):
dirs, files = xbmcvfs.listdir(path) dirs, files = xbmcvfs.listdir(path)
@ -367,6 +390,7 @@ def get_zip_directory(path, folder):
if result: if result:
return result return result
def copytree(path, dest): def copytree(path, dest):
''' Copy folder content from one to another. ''' Copy folder content from one to another.
@ -384,6 +408,7 @@ def copytree(path, dest):
LOG.info("Copied %s", path) LOG.info("Copied %s", path)
def copy_recursive(path, dirs, dest): def copy_recursive(path, dirs, dest):
for directory in dirs: for directory in dirs:
@ -400,6 +425,7 @@ def copy_recursive(path, dirs, dest):
for file in files: for file in files:
copy_file(os.path.join(dirs_dir, file.decode('utf-8')), os.path.join(dest_dir, file.decode('utf-8'))) copy_file(os.path.join(dirs_dir, file.decode('utf-8')), os.path.join(dest_dir, file.decode('utf-8')))
def copy_file(path, dest): def copy_file(path, dest):
''' Copy specific file. ''' Copy specific file.
@ -410,6 +436,7 @@ def copy_file(path, dest):
xbmcvfs.copy(path, dest) xbmcvfs.copy(path, dest)
LOG.debug("copy: %s to %s", path, dest) LOG.debug("copy: %s to %s", path, dest)
def normalize_string(text): def normalize_string(text):
''' For theme media, do not modify unless modified in TV Tunes. ''' For theme media, do not modify unless modified in TV Tunes.
@ -431,11 +458,13 @@ def normalize_string(text):
return text return text
def split_list(itemlist, size): def split_list(itemlist, size):
''' Split up list in pieces of size. Will generate a list of lists ''' 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)] return [itemlist[i:i + size] for i in range(0, len(itemlist), size)]
def convert_to_local(date): def convert_to_local(date):

View file

@ -6,15 +6,17 @@ import logging
import xbmcgui import xbmcgui
from . import _, LibraryException from .utils import should_stop
from utils import should_stop from .exceptions import LibraryException
from .translate import _
################################################################################################# #################################################################################################
LOG = logging.getLogger("JELLYFIN."+__name__) LOG = logging.getLogger("JELLYFIN." + __name__)
################################################################################################# #################################################################################################
def progress(message=None): def progress(message=None):
''' Will start and close the progress dialog. ''' Will start and close the progress dialog.
@ -61,6 +63,7 @@ def catch(errors=(Exception,)):
return wrapper return wrapper
return decorator return decorator
def silent_catch(errors=(Exception,)): def silent_catch(errors=(Exception,)):
''' Wrapper to catch exceptions and ignore them ''' Wrapper to catch exceptions and ignore them
@ -76,6 +79,7 @@ def silent_catch(errors=(Exception,)):
return wrapper return wrapper
return decorator return decorator
def stop(default=None): def stop(default=None):
''' Wrapper to catch exceptions and return using catch ''' Wrapper to catch exceptions and return using catch
@ -100,6 +104,7 @@ def stop(default=None):
return wrapper return wrapper
return decorator return decorator
def jellyfin_item(): def jellyfin_item():
''' Wrapper to retrieve the jellyfin_db item. ''' Wrapper to retrieve the jellyfin_db item.
@ -113,6 +118,7 @@ def jellyfin_item():
return wrapper return wrapper
return decorator return decorator
def library_check(): def library_check():
''' Wrapper to retrieve the library ''' Wrapper to retrieve the library
@ -148,7 +154,7 @@ def library_check():
return return
view = {'Id': views[0], 'Name': views[1]} view = {'Id': views[0], 'Name': views[1]}
else: # Grab the first music library else: # Grab the first music library
return return
else: else:
for ancestor in ancestors: for ancestor in ancestors:

View file

@ -2,7 +2,6 @@
################################################################################################# #################################################################################################
import json
import logging import logging
import os import os
import xml.etree.ElementTree as etree import xml.etree.ElementTree as etree
@ -13,10 +12,11 @@ from . import _, indent, write_xml, dialog, settings
################################################################################################# #################################################################################################
LOG = logging.getLogger("JELLYFIN."+__name__) LOG = logging.getLogger("JELLYFIN." + __name__)
################################################################################################# #################################################################################################
def sources(): def sources():
''' Create master lock compatible sources. ''' Create master lock compatible sources.
@ -77,6 +77,7 @@ def sources():
indent(xml) indent(xml)
write_xml(etree.tostring(xml, 'UTF-8'), file) write_xml(etree.tostring(xml, 'UTF-8'), file)
def tvtunes_nfo(path, urls): def tvtunes_nfo(path, urls):
''' Create tvtunes.nfo ''' Create tvtunes.nfo
@ -96,6 +97,7 @@ def tvtunes_nfo(path, urls):
indent(xml) indent(xml)
write_xml(etree.tostring(xml, 'UTF-8'), path) write_xml(etree.tostring(xml, 'UTF-8'), path)
def advanced_settings(): def advanced_settings():
''' Track the existence of <cleanonupdate>true</cleanonupdate> ''' Track the existence of <cleanonupdate>true</cleanonupdate>

View file

@ -9,21 +9,25 @@ from helpers import has_attribute
################################################################################################# #################################################################################################
class NullHandler(logging.Handler): class NullHandler(logging.Handler):
def emit(self, record): def emit(self, record):
print(self.format(record)) print(self.format(record))
loghandler = NullHandler loghandler = NullHandler
LOG = logging.getLogger('Jellyfin') LOG = logging.getLogger('Jellyfin')
################################################################################################# #################################################################################################
def config(level=logging.INFO): def config(level=logging.INFO):
logger = logging.getLogger('Jellyfin') logger = logging.getLogger('Jellyfin')
logger.addHandler(Jellyfin.loghandler()) logger.addHandler(Jellyfin.loghandler())
logger.setLevel(level) logger.setLevel(level)
def ensure_client(): def ensure_client():
def decorator(func): def decorator(func):
@ -109,7 +113,7 @@ class Jellyfin(object):
@ensure_client() @ensure_client()
def __getattr__(self, name): def __getattr__(self, name):
return getattr(self.client[self.server_id], name) return getattr(self.client[self.server_id], name)
def construct(self): def construct(self):
self.client[self.server_id] = JellyfinClient() self.client[self.server_id] = JellyfinClient()

View file

@ -12,10 +12,11 @@ from core.connection_manager import ConnectionManager, CONNECTION_STATE
################################################################################################# #################################################################################################
LOG = logging.getLogger('JELLYFIN.'+__name__) LOG = logging.getLogger('JELLYFIN.' + __name__)
################################################################################################# #################################################################################################
def callback(message, data): def callback(message, data):
''' Callback function should received message, data ''' Callback function should received message, data

View file

@ -1 +0,0 @@

View file

@ -225,15 +225,15 @@ class API(object):
def get_themes(self, item_id): def get_themes(self, item_id):
return self.items("/%s/ThemeMedia" % item_id, params={ return self.items("/%s/ThemeMedia" % item_id, params={
'UserId': "{UserId}", 'UserId': "{UserId}",
'InheritFromParent': True 'InheritFromParent': True
}) })
def get_items_theme_song(self, parent_id): def get_items_theme_song(self, parent_id):
return self.users("/Items", params={ return self.users("/Items", params={
'HasThemeSong': True, 'HasThemeSong': True,
'ParentId': parent_id 'ParentId': parent_id
}) })
def get_plugins(self): def get_plugins(self):
return self._get("Plugins") return self._get("Plugins")

View file

@ -12,7 +12,7 @@ import logging
DEFAULT_HTTP_MAX_RETRIES = 3 DEFAULT_HTTP_MAX_RETRIES = 3
DEFAULT_HTTP_TIMEOUT = 30 DEFAULT_HTTP_TIMEOUT = 30
LOG = logging.getLogger('JELLYFIN.'+__name__) LOG = logging.getLogger('JELLYFIN.' + __name__)
################################################################################################# #################################################################################################

View file

@ -12,11 +12,11 @@ from distutils.version import LooseVersion
import urllib3 import urllib3
from credentials import Credentials from credentials import Credentials
from http import HTTP from http import HTTP # noqa: I201,I100
################################################################################################# #################################################################################################
LOG = logging.getLogger('JELLYFIN.'+__name__) LOG = logging.getLogger('JELLYFIN.' + __name__)
CONNECTION_STATE = { CONNECTION_STATE = {
'Unavailable': 0, 'Unavailable': 0,
'ServerSelection': 1, 'ServerSelection': 1,
@ -31,6 +31,7 @@ CONNECTION_MODE = {
################################################################################################# #################################################################################################
def get_server_address(server, mode): def get_server_address(server, mode):
modes = { modes = {
@ -86,7 +87,7 @@ class ConnectionManager(object):
credentials = self.credentials.get_credentials() credentials = self.credentials.get_credentials()
found_servers = self._find_servers(self._server_discovery()) found_servers = self._find_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") LOG.info("Found no servers")
return list() return list()
@ -178,7 +179,7 @@ class ConnectionManager(object):
LOG.info("beginning connection tests") LOG.info("beginning connection tests")
return self._test_next_connection_mode(tests, 0, server, options) return self._test_next_connection_mode(tests, 0, server, options)
def get_server_address(self, server, mode): #TODO: De-duplicated (Duplicated from above when getting rid of shortcuts) def get_server_address(self, server, mode): # TODO: De-duplicated (Duplicated from above when getting rid of shortcuts)
modes = { modes = {
CONNECTION_MODE['Local']: server.get('LocalAddress'), CONNECTION_MODE['Local']: server.get('LocalAddress'),
@ -239,8 +240,10 @@ class ConnectionManager(object):
request.pop('dataType') request.pop('dataType')
headers['X-Application'] = self._add_app_info() headers['X-Application'] = self._add_app_info()
headers['Content-type'] = request.get('contentType', headers['Content-type'] = request.get(
'application/x-www-form-urlencoded; charset=UTF-8') 'contentType',
'application/x-www-form-urlencoded; charset=UTF-8'
)
def _connect_to_servers(self, servers, options): def _connect_to_servers(self, servers, options):
@ -379,7 +382,7 @@ class ConnectionManager(object):
MESSAGE = "who is JellyfinServer?" MESSAGE = "who is JellyfinServer?"
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.settimeout(1.0) # This controls the socket.timeout exception sock.settimeout(1.0) # This controls the socket.timeout exception
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 20) sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 20)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
@ -399,7 +402,7 @@ class ConnectionManager(object):
while True: while True:
try: try:
data, addr = sock.recvfrom(1024) # buffer size data, addr = sock.recvfrom(1024) # buffer size
servers.append(json.loads(data)) servers.append(json.loads(data))
except socket.timeout: except socket.timeout:
@ -446,7 +449,7 @@ class ConnectionManager(object):
'Id': found_server['Id'], 'Id': found_server['Id'],
'LocalAddress': server or found_server['Address'], 'LocalAddress': server or found_server['Address'],
'Name': found_server['Name'] 'Name': found_server['Name']
} #TODO } # TODO
info['LastConnectionMode'] = CONNECTION_MODE['Manual'] if info.get('ManualAddress') else CONNECTION_MODE['Local'] info['LastConnectionMode'] = CONNECTION_MODE['Manual'] if info.get('ManualAddress') else CONNECTION_MODE['Local']
servers.append(info) servers.append(info)
@ -461,7 +464,7 @@ class ConnectionManager(object):
# Determine the port, if any # Determine the port, if any
parts = info['Address'].split(':') parts = info['Address'].split(':')
if len(parts) > 1: if len(parts) > 1:
port_string = parts[len(parts)-1] port_string = parts[len(parts) - 1]
try: try:
address += ":%s" % int(port_string) address += ":%s" % int(port_string)
@ -496,7 +499,7 @@ class ConnectionManager(object):
def _after_connect_validated(self, server, credentials, system_info, connection_mode, verify_authentication, options): def _after_connect_validated(self, server, credentials, system_info, connection_mode, verify_authentication, options):
if options.get('enableAutoLogin') == False: if options.get('enableAutoLogin') is False:
self.config.data['auth.user_id'] = server.pop('UserId', None) self.config.data['auth.user_id'] = server.pop('UserId', None)
self.config.data['auth.token'] = server.pop('AccessToken', None) self.config.data['auth.token'] = server.pop('AccessToken', None)
@ -581,7 +584,8 @@ class ConnectionManager(object):
if server['Id'] == result['ServerId']: if server['Id'] == result['ServerId']:
found_server = server found_server = server
break break
else: return # No server found else:
return # No server found
if options.get('updateDateLastAccessed') is not False: if options.get('updateDateLastAccessed') is not False:
found_server['DateLastAccessed'] = datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ') found_server['DateLastAccessed'] = datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ')

View file

@ -2,18 +2,17 @@
################################################################################################# #################################################################################################
import json
import logging import logging
import os
import time import time
from datetime import datetime from datetime import datetime
################################################################################################# #################################################################################################
LOG = logging.getLogger('JELLYFIN.'+__name__) LOG = logging.getLogger('JELLYFIN.' + __name__)
################################################################################################# #################################################################################################
class Credentials(object): class Credentials(object):
credentials = None credentials = None

View file

@ -2,10 +2,9 @@
################################################################################################# #################################################################################################
class HTTPException(Exception): class HTTPException(Exception):
# Jellyfin HTTP exception # Jellyfin HTTP exception
def __init__(self, status, message): def __init__(self, status, message):
self.status = status self.status = status
self.message = message self.message = message

View file

@ -11,7 +11,7 @@ from exceptions import HTTPException
################################################################################################# #################################################################################################
LOG = logging.getLogger('Jellyfin.'+__name__) LOG = logging.getLogger('Jellyfin.' + __name__)
################################################################################################# #################################################################################################
@ -127,7 +127,7 @@ class HTTP(object):
raise HTTPException("AccessRestricted", error) raise HTTPException("AccessRestricted", error)
else: 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() self.client.auth.revoke_token()
raise HTTPException("Unauthorized", error) raise HTTPException("Unauthorized", error)

View file

@ -5,7 +5,6 @@
import json import json
import logging import logging
import threading import threading
import time
import xbmc import xbmc
@ -13,7 +12,7 @@ from ..resources import websocket
################################################################################################## ##################################################################################################
LOG = logging.getLogger('JELLYFIN.'+__name__) LOG = logging.getLogger('JELLYFIN.' + __name__)
################################################################################################## ##################################################################################################

View file

@ -7,9 +7,10 @@ from uuid import uuid4
################################################################################################# #################################################################################################
LOG = logging.getLogger('JELLYFIN.'+__name__) LOG = logging.getLogger('JELLYFIN.' + __name__)
################################################################################################# #################################################################################################
def generate_client_id(): def generate_client_id():
return str("%012X" % uuid4()) return str("%012X" % uuid4())

View file

@ -43,8 +43,6 @@ import base64
import threading import threading
import time import time
import logging import logging
import traceback
import sys
""" """
websocket python client. websocket python client.
@ -89,12 +87,14 @@ class WebSocketConnectionClosedException(WebSocketException):
""" """
pass pass
class WebSocketTimeoutException(WebSocketException): class WebSocketTimeoutException(WebSocketException):
""" """
WebSocketTimeoutException will be raised at socket timeout during read/write data. WebSocketTimeoutException will be raised at socket timeout during read/write data.
""" """
pass pass
default_timeout = None default_timeout = None
traceEnabled = False traceEnabled = False
@ -135,8 +135,10 @@ def _wrap_sni_socket(sock, sslopt, hostname):
if sslopt.get('cert_reqs', ssl.CERT_NONE) != ssl.CERT_NONE: if sslopt.get('cert_reqs', ssl.CERT_NONE) != ssl.CERT_NONE:
capath = ssl.get_default_verify_paths().capath capath = ssl.get_default_verify_paths().capath
context.load_verify_locations(cafile=sslopt.get('ca_certs', None), context.load_verify_locations(
capath=sslopt.get('ca_cert_path', capath)) cafile=sslopt.get('ca_certs', None),
capath=sslopt.get('ca_cert_path', capath)
)
return context.wrap_socket( return context.wrap_socket(
sock, sock,
@ -217,9 +219,10 @@ def create_connection(url, timeout=None, **options):
websock.connect(url, **options) websock.connect(url, **options)
return websock return websock
_MAX_INTEGER = (1 << 32) -1
_MAX_INTEGER = (1 << 32) - 1
_AVAILABLE_KEY_CHARS = range(0x21, 0x2f + 1) + range(0x3a, 0x7e + 1) _AVAILABLE_KEY_CHARS = range(0x21, 0x2f + 1) + range(0x3a, 0x7e + 1)
_MAX_CHAR_BYTE = (1<<8) -1 _MAX_CHAR_BYTE = (1 << 8) - 1
# ref. Websocket gets an update, and it breaks stuff. # ref. Websocket gets an update, and it breaks stuff.
# http://axod.blogspot.com/2010/06/websocket-gets-update-and-it-breaks.html # http://axod.blogspot.com/2010/06/websocket-gets-update-and-it-breaks.html
@ -233,7 +236,7 @@ def _create_sec_websocket_key():
_HEADERS_TO_CHECK = { _HEADERS_TO_CHECK = {
"upgrade": "websocket", "upgrade": "websocket",
"connection": "upgrade", "connection": "upgrade",
} }
class ABNF(object): class ABNF(object):
@ -244,16 +247,16 @@ class ABNF(object):
""" """
# operation code values. # operation code values.
OPCODE_CONT = 0x0 OPCODE_CONT = 0x0
OPCODE_TEXT = 0x1 OPCODE_TEXT = 0x1
OPCODE_BINARY = 0x2 OPCODE_BINARY = 0x2
OPCODE_CLOSE = 0x8 OPCODE_CLOSE = 0x8
OPCODE_PING = 0x9 OPCODE_PING = 0x9
OPCODE_PONG = 0xa OPCODE_PONG = 0xa
# available operation code value tuple # available operation code value tuple
OPCODES = (OPCODE_CONT, OPCODE_TEXT, OPCODE_BINARY, OPCODE_CLOSE, OPCODES = (OPCODE_CONT, OPCODE_TEXT, OPCODE_BINARY, OPCODE_CLOSE,
OPCODE_PING, OPCODE_PONG) OPCODE_PING, OPCODE_PONG)
# opcode human readable string # opcode human readable string
OPCODE_MAP = { OPCODE_MAP = {
@ -263,10 +266,10 @@ class ABNF(object):
OPCODE_CLOSE: "close", OPCODE_CLOSE: "close",
OPCODE_PING: "ping", OPCODE_PING: "ping",
OPCODE_PONG: "pong" OPCODE_PONG: "pong"
} }
# data length threashold. # data length threashold.
LENGTH_7 = 0x7d LENGTH_7 = 0x7d
LENGTH_16 = 1 << 16 LENGTH_16 = 1 << 16
LENGTH_63 = 1 << 63 LENGTH_63 = 1 << 63
@ -287,8 +290,8 @@ class ABNF(object):
def __str__(self): def __str__(self):
return "fin=" + str(self.fin) \ return "fin=" + str(self.fin) \
+ " opcode=" + str(self.opcode) \ + " opcode=" + str(self.opcode) \
+ " data=" + str(self.data) + " data=" + str(self.data)
@staticmethod @staticmethod
def create_frame(data, opcode): def create_frame(data, opcode):
@ -318,9 +321,7 @@ class ABNF(object):
if length >= ABNF.LENGTH_63: if length >= ABNF.LENGTH_63:
raise ValueError("data is too long") raise ValueError("data is too long")
frame_header = chr(self.fin << 7 frame_header = chr(self.fin << 7 | self.rsv1 << 6 | self.rsv2 << 5 | self.rsv3 << 4 | self.opcode)
| self.rsv1 << 6 | self.rsv2 << 5 | self.rsv3 << 4
| self.opcode)
if length < ABNF.LENGTH_7: if length < ABNF.LENGTH_7:
frame_header += chr(self.mask << 7 | length) frame_header += chr(self.mask << 7 | length)
elif length < ABNF.LENGTH_16: elif length < ABNF.LENGTH_16:
@ -582,8 +583,7 @@ class WebSocket(object):
if traceEnabled: if traceEnabled:
logger.debug("send: " + repr(data)) logger.debug("send: " + repr(data))
while data: while data:
l = self._send(data) data = data[self._send(data):]
data = data[l:]
return length return length
def send_binary(self, payload): def send_binary(self, payload):
@ -685,7 +685,6 @@ class WebSocket(object):
self._frame_mask = None self._frame_mask = None
return ABNF(fin, rsv1, rsv2, rsv3, opcode, has_mask, payload) return ABNF(fin, rsv1, rsv2, rsv3, opcode, has_mask, payload)
def send_close(self, status=STATUS_NORMAL, reason=""): def send_close(self, status=STATUS_NORMAL, reason=""):
""" """
send close data to the server. send close data to the server.
@ -709,7 +708,7 @@ class WebSocket(object):
try: try:
self.sock.shutdown(socket.SHUT_RDWR) self.sock.shutdown(socket.SHUT_RDWR)
except: except: # noqa: E722
pass pass
''' '''
@ -766,7 +765,6 @@ class WebSocket(object):
raise WebSocketConnectionClosedException() raise WebSocketConnectionClosedException()
return bytes return bytes
def _recv_strict(self, bufsize): def _recv_strict(self, bufsize):
shortage = bufsize - sum(len(x) for x in self._recv_buffer) shortage = bufsize - sum(len(x) for x in self._recv_buffer)
while shortage > 0: while shortage > 0:
@ -781,7 +779,6 @@ class WebSocket(object):
self._recv_buffer = [unified[bufsize:]] self._recv_buffer = [unified[bufsize:]]
return unified[:bufsize] return unified[:bufsize]
def _recv_line(self): def _recv_line(self):
line = [] line = []
while True: while True:
@ -844,7 +841,7 @@ class WebSocketApp(object):
close websocket connection. close websocket connection.
""" """
self.keep_running = False self.keep_running = False
if(self.sock != None): if self.sock is not None:
self.sock.close() self.sock.close()
def _send_ping(self, interval): def _send_ping(self, interval):
@ -890,7 +887,7 @@ class WebSocketApp(object):
try: try:
data = self.sock.recv() data = self.sock.recv()
if data is None or self.keep_running == False: if data is None or self.keep_running is False:
break break
self._callback(self.on_message, data) self._callback(self.on_message, data)

View file

@ -5,7 +5,6 @@
import logging import logging
import Queue import Queue
import threading import threading
import sys
from datetime import datetime, timedelta from datetime import datetime, timedelta
import xbmc import xbmc
@ -16,13 +15,13 @@ from database import Database, jellyfin_db, get_sync, save_sync
from full_sync import FullSync from full_sync import FullSync
from views import Views from views import Views
from downloader import GetItemWorker from downloader import GetItemWorker
from helper import _, api, stop, settings, window, dialog, event, progress, LibraryException from helper import _, api, stop, settings, window, dialog, event, LibraryException
from helper.utils import split_list, set_screensaver, get_screensaver from helper.utils import split_list, set_screensaver, get_screensaver
from jellyfin import Jellyfin from jellyfin import Jellyfin
################################################################################################## ##################################################################################################
LOG = logging.getLogger("JELLYFIN."+__name__) LOG = logging.getLogger("JELLYFIN." + __name__)
LIMIT = min(int(settings('limitIndex') or 50), 50) LIMIT = min(int(settings('limitIndex') or 50), 50)
DTHREADS = int(settings('limitThreads') or 3) DTHREADS = int(settings('limitThreads') or 3)
MEDIA = { MEDIA = {
@ -41,7 +40,6 @@ MEDIA = {
################################################################################################## ##################################################################################################
class Library(threading.Thread): class Library(threading.Thread):
started = False started = False
@ -52,7 +50,6 @@ class Library(threading.Thread):
progress_updates = None progress_updates = None
total_updates = 0 total_updates = 0
def __init__(self, monitor): def __init__(self, monitor):
self.media = {'Movies': Movies, 'TVShows': TVShows, 'MusicVideos': MusicVideos, 'Music': Music} self.media = {'Movies': Movies, 'TVShows': TVShows, 'MusicVideos': MusicVideos, 'Music': Music}
@ -159,11 +156,11 @@ class Library(threading.Thread):
self.progress_updates = xbmcgui.DialogProgressBG() self.progress_updates = xbmcgui.DialogProgressBG()
self.progress_updates.create(_('addon_name'), _(33178)) self.progress_updates.create(_('addon_name'), _(33178))
self.progress_updates.update(int((float(self.total_updates - queue_size) / float(self.total_updates))*100), message="%s: %s" % (_(33178), queue_size)) self.progress_updates.update(int((float(self.total_updates - queue_size) / float(self.total_updates)) * 100), message="%s: %s" % (_(33178), queue_size))
elif queue_size: elif queue_size:
self.progress_updates.update(int((float(self.total_updates - queue_size) / float(self.total_updates))*100), message="%s: %s" % (_(33178), queue_size)) self.progress_updates.update(int((float(self.total_updates - queue_size) / float(self.total_updates)) * 100), message="%s: %s" % (_(33178), queue_size))
else: else:
self.progress_updates.update(int((float(self.total_updates - queue_size) / float(self.total_updates))*100), message=_(33178)) self.progress_updates.update(int((float(self.total_updates - queue_size) / float(self.total_updates)) * 100), message=_(33178))
if not settings('dbSyncScreensaver.bool') and self.screensaver is None: if not settings('dbSyncScreensaver.bool') and self.screensaver is None:
@ -171,8 +168,7 @@ class Library(threading.Thread):
self.screensaver = get_screensaver() self.screensaver = get_screensaver()
set_screensaver(value="") set_screensaver(value="")
if (self.pending_refresh and not self.download_threads and not self.writer_threads['updated'] and 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']):
not self.writer_threads['userdata'] and not self.writer_threads['removed']):
self.pending_refresh = False self.pending_refresh = False
self.save_last_sync() self.save_last_sync()
self.total_updates = 0 self.total_updates = 0
@ -189,9 +185,9 @@ class Library(threading.Thread):
set_screensaver(value=self.screensaver) set_screensaver(value=self.screensaver)
self.screensaver = None self.screensaver = None
if xbmc.getCondVisibility('Container.Content(musicvideos)'): # Prevent cursor from moving if xbmc.getCondVisibility('Container.Content(musicvideos)'): # Prevent cursor from moving
xbmc.executebuiltin('Container.Refresh') xbmc.executebuiltin('Container.Refresh')
else: # Update widgets else: # Update widgets
xbmc.executebuiltin('UpdateLibrary(video)') xbmc.executebuiltin('UpdateLibrary(video)')
if xbmc.getCondVisibility('Window.IsMedia'): if xbmc.getCondVisibility('Window.IsMedia'):
@ -313,7 +309,6 @@ class Library(threading.Thread):
LOG.info("-->[ q:notify/%s ]", id(new_thread)) LOG.info("-->[ q:notify/%s ]", id(new_thread))
self.notify_threads.append(new_thread) self.notify_threads.append(new_thread)
def startup(self): def startup(self):
''' Run at startup. ''' Run at startup.
@ -491,7 +486,7 @@ class Library(threading.Thread):
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: for library in available:
name, media = db.get_view(library) name, media = db.get_view(library)
if media in ('movies', 'tvshows', 'musicvideos', 'mixed', 'music'): if media in ('movies', 'tvshows', 'musicvideos', 'mixed', 'music'):
libraries.append({'Id': library, 'Name': name}) libraries.append({'Id': library, 'Name': name})
@ -546,7 +541,6 @@ class Library(threading.Thread):
return True return True
def userdata(self, data): def userdata(self, data):
''' Add item_id to userdata queue. ''' Add item_id to userdata queue.
@ -654,6 +648,7 @@ class UpdatedWorker(threading.Thread):
LOG.info("--<[ q:updated/%s ]", id(self)) LOG.info("--<[ q:updated/%s ]", id(self))
self.is_done = True self.is_done = True
class UserDataWorker(threading.Thread): class UserDataWorker(threading.Thread):
is_done = False is_done = False
@ -697,6 +692,7 @@ class UserDataWorker(threading.Thread):
LOG.info("--<[ q:userdata/%s ]", id(self)) LOG.info("--<[ q:userdata/%s ]", id(self))
self.is_done = True self.is_done = True
class SortWorker(threading.Thread): class SortWorker(threading.Thread):
is_done = False is_done = False
@ -742,6 +738,7 @@ class SortWorker(threading.Thread):
LOG.info("--<[ q:sort/%s ]", id(self)) LOG.info("--<[ q:sort/%s ]", id(self))
self.is_done = True self.is_done = True
class RemovedWorker(threading.Thread): class RemovedWorker(threading.Thread):
is_done = False is_done = False
@ -789,6 +786,7 @@ class RemovedWorker(threading.Thread):
LOG.info("--<[ q:removed/%s ]", id(self)) LOG.info("--<[ q:removed/%s ]", id(self))
self.is_done = True self.is_done = True
class NotifyWorker(threading.Thread): class NotifyWorker(threading.Thread):
is_done = False is_done = False

View file

@ -6,23 +6,21 @@ import binascii
import json import json
import logging import logging
import threading import threading
import sys
import xbmc import xbmc
import xbmcgui
import connect import connect
import downloader import downloader
import player import player
from client import get_device_id from client import get_device_id
from objects import Actions, PlaylistWorker, on_play, on_update, special_listener from objects import PlaylistWorker, on_play, on_update, special_listener
from helper import _, settings, window, dialog, event, api, JSONRPC from helper import _, settings, window, dialog, api, JSONRPC
from jellyfin import Jellyfin from jellyfin import Jellyfin
from webservice import WebService from webservice import WebService
################################################################################################# #################################################################################################
LOG = logging.getLogger("JELLYFIN."+__name__) LOG = logging.getLogger("JELLYFIN." + __name__)
################################################################################################# #################################################################################################
@ -84,7 +82,7 @@ class Monitor(xbmc.Monitor):
Otherwise the next played item will be added the previous queue. Otherwise the next played item will be added the previous queue.
''' '''
if method == "Player.OnStop": 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)"):
@ -144,7 +142,7 @@ class Monitor(xbmc.Monitor):
self.void_responder(data, item) self.void_responder(data, item)
elif method == 'GetServerAddress': elif method == 'GetServerAddress':
server_data = server.auth.get_server_info(server.auth.server_id) server_data = server.auth.get_server_info(server.auth.server_id)
server_address = server.auth.get_server_address(server_data, server_data['LastConnectionMode']) server_address = server.auth.get_server_address(server_data, server_data['LastConnectionMode'])
self.void_responder(data, server_address) self.void_responder(data, server_address)
@ -392,7 +390,7 @@ class Monitor(xbmc.Monitor):
elif command == 'DisplayMessage': elif command == 'DisplayMessage':
dialog("notification", heading=args['Header'], message=args['Text'], dialog("notification", heading=args['Header'], message=args['Text'],
icon="{jellyfin}", time=int(settings('displayMessage'))*1000) icon="{jellyfin}", time=int(settings('displayMessage')) * 1000)
elif command == 'SendString': elif command == 'SendString':
JSONRPC('Input.SendText').execute({'text': args['String'], 'done': False}) JSONRPC('Input.SendText').execute({'text': args['String'], 'done': False})

View file

@ -1,5 +1,3 @@
version = "171076031"
from movies import Movies from movies import Movies
from musicvideos import MusicVideos from musicvideos import MusicVideos
from tvshows import TVShows from tvshows import TVShows

View file

@ -2,7 +2,6 @@
################################################################################################# #################################################################################################
import json
import logging import logging
import threading import threading
import sys import sys
@ -16,13 +15,13 @@ import xbmcaddon
import database import database
from downloader import TheVoid from downloader import TheVoid
from obj import Objects from obj import Objects
from helper import _, playutils, api, window, settings, dialog, JSONRPC from helper import _, playutils, api, window, settings, dialog
from dialogs import resume from dialogs import resume
from utils import get_play_action from utils import get_play_action
################################################################################################# #################################################################################################
LOG = logging.getLogger("JELLYFIN."+__name__) LOG = logging.getLogger("JELLYFIN." + __name__)
################################################################################################# #################################################################################################
@ -58,7 +57,7 @@ class Actions(object):
play.set_external_subs(source, listitem) play.set_external_subs(source, listitem)
self.set_playlist(item, listitem, db_id, transcode) self.set_playlist(item, listitem, db_id, transcode)
index = max(kodi_playlist.getposition(), 0) + 1 # Can return -1 index = max(kodi_playlist.getposition(), 0) + 1 # Can return -1
force_play = False force_play = False
self.stack[0][1].setPath(self.stack[0][0]) self.stack[0][1].setPath(self.stack[0][0])
@ -79,7 +78,8 @@ class Actions(object):
index += 1 index += 1
if force_play: if force_play:
if len(sys.argv) > 1: xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, self.stack[0][1]) if len(sys.argv) > 1:
xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, self.stack[0][1])
xbmc.Player().play(kodi_playlist, windowed=False) xbmc.Player().play(kodi_playlist, windowed=False)
def set_playlist(self, item, listitem, db_id=None, transcode=False): def set_playlist(self, item, listitem, db_id=None, transcode=False):
@ -177,7 +177,7 @@ class Actions(object):
playlist = self.get_playlist(item) playlist = self.get_playlist(item)
player = xbmc.Player() player = xbmc.Player()
#xbmc.executebuiltin("Playlist.Clear") # Clear playlist to remove the previous item from playlist position no.2 # xbmc.executebuiltin("Playlist.Clear") # Clear playlist to remove the previous item from playlist position no.2
if clear: if clear:
if player.isPlaying(): if player.isPlaying():
@ -186,7 +186,7 @@ class Actions(object):
xbmc.executebuiltin('ActivateWindow(busydialognocancel)') xbmc.executebuiltin('ActivateWindow(busydialognocancel)')
index = 0 index = 0
else: else:
index = max(playlist.getposition(), 0) + 1 # Can return -1 index = max(playlist.getposition(), 0) + 1 # Can return -1
listitem = xbmcgui.ListItem() listitem = xbmcgui.ListItem()
LOG.info("[ playlist/%s ] %s", item['Id'], item['Name']) LOG.info("[ playlist/%s ] %s", item['Id'], item['Name'])
@ -282,7 +282,7 @@ class Actions(object):
''' Set listitem for video content. That also include streams. ''' Set listitem for video content. That also include streams.
''' '''
API = api.API(item, self.server) API = api.API(item, self.server)
is_video = obj['MediaType'] in ('Video', 'Audio') # audiobook is_video = obj['MediaType'] in ('Video', 'Audio') # audiobook
obj['Genres'] = " / ".join(obj['Genres'] or []) obj['Genres'] = " / ".join(obj['Genres'] or [])
obj['Studios'] = [API.validate_studio(studio) for studio in (obj['Studios'] or [])] obj['Studios'] = [API.validate_studio(studio) for studio in (obj['Studios'] or [])]
@ -312,21 +312,21 @@ class Actions(object):
if not intro and not obj['Type'] == 'Trailer': if not intro and not obj['Type'] == 'Trailer':
obj['Artwork']['Primary'] = obj['Artwork']['Primary'] \ obj['Artwork']['Primary'] = obj['Artwork']['Primary'] \
or "special://home/addons/plugin.video.jellyfin/resources/icon.png" or "special://home/addons/plugin.video.jellyfin/resources/icon.png"
else: else:
obj['Artwork']['Primary'] = obj['Artwork']['Primary'] \ obj['Artwork']['Primary'] = obj['Artwork']['Primary'] \
or obj['Artwork']['Thumb'] \ or obj['Artwork']['Thumb'] \
or (obj['Artwork']['Backdrop'][0] \ or (obj['Artwork']['Backdrop'][0]
if len(obj['Artwork']['Backdrop']) \ if len(obj['Artwork']['Backdrop'])
else "special://home/addons/plugin.video.jellyfin/resources/fanart.png") else "special://home/addons/plugin.video.jellyfin/resources/fanart.png")
obj['Artwork']['Primary'] += "&KodiTrailer=true" \ obj['Artwork']['Primary'] += "&KodiTrailer=true" \
if obj['Type'] == 'Trailer' else "&KodiCinemaMode=true" if obj['Type'] == 'Trailer' else "&KodiCinemaMode=true"
obj['Artwork']['Backdrop'] = [obj['Artwork']['Primary']] obj['Artwork']['Backdrop'] = [obj['Artwork']['Primary']]
self.set_artwork(obj['Artwork'], listitem, obj['Type']) self.set_artwork(obj['Artwork'], listitem, obj['Type'])
if intro or obj['Type'] == 'Trailer': if intro or obj['Type'] == 'Trailer':
listitem.setArt({'poster': ""}) # Clear the poster value for intros / trailers to prevent issues in skins listitem.setArt({'poster': ""}) # Clear the poster value for intros / trailers to prevent issues in skins
listitem.setIconImage('DefaultVideo.png') listitem.setIconImage('DefaultVideo.png')
listitem.setThumbnailImage(obj['Artwork']['Primary']) listitem.setThumbnailImage(obj['Artwork']['Primary'])
@ -442,9 +442,9 @@ class Actions(object):
listitem.setProperty('IsPlayable', 'true') listitem.setProperty('IsPlayable', 'true')
listitem.setProperty('IsFolder', 'false') listitem.setProperty('IsFolder', 'false')
if obj['Resume'] and seektime != False: if obj['Resume'] and seektime is not False:
listitem.setProperty('resumetime', str(obj['Resume'])) listitem.setProperty('resumetime', str(obj['Resume']))
listitem.setProperty('StartPercent', str(((obj['Resume']/obj['Runtime']) * 100) - 0.40)) listitem.setProperty('StartPercent', str(((obj['Resume'] / obj['Runtime']) * 100) - 0.40))
else: else:
listitem.setProperty('resumetime', '0') listitem.setProperty('resumetime', '0')
@ -478,11 +478,11 @@ class Actions(object):
obj['PlayCount'] = API.get_playcount(obj['Played'], obj['PlayCount']) or 0 obj['PlayCount'] = API.get_playcount(obj['Played'], obj['PlayCount']) or 0
obj['Overlay'] = 7 if obj['Played'] else 6 obj['Overlay'] = 7 if obj['Played'] else 6
obj['Artwork']['Primary'] = obj['Artwork']['Primary'] \ obj['Artwork']['Primary'] = obj['Artwork']['Primary'] \
or "special://home/addons/plugin.video.jellyfin/resources/icon.png" 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" or "special://home/addons/plugin.video.jellyfin/resources/fanart.png"
obj['Artwork']['Backdrop'] = obj['Artwork']['Backdrop'] \ obj['Artwork']['Backdrop'] = obj['Artwork']['Backdrop'] \
or ["special://home/addons/plugin.video.jellyfin/resources/fanart.png"] or ["special://home/addons/plugin.video.jellyfin/resources/fanart.png"]
metadata = { metadata = {
'title': obj['Title'], 'title': obj['Title'],
@ -625,7 +625,7 @@ class Actions(object):
'clearlogo': "Logo", 'clearlogo': "Logo",
'discart': "Disc", 'discart': "Disc",
'fanart': "Backdrop", 'fanart': "Backdrop",
'fanart_image': "Backdrop", # in case 'fanart_image': "Backdrop", # in case
'thumb': "Primary" 'thumb': "Primary"
} }
else: else:
@ -671,9 +671,9 @@ class Actions(object):
dialog.doModal() dialog.doModal()
if dialog.is_selected(): if dialog.is_selected():
if not dialog.get_selected(): # Start from beginning selected. if not dialog.get_selected(): # Start from beginning selected.
return False return False
else: # User backed out else: # User backed out
LOG.info("User exited without a selection.") LOG.info("User exited without a selection.")
return return
@ -688,9 +688,7 @@ class Actions(object):
return False return False
if (not xbmc.getCondVisibility('Window.IsMedia') and if (not xbmc.getCondVisibility('Window.IsMedia') and ((item['Type'] == 'Audio' and not xbmc.getCondVisibility('Integer.IsGreater(Playlist.Length(music),1)')) or not xbmc.getCondVisibility('Integer.IsGreater(Playlist.Length(video),1)'))):
((item['Type'] == 'Audio' and not xbmc.getCondVisibility('Integer.IsGreater(Playlist.Length(music),1)')) or
not xbmc.getCondVisibility('Integer.IsGreater(Playlist.Length(video),1)'))):
return True return True
@ -733,6 +731,7 @@ def on_update(data, server):
window('jellyfin.skip.%s' % item[0], clear=True) window('jellyfin.skip.%s' % item[0], clear=True)
def on_play(data, server): def on_play(data, server):
''' Setup progress for jellyfin playback. ''' Setup progress for jellyfin playback.
@ -781,6 +780,7 @@ def on_play(data, server):
item['PlaybackInfo'] = {'Path': file} item['PlaybackInfo'] = {'Path': file}
playutils.set_properties(item, 'DirectStream' if settings('useDirectPaths') == '0' else 'DirectPlay') playutils.set_properties(item, 'DirectStream' if settings('useDirectPaths') == '0' else 'DirectPlay')
def special_listener(): def special_listener():
''' Corner cases that needs to be listened to. ''' Corner cases that needs to be listened to.
@ -790,12 +790,11 @@ def special_listener():
isPlaying = player.isPlaying() isPlaying = player.isPlaying()
count = int(window('jellyfin.external_count') or 0) count = int(window('jellyfin.external_count') or 0)
if (not isPlaying and xbmc.getCondVisibility('Window.IsVisible(DialogContextMenu.xml)') and if (not isPlaying and xbmc.getCondVisibility('Window.IsVisible(DialogContextMenu.xml)') and xbmc.getInfoLabel('Control.GetLabel(1002)') == xbmc.getLocalizedString(12021)):
xbmc.getInfoLabel('Control.GetLabel(1002)') == xbmc.getLocalizedString(12021)):
control = int(xbmcgui.Window(10106).getFocusId()) control = int(xbmcgui.Window(10106).getFocusId())
if control == 1002: # Start from beginning if control == 1002: # Start from beginning
LOG.info("Resume dialog: Start from beginning selected.") LOG.info("Resume dialog: Start from beginning selected.")
window('jellyfin.resume.bool', False) window('jellyfin.resume.bool', False)
@ -806,7 +805,7 @@ def special_listener():
elif isPlaying and not window('jellyfin.external_check'): elif isPlaying and not window('jellyfin.external_check'):
time = player.getTime() time = player.getTime()
if time > 1: # Not external player. if time > 1: # Not external player.
window('jellyfin.external_check', value="true") window('jellyfin.external_check', value="true")
window('jellyfin.external_count', value="0") window('jellyfin.external_count', value="0")

View file

@ -12,12 +12,12 @@ import xbmcvfs
import queries as QU import queries as QU
import queries_texture as QUTEX import queries_texture as QUTEX
from helper import window, settings from helper import settings
import requests import requests
################################################################################################## ##################################################################################################
LOG = logging.getLogger("JELLYFIN."+__name__) LOG = logging.getLogger("JELLYFIN." + __name__)
################################################################################################## ##################################################################################################
@ -37,7 +37,6 @@ class Artwork(object):
'port': settings('webServerPort') 'port': settings('webServerPort')
} }
def update(self, image_url, kodi_id, media, image): def update(self, image_url, kodi_id, media, image):
''' Update artwork in the video database. ''' Update artwork in the video database.
@ -210,7 +209,7 @@ class GetArtworkWorker(threading.Thread):
prep = req.prepare() prep = req.prepare()
prep.url = "http://%s:%s/image/image://%s" % (self.kodi['host'], self.kodi['port'], url) prep.url = "http://%s:%s/image/image://%s" % (self.kodi['host'], self.kodi['port'], url)
s.send(prep, timeout=(0.01, 0.01)) s.send(prep, timeout=(0.01, 0.01))
s.content # release the connection s.content # release the connection
except Exception as error: except Exception as error:
LOG.exception(error) LOG.exception(error)
@ -220,8 +219,6 @@ class GetArtworkWorker(threading.Thread):
break break
""" """
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
@ -381,4 +378,3 @@ class Artwork(object):
count += 1 count += 1
""" """

View file

@ -4,22 +4,19 @@
import logging import logging
import xbmc
import artwork import artwork
import queries as QU import queries as QU
from helper import values from helper import values
################################################################################################## ##################################################################################################
LOG = logging.getLogger("JELLYFIN."+__name__) LOG = logging.getLogger("JELLYFIN." + __name__)
################################################################################################## ##################################################################################################
class Kodi(object): class Kodi(object):
def __init__(self): def __init__(self):
self.artwork = artwork.Artwork(self.cursor) self.artwork = artwork.Artwork(self.cursor)
@ -215,7 +212,7 @@ class Kodi(object):
return self.add_studio(*args) return self.add_studio(*args)
def add_streams(self, file_id, streams, runtime): def add_streams(self, file_id, streams, runtime):
''' First remove any existing entries ''' First remove any existing entries
Then re-add video, audio and subtitles. Then re-add video, audio and subtitles.
''' '''

View file

@ -9,14 +9,13 @@ import queries as QU
################################################################################################## ##################################################################################################
LOG = logging.getLogger("JELLYFIN."+__name__) LOG = logging.getLogger("JELLYFIN." + __name__)
################################################################################################## ##################################################################################################
class Movies(Kodi): class Movies(Kodi):
def __init__(self, cursor): def __init__(self, cursor):
self.cursor = cursor self.cursor = cursor
@ -29,7 +28,7 @@ class Movies(Kodi):
def create_entry_rating(self): def create_entry_rating(self):
self.cursor.execute(QU.create_rating) self.cursor.execute(QU.create_rating)
return self.cursor.fetchone()[0] + 1 return self.cursor.fetchone()[0] + 1
def create_entry(self): def create_entry(self):

View file

@ -9,7 +9,7 @@ from kodi import Kodi
################################################################################################## ##################################################################################################
LOG = logging.getLogger("JELLYFIN."+__name__) LOG = logging.getLogger("JELLYFIN." + __name__)
################################################################################################## ##################################################################################################
@ -34,17 +34,17 @@ class Music(Kodi):
def create_entry_album(self): def create_entry_album(self):
self.cursor.execute(QU.create_album) self.cursor.execute(QU.create_album)
return self.cursor.fetchone()[0] + 1 return self.cursor.fetchone()[0] + 1
def create_entry_song(self): def create_entry_song(self):
self.cursor.execute(QU.create_song) self.cursor.execute(QU.create_song)
return self.cursor.fetchone()[0] + 1 return self.cursor.fetchone()[0] + 1
def create_entry_genre(self): def create_entry_genre(self):
self.cursor.execute(QU.create_genre) self.cursor.execute(QU.create_genre)
return self.cursor.fetchone()[0] + 1 return self.cursor.fetchone()[0] + 1
def update_path(self, *args): def update_path(self, *args):
@ -212,7 +212,7 @@ class Music(Kodi):
''' Add genres, but delete current genres first. ''' Add genres, but delete current genres first.
Album_genres was removed in kodi 18 Album_genres was removed in kodi 18
''' '''
if media == 'album' and self.version_id < 72 : if media == 'album' and self.version_id < 72:
self.cursor.execute(QU.delete_genres_album, (kodi_id,)) self.cursor.execute(QU.delete_genres_album, (kodi_id,))
for genre in genres: for genre in genres:
@ -258,11 +258,11 @@ class Music(Kodi):
return self.cursor.fetchone()[0] return self.cursor.fetchone()[0]
#current bug in Kodi 18 that will ask for a scan of music tags unless this is set without a lastscanned # current bug in Kodi 18 that will ask for a scan of music tags unless this is set without a lastscanned
def update_versiontagscan(self): def update_versiontagscan(self):
if self.version_id < 72: if self.version_id < 72:
return return
else: else:
self.cursor.execute(QU.get_versiontagcount) self.cursor.execute(QU.get_versiontagcount)
if self.cursor.fetchone()[0] == 0: if self.cursor.fetchone()[0] == 0:
self.cursor.execute(QU.update_versiontag, (self.version_id,)) self.cursor.execute(QU.update_versiontag, (self.version_id,))

View file

@ -9,14 +9,13 @@ from kodi import Kodi
################################################################################################## ##################################################################################################
log = logging.getLogger("JELLYFIN."+__name__) log = logging.getLogger("JELLYFIN." + __name__)
################################################################################################## ##################################################################################################
class MusicVideos(Kodi): class MusicVideos(Kodi):
def __init__(self, cursor): def __init__(self, cursor):
self.cursor = cursor self.cursor = cursor
@ -31,7 +30,7 @@ class MusicVideos(Kodi):
try: try:
self.cursor.execute(QU.get_musicvideo, args) self.cursor.execute(QU.get_musicvideo, args)
return self.cursor.fetchone()[0] return self.cursor.fetchone()[0]
except TypeError: except TypeError:
return return

File diff suppressed because it is too large Load diff

View file

@ -1,238 +1,260 @@
create_artist = """ SELECT coalesce(max(idArtist), 1) create_artist = """
FROM artist SELECT coalesce(max(idArtist), 1)
""" FROM artist
create_album = """ SELECT coalesce(max(idAlbum), 0) """
FROM album create_album = """
""" SELECT coalesce(max(idAlbum), 0)
create_song = """ SELECT coalesce(max(idSong), 0) FROM album
FROM song """
""" create_song = """
create_genre = """ SELECT coalesce(max(idGenre), 0) SELECT coalesce(max(idSong), 0)
FROM genre FROM song
""" """
create_genre = """
SELECT coalesce(max(idGenre), 0)
FROM genre
"""
get_artist = """
get_artist = """ SELECT idArtist, strArtist SELECT idArtist, strArtist
FROM artist FROM artist
WHERE strMusicBrainzArtistID = ? WHERE strMusicBrainzArtistID = ?
""" """
get_artist_obj = [ "{ArtistId}","{Name}","{UniqueId}" get_artist_obj = ["{ArtistId}", "{Name}", "{UniqueId}"]
] get_artist_by_name = """
get_artist_by_name = """ SELECT idArtist SELECT idArtist
FROM artist FROM artist
WHERE strArtist = ? WHERE strArtist = ?
COLLATE NOCASE COLLATE NOCASE
""" """
get_artist_by_id = """ SELECT * get_artist_by_id = """
FROM artist SELECT *
WHERE idArtist = ? FROM artist
""" WHERE idArtist = ?
get_artist_by_id_obj = [ "{ArtistId}" """
] get_artist_by_id_obj = ["{ArtistId}"]
get_album_by_id = """ SELECT * get_album_by_id = """
FROM album SELECT *
WHERE idAlbum = ? FROM album
""" WHERE idAlbum = ?
get_album_by_id_obj = [ "{AlbumId}" """
] get_album_by_id_obj = ["{AlbumId}"]
get_song_by_id = """ SELECT * get_song_by_id = """
FROM song SELECT *
WHERE idSong = ? FROM song
""" WHERE idSong = ?
get_song_by_id_obj = [ "{SongId}" """
] get_song_by_id_obj = ["{SongId}"]
get_album = """ SELECT idAlbum get_album = """
FROM album SELECT idAlbum
WHERE strMusicBrainzAlbumID = ? FROM album
""" WHERE strMusicBrainzAlbumID = ?
get_album_obj = [ "{AlbumId}","{Title}","{UniqueId}","{Artists}","album" """
] get_album_obj = ["{AlbumId}", "{Title}", "{UniqueId}", "{Artists}", "album"]
get_album_by_name = """ SELECT idAlbum, strArtists get_album_by_name = """
FROM album SELECT idAlbum, strArtists
WHERE strAlbum = ? FROM album
""" WHERE strAlbum = ?
get_album_by_name72 = """ SELECT idAlbum, strArtistDisp """
FROM album get_album_by_name72 = """
WHERE strAlbum = ? SELECT idAlbum, strArtistDisp
""" FROM album
get_album_artist = """ SELECT strArtists WHERE strAlbum = ?
FROM album """
WHERE idAlbum = ? get_album_artist = """
""" SELECT strArtists
get_album_artist72 = """ SELECT strArtistDisp FROM album
FROM album WHERE idAlbum = ?
WHERE idAlbum = ? """
""" get_album_artist72 = """
get_album_artist_obj = [ "{AlbumId}","{strAlbumArtists}" SELECT strArtistDisp
] FROM album
get_genre = """ SELECT idGenre WHERE idAlbum = ?
FROM genre """
WHERE strGenre = ? get_album_artist_obj = ["{AlbumId}", "{strAlbumArtists}"]
COLLATE NOCASE get_genre = """
""" SELECT idGenre
get_total_episodes = """ SELECT totalCount FROM genre
FROM tvshowcounts WHERE strGenre = ?
WHERE idShow = ? COLLATE NOCASE
""" """
get_total_episodes = """
SELECT totalCount
FROM tvshowcounts
WHERE idShow = ?
"""
add_artist = """
add_artist = """ INSERT INTO artist(idArtist, strArtist, strMusicBrainzArtistID) INSERT INTO artist(idArtist, strArtist, strMusicBrainzArtistID)
VALUES (?, ?, ?) VALUES (?, ?, ?)
""" """
add_album = """ INSERT INTO album(idAlbum, strAlbum, strMusicBrainzAlbumID, strReleaseType) add_album = """
VALUES (?, ?, ?, ?) INSERT INTO album(idAlbum, strAlbum, strMusicBrainzAlbumID, strReleaseType)
""" VALUES (?, ?, ?, ?)
add_album72 = """ INSERT INTO album(idAlbum, strAlbum, strMusicBrainzAlbumID, strReleaseType, bScrapedMBID) """
VALUES (?, ?, ?, ?, 1) add_album72 = """
""" INSERT INTO album(idAlbum, strAlbum, strMusicBrainzAlbumID, strReleaseType, bScrapedMBID)
add_single = """ INSERT INTO album(idAlbum, strGenres, iYear, strReleaseType) VALUES (?, ?, ?, ?, 1)
VALUES (?, ?, ?, ?) """
""" add_single = """
add_single_obj = [ "{AlbumId}","{Genre}","{Year}","single" INSERT INTO album(idAlbum, strGenres, iYear, strReleaseType)
] VALUES (?, ?, ?, ?)
add_song = """ INSERT INTO song(idSong, idAlbum, idPath, strArtists, strGenres, strTitle, iTrack, """
iDuration, iYear, strFileName, strMusicBrainzTrackID, iTimesPlayed, lastplayed, add_single_obj = ["{AlbumId}", "{Genre}", "{Year}", "single"]
rating, comment, dateAdded) add_song = """
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) INSERT INTO song(idSong, idAlbum, idPath, strArtists, strGenres, strTitle, iTrack,
""" iDuration, iYear, strFileName, strMusicBrainzTrackID, iTimesPlayed, lastplayed,
add_song72 = """ INSERT INTO song(idSong, idAlbum, idPath, strArtistDisp, strGenres, strTitle, iTrack, rating, comment, dateAdded)
iDuration, iYear, strFileName, strMusicBrainzTrackID, iTimesPlayed, lastplayed, VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
rating, comment, dateAdded) """
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) add_song72 = """
""" INSERT INTO song(idSong, idAlbum, idPath, strArtistDisp, strGenres, strTitle, iTrack,
add_song_obj = [ "{SongId}","{AlbumId}","{PathId}","{Artists}","{Genre}","{Title}","{Index}", iDuration, iYear, strFileName, strMusicBrainzTrackID, iTimesPlayed, lastplayed,
"{Runtime}","{Year}","{Filename}","{UniqueId}","{PlayCount}","{DatePlayed}","{Rating}", rating, comment, dateAdded)
"{Comment}","{DateAdded}" VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
] """
add_genre = """ INSERT INTO genre(idGenre, strGenre) add_song_obj = ["{SongId}", "{AlbumId}", "{PathId}", "{Artists}", "{Genre}", "{Title}", "{Index}",
VALUES (?, ?) "{Runtime}", "{Year}", "{Filename}", "{UniqueId}", "{PlayCount}", "{DatePlayed}", "{Rating}",
""" "{Comment}", "{DateAdded}"]
add_genres_obj = [ "{AlbumId}","{Genres}","album" add_genre = """
] INSERT INTO genre(idGenre, strGenre)
VALUES (?, ?)
"""
add_genres_obj = ["{AlbumId}", "{Genres}", "album"]
update_path = """
update_path = """ UPDATE path UPDATE path
SET strPath = ? SET strPath = ?
WHERE idPath = ? WHERE idPath = ?
""" """
update_path_obj = [ "{Path}","{PathId}" update_path_obj = ["{Path}", "{PathId}"]
] update_role = """
update_role = """ INSERT OR REPLACE INTO role(idRole, strRole) INSERT OR REPLACE INTO role(idRole, strRole)
VALUES (?, ?) VALUES (?, ?)
""" """
update_role_obj = [ 1,"Composer" update_role_obj = [1, "Composer"]
] update_artist_name = """
update_artist_name = """ UPDATE artist UPDATE artist
SET strArtist = ? SET strArtist = ?
WHERE idArtist = ? WHERE idArtist = ?
""" """
update_artist_name_obj = [ "{Name}","{ArtistId}" update_artist_name_obj = ["{Name}", "{ArtistId}"]
] update_artist = """
update_artist = """ UPDATE artist UPDATE artist
SET strGenres = ?, strBiography = ?, strImage = ?, strFanart = ?, lastScraped = ? SET strGenres = ?, strBiography = ?, strImage = ?, strFanart = ?, lastScraped = ?
WHERE idArtist = ? WHERE idArtist = ?
""" """
update_link = """ INSERT OR REPLACE INTO album_artist(idArtist, idAlbum, strArtist) update_link = """
VALUES (?, ?, ?) INSERT OR REPLACE INTO album_artist(idArtist, idAlbum, strArtist)
""" VALUES (?, ?, ?)
update_link_obj = [ "{ArtistId}","{AlbumId}","{Name}" """
] update_link_obj = ["{ArtistId}", "{AlbumId}", "{Name}"]
update_discography = """ INSERT OR REPLACE INTO discography(idArtist, strAlbum, strYear) update_discography = """
VALUES (?, ?, ?) INSERT OR REPLACE INTO discography(idArtist, strAlbum, strYear)
""" VALUES (?, ?, ?)
update_discography_obj = [ "{ArtistId}","{Title}","{Year}" """
] update_discography_obj = ["{ArtistId}", "{Title}", "{Year}"]
update_album = """ UPDATE album update_album = """
SET strArtists = ?, iYear = ?, strGenres = ?, strReview = ?, strImage = ?, UPDATE album
iUserrating = ?, lastScraped = ?, strReleaseType = ? SET strArtists = ?, iYear = ?, strGenres = ?, strReview = ?, strImage = ?,
WHERE idAlbum = ? iUserrating = ?, lastScraped = ?, strReleaseType = ?
""" WHERE idAlbum = ?
update_album72 = """ UPDATE album """
SET strArtistDisp = ?, iYear = ?, strGenres = ?, strReview = ?, strImage = ?, update_album72 = """
iUserrating = ?, lastScraped = ?, bScrapedMBID = 1, strReleaseType = ? UPDATE album
WHERE idAlbum = ? SET strArtistDisp = ?, iYear = ?, strGenres = ?, strReview = ?, strImage = ?,
""" iUserrating = ?, lastScraped = ?, bScrapedMBID = 1, strReleaseType = ?
update_album_obj = [ "{Artists}","{Year}","{Genre}","{Bio}","{Thumb}","{Rating}","{LastScraped}", WHERE idAlbum = ?
"album","{AlbumId}" """
update_album_obj = ["{Artists}", "{Year}", "{Genre}", "{Bio}", "{Thumb}", "{Rating}", "{LastScraped}", "album", "{AlbumId}"]
] update_album_artist = """
update_album_artist = """ UPDATE album UPDATE album
SET strArtists = ? SET strArtists = ?
WHERE idAlbum = ? WHERE idAlbum = ?
""" """
update_album_artist72 = """ UPDATE album update_album_artist72 = """
SET strArtistDisp = ? UPDATE album
WHERE idAlbum = ? SET strArtistDisp = ?
""" WHERE idAlbum = ?
update_song = """ UPDATE song """
SET idAlbum = ?, strArtists = ?, strGenres = ?, strTitle = ?, iTrack = ?, update_song = """
iDuration = ?, iYear = ?, strFilename = ?, iTimesPlayed = ?, lastplayed = ?, UPDATE song
rating = ?, comment = ?, dateAdded = ? SET idAlbum = ?, strArtists = ?, strGenres = ?, strTitle = ?, iTrack = ?,
WHERE idSong = ? iDuration = ?, iYear = ?, strFilename = ?, iTimesPlayed = ?, lastplayed = ?,
""" rating = ?, comment = ?, dateAdded = ?
update_song72 = """ UPDATE song WHERE idSong = ?
SET idAlbum = ?, strArtistDisp = ?, strGenres = ?, strTitle = ?, iTrack = ?, """
iDuration = ?, iYear = ?, strFilename = ?, iTimesPlayed = ?, lastplayed = ?, update_song72 = """
rating = ?, comment = ?, dateAdded = ? UPDATE song
WHERE idSong = ? SET idAlbum = ?, strArtistDisp = ?, strGenres = ?, strTitle = ?, iTrack = ?,
""" iDuration = ?, iYear = ?, strFilename = ?, iTimesPlayed = ?, lastplayed = ?,
update_song_obj = [ "{AlbumId}","{Artists}","{Genre}","{Title}","{Index}","{Runtime}","{Year}", rating = ?, comment = ?, dateAdded = ?
"{Filename}","{PlayCount}","{DatePlayed}","{Rating}","{Comment}", WHERE idSong = ?
"{DateAdded}","{SongId}" """
] update_song_obj = ["{AlbumId}", "{Artists}", "{Genre}", "{Title}", "{Index}", "{Runtime}", "{Year}",
update_song_artist = """ INSERT OR REPLACE INTO song_artist(idArtist, idSong, idRole, iOrder, strArtist) "{Filename}", "{PlayCount}", "{DatePlayed}", "{Rating}", "{Comment}",
VALUES (?, ?, ?, ?, ?) "{DateAdded}", "{SongId}"]
""" update_song_artist = """
update_song_artist_obj = [ "{ArtistId}","{SongId}",1,"{Index}","{Name}" INSERT OR REPLACE INTO song_artist(idArtist, idSong, idRole, iOrder, strArtist)
] VALUES (?, ?, ?, ?, ?)
update_song_album = """ INSERT OR REPLACE INTO albuminfosong(idAlbumInfoSong, idAlbumInfo, iTrack, """
strTitle, iDuration) update_song_artist_obj = ["{ArtistId}", "{SongId}", 1, "{Index}", "{Name}"]
VALUES (?, ?, ?, ?, ?) update_song_album = """
""" INSERT OR REPLACE INTO albuminfosong(idAlbumInfoSong, idAlbumInfo, iTrack,
update_song_album_obj = [ "{SongId}","{AlbumId}","{Index}","{Title}","{Runtime}" strTitle, iDuration)
] VALUES (?, ?, ?, ?, ?)
update_song_rating = """ UPDATE song """
SET iTimesPlayed = ?, lastplayed = ?, rating = ? update_song_album_obj = ["{SongId}", "{AlbumId}", "{Index}", "{Title}", "{Runtime}"]
WHERE idSong = ? update_song_rating = """
""" UPDATE song
update_song_rating_obj = [ "{PlayCount}","{DatePlayed}","{Rating}","{KodiId}" SET iTimesPlayed = ?, lastplayed = ?, rating = ?
] WHERE idSong = ?
update_genre_album = """ INSERT OR REPLACE INTO album_genre(idGenre, idAlbum) """
VALUES (?, ?) update_song_rating_obj = ["{PlayCount}", "{DatePlayed}", "{Rating}", "{KodiId}"]
""" update_genre_album = """
update_genre_song = """ INSERT OR REPLACE INTO song_genre(idGenre, idSong) INSERT OR REPLACE INTO album_genre(idGenre, idAlbum)
VALUES (?, ?) VALUES (?, ?)
""" """
update_genre_song_obj = [ "{SongId}","{Genres}","song" update_genre_song = """
] INSERT OR REPLACE INTO song_genre(idGenre, idSong)
VALUES (?, ?)
"""
update_genre_song_obj = ["{SongId}", "{Genres}", "song"]
delete_genres_album = """
delete_genres_album = """ DELETE FROM album_genre DELETE FROM album_genre
WHERE idAlbum = ? WHERE idAlbum = ?
""" """
delete_genres_song = """ DELETE FROM song_genre delete_genres_song = """
WHERE idSong = ? DELETE FROM song_genre
""" WHERE idSong = ?
delete_artist = """ DELETE FROM artist """
WHERE idArtist = ? delete_artist = """
""" DELETE FROM artist
delete_album = """ DELETE FROM album WHERE idArtist = ?
WHERE idAlbum = ? """
""" delete_album = """
delete_song = """ DELETE FROM song DELETE FROM album
WHERE idSong = ? WHERE idAlbum = ?
""" """
get_version = """ SELECT idVersion delete_song = """
FROM version DELETE FROM song
""" WHERE idSong = ?
update_versiontag = """ INSERT OR REPLACE INTO versiontagscan(idVersion, iNeedsScan) """
VALUES (?, 0) get_version = """
""" SELECT idVersion
get_versiontagcount = """ SELECT COUNT(*) FROM version
FROM versiontagscan """
""" update_versiontag = """
INSERT OR REPLACE INTO versiontagscan(idVersion, iNeedsScan)
VALUES (?, 0)
"""
get_versiontagcount = """
SELECT COUNT (*)
FROM versiontagscan
"""

View file

@ -1,11 +1,12 @@
get_cache = """ SELECT cachedurl get_cache = """
FROM texture SELECT cachedurl
WHERE url = ? FROM texture
""" WHERE url = ?
"""
delete_cache = """
delete_cache = """ DELETE FROM texture DELETE FROM texture
WHERE url = ? WHERE url = ?
""" """

View file

@ -9,14 +9,13 @@ from kodi import Kodi
################################################################################################## ##################################################################################################
LOG = logging.getLogger("JELLYFIN."+__name__) LOG = logging.getLogger("JELLYFIN." + __name__)
################################################################################################## ##################################################################################################
class TVShows(Kodi): class TVShows(Kodi):
def __init__(self, cursor): def __init__(self, cursor):
self.cursor = cursor self.cursor = cursor
@ -24,7 +23,7 @@ class TVShows(Kodi):
def create_entry_unique_id(self): def create_entry_unique_id(self):
self.cursor.execute(QU.create_unique_id) self.cursor.execute(QU.create_unique_id)
return self.cursor.fetchone()[0] + 1 return self.cursor.fetchone()[0] + 1
def create_entry_rating(self): def create_entry_rating(self):
@ -39,12 +38,12 @@ class TVShows(Kodi):
def create_entry_season(self): def create_entry_season(self):
self.cursor.execute(QU.create_season) self.cursor.execute(QU.create_season)
return self.cursor.fetchone()[0] + 1 return self.cursor.fetchone()[0] + 1
def create_entry_episode(self): def create_entry_episode(self):
self.cursor.execute(QU.create_episode) self.cursor.execute(QU.create_episode)
return self.cursor.fetchone()[0] + 1 return self.cursor.fetchone()[0] + 1
def get(self, *args): def get(self, *args):
@ -69,7 +68,7 @@ class TVShows(Kodi):
try: try:
self.cursor.execute(QU.get_rating, args) self.cursor.execute(QU.get_rating, args)
return self.cursor.fetchone()[0] return self.cursor.fetchone()[0]
except TypeError: except TypeError:
return return
@ -93,7 +92,7 @@ class TVShows(Kodi):
try: try:
self.cursor.execute(QU.get_unique_id, args) self.cursor.execute(QU.get_unique_id, args)
return self.cursor.fetchone()[0] return self.cursor.fetchone()[0]
except TypeError: except TypeError:
return return

View file

@ -2,7 +2,6 @@
################################################################################################## ##################################################################################################
import json
import logging import logging
import urllib import urllib
@ -10,11 +9,11 @@ import downloader as server
from obj import Objects from obj import Objects
from kodi import Movies as KodiDb, queries as QU from kodi import Movies as KodiDb, queries as QU
from database import jellyfin_db, queries as QUEM from database import jellyfin_db, queries as QUEM
from helper import api, catch, stop, validate, jellyfin_item, library_check, values, settings, Local from helper import api, stop, validate, jellyfin_item, library_check, values, settings, Local
################################################################################################## ##################################################################################################
LOG = logging.getLogger("JELLYFIN."+__name__) LOG = logging.getLogger("JELLYFIN." + __name__)
################################################################################################## ##################################################################################################
@ -52,8 +51,7 @@ class Movies(KodiDb):
obj['MovieId'] = e_item[0] obj['MovieId'] = e_item[0]
obj['FileId'] = e_item[1] obj['FileId'] = e_item[1]
obj['PathId'] = e_item[2] obj['PathId'] = e_item[2]
except TypeError as error: except TypeError:
update = False update = False
LOG.debug("MovieId %s not found", obj['Id']) LOG.debug("MovieId %s not found", obj['Id'])
obj['MovieId'] = self.create_entry() obj['MovieId'] = self.create_entry()
@ -103,13 +101,11 @@ class Movies(KodiDb):
obj['Tags'] = tags obj['Tags'] = tags
if update: if update:
self.movie_update(obj) self.movie_update(obj)
else: else:
self.movie_add(obj) self.movie_add(obj)
self.update_path(*values(obj, QU.update_path_movie_obj)) self.update_path(*values(obj, QU.update_path_movie_obj))
self.update_file(*values(obj, QU.update_file_obj)) self.update_file(*values(obj, QU.update_file_obj))
self.add_tags(*values(obj, QU.add_tags_movie_obj)) self.add_tags(*values(obj, QU.add_tags_movie_obj))
@ -192,7 +188,6 @@ class Movies(KodiDb):
} }
obj['Filename'] = "%s?%s" % (obj['Path'], urllib.urlencode(params)) obj['Filename'] = "%s?%s" % (obj['Path'], urllib.urlencode(params))
@stop() @stop()
@jellyfin_item() @jellyfin_item()
def boxset(self, item, e_item): def boxset(self, item, e_item):
@ -213,8 +208,7 @@ class Movies(KodiDb):
try: try:
obj['SetId'] = e_item[0] obj['SetId'] = e_item[0]
self.update_boxset(*values(obj, QU.update_set_obj)) self.update_boxset(*values(obj, QU.update_set_obj))
except TypeError as error: except TypeError:
LOG.debug("SetId %s not found", obj['Id']) LOG.debug("SetId %s not found", obj['Id'])
obj['SetId'] = self.add_boxset(*values(obj, QU.add_set_obj)) obj['SetId'] = self.add_boxset(*values(obj, QU.add_set_obj))

View file

@ -2,19 +2,17 @@
################################################################################################## ##################################################################################################
import json
import datetime import datetime
import logging import logging
import urllib
from obj import Objects from obj import Objects
from kodi import Music as KodiDb, queries_music as QU from kodi import Music as KodiDb, queries_music as QU
from database import jellyfin_db, queries as QUEM from database import jellyfin_db, queries as QUEM
from helper import api, catch, stop, validate, jellyfin_item, values, library_check, settings, Local from helper import api, stop, validate, jellyfin_item, values, library_check, Local
################################################################################################## ##################################################################################################
LOG = logging.getLogger("JELLYFIN."+__name__) LOG = logging.getLogger("JELLYFIN." + __name__)
################################################################################################## ##################################################################################################
@ -50,8 +48,7 @@ class Music(KodiDb):
try: try:
obj['ArtistId'] = e_item[0] obj['ArtistId'] = e_item[0]
except TypeError as error: except TypeError:
update = False update = False
obj['ArtistId'] = None obj['ArtistId'] = None
LOG.debug("ArtistId %s not found", obj['Id']) LOG.debug("ArtistId %s not found", obj['Id'])
@ -77,13 +74,11 @@ class Music(KodiDb):
if obj['Backdrops']: if obj['Backdrops']:
obj['Backdrops'] = "<fanart>%s</fanart>" % obj['Backdrops'][0] obj['Backdrops'] = "<fanart>%s</fanart>" % obj['Backdrops'][0]
if update: if update:
self.artist_update(obj) self.artist_update(obj)
else: else:
self.artist_add(obj) self.artist_add(obj)
self.update(obj['Genre'], obj['Bio'], obj['Thumb'], obj['Backdrops'], obj['LastScraped'], obj['ArtistId']) self.update(obj['Genre'], obj['Bio'], obj['Thumb'], obj['Backdrops'], obj['LastScraped'], obj['ArtistId'])
self.artwork.add(obj['Artwork'], obj['ArtistId'], "artist") self.artwork.add(obj['Artwork'], obj['ArtistId'], "artist")
self.item_ids.append(obj['Id']) self.item_ids.append(obj['Id'])
@ -106,7 +101,6 @@ class Music(KodiDb):
self.jellyfin_db.update_reference(*values(obj, QUEM.update_reference_obj)) self.jellyfin_db.update_reference(*values(obj, QUEM.update_reference_obj))
LOG.info("UPDATE artist [%s] %s: %s", obj['ArtistId'], obj['Name'], obj['Id']) LOG.info("UPDATE artist [%s] %s: %s", obj['ArtistId'], obj['Name'], obj['Id'])
@stop() @stop()
@jellyfin_item() @jellyfin_item()
def album(self, item, e_item): def album(self, item, e_item):
@ -121,8 +115,7 @@ class Music(KodiDb):
try: try:
obj['AlbumId'] = e_item[0] obj['AlbumId'] = e_item[0]
except TypeError as error: except TypeError:
update = False update = False
obj['AlbumId'] = None obj['AlbumId'] = None
LOG.debug("AlbumId %s not found", obj['Id']) LOG.debug("AlbumId %s not found", obj['Id'])
@ -144,13 +137,11 @@ class Music(KodiDb):
if obj['Thumb']: if obj['Thumb']:
obj['Thumb'] = "<thumb>%s</thumb>" % obj['Thumb'] obj['Thumb'] = "<thumb>%s</thumb>" % obj['Thumb']
if update: if update:
self.album_update(obj) self.album_update(obj)
else: else:
self.album_add(obj) self.album_add(obj)
self.artist_link(obj) self.artist_link(obj)
self.artist_discography(obj) self.artist_discography(obj)
self.update_album(*values(obj, QU.update_album_obj)) self.update_album(*values(obj, QU.update_album_obj))
@ -217,7 +208,6 @@ class Music(KodiDb):
self.link(*values(temp_obj, QU.update_link_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() @stop()
@jellyfin_item() @jellyfin_item()
def song(self, item, e_item): def song(self, item, e_item):
@ -234,8 +224,7 @@ class Music(KodiDb):
obj['SongId'] = e_item[0] obj['SongId'] = e_item[0]
obj['PathId'] = e_item[2] obj['PathId'] = e_item[2]
obj['AlbumId'] = e_item[3] obj['AlbumId'] = e_item[3]
except TypeError as error: except TypeError:
update = False update = False
obj['SongId'] = self.create_entry_song() obj['SongId'] = self.create_entry_song()
LOG.debug("SongId %s not found", obj['Id']) LOG.debug("SongId %s not found", obj['Id'])
@ -269,15 +258,13 @@ class Music(KodiDb):
if obj['Disc'] != 1: if obj['Disc'] != 1:
obj['Index'] = obj['Disc'] * 2 ** 16 + obj['Index'] obj['Index'] = obj['Disc'] * 2 ** 16 + obj['Index']
if update: if update:
self.song_update(obj) self.song_update(obj)
else: else:
self.song_add(obj) self.song_add(obj)
self.link_song_album(*values(obj, QU.update_song_album_obj)) self.link_song_album(*values(obj, QU.update_song_album_obj))
self.add_role(*values(obj, QU.update_role_obj)) # defaultt role self.add_role(*values(obj, QU.update_role_obj)) # defaultt role
self.song_artist_link(obj) self.song_artist_link(obj)
self.song_artist_discography(obj) self.song_artist_discography(obj)
@ -415,7 +402,6 @@ class Music(KodiDb):
obj['AlbumId'] = self.create_entry_album() obj['AlbumId'] = self.create_entry_album()
self.add_single(*values(obj, QU.add_single_obj)) self.add_single(*values(obj, QU.add_single_obj))
@stop() @stop()
@jellyfin_item() @jellyfin_item()
def userdata(self, item, e_item): def userdata(self, item, e_item):

View file

@ -10,11 +10,11 @@ import urllib
from obj import Objects from obj import Objects
from kodi import MusicVideos as KodiDb, queries as QU from kodi import MusicVideos as KodiDb, queries as QU
from database import jellyfin_db, queries as QUEM from database import jellyfin_db, queries as QUEM
from helper import api, catch, stop, validate, library_check, jellyfin_item, values, Local from helper import api, stop, validate, library_check, jellyfin_item, values, Local
################################################################################################## ##################################################################################################
LOG = logging.getLogger("JELLYFIN."+__name__) LOG = logging.getLogger("JELLYFIN." + __name__)
################################################################################################## ##################################################################################################
@ -55,8 +55,7 @@ class MusicVideos(KodiDb):
obj['MvideoId'] = e_item[0] obj['MvideoId'] = e_item[0]
obj['FileId'] = e_item[1] obj['FileId'] = e_item[1]
obj['PathId'] = e_item[2] obj['PathId'] = e_item[2]
except TypeError as error: except TypeError:
update = False update = False
LOG.debug("MvideoId for %s not found", obj['Id']) LOG.debug("MvideoId for %s not found", obj['Id'])
obj['MvideoId'] = self.create_entry() obj['MvideoId'] = self.create_entry()
@ -114,13 +113,11 @@ class MusicVideos(KodiDb):
obj['Tags'] = tags obj['Tags'] = tags
if update: if update:
self.musicvideo_update(obj) self.musicvideo_update(obj)
else: else:
self.musicvideo_add(obj) self.musicvideo_add(obj)
self.update_path(*values(obj, QU.update_path_mvideo_obj)) self.update_path(*values(obj, QU.update_path_mvideo_obj))
self.update_file(*values(obj, QU.update_file_obj)) self.update_file(*values(obj, QU.update_file_obj))
self.add_tags(*values(obj, QU.add_tags_mvideo_obj)) self.add_tags(*values(obj, QU.add_tags_mvideo_obj))
@ -176,7 +173,6 @@ class MusicVideos(KodiDb):
} }
obj['Filename'] = "%s?%s" % (obj['Path'], urllib.urlencode(params)) obj['Filename'] = "%s?%s" % (obj['Path'], urllib.urlencode(params))
@stop() @stop()
@jellyfin_item() @jellyfin_item()
def userdata(self, item, e_item): def userdata(self, item, e_item):
@ -185,7 +181,7 @@ class MusicVideos(KodiDb):
Poster with progress bar Poster with progress bar
''' '''
server_data = self.server.auth.get_server_info(self.server.auth.server_id) server_data = self.server.auth.get_server_info(self.server.auth.server_id)
server_address = self.server.auth.get_server_address(server_data, server_data['LastConnectionMode']) server_address = self.server.auth.get_server_address(server_data, server_data['LastConnectionMode'])
API = api.API(item, server_address) API = api.API(item, server_address)
obj = self.objects.map(item, 'MusicVideoUserData') obj = self.objects.map(item, 'MusicVideoUserData')

View file

@ -8,7 +8,7 @@ import os
################################################################################################## ##################################################################################################
LOG = logging.getLogger("JELLYFIN."+__name__) LOG = logging.getLogger("JELLYFIN." + __name__)
################################################################################################## ##################################################################################################
@ -44,7 +44,7 @@ class Objects(object):
"$": lead the key name with $. Only one key value can be requested per element. "$": 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 ":": indicates it's a list of elements [], i.e. MediaSources/0/MediaStreams:?$Name
MediaStreams is a list. MediaStreams is a list.
"/": indicates where to go directly "/": indicates where to go directly
''' '''
self.mapped_item = {} self.mapped_item = {}
@ -145,7 +145,7 @@ class Objects(object):
result = False result = False
for key, value in filters.iteritems(): for key, value in filters.iteritems():
inverse = False inverse = False
if value.startswith('!'): if value.startswith('!'):

View file

@ -2,7 +2,6 @@
################################################################################################## ##################################################################################################
import json
import logging import logging
import sqlite3 import sqlite3
import urllib import urllib
@ -12,11 +11,11 @@ from obj import Objects
from kodi import TVShows as KodiDb, queries as QU from kodi import TVShows as KodiDb, queries as QU
import downloader as server import downloader as server
from database import jellyfin_db, queries as QUEM from database import jellyfin_db, queries as QUEM
from helper import api, catch, stop, validate, jellyfin_item, library_check, settings, values, Local from helper import api, stop, validate, jellyfin_item, library_check, settings, values, Local
################################################################################################## ##################################################################################################
LOG = logging.getLogger("JELLYFIN."+__name__) LOG = logging.getLogger("JELLYFIN." + __name__)
################################################################################################## ##################################################################################################
@ -65,8 +64,7 @@ class TVShows(KodiDb):
try: try:
obj['ShowId'] = e_item[0] obj['ShowId'] = e_item[0]
obj['PathId'] = e_item[2] obj['PathId'] = e_item[2]
except TypeError as error: except TypeError:
update = False update = False
LOG.debug("ShowId %s not found", obj['Id']) LOG.debug("ShowId %s not found", obj['Id'])
obj['ShowId'] = self.create_entry() obj['ShowId'] = self.create_entry()
@ -76,7 +74,6 @@ class TVShows(KodiDb):
update = False update = False
LOG.info("ShowId %s missing from kodi. repairing the entry.", obj['ShowId']) LOG.info("ShowId %s missing from kodi. repairing the entry.", obj['ShowId'])
obj['Path'] = API.get_file_path(obj['Path']) obj['Path'] = API.get_file_path(obj['Path'])
obj['LibraryId'] = library['Id'] obj['LibraryId'] = library['Id']
obj['LibraryName'] = library['Name'] obj['LibraryName'] = library['Name']
@ -107,13 +104,11 @@ class TVShows(KodiDb):
obj['Tags'] = tags obj['Tags'] = tags
if update: if update:
self.tvshow_update(obj) self.tvshow_update(obj)
else: else:
self.tvshow_add(obj) self.tvshow_add(obj)
self.link(*values(obj, QU.update_tvshow_link_obj)) self.link(*values(obj, QU.update_tvshow_link_obj))
self.update_path(*values(obj, QU.update_path_tvshow_obj)) self.update_path(*values(obj, QU.update_path_tvshow_obj))
self.add_tags(*values(obj, QU.add_tags_tvshow_obj)) self.add_tags(*values(obj, QU.add_tags_tvshow_obj))
@ -161,10 +156,10 @@ class TVShows(KodiDb):
''' Add object to kodi. ''' Add object to kodi.
''' '''
obj['RatingId'] = self.create_entry_rating() obj['RatingId'] = self.create_entry_rating()
self.add_ratings(*values(obj, QU.add_rating_tvshow_obj)) 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)) 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'])
@ -180,10 +175,10 @@ class TVShows(KodiDb):
''' Update object to kodi. ''' Update object to kodi.
''' '''
obj['RatingId'] = self.get_rating_id(*values(obj, QU.get_unique_id_tvshow_obj)) obj['RatingId'] = self.get_rating_id(*values(obj, QU.get_unique_id_tvshow_obj))
self.update_ratings(*values(obj, QU.update_rating_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)) self.update_unique_id(*values(obj, QU.update_unique_id_tvshow_obj))
self.update(*values(obj, QU.update_tvshow_obj)) self.update(*values(obj, QU.update_tvshow_obj))
@ -209,7 +204,6 @@ class TVShows(KodiDb):
obj['TopLevel'] = "plugin://plugin.video.jellyfin/" obj['TopLevel'] = "plugin://plugin.video.jellyfin/"
obj['Path'] = "%s%s/" % (obj['TopLevel'], obj['Id']) obj['Path'] = "%s%s/" % (obj['TopLevel'], obj['Id'])
@stop() @stop()
def season(self, item, show_id=None): def season(self, item, show_id=None):
@ -244,7 +238,6 @@ class TVShows(KodiDb):
self.artwork.add(obj['Artwork'], obj['SeasonId'], "season") self.artwork.add(obj['Artwork'], obj['SeasonId'], "season")
LOG.info("UPDATE season [%s/%s] %s: %s", obj['ShowId'], obj['SeasonId'], obj['Title'] or obj['Index'], obj['Id']) LOG.info("UPDATE season [%s/%s] %s: %s", obj['ShowId'], obj['SeasonId'], obj['Title'] or obj['Index'], obj['Id'])
@stop() @stop()
@jellyfin_item() @jellyfin_item()
def episode(self, item, e_item): def episode(self, item, e_item):
@ -275,8 +268,7 @@ class TVShows(KodiDb):
obj['EpisodeId'] = e_item[0] obj['EpisodeId'] = e_item[0]
obj['FileId'] = e_item[1] obj['FileId'] = e_item[1]
obj['PathId'] = e_item[2] obj['PathId'] = e_item[2]
except TypeError as error: except TypeError:
update = False update = False
LOG.debug("EpisodeId %s not found", obj['Id']) LOG.debug("EpisodeId %s not found", obj['Id'])
obj['EpisodeId'] = self.create_entry_episode() obj['EpisodeId'] = self.create_entry_episode()
@ -286,7 +278,6 @@ class TVShows(KodiDb):
update = False update = False
LOG.info("EpisodeId %s missing from kodi. repairing the entry.", obj['EpisodeId']) LOG.info("EpisodeId %s missing from kodi. repairing the entry.", obj['EpisodeId'])
obj['Path'] = API.get_file_path(obj['Path']) obj['Path'] = API.get_file_path(obj['Path'])
obj['Index'] = obj['Index'] or -1 obj['Index'] = obj['Index'] or -1
obj['Writers'] = " / ".join(obj['Writers'] or []) obj['Writers'] = " / ".join(obj['Writers'] or [])
@ -319,7 +310,7 @@ class TVShows(KodiDb):
if obj['AirsAfterSeason']: if obj['AirsAfterSeason']:
obj['AirsBeforeSeason'] = obj['AirsAfterSeason'] obj['AirsBeforeSeason'] = obj['AirsAfterSeason']
obj['AirsBeforeEpisode'] = 4096 # Kodi default number for afterseason ordering obj['AirsBeforeEpisode'] = 4096 # Kodi default number for afterseason ordering
if obj['MultiEpisode']: if obj['MultiEpisode']:
obj['Title'] = "| %02d | %s" % (obj['MultiEpisode'], obj['Title']) obj['Title'] = "| %02d | %s" % (obj['MultiEpisode'], obj['Title'])
@ -329,13 +320,11 @@ class TVShows(KodiDb):
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: if update:
self.episode_update(obj) self.episode_update(obj)
else: else:
self.episode_add(obj) self.episode_add(obj)
self.update_path(*values(obj, QU.update_path_episode_obj)) self.update_path(*values(obj, QU.update_path_episode_obj))
self.update_file(*values(obj, QU.update_file_obj)) self.update_file(*values(obj, QU.update_file_obj))
self.add_people(*values(obj, QU.add_people_episode_obj)) self.add_people(*values(obj, QU.add_people_episode_obj))
@ -359,10 +348,10 @@ class TVShows(KodiDb):
''' Add object to kodi. ''' Add object to kodi.
''' '''
obj['RatingId'] = self.create_entry_rating() obj['RatingId'] = self.create_entry_rating()
self.add_ratings(*values(obj, QU.add_rating_episode_obj)) 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)) self.add_unique_id(*values(obj, QU.add_unique_id_episode_obj))
obj['PathId'] = self.add_path(*values(obj, QU.add_path_obj)) obj['PathId'] = self.add_path(*values(obj, QU.add_path_obj))
@ -370,8 +359,7 @@ class TVShows(KodiDb):
try: try:
self.add_episode(*values(obj, QU.add_episode_obj)) self.add_episode(*values(obj, QU.add_episode_obj))
except sqlite3.IntegrityError as error: except sqlite3.IntegrityError:
LOG.error("IntegrityError for %s", obj) LOG.error("IntegrityError for %s", obj)
obj['EpisodeId'] = self.create_entry_episode() obj['EpisodeId'] = self.create_entry_episode()
@ -387,7 +375,7 @@ class TVShows(KodiDb):
obj['RatingId'] = self.get_rating_id(*values(obj, QU.get_rating_episode_obj)) obj['RatingId'] = self.get_rating_id(*values(obj, QU.get_rating_episode_obj))
self.update_ratings(*values(obj, QU.update_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_unique_id(*values(obj, QU.update_unique_id_episode_obj))
self.update_episode(*values(obj, QU.update_episode_obj)) self.update_episode(*values(obj, QU.update_episode_obj))
@ -440,7 +428,6 @@ class TVShows(KodiDb):
return True return True
@stop() @stop()
@jellyfin_item() @jellyfin_item()
def userdata(self, item, e_item): def userdata(self, item, e_item):

View file

@ -8,7 +8,7 @@ from helper import JSONRPC
################################################################################################# #################################################################################################
LOG = logging.getLogger("JELLYFIN."+__name__) LOG = logging.getLogger("JELLYFIN." + __name__)
################################################################################################# #################################################################################################

View file

@ -2,7 +2,6 @@
################################################################################################# #################################################################################################
import json
import logging import logging
import os import os
@ -15,7 +14,7 @@ from jellyfin import Jellyfin
################################################################################################# #################################################################################################
LOG = logging.getLogger("JELLYFIN."+__name__) LOG = logging.getLogger("JELLYFIN." + __name__)
################################################################################################# #################################################################################################
@ -326,7 +325,7 @@ class Player(xbmc.Player):
try: try:
played = float(item['CurrentPosition'] * 10000000) / int(item['Runtime']) * 100 played = float(item['CurrentPosition'] * 10000000) / int(item['Runtime']) * 100
except ZeroDivisionError: # Runtime is 0. except ZeroDivisionError: # Runtime is 0.
played = 0 played = 0
if played > 2.0 and not self.up_next: if played > 2.0 and not self.up_next:
@ -338,7 +337,6 @@ class Player(xbmc.Player):
return return
result = JSONRPC('Application.GetProperties').execute({'properties': ["volume", "muted"]}) result = JSONRPC('Application.GetProperties').execute({'properties': ["volume", "muted"]})
result = result.get('result', {}) result = result.get('result', {})
item['Volume'] = result.get('volume') item['Volume'] = result.get('volume')
@ -415,7 +413,6 @@ class Player(xbmc.Player):
LOG.info("<[ transcode/%s ]", item['Id']) LOG.info("<[ transcode/%s ]", item['Id'])
item['Server'].jellyfin.close_transcode(item['DeviceId']) item['Server'].jellyfin.close_transcode(item['DeviceId'])
path = xbmc.translatePath("special://profile/addon_data/plugin.video.jellyfin/temp/").decode('utf-8') path = xbmc.translatePath("special://profile/addon_data/plugin.video.jellyfin/temp/").decode('utf-8')
if xbmcvfs.exists(path): if xbmcvfs.exists(path):

View file

@ -4,13 +4,11 @@
import logging import logging
import xbmc from helper import _, settings, dialog, JSONRPC
from helper import _, settings, dialog, JSONRPC, compare_version
################################################################################################# #################################################################################################
LOG = logging.getLogger("JELLYFIN."+__name__) LOG = logging.getLogger("JELLYFIN." + __name__)
################################################################################################# #################################################################################################
@ -43,7 +41,7 @@ class Setup(object):
settings('enableTextureCache.bool', False) settings('enableTextureCache.bool', False)
dialog("ok", heading="{jellyfin}", line1=_(33103)) dialog("ok", heading="{jellyfin}", line1=_(33103))
return return
result = get_setting.execute({'setting': "services.webserverport"}) result = get_setting.execute({'setting': "services.webserverport"})

View file

@ -11,15 +11,13 @@ import xml.etree.ElementTree as etree
import xbmc import xbmc
import xbmcvfs import xbmcvfs
import downloader as server
from database import Database, jellyfin_db, get_sync, save_sync from database import Database, jellyfin_db, get_sync, save_sync
from objects.kodi import kodi
from helper import _, api, indent, write_xml, window, event from helper import _, api, indent, write_xml, window, event
from jellyfin import Jellyfin from jellyfin import Jellyfin
################################################################################################# #################################################################################################
LOG = logging.getLogger("JELLYFIN."+__name__) LOG = logging.getLogger("JELLYFIN." + __name__)
NODES = { NODES = {
'tvshows': [ 'tvshows': [
('all', None), ('all', None),
@ -70,7 +68,7 @@ DYNNODES = {
('FirstLetter', _(33171)), ('FirstLetter', _(33171)),
('Genres', _(135)), ('Genres', _(135)),
('Random', _(30229)), ('Random', _(30229)),
#('Recommended', _(30230)) # ('Recommended', _(30230))
], ],
'musicvideos': [ 'musicvideos': [
('all', None), ('all', None),
@ -136,6 +134,7 @@ def verify_kodi_defaults():
if not xbmcvfs.exists(playlist_path): if not xbmcvfs.exists(playlist_path):
xbmcvfs.mkdirs(playlist_path) xbmcvfs.mkdirs(playlist_path)
class Views(object): class Views(object):
sync = None sync = None
@ -246,7 +245,7 @@ class Views(object):
temp_view['Media'] = media temp_view['Media'] = media
self.add_playlist(playlist_path, temp_view, True) self.add_playlist(playlist_path, temp_view, True)
self.add_nodes(node_path, temp_view, True) self.add_nodes(node_path, temp_view, True)
else: # Compensate for the duplicate. else: # Compensate for the duplicate.
index += 1 index += 1
else: else:
if view['Media'] in ('movies', 'tvshows', 'musicvideos'): if view['Media'] in ('movies', 'tvshows', 'musicvideos'):
@ -421,7 +420,6 @@ class Views(object):
etree.SubElement(xml, 'match') etree.SubElement(xml, 'match')
etree.SubElement(xml, 'content') etree.SubElement(xml, 'content')
label = xml.find('label') label = xml.find('label')
label.text = str(name) if type(name) == int else name label.text = str(name) if type(name) == int else name
@ -438,7 +436,7 @@ class Views(object):
rule = etree.SubElement(xml, 'rule', {'field': "tag", 'operator': "is"}) rule = etree.SubElement(xml, 'rule', {'field': "tag", 'operator': "is"})
etree.SubElement(rule, 'value').text = view['Tag'] 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
indent(xml) indent(xml)
write_xml(etree.tostring(xml, 'UTF-8'), file) write_xml(etree.tostring(xml, 'UTF-8'), file)
@ -642,7 +640,7 @@ class Views(object):
if rule.attrib['field'] == 'inprogress': if rule.attrib['field'] == 'inprogress':
break break
else: 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" content.text = "episodes"
@ -661,7 +659,6 @@ class Views(object):
else: else:
etree.SubElement(root, 'content').text = "episodes" etree.SubElement(root, 'content').text = "episodes"
def order_media_folders(self, folders): def order_media_folders(self, folders):
''' Returns a list of sorted media folders based on the Jellyfin views. ''' Returns a list of sorted media folders based on the Jellyfin views.
@ -704,7 +701,7 @@ class Views(object):
for library in (libraries or []): for library in (libraries or []):
view = {'Id': library[0], 'Name': library[1], 'Tag': library[1], 'Media': library[2]} view = {'Id': library[0], 'Name': library[1], 'Tag': library[1], 'Media': library[2]}
if library[0] in [x.replace('Mixed:', "") for x in self.sync['Whitelist']]: # Synced libraries if library[0] 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'):
@ -718,7 +715,7 @@ class Views(object):
temp_view['Name'] = "%s (%s)" % (view['Name'], _(media)) temp_view['Name'] = "%s (%s)" % (view['Name'], _(media))
self.window_node(index, temp_view, *node) self.window_node(index, temp_view, *node)
self.window_wnode(windex, temp_view, *node) self.window_wnode(windex, temp_view, *node)
else: # Add one to compensate for the duplicate. else: # Add one to compensate for the duplicate.
index += 1 index += 1
windex += 1 windex += 1
else: else:
@ -734,7 +731,7 @@ class Views(object):
elif view['Media'] == 'music': elif view['Media'] == 'music':
self.window_node(index, view, 'music') self.window_node(index, view, 'music')
else: # Dynamic entry else: # Dynamic entry
if view['Media'] in ('homevideos', 'books', 'playlists'): if view['Media'] in ('homevideos', 'books', 'playlists'):
self.window_wnode(windex, view, 'browse') self.window_wnode(windex, view, 'browse')
windex += 1 windex += 1
@ -781,7 +778,7 @@ class Views(object):
if node in ('all', 'music'): if node in ('all', 'music'):
window_prop = "Jellyfin.nodes.%s" % index window_prop = "Jellyfin.nodes.%s" % index
window('%s.index' % window_prop, path.replace('all.xml', "")) # dir window('%s.index' % window_prop, path.replace('all.xml', "")) # dir
window('%s.title' % window_prop, view['Name'].encode('utf-8')) window('%s.title' % window_prop, view['Name'].encode('utf-8'))
window('%s.content' % window_prop, path) window('%s.content' % window_prop, path)
@ -833,7 +830,7 @@ class Views(object):
if node == 'all': if node == 'all':
window_prop = "Jellyfin.wnodes.%s" % index window_prop = "Jellyfin.wnodes.%s" % index
window('%s.index' % window_prop, path.replace('all.xml', "")) # dir window('%s.index' % window_prop, path.replace('all.xml', "")) # dir
window('%s.title' % window_prop, view['Name'].encode('utf-8')) window('%s.title' % window_prop, view['Name'].encode('utf-8'))
window('%s.content' % window_prop, path) window('%s.content' % window_prop, path)
@ -909,16 +906,16 @@ class Views(object):
total = int(window((name or 'Jellyfin.nodes') + '.total') or 0) total = int(window((name or 'Jellyfin.nodes') + '.total') or 0)
props = [ props = [
"index","id","path","artwork","title","content","type" "index", "id", "path", "artwork", "title", "content", "type"
"inprogress.content","inprogress.title", "inprogress.content", "inprogress.title",
"inprogress.content","inprogress.path", "inprogress.content", "inprogress.path",
"nextepisodes.title","nextepisodes.content", "nextepisodes.title", "nextepisodes.content",
"nextepisodes.path","unwatched.title", "nextepisodes.path", "unwatched.title",
"unwatched.content","unwatched.path", "unwatched.content", "unwatched.path",
"recent.title","recent.content","recent.path", "recent.title", "recent.content", "recent.path",
"recentepisodes.title","recentepisodes.content", "recentepisodes.title", "recentepisodes.content",
"recentepisodes.path","inprogressepisodes.title", "recentepisodes.path", "inprogressepisodes.title",
"inprogressepisodes.content","inprogressepisodes.path" "inprogressepisodes.content", "inprogressepisodes.path"
] ]
for i in range(total): for i in range(total):
for prop in props: for prop in props:

View file

@ -13,10 +13,11 @@ import xbmc
################################################################################################# #################################################################################################
PORT = 57578 PORT = 57578
LOG = logging.getLogger("JELLYFIN."+__name__) LOG = logging.getLogger("JELLYFIN." + __name__)
################################################################################################# #################################################################################################
class WebService(threading.Thread): class WebService(threading.Thread):
''' Run a webservice to trigger playback. ''' Run a webservice to trigger playback.
@ -127,7 +128,7 @@ class requestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
% (params.get('Id'), params.get('KodiId'), params.get('Name'), params.get('transcode') or False)) % (params.get('Id'), params.get('KodiId'), params.get('Name'), params.get('transcode') or False))
self.send_response(200) self.send_response(200)
self.send_header('Content-type','text/html') self.send_header('Content-type', 'text/html')
self.end_headers() self.end_headers()
self.wfile.write(path) self.wfile.write(path)
@ -142,4 +143,3 @@ class requestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
self.send_error(500, "Exception occurred: %s" % error) self.send_error(500, "Exception occurred: %s" % error)
return return

View file

@ -19,8 +19,8 @@ sys.path.insert(0, __base__)
################################################################################################# #################################################################################################
from entrypoint import Service from entrypoint import Service # noqa: F402
from helper import settings from helper import settings # noqa: F402
################################################################################################# #################################################################################################

View file

@ -2,3 +2,7 @@
max-line-length = 9999 max-line-length = 9999
import-order-style = pep8 import-order-style = pep8
exclude = ./.git,./.vscode,./libraries exclude = ./.git,./.vscode,./libraries
extend-ignore =
I202
per-file-ignores =
*/__init__.py: F401