Merge pull request #425 from mcarlton00/BEGONE-SATAN

Remove TheVoid
This commit is contained in:
Odd Stråbø 2020-11-26 16:55:27 +01:00 committed by GitHub
commit a7e4fa8a5c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 158 additions and 305 deletions

View file

@ -9,9 +9,8 @@ from datetime import date
from six.moves import range, queue as Queue from six.moves import range, queue as Queue
from kodi_six import xbmc
import requests import requests
from helper import settings, stop, event, window, create_id from helper import settings, stop, window
from jellyfin import Jellyfin from jellyfin import Jellyfin
from jellyfin import api from jellyfin import api
from helper.exceptions import HTTPException from helper.exceptions import HTTPException
@ -34,13 +33,6 @@ def get_jellyfinserver_url(handler):
return "{server}/%s" % handler return "{server}/%s" % handler
def browse_info():
return (
"DateCreated,EpisodeCount,SeasonCount,Path,Genres,Studios,Taglines,MediaStreams,Overview,Etag,"
"ProductionLocations,Width,Height,RecursiveItemCount,ChildCount"
)
def _http(action, url, request=None, server_id=None): def _http(action, url, request=None, server_id=None):
if request is None: if request is None:
@ -89,42 +81,6 @@ def get_single_item(parent_id, media):
}) })
def get_filtered_section(parent_id=None, media=None, limit=None, recursive=None, sort=None, sort_order=None,
filters=None, extra=None, server_id=None):
''' Get dynamic listings.
'''
params = {
'ParentId': parent_id,
'IncludeItemTypes': media,
'IsMissing': False,
'Recursive': recursive if recursive is not None else True,
'Limit': limit,
'SortBy': sort or "SortName",
'SortOrder': sort_order or "Ascending",
'ImageTypeLimit': 1,
'IsVirtualUnaired': False,
'Fields': browse_info()
}
if filters:
if 'Boxsets' in filters:
filters.remove('Boxsets')
params['CollapseBoxSetItems'] = settings('groupedSets.bool')
params['Filters'] = ','.join(filters)
if settings('getCast.bool'):
params['Fields'] += ",People"
if media and 'Photo' in media:
params['Fields'] += ",Width,Height"
if extra is not None:
params.update(extra)
return _get("Users/{UserId}/Items", params, server_id)
def get_movies_by_boxset(boxset_id): def get_movies_by_boxset(boxset_id):
for items in get_items(boxset_id, "Movie"): for items in get_items(boxset_id, "Movie"):
@ -381,43 +337,3 @@ class GetItemWorker(threading.Thread):
if window('jellyfin_should_stop.bool'): if window('jellyfin_should_stop.bool'):
break break
class TheVoid(object):
def __init__(self, method, data):
''' If you call get, this will block until response is received.
This is used to communicate between entrypoints.
'''
if type(data) != dict:
raise Exception("unexpected data format")
data['VoidName'] = str(create_id())
LOG.info("---[ contact MU-TH-UR 6000/%s ]", method)
LOG.debug(data)
event(method, data)
self.method = method
self.data = data
def get(self):
while True:
response = window('jellyfin_%s.json' % self.data['VoidName'])
if response != "":
LOG.debug("--<[ nostromo/jellyfin_%s.json ]", self.data['VoidName'])
window('jellyfin_%s' % self.data['VoidName'], clear=True)
return response
if window('jellyfin_should_stop.bool'):
LOG.info("Abandon mission! A black hole just swallowed [ %s/%s ]", self.method, self.data['VoidName'])
return
xbmc.sleep(100)
LOG.info("--[ void/%s ]", self.data['VoidName'])

View file

@ -11,8 +11,8 @@ from kodi_six import xbmc, xbmcaddon
import database import database
from dialogs import context from dialogs import context
from helper import translate, settings, dialog from helper import translate, settings, dialog
from downloader import TheVoid
from helper import LazyLogger from helper import LazyLogger
from jellyfin import Jellyfin
################################################################################################# #################################################################################################
@ -39,10 +39,11 @@ class Context(object):
try: try:
self.kodi_id = sys.listitem.getVideoInfoTag().getDbId() or None self.kodi_id = sys.listitem.getVideoInfoTag().getDbId() or None
self.media = self.get_media_type() self.media = self.get_media_type()
self.server = sys.listitem.getProperty('jellyfinserver') or None self.server_id = sys.listitem.getProperty('jellyfinserver') or None
self.api_client = Jellyfin(self.server_id).get_client().jellyfin
item_id = sys.listitem.getProperty('jellyfinid') item_id = sys.listitem.getProperty('jellyfinid')
except AttributeError: except AttributeError:
self.server = None self.server_id = None
if xbmc.getInfoLabel('ListItem.Property(jellyfinid)'): if xbmc.getInfoLabel('ListItem.Property(jellyfinid)'):
item_id = xbmc.getInfoLabel('ListItem.Property(jellyfinid)') item_id = xbmc.getInfoLabel('ListItem.Property(jellyfinid)')
@ -51,8 +52,8 @@ class Context(object):
self.media = xbmc.getInfoLabel('ListItem.DBTYPE') self.media = xbmc.getInfoLabel('ListItem.DBTYPE')
item_id = None item_id = None
if self.server or item_id: if self.server_id or item_id:
self.item = TheVoid('GetItem', {'ServerId': self.server, 'Id': item_id}).get() self.item = self.api_client.get_item(item_id)
else: else:
self.item = self.get_item_id() self.item = self.get_item_id()
@ -143,13 +144,13 @@ class Context(object):
selected = self._selected_option selected = self._selected_option
if selected == OPTIONS['Refresh']: if selected == OPTIONS['Refresh']:
TheVoid('RefreshItem', {'ServerId': self.server, 'Id': self.item['Id']}) self.api_client.refresh_item(self.item['Id'])
elif selected == OPTIONS['AddFav']: elif selected == OPTIONS['AddFav']:
TheVoid('FavoriteItem', {'ServerId': self.server, 'Id': self.item['Id'], 'Favorite': True}) self.api_client.favorite(self.item['Id'], True)
elif selected == OPTIONS['RemoveFav']: elif selected == OPTIONS['RemoveFav']:
TheVoid('FavoriteItem', {'ServerId': self.server, 'Id': self.item['Id'], 'Favorite': False}) self.api_client.favorite(self.item['Id'], False)
elif selected == OPTIONS['Addon']: elif selected == OPTIONS['Addon']:
xbmc.executebuiltin('Addon.OpenSettings(plugin.video.jellyfin)') xbmc.executebuiltin('Addon.OpenSettings(plugin.video.jellyfin)')
@ -159,7 +160,7 @@ class Context(object):
def delete_item(self): def delete_item(self):
if settings('skipContextMenu.bool') and dialog("yesno", "{jellyfin}", translate(33015)): if settings('skipContextMenu.bool') and dialog("yesno", "{jellyfin}", translate(33015)):
TheVoid('DeleteItem', {'ServerId': self.server, 'Id': self.item['Id']}) self.api_client.delete_item(self.item['Id'])
def transcode(self): def transcode(self):
filename = xbmc.getInfoLabel("ListItem.Filenameandpath") filename = xbmc.getInfoLabel("ListItem.Filenameandpath")

View file

@ -3,6 +3,7 @@ from __future__ import division, absolute_import, print_function, unicode_litera
################################################################################################# #################################################################################################
import json
import sys import sys
import os import os
@ -13,10 +14,10 @@ from kodi_six import xbmc, xbmcvfs, xbmcgui, xbmcplugin, xbmcaddon
import client import client
from database import reset, get_sync, Database, jellyfin_db, get_credentials from database import reset, get_sync, Database, jellyfin_db, get_credentials
from objects import Objects, Actions from objects import Objects, Actions
from downloader import TheVoid
from helper import translate, event, settings, window, dialog, api, JSONRPC from helper import translate, event, settings, window, dialog, api, JSONRPC
from helper.utils import JsonDebugPrinter from helper.utils import JsonDebugPrinter
from helper import LazyLogger from helper import LazyLogger
from jellyfin import Jellyfin
################################################################################################# #################################################################################################
@ -53,28 +54,48 @@ class Events(object):
if server == 'None': if server == 'None':
server = None server = None
jellyfin_client = Jellyfin(server).get_client()
api_client = jellyfin_client.jellyfin
addon_data = xbmc.translatePath("special://profile/addon_data/plugin.video.jellyfin/data.json")
with open(addon_data, 'rb') as infile:
data = json.load(infile)
try:
server_data = data['Servers'][0]
api_client.config.data['auth.server'] = server_data.get('address')
api_client.config.data['auth.server-name'] = server_data.get('Name')
api_client.config.data['auth.user_id'] = server_data.get('UserId')
api_client.config.data['auth.token'] = server_data.get('AccessToken')
except Exception as e:
LOG.warning('Addon appears to not be configured yet: {}'.format(e))
LOG.info("path: %s params: %s", path, JsonDebugPrinter(params)) LOG.info("path: %s params: %s", path, JsonDebugPrinter(params))
if '/extrafanart' in base_url: if '/extrafanart' in base_url:
jellyfin_path = path[1:] jellyfin_path = path[1:]
jellyfin_id = params.get('id') jellyfin_id = params.get('id')
get_fanart(jellyfin_id, jellyfin_path, server) get_fanart(jellyfin_id, jellyfin_path, server, api_client)
elif '/Extras' in base_url or '/VideoFiles' in base_url: elif '/Extras' in base_url or '/VideoFiles' in base_url:
jellyfin_path = path[1:] jellyfin_path = path[1:]
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, api_client)
elif mode == 'play': elif mode == 'play':
item = TheVoid('GetItem', {'Id': params['id'], 'ServerId': server}).get() item = api_client.get_item(params['id'])
item["resumePlayback"] = sys.argv[3].split(":")[1] == "true" item["resumePlayback"] = sys.argv[3].split(":")[1] == "true"
Actions(server).play(item, params.get('dbid'), params.get('transcode') == 'true', playlist=params.get('playlist') == 'true') Actions(server, api_client).play(item, params.get('dbid'), params.get('transcode') == 'true', playlist=params.get('playlist') == 'true')
elif mode == 'playlist': elif mode == 'playlist':
event('PlayPlaylist', {'Id': params['id'], 'ServerId': server}) api_client.post_session(api_client.config.data['app.session'], "Playing", {
'PlayCommand': "PlayNow",
'ItemIds': params['id'],
'StartPositionTicks': 0
})
elif mode == 'deviceid': elif mode == 'deviceid':
client.reset_device_id() client.reset_device_id()
elif mode == 'reset': elif mode == 'reset':
@ -86,7 +107,7 @@ class Events(object):
elif mode == 'nextepisodes': elif mode == 'nextepisodes':
get_next_episodes(params['id'], params['limit']) get_next_episodes(params['id'], params['limit'])
elif mode == 'browse': elif mode == 'browse':
browse(params.get('type'), params.get('id'), params.get('folder'), server) browse(params.get('type'), params.get('id'), params.get('folder'), server, api_client)
elif mode == 'synclib': elif mode == 'synclib':
event('SyncLibrary', {'Id': params.get('id')}) event('SyncLibrary', {'Id': params.get('id')})
elif mode == 'updatelib': elif mode == 'updatelib':
@ -112,11 +133,11 @@ class Events(object):
elif mode == 'settings': elif mode == 'settings':
xbmc.executebuiltin('Addon.OpenSettings(plugin.video.jellyfin)') xbmc.executebuiltin('Addon.OpenSettings(plugin.video.jellyfin)')
elif mode == 'adduser': elif mode == 'adduser':
add_user() add_user(api_client)
elif mode == 'updatepassword': elif mode == 'updatepassword':
event('UpdatePassword') event('UpdatePassword')
elif mode == 'thememedia': elif mode == 'thememedia':
get_themes() get_themes(api_client)
elif mode == 'managelibs': elif mode == 'managelibs':
manage_libraries() manage_libraries()
elif mode == 'backup': elif mode == 'backup':
@ -232,7 +253,7 @@ def manage_libraries():
xbmcplugin.endOfDirectory(PROCESS_HANDLE) xbmcplugin.endOfDirectory(PROCESS_HANDLE)
def browse(media, view_id=None, folder=None, server_id=None): def browse(media, view_id=None, folder=None, server_id=None, api_client=None):
''' Browse content dynamically. ''' Browse content dynamically.
''' '''
@ -262,7 +283,7 @@ def browse(media, view_id=None, folder=None, server_id=None):
if view_id: if view_id:
view = TheVoid('GetItem', {'ServerId': server_id, 'Id': view_id}).get() view = api_client.get_item(view_id)
xbmcplugin.setPluginCategory(PROCESS_HANDLE, view['Name']) xbmcplugin.setPluginCategory(PROCESS_HANDLE, view['Name'])
content_type = "files" content_type = "files"
@ -277,51 +298,49 @@ def browse(media, view_id=None, folder=None, server_id=None):
content_type = "artists" content_type = "artists"
if folder == 'recentlyadded': if folder == 'recentlyadded':
listing = TheVoid('RecentlyAdded', {'Id': view_id, 'ServerId': server_id}).get() listing = api_client.get_recently_added(None, view_id, None)
elif folder == 'genres': elif folder == 'genres':
listing = TheVoid('Genres', {'Id': view_id, 'ServerId': server_id}).get() listing = api_client.get_genres(view_id)
elif media == 'livetv': elif media == 'livetv':
listing = TheVoid('LiveTV', {'Id': view_id, 'ServerId': server_id}).get() listing = api_client.get_channels()
elif folder == 'unwatched': elif folder == 'unwatched':
listing = TheVoid('Browse', {'Id': view_id, 'ServerId': server_id, 'Filters': ['IsUnplayed']}).get() listing = get_filtered_section(view_id, None, None, None, None, None, ['IsUnplayed'], None, server_id, api_client)
elif folder == 'favorite': elif folder == 'favorite':
listing = TheVoid('Browse', {'Id': view_id, 'ServerId': server_id, 'Filters': ['IsFavorite']}).get() listing = get_filtered_section(view_id, None, None, None, None, None, ['IsFavorite'], None, server_id, api_client)
elif folder == 'inprogress': elif folder == 'inprogress':
listing = TheVoid('Browse', {'Id': view_id, 'ServerId': server_id, 'Filters': ['IsResumable']}).get() listing = get_filtered_section(view_id, None, None, None, None, None, ['IsResumable'], None, server_id, api_client)
elif folder == 'boxsets': elif folder == 'boxsets':
listing = TheVoid('Browse', {'Id': view_id, 'ServerId': server_id, 'Media': get_media_type('boxsets'), 'Recursive': True}).get() listing = get_filtered_section(view_id, get_media_type('boxsets'), None, True, None, None, None, None, server_id, api_client)
elif folder == 'random': elif folder == 'random':
listing = TheVoid('Browse', {'Id': view_id, 'ServerId': server_id, 'Media': get_media_type(content_type), 'Sort': "Random", 'Limit': 25, 'Recursive': True}).get() listing = get_filtered_section(view_id, get_media_type(content_type), 25, True, "Random", None, None, None, server_id, api_client)
elif (folder or "").startswith('firstletter-'): elif (folder or "").startswith('firstletter-'):
listing = TheVoid('Browse', {'Id': view_id, 'ServerId': server_id, 'Media': get_media_type(content_type), 'Params': {'NameStartsWith': folder.split('-')[1]}}).get() listing = get_filtered_section(view_id, get_media_type(content_type), None, None, None, None, None, {'NameStartsWith': folder.split('-')[1]}, server_id, api_client)
elif (folder or "").startswith('genres-'): elif (folder or "").startswith('genres-'):
listing = TheVoid('Browse', {'Id': view_id, 'ServerId': server_id, 'Media': get_media_type(content_type), 'Params': {'GenreIds': folder.split('-')[1]}}).get() listing = get_filtered_section(view_id, get_media_type(content_type), None, None, None, None, None, {'GenreIds': folder.split('-')[1]}, server_id, api_client)
elif folder == 'favepisodes': elif folder == 'favepisodes':
listing = TheVoid('Browse', {'Media': get_media_type(content_type), 'ServerId': server_id, 'Limit': 25, 'Filters': ['IsFavorite']}).get() listing = get_filtered_section(None, get_media_type(content_type), 25, None, None, None, ['IsFavorite'], None, server_id, api_client)
elif folder and media == 'playlists': elif folder and media == 'playlists':
listing = TheVoid('Browse', {'Id': folder, 'ServerId': server_id, 'Recursive': False, 'Sort': 'None'}).get() listing = get_filtered_section(folder, get_media_type(content_type), None, False, 'None', None, None, None, server_id, api_client)
elif media == 'homevideos': elif media == 'homevideos':
listing = TheVoid('Browse', {'Id': folder or view_id, 'Media': get_media_type(content_type), 'ServerId': server_id, 'Recursive': False}).get() listing = get_filtered_section(folder or view_id, get_media_type(content_type), None, False, None, None, None, None, server_id, api_client)
elif media == 'movies': elif media in ['movies', 'episodes']:
listing = TheVoid('Browse', {'Id': folder or view_id, 'Media': get_media_type(content_type), 'ServerId': server_id, 'Recursive': True}).get() listing = get_filtered_section(folder or view_id, get_media_type(content_type), None, True, None, None, None, None, server_id, api_client)
elif media in ('boxset', 'library'): elif media in ('boxset', 'library'):
listing = TheVoid('Browse', {'Id': folder or view_id, 'ServerId': server_id, 'Recursive': True}).get() listing = get_filtered_section(folder or view_id, None, None, True, None, None, None, None, server_id, api_client)
elif media == 'episodes':
listing = TheVoid('Browse', {'Id': folder or view_id, 'Media': get_media_type(content_type), 'ServerId': server_id, 'Recursive': True}).get()
elif media == 'boxsets': elif media == 'boxsets':
listing = TheVoid('Browse', {'Id': folder or view_id, 'ServerId': server_id, 'Recursive': False, 'Filters': ["Boxsets"]}).get() listing = get_filtered_section(folder or view_id, None, None, False, None, None, ['Boxsets'], None, server_id, api_client)
elif media == 'tvshows': elif media == 'tvshows':
listing = TheVoid('Browse', {'Id': folder or view_id, 'ServerId': server_id, 'Recursive': True, 'Media': get_media_type(content_type)}).get() listing = get_filtered_section(folder or view_id, get_media_type(content_type), None, True, None, None, None, None, server_id, api_client)
elif media == 'seasons': elif media == 'seasons':
listing = TheVoid('BrowseSeason', {'Id': folder, 'ServerId': server_id}).get() listing = api_client.get_seasons(folder)
elif media != 'files': elif media != 'files':
listing = TheVoid('Browse', {'Id': folder or view_id, 'ServerId': server_id, 'Recursive': False, 'Media': get_media_type(content_type)}).get() listing = get_filtered_section(folder or view_id, get_media_type(content_type), None, False, None, None, None, None, server_id, api_client)
else: else:
listing = TheVoid('Browse', {'Id': folder or view_id, 'ServerId': server_id, 'Recursive': False}).get() listing = get_filtered_section(folder or view_id, None, None, False, None, None, None, None, server_id, api_client)
if listing: if listing:
actions = Actions(server_id) actions = Actions(server_id, api_client)
list_li = [] list_li = []
listing = listing if type(listing) == list else listing.get('Items', []) listing = listing if type(listing) == list else listing.get('Items', [])
@ -405,7 +424,7 @@ def browse_subfolders(media, view_id, server_id=None):
''' '''
from views import DYNNODES from views import DYNNODES
view = TheVoid('GetItem', {'ServerId': server_id, 'Id': view_id}).get() view = Jellyfin(server_id).get_client().jellyfin.get_item(view_id)
xbmcplugin.setPluginCategory(PROCESS_HANDLE, view['Name']) xbmcplugin.setPluginCategory(PROCESS_HANDLE, view['Name'])
nodes = DYNNODES[media] nodes = DYNNODES[media]
@ -431,7 +450,7 @@ def browse_letters(media, view_id, server_id=None):
''' '''
letters = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ" letters = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ"
view = TheVoid('GetItem', {'ServerId': server_id, 'Id': view_id}).get() view = Jellyfin(server_id).get_client().jellyfin.get_item(view_id)
xbmcplugin.setPluginCategory(PROCESS_HANDLE, view['Name']) xbmcplugin.setPluginCategory(PROCESS_HANDLE, view['Name'])
for node in letters: for node in letters:
@ -486,7 +505,7 @@ def get_media_type(media):
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, api_client=None):
''' Get extra fanart for listitems. This is called by skinhelper. ''' Get extra fanart for listitems. This is called by skinhelper.
Images are stored locally, due to the Kodi caching system. Images are stored locally, due to the Kodi caching system.
@ -501,14 +520,13 @@ def get_fanart(item_id, path, server_id=None):
objects = Objects() objects = Objects()
list_li = [] list_li = []
directory = xbmc.translatePath("special://thumbnails/jellyfin/%s/" % item_id) directory = xbmc.translatePath("special://thumbnails/jellyfin/%s/" % item_id)
server = TheVoid('GetServerAddress', {'ServerId': server_id}).get()
if not xbmcvfs.exists(directory): if not xbmcvfs.exists(directory):
xbmcvfs.mkdirs(directory) xbmcvfs.mkdirs(directory)
item = TheVoid('GetItem', {'ServerId': server_id, 'Id': item_id}).get() item = api_client.get_item(item_id)
obj = objects.map(item, 'Artwork') obj = objects.map(item, 'Artwork')
backdrops = api.API(item, server).get_all_artwork(obj) backdrops = api.API(item).get_all_artwork(obj)
tags = obj['BackdropTags'] tags = obj['BackdropTags']
for index, backdrop in enumerate(backdrops): for index, backdrop in enumerate(backdrops):
@ -531,7 +549,7 @@ def get_fanart(item_id, path, server_id=None):
xbmcplugin.endOfDirectory(PROCESS_HANDLE) xbmcplugin.endOfDirectory(PROCESS_HANDLE)
def get_video_extras(item_id, path, server_id=None): def get_video_extras(item_id, path, server_id=None, api_client=None):
''' Returns the video files for the item as plugin listing, can be used ''' Returns the video files for the item as plugin listing, can be used
to browse actual files or video extras, etc. to browse actual files or video extras, etc.
@ -542,8 +560,8 @@ def get_video_extras(item_id, path, server_id=None):
if not item_id: if not item_id:
return return
TheVoid('GetItem', {'ServerId': server_id, 'Id': item_id}).get() # TODO implement????
# TODO: Investigate the void (issue #228) # Jellyfin(server_id).get_client().jellyfin.get_item(item_id)
""" """
def getVideoFiles(jellyfinId,jellyfinPath): def getVideoFiles(jellyfinId,jellyfinPath):
@ -730,15 +748,15 @@ def create_listitem(item):
return li return li
def add_user(): def add_user(api_client):
''' Add or remove users from the default server session. ''' Add or remove users from the default server session.
''' '''
if not window('jellyfin_online.bool'): if not window('jellyfin_online.bool'):
return return
session = TheVoid('GetSession', {}).get() session = api_client.get_device(client.get_device_id())
users = TheVoid('GetUsers', {'IsDisabled': False, 'IsHidden': False}).get() users = api_client.get_users()
current = session[0]['AdditionalUsers'] current = session[0]['AdditionalUsers']
result = dialog("select", translate(33061), [translate(33062), translate(33063)] if current else [translate(33062)]) result = dialog("select", translate(33061), [translate(33062), translate(33063)] if current else [translate(33062)])
@ -765,7 +783,7 @@ def add_user():
event('AddUser', {'Id': user['UserId'], 'Add': False}) event('AddUser', {'Id': user['UserId'], 'Add': False})
def get_themes(): def get_themes(api_client):
''' Add theme media locally, via strm. This is only for tv tunes. ''' Add theme media locally, via strm. This is only for tv tunes.
If another script is used, adjust this code. If another script is used, adjust this code.
@ -796,18 +814,17 @@ def get_themes():
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 = api_client.config.data['auth.server']
token = TheVoid('GetToken', {'ServerId': None}).get()
for view in views: for view in views:
result = TheVoid('GetThemes', {'Type': "Video", 'Id': view}).get() result = api_client.get_items_theme_video(view)
for item in result['Items']: for item in result['Items']:
folder = normalize_string(item['Name']) folder = normalize_string(item['Name'])
items[item['Id']] = folder items[item['Id']] = folder
result = TheVoid('GetThemes', {'Type': "Song", 'Id': view}).get() result = api_client.get_items_theme_song(view)
for item in result['Items']: for item in result['Items']:
@ -822,11 +839,11 @@ def get_themes():
if not xbmcvfs.exists(nfo_path): if not xbmcvfs.exists(nfo_path):
xbmcvfs.mkdir(nfo_path) xbmcvfs.mkdir(nfo_path)
themes = TheVoid('GetTheme', {'Id': item}).get() themes = api_client.get_themes(item)
paths = [] paths = []
for theme in themes['ThemeVideosResult']['Items'] + themes['ThemeSongsResult']['Items']: for theme in themes['ThemeVideosResult']['Items'] + themes['ThemeSongsResult']['Items']:
putils = PlayUtils(theme, False, None, server, token) putils = PlayUtils(theme, False, None, server, api_client)
if play: if play:
paths.append(putils.direct_play(theme['MediaSources'][0])) paths.append(putils.direct_play(theme['MediaSources'][0]))
@ -902,3 +919,46 @@ def backup():
LOG.info("backup completed") LOG.info("backup completed")
dialog("ok", "{jellyfin}", "%s %s" % (translate(33091), backup)) dialog("ok", "{jellyfin}", "%s %s" % (translate(33091), backup))
def get_filtered_section(parent_id=None, media=None, limit=None, recursive=None, sort=None, sort_order=None,
filters=None, extra=None, server_id=None, api_client=None):
''' Get dynamic listings.
'''
params = {
'ParentId': parent_id,
'IncludeItemTypes': media,
'IsMissing': False,
'Recursive': recursive if recursive is not None else True,
'Limit': limit,
'SortBy': sort or "SortName",
'SortOrder': sort_order or "Ascending",
'ImageTypeLimit': 1,
'IsVirtualUnaired': False,
'Fields': browse_info()
}
if filters:
if 'Boxsets' in filters:
filters.remove('Boxsets')
params['CollapseBoxSetItems'] = settings('groupedSets.bool')
params['Filters'] = ','.join(filters)
if settings('getCast.bool'):
params['Fields'] += ",People"
if media and 'Photo' in media:
params['Fields'] += ",Width,Height"
if extra is not None:
params.update(extra)
return api_client._get("Users/{UserId}/Items", params)
def browse_info():
return (
"DateCreated,EpisodeCount,SeasonCount,Path,Genres,Studios,Taglines,MediaStreams,Overview,Etag,"
"ProductionLocations,Width,Height,RecursiveItemCount,ChildCount"
)

View file

@ -11,7 +11,6 @@ from kodi_six import xbmc, xbmcvfs
import client import client
import requests import requests
from downloader import TheVoid
from helper import LazyLogger from helper import LazyLogger
from . import translate, settings, window, dialog, api from . import translate, settings, window, dialog, api
@ -53,30 +52,26 @@ def set_properties(item, method, server_id=None):
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, api_client=None):
''' Item will be updated with the property PlaybackInfo, which ''' Item will be updated with the property PlaybackInfo, which
holds all the playback information. holds all the playback information.
''' '''
self.item = item self.item = item
self.item['PlaybackInfo'] = {} self.item['PlaybackInfo'] = {}
self.api_client = api_client
self.info = { self.info = {
'ServerId': server_id, 'ServerId': server_id,
'ServerAddress': server, 'ServerAddress': server,
'ForceTranscode': force_transcode, 'ForceTranscode': force_transcode,
'Token': token or TheVoid('GetToken', {'ServerId': server_id}).get() 'Token': api_client.config.data['auth.token']
} }
def get_sources(self, source_id=None): def get_sources(self, source_id=None):
''' Return sources based on the optional source_id or the device profile. ''' Return sources based on the optional source_id or the device profile.
''' '''
params = { info = self.api_client.get_play_info(self.item['Id'], self.get_device_profile())
'ServerId': self.info['ServerId'],
'Id': self.item['Id'],
'Profile': self.get_device_profile()
}
info = TheVoid('GetPlaybackInfo', params).get()
LOG.info(info) LOG.info(info)
self.info['PlaySessionId'] = info['PlaySessionId'] self.info['PlaySessionId'] = info['PlaySessionId']
sources = [] sources = []
@ -217,14 +212,7 @@ class PlayUtils(object):
''' Get live stream media info. ''' Get live stream media info.
''' '''
params = { info = self.api_client.get_live_stream(self.item['Id'], self.info['PlaySessionId'], source['OpenToken'], self.get_device_profile())
'ServerId': self.info['ServerId'],
'Id': self.item['Id'],
'Profile': self.get_device_profile(),
'PlaySessionId': self.info['PlaySessionId'],
'Token': source['OpenToken']
}
info = TheVoid('GetLiveStream', params).get()
LOG.info(info) LOG.info(info)
if info['MediaSource'].get('RequiresClosing'): if info['MediaSource'].get('RequiresClosing'):
@ -509,7 +497,7 @@ class PlayUtils(object):
mapping = {} mapping = {}
kodi = 0 kodi = 0
server_settings = TheVoid('GetTranscodeOptions', {'ServerId': self.info['ServerId']}).get() server_settings = self.api_client.get_transcode_settings()
for stream in source['MediaStreams']: for stream in source['MediaStreams']:
if stream['SupportsExternalStream'] and stream['Type'] == 'Subtitle' and stream['DeliveryMethod'] == 'External': if stream['SupportsExternalStream'] and stream['Type'] == 'Subtitle' and stream['DeliveryMethod'] == 'External':
@ -598,7 +586,7 @@ class PlayUtils(object):
subs_streams = collections.OrderedDict() subs_streams = collections.OrderedDict()
streams = source['MediaStreams'] streams = source['MediaStreams']
server_settings = TheVoid('GetTranscodeOptions', {'ServerId': self.info['ServerId']}).get() server_settings = self.api_client.get_transcode_settings()
allow_burned_subs = settings('allowBurnedSubs.bool') allow_burned_subs = settings('allowBurnedSubs.bool')
for stream in streams: for stream in streams:
@ -665,6 +653,7 @@ class PlayUtils(object):
if subtitle: if subtitle:
index = subtitle index = subtitle
server_settings = self.api_client.get_transcode_settings()
stream = streams[index] stream = streams[index]
if server_settings['EnableSubtitleExtraction'] and stream['SupportsExternalStream']: if server_settings['EnableSubtitleExtraction'] and stream['SupportsExternalStream']:
@ -683,6 +672,8 @@ class PlayUtils(object):
index = subs_streams[selection[resp]] if resp > -1 else source.get('DefaultSubtitleStreamIndex') index = subs_streams[selection[resp]] if resp > -1 else source.get('DefaultSubtitleStreamIndex')
if index is not None: if index is not None:
server_settings = self.api_client.get_transcode_settings()
stream = streams[index] stream = streams[index]
if server_settings['EnableSubtitleExtraction'] and stream['SupportsExternalStream']: if server_settings['EnableSubtitleExtraction'] and stream['SupportsExternalStream']:

View file

@ -54,6 +54,7 @@ class Jellyfin(object):
self.server_id = server_id or "default" self.server_id = server_id or "default"
def get_client(self): def get_client(self):
# type: () -> JellyfinClient
return self.client[self.server_id] return self.client[self.server_id]
def close(self): def close(self):

View file

@ -204,6 +204,9 @@ class ConnectionManager(object):
if server['Id'] == server_id: if server['Id'] == server_id:
return server return server
def get_server_address(self, server_id):
return self.get_server_info(server_id or self.server_id).get('address')
def get_public_users(self): def get_public_users(self):
return self.client.jellyfin.get_public_users() return self.client.jellyfin.get_public_users()

View file

@ -10,7 +10,6 @@ import threading
from kodi_six import xbmc from kodi_six import xbmc
import connect import connect
import downloader
import player import player
from client import get_device_id from client import get_device_id
from objects import PlaylistWorker, on_play, on_update, special_listener from objects import PlaylistWorker, on_play, on_update, special_listener
@ -53,12 +52,7 @@ class Monitor(xbmc.Monitor):
if sender == 'plugin.video.jellyfin': if sender == 'plugin.video.jellyfin':
method = method.split('.')[1] method = method.split('.')[1]
if method not in ('GetItem', 'ReportProgressRequested', 'LoadServer', 'RandomItems', 'Recommended', if method not in ('ReportProgressRequested', 'LoadServer', 'AddUser', 'PlayPlaylist', 'Play', 'Playstate', 'GeneralCommand'):
'GetServerAddress', 'GetPlaybackInfo', 'Browse', 'GetImages', 'GetToken',
'PlayPlaylist', 'Play', 'GetIntros', 'GetAdditionalParts', 'RefreshItem', 'Genres',
'FavoriteItem', 'DeleteItem', 'AddUser', 'GetSession', 'GetUsers', 'GetThemes',
'GetTheme', 'Playstate', 'GeneralCommand', 'GetTranscodeOptions', 'RecentlyAdded',
'BrowseSeason', 'LiveTV', 'GetLiveStream'):
return return
data = json.loads(data)[0] data = json.loads(data)[0]
@ -122,116 +116,17 @@ class Monitor(xbmc.Monitor):
LOG.exception(error) LOG.exception(error)
server = Jellyfin() server = Jellyfin()
if method == 'GetItem': server = server.get_client()
item = server.jellyfin.get_item(data['Id']) if method == 'Play':
self.void_responder(data, item)
elif method == 'GetAdditionalParts': items = server.jellyfin.get_items(data['ItemIds'])
item = server.jellyfin.get_additional_parts(data['Id']) PlaylistWorker(data.get('ServerId'), items, data['PlayCommand'] == 'PlayNow',
self.void_responder(data, item) data.get('StartPositionTicks', 0), data.get('AudioStreamIndex'),
data.get('SubtitleStreamIndex')).start()
elif method == 'GetIntros':
item = server.jellyfin.get_intros(data['Id'])
self.void_responder(data, item)
elif method == 'GetImages':
item = server.jellyfin.get_images(data['Id'])
self.void_responder(data, item)
elif method == 'GetServerAddress':
server_address = server.auth.get_server_info(server.auth.server_id)['address']
self.void_responder(data, server_address)
elif method == 'GetPlaybackInfo':
sources = server.jellyfin.get_play_info(data['Id'], data['Profile'])
self.void_responder(data, sources)
elif method == 'GetLiveStream':
sources = server.jellyfin.get_live_stream(data['Id'], data['PlaySessionId'], data['Token'], data['Profile'])
self.void_responder(data, sources)
elif method == 'GetToken':
token = server.auth.jellyfin_token()
self.void_responder(data, token)
elif method == 'GetSession':
session = server.jellyfin.get_device(self.device_id)
self.void_responder(data, session)
elif method == 'GetUsers':
users = server.jellyfin.get_users()
self.void_responder(data, users)
elif method == 'GetTranscodeOptions':
result = server.jellyfin.get_transcode_settings()
self.void_responder(data, result)
elif method == 'GetThemes':
if data['Type'] == 'Video':
theme = server.jellyfin.get_items_theme_video(data['Id'])
else:
theme = server.jellyfin.get_items_theme_song(data['Id'])
self.void_responder(data, theme)
elif method == 'GetTheme':
theme = server.jellyfin.get_themes(data['Id'])
self.void_responder(data, theme)
elif method == 'Browse':
result = downloader.get_filtered_section(data.get('Id'), data.get('Media'), data.get('Limit'),
data.get('Recursive'), data.get('Sort'), data.get('SortOrder'),
data.get('Filters'), data.get('Params'), data.get('ServerId'))
self.void_responder(data, result)
elif method == 'BrowseSeason':
result = server.jellyfin.get_seasons(data['Id'])
self.void_responder(data, result)
elif method == 'LiveTV':
result = server.jellyfin.get_channels()
self.void_responder(data, result)
elif method == 'RecentlyAdded':
result = server.jellyfin.get_recently_added(data.get('Media'), data.get('Id'), data.get('Limit'))
self.void_responder(data, result)
elif method == 'Genres':
result = server.jellyfin.get_genres(data.get('Id'))
self.void_responder(data, result)
elif method == 'Recommended':
result = server.jellyfin.get_recommendation(data.get('Id'), data.get('Limit'))
self.void_responder(data, result)
elif method == 'RefreshItem':
server.jellyfin.refresh_item(data['Id'])
elif method == 'FavoriteItem':
server.jellyfin.favorite(data['Id'], data['Favorite'])
elif method == 'DeleteItem':
server.jellyfin.delete_item(data['Id'])
# TODO no clue if this is called by anything
elif method == 'PlayPlaylist': elif method == 'PlayPlaylist':
server.jellyfin.post_session(server.config.data['app.session'], "Playing", { server.jellyfin.post_session(server.config.data['app.session'], "Playing", {
@ -240,14 +135,6 @@ class Monitor(xbmc.Monitor):
'StartPositionTicks': 0 'StartPositionTicks': 0
}) })
elif method == 'Play':
items = server.jellyfin.get_items(data['ItemIds'])
PlaylistWorker(data.get('ServerId'), items, data['PlayCommand'] == 'PlayNow',
data.get('StartPositionTicks', 0), data.get('AudioStreamIndex'),
data.get('SubtitleStreamIndex')).start()
elif method in ('ReportProgressRequested', 'Player.OnAVChange'): elif method in ('ReportProgressRequested', 'Player.OnAVChange'):
self.player.report_playback(data.get('Report', True)) self.player.report_playback(data.get('Report', True))
@ -270,14 +157,9 @@ class Monitor(xbmc.Monitor):
elif method == 'VideoLibrary.OnUpdate': elif method == 'VideoLibrary.OnUpdate':
on_update(data, server) on_update(data, server)
def void_responder(self, data, result):
window('jellyfin_%s.json' % data['VoidName'], result)
LOG.debug("--->[ nostromo/jellyfin_%s.json ] sent", data['VoidName'])
def server_instance(self, server_id=None): def server_instance(self, server_id=None):
server = Jellyfin(server_id) server = Jellyfin(server_id).get_client()
self.post_capabilities(server) self.post_capabilities(server)
if server_id is not None: if server_id is not None:

View file

@ -10,7 +10,6 @@ from datetime import timedelta
from kodi_six import xbmc, xbmcgui, xbmcplugin, xbmcaddon from kodi_six import xbmc, xbmcgui, xbmcplugin, xbmcaddon
import database import database
from downloader import TheVoid
from helper import translate, playutils, api, window, settings, dialog from helper import translate, playutils, api, window, settings, dialog
from dialogs import resume from dialogs import resume
from helper import LazyLogger from helper import LazyLogger
@ -26,10 +25,11 @@ LOG = LazyLogger(__name__)
class Actions(object): class Actions(object):
def __init__(self, server_id=None): def __init__(self, server_id=None, api_client=None):
self.server_id = server_id or None self.server_id = server_id or None
self.server = TheVoid('GetServerAddress', {'ServerId': self.server_id}).get() self.api_client = api_client
self.server = self.api_client.config.data['auth.server']
self.stack = [] self.stack = []
def get_playlist(self, item): def get_playlist(self, item):
@ -50,7 +50,7 @@ class Actions(object):
transcode = transcode or settings('playFromTranscode.bool') transcode = transcode or settings('playFromTranscode.bool')
kodi_playlist = self.get_playlist(item) kodi_playlist = self.get_playlist(item)
play = playutils.PlayUtils(item, transcode, self.server_id, self.server) play = playutils.PlayUtils(item, transcode, self.server_id, self.server, self.api_client)
source = play.select_source(play.get_sources()) source = play.select_source(play.get_sources())
play.set_external_subs(source, listitem) play.set_external_subs(source, listitem)
@ -112,7 +112,7 @@ class Actions(object):
''' if we have any play them when the movie/show is not being resumed. ''' if we have any play them when the movie/show is not being resumed.
''' '''
intros = TheVoid('GetIntros', {'ServerId': self.server_id, 'Id': item['Id']}).get() intros = self.api_client.get_intros(item['Id'])
if intros['Items']: if intros['Items']:
enabled = True enabled = True
@ -131,7 +131,7 @@ class Actions(object):
listitem = xbmcgui.ListItem() listitem = xbmcgui.ListItem()
LOG.info("[ intro/%s ] %s", intro['Id'], intro['Name']) LOG.info("[ intro/%s ] %s", intro['Id'], intro['Name'])
play = playutils.PlayUtils(intro, False, self.server_id, self.server) play = playutils.PlayUtils(intro, False, self.server_id, self.server, self.api_client)
play.select_source(play.get_sources()) play.select_source(play.get_sources())
self.set_listitem(intro, listitem, intro=True) self.set_listitem(intro, listitem, intro=True)
listitem.setPath(intro['PlaybackInfo']['Path']) listitem.setPath(intro['PlaybackInfo']['Path'])
@ -145,14 +145,13 @@ class Actions(object):
''' Create listitems and add them to the stack of playlist. ''' Create listitems and add them to the stack of playlist.
''' '''
parts = TheVoid('GetAdditionalParts', {'ServerId': self.server_id, 'Id': item_id}).get() parts = self.api_client.get_additional_parts(item_id)
for part in parts['Items']: for part in parts['Items']:
listitem = xbmcgui.ListItem() listitem = xbmcgui.ListItem()
LOG.info("[ part/%s ] %s", part['Id'], part['Name']) LOG.info("[ part/%s ] %s", part['Id'], part['Name'])
play = playutils.PlayUtils(part, False, self.server_id, self.server) play = playutils.PlayUtils(part, False, self.server_id, self.server, self.api_client)
source = play.select_source(play.get_sources()) source = play.select_source(play.get_sources())
play.set_external_subs(source, listitem) play.set_external_subs(source, listitem)
self.set_listitem(part, listitem) self.set_listitem(part, listitem)
@ -183,7 +182,7 @@ class Actions(object):
listitem = xbmcgui.ListItem() listitem = xbmcgui.ListItem()
LOG.info("[ playlist/%s ] %s", item['Id'], item['Name']) LOG.info("[ playlist/%s ] %s", item['Id'], item['Name'])
play = playutils.PlayUtils(item, False, self.server_id, self.server) play = playutils.PlayUtils(item, False, self.server_id, self.server, self.api_client)
source = play.select_source(play.get_sources()) source = play.select_source(play.get_sources())
play.set_external_subs(source, listitem) play.set_external_subs(source, listitem)
@ -213,7 +212,7 @@ class Actions(object):
server_address, item['Id'], token) server_address, item['Id'], token)
listitem.setPath(path) listitem.setPath(path)
play = playutils.PlayUtils(item, False, self.server_id, self.server) play = playutils.PlayUtils(item, False, self.server_id, self.server, self.api_client)
source = play.select_source(play.get_sources()) source = play.select_source(play.get_sources())
play.set_external_subs(source, listitem) play.set_external_subs(source, listitem)