mirror of
https://github.com/jellyfin/jellyfin-kodi.git
synced 2025-01-27 10:26:10 +00:00
commit
a7e4fa8a5c
8 changed files with 158 additions and 305 deletions
|
@ -9,9 +9,8 @@ from datetime import date
|
|||
|
||||
from six.moves import range, queue as Queue
|
||||
|
||||
from kodi_six import xbmc
|
||||
import requests
|
||||
from helper import settings, stop, event, window, create_id
|
||||
from helper import settings, stop, window
|
||||
from jellyfin import Jellyfin
|
||||
from jellyfin import api
|
||||
from helper.exceptions import HTTPException
|
||||
|
@ -34,13 +33,6 @@ def get_jellyfinserver_url(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):
|
||||
|
||||
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):
|
||||
|
||||
for items in get_items(boxset_id, "Movie"):
|
||||
|
@ -381,43 +337,3 @@ class GetItemWorker(threading.Thread):
|
|||
|
||||
if window('jellyfin_should_stop.bool'):
|
||||
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'])
|
||||
|
|
|
@ -11,8 +11,8 @@ from kodi_six import xbmc, xbmcaddon
|
|||
import database
|
||||
from dialogs import context
|
||||
from helper import translate, settings, dialog
|
||||
from downloader import TheVoid
|
||||
from helper import LazyLogger
|
||||
from jellyfin import Jellyfin
|
||||
|
||||
#################################################################################################
|
||||
|
||||
|
@ -39,10 +39,11 @@ class Context(object):
|
|||
try:
|
||||
self.kodi_id = sys.listitem.getVideoInfoTag().getDbId() or None
|
||||
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')
|
||||
except AttributeError:
|
||||
self.server = None
|
||||
self.server_id = None
|
||||
|
||||
if 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')
|
||||
item_id = None
|
||||
|
||||
if self.server or item_id:
|
||||
self.item = TheVoid('GetItem', {'ServerId': self.server, 'Id': item_id}).get()
|
||||
if self.server_id or item_id:
|
||||
self.item = self.api_client.get_item(item_id)
|
||||
else:
|
||||
self.item = self.get_item_id()
|
||||
|
||||
|
@ -143,13 +144,13 @@ class Context(object):
|
|||
selected = self._selected_option
|
||||
|
||||
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']:
|
||||
TheVoid('FavoriteItem', {'ServerId': self.server, 'Id': self.item['Id'], 'Favorite': True})
|
||||
self.api_client.favorite(self.item['Id'], True)
|
||||
|
||||
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']:
|
||||
xbmc.executebuiltin('Addon.OpenSettings(plugin.video.jellyfin)')
|
||||
|
@ -159,7 +160,7 @@ class Context(object):
|
|||
|
||||
def delete_item(self):
|
||||
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):
|
||||
filename = xbmc.getInfoLabel("ListItem.Filenameandpath")
|
||||
|
|
|
@ -3,6 +3,7 @@ from __future__ import division, absolute_import, print_function, unicode_litera
|
|||
|
||||
#################################################################################################
|
||||
|
||||
import json
|
||||
import sys
|
||||
import os
|
||||
|
||||
|
@ -13,10 +14,10 @@ from kodi_six import xbmc, xbmcvfs, xbmcgui, xbmcplugin, xbmcaddon
|
|||
import client
|
||||
from database import reset, get_sync, Database, jellyfin_db, get_credentials
|
||||
from objects import Objects, Actions
|
||||
from downloader import TheVoid
|
||||
from helper import translate, event, settings, window, dialog, api, JSONRPC
|
||||
from helper.utils import JsonDebugPrinter
|
||||
from helper import LazyLogger
|
||||
from jellyfin import Jellyfin
|
||||
|
||||
#################################################################################################
|
||||
|
||||
|
@ -53,28 +54,48 @@ class Events(object):
|
|||
if 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))
|
||||
|
||||
if '/extrafanart' in base_url:
|
||||
|
||||
jellyfin_path = path[1:]
|
||||
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:
|
||||
|
||||
jellyfin_path = path[1:]
|
||||
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':
|
||||
|
||||
item = TheVoid('GetItem', {'Id': params['id'], 'ServerId': server}).get()
|
||||
item = api_client.get_item(params['id'])
|
||||
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':
|
||||
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':
|
||||
client.reset_device_id()
|
||||
elif mode == 'reset':
|
||||
|
@ -86,7 +107,7 @@ class Events(object):
|
|||
elif mode == 'nextepisodes':
|
||||
get_next_episodes(params['id'], params['limit'])
|
||||
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':
|
||||
event('SyncLibrary', {'Id': params.get('id')})
|
||||
elif mode == 'updatelib':
|
||||
|
@ -112,11 +133,11 @@ class Events(object):
|
|||
elif mode == 'settings':
|
||||
xbmc.executebuiltin('Addon.OpenSettings(plugin.video.jellyfin)')
|
||||
elif mode == 'adduser':
|
||||
add_user()
|
||||
add_user(api_client)
|
||||
elif mode == 'updatepassword':
|
||||
event('UpdatePassword')
|
||||
elif mode == 'thememedia':
|
||||
get_themes()
|
||||
get_themes(api_client)
|
||||
elif mode == 'managelibs':
|
||||
manage_libraries()
|
||||
elif mode == 'backup':
|
||||
|
@ -232,7 +253,7 @@ def manage_libraries():
|
|||
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.
|
||||
'''
|
||||
|
@ -262,7 +283,7 @@ def browse(media, view_id=None, folder=None, server_id=None):
|
|||
|
||||
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'])
|
||||
|
||||
content_type = "files"
|
||||
|
@ -277,51 +298,49 @@ def browse(media, view_id=None, folder=None, server_id=None):
|
|||
content_type = "artists"
|
||||
|
||||
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':
|
||||
listing = TheVoid('Genres', {'Id': view_id, 'ServerId': server_id}).get()
|
||||
listing = api_client.get_genres(view_id)
|
||||
elif media == 'livetv':
|
||||
listing = TheVoid('LiveTV', {'Id': view_id, 'ServerId': server_id}).get()
|
||||
listing = api_client.get_channels()
|
||||
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':
|
||||
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':
|
||||
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':
|
||||
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':
|
||||
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-'):
|
||||
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-'):
|
||||
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':
|
||||
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':
|
||||
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':
|
||||
listing = TheVoid('Browse', {'Id': folder or view_id, 'Media': get_media_type(content_type), 'ServerId': server_id, 'Recursive': False}).get()
|
||||
elif media == 'movies':
|
||||
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, False, None, None, None, None, server_id, api_client)
|
||||
elif media in ['movies', 'episodes']:
|
||||
listing = get_filtered_section(folder or view_id, get_media_type(content_type), None, True, None, None, None, None, server_id, api_client)
|
||||
elif media in ('boxset', 'library'):
|
||||
listing = TheVoid('Browse', {'Id': folder or view_id, 'ServerId': server_id, 'Recursive': True}).get()
|
||||
elif media == '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, None, None, True, None, None, None, None, server_id, api_client)
|
||||
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':
|
||||
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':
|
||||
listing = TheVoid('BrowseSeason', {'Id': folder, 'ServerId': server_id}).get()
|
||||
listing = api_client.get_seasons(folder)
|
||||
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:
|
||||
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:
|
||||
|
||||
actions = Actions(server_id)
|
||||
actions = Actions(server_id, api_client)
|
||||
list_li = []
|
||||
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
|
||||
|
||||
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'])
|
||||
nodes = DYNNODES[media]
|
||||
|
||||
|
@ -431,7 +450,7 @@ def browse_letters(media, view_id, server_id=None):
|
|||
'''
|
||||
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'])
|
||||
|
||||
for node in letters:
|
||||
|
@ -486,7 +505,7 @@ def get_media_type(media):
|
|||
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.
|
||||
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()
|
||||
list_li = []
|
||||
directory = xbmc.translatePath("special://thumbnails/jellyfin/%s/" % item_id)
|
||||
server = TheVoid('GetServerAddress', {'ServerId': server_id}).get()
|
||||
|
||||
if not xbmcvfs.exists(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')
|
||||
backdrops = api.API(item, server).get_all_artwork(obj)
|
||||
backdrops = api.API(item).get_all_artwork(obj)
|
||||
tags = obj['BackdropTags']
|
||||
|
||||
for index, backdrop in enumerate(backdrops):
|
||||
|
@ -531,7 +549,7 @@ def get_fanart(item_id, path, server_id=None):
|
|||
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
|
||||
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:
|
||||
return
|
||||
|
||||
TheVoid('GetItem', {'ServerId': server_id, 'Id': item_id}).get()
|
||||
# TODO: Investigate the void (issue #228)
|
||||
# TODO implement????
|
||||
# Jellyfin(server_id).get_client().jellyfin.get_item(item_id)
|
||||
|
||||
"""
|
||||
def getVideoFiles(jellyfinId,jellyfinPath):
|
||||
|
@ -730,15 +748,15 @@ def create_listitem(item):
|
|||
return li
|
||||
|
||||
|
||||
def add_user():
|
||||
def add_user(api_client):
|
||||
|
||||
''' Add or remove users from the default server session.
|
||||
'''
|
||||
if not window('jellyfin_online.bool'):
|
||||
return
|
||||
|
||||
session = TheVoid('GetSession', {}).get()
|
||||
users = TheVoid('GetUsers', {'IsDisabled': False, 'IsHidden': False}).get()
|
||||
session = api_client.get_device(client.get_device_id())
|
||||
users = api_client.get_users()
|
||||
current = session[0]['AdditionalUsers']
|
||||
|
||||
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})
|
||||
|
||||
|
||||
def get_themes():
|
||||
def get_themes(api_client):
|
||||
|
||||
''' Add theme media locally, via strm. This is only for tv tunes.
|
||||
If another script is used, adjust this code.
|
||||
|
@ -796,18 +814,17 @@ def get_themes():
|
|||
views = [x[0] for x in all_views if x[2] in ('movies', 'tvshows', 'mixed')]
|
||||
|
||||
items = {}
|
||||
server = TheVoid('GetServerAddress', {'ServerId': None}).get()
|
||||
token = TheVoid('GetToken', {'ServerId': None}).get()
|
||||
server = api_client.config.data['auth.server']
|
||||
|
||||
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']:
|
||||
|
||||
folder = normalize_string(item['Name'])
|
||||
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']:
|
||||
|
||||
|
@ -822,11 +839,11 @@ def get_themes():
|
|||
if not xbmcvfs.exists(nfo_path):
|
||||
xbmcvfs.mkdir(nfo_path)
|
||||
|
||||
themes = TheVoid('GetTheme', {'Id': item}).get()
|
||||
themes = api_client.get_themes(item)
|
||||
paths = []
|
||||
|
||||
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:
|
||||
paths.append(putils.direct_play(theme['MediaSources'][0]))
|
||||
|
@ -902,3 +919,46 @@ def backup():
|
|||
|
||||
LOG.info("backup completed")
|
||||
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"
|
||||
)
|
||||
|
|
|
@ -11,7 +11,6 @@ from kodi_six import xbmc, xbmcvfs
|
|||
|
||||
import client
|
||||
import requests
|
||||
from downloader import TheVoid
|
||||
from helper import LazyLogger
|
||||
|
||||
from . import translate, settings, window, dialog, api
|
||||
|
@ -53,30 +52,26 @@ def set_properties(item, method, server_id=None):
|
|||
|
||||
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
|
||||
holds all the playback information.
|
||||
'''
|
||||
self.item = item
|
||||
self.item['PlaybackInfo'] = {}
|
||||
self.api_client = api_client
|
||||
self.info = {
|
||||
'ServerId': server_id,
|
||||
'ServerAddress': server,
|
||||
'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):
|
||||
|
||||
''' Return sources based on the optional source_id or the device profile.
|
||||
'''
|
||||
params = {
|
||||
'ServerId': self.info['ServerId'],
|
||||
'Id': self.item['Id'],
|
||||
'Profile': self.get_device_profile()
|
||||
}
|
||||
info = TheVoid('GetPlaybackInfo', params).get()
|
||||
info = self.api_client.get_play_info(self.item['Id'], self.get_device_profile())
|
||||
LOG.info(info)
|
||||
self.info['PlaySessionId'] = info['PlaySessionId']
|
||||
sources = []
|
||||
|
@ -217,14 +212,7 @@ class PlayUtils(object):
|
|||
|
||||
''' Get live stream media info.
|
||||
'''
|
||||
params = {
|
||||
'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()
|
||||
info = self.api_client.get_live_stream(self.item['Id'], self.info['PlaySessionId'], source['OpenToken'], self.get_device_profile())
|
||||
LOG.info(info)
|
||||
|
||||
if info['MediaSource'].get('RequiresClosing'):
|
||||
|
@ -509,7 +497,7 @@ class PlayUtils(object):
|
|||
mapping = {}
|
||||
kodi = 0
|
||||
|
||||
server_settings = TheVoid('GetTranscodeOptions', {'ServerId': self.info['ServerId']}).get()
|
||||
server_settings = self.api_client.get_transcode_settings()
|
||||
|
||||
for stream in source['MediaStreams']:
|
||||
if stream['SupportsExternalStream'] and stream['Type'] == 'Subtitle' and stream['DeliveryMethod'] == 'External':
|
||||
|
@ -598,7 +586,7 @@ class PlayUtils(object):
|
|||
subs_streams = collections.OrderedDict()
|
||||
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')
|
||||
|
||||
for stream in streams:
|
||||
|
@ -665,6 +653,7 @@ class PlayUtils(object):
|
|||
if subtitle:
|
||||
|
||||
index = subtitle
|
||||
server_settings = self.api_client.get_transcode_settings()
|
||||
stream = streams[index]
|
||||
|
||||
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')
|
||||
|
||||
if index is not None:
|
||||
|
||||
server_settings = self.api_client.get_transcode_settings()
|
||||
stream = streams[index]
|
||||
|
||||
if server_settings['EnableSubtitleExtraction'] and stream['SupportsExternalStream']:
|
||||
|
|
|
@ -54,6 +54,7 @@ class Jellyfin(object):
|
|||
self.server_id = server_id or "default"
|
||||
|
||||
def get_client(self):
|
||||
# type: () -> JellyfinClient
|
||||
return self.client[self.server_id]
|
||||
|
||||
def close(self):
|
||||
|
|
|
@ -204,6 +204,9 @@ class ConnectionManager(object):
|
|||
if server['Id'] == server_id:
|
||||
return server
|
||||
|
||||
def get_server_address(self, server_id):
|
||||
return self.get_server_info(server_id or self.server_id).get('address')
|
||||
|
||||
def get_public_users(self):
|
||||
return self.client.jellyfin.get_public_users()
|
||||
|
||||
|
|
|
@ -10,7 +10,6 @@ import threading
|
|||
from kodi_six import xbmc
|
||||
|
||||
import connect
|
||||
import downloader
|
||||
import player
|
||||
from client import get_device_id
|
||||
from objects import PlaylistWorker, on_play, on_update, special_listener
|
||||
|
@ -53,12 +52,7 @@ class Monitor(xbmc.Monitor):
|
|||
if sender == 'plugin.video.jellyfin':
|
||||
method = method.split('.')[1]
|
||||
|
||||
if method not in ('GetItem', 'ReportProgressRequested', 'LoadServer', 'RandomItems', 'Recommended',
|
||||
'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'):
|
||||
if method not in ('ReportProgressRequested', 'LoadServer', 'AddUser', 'PlayPlaylist', 'Play', 'Playstate', 'GeneralCommand'):
|
||||
return
|
||||
|
||||
data = json.loads(data)[0]
|
||||
|
@ -122,116 +116,17 @@ class Monitor(xbmc.Monitor):
|
|||
LOG.exception(error)
|
||||
server = Jellyfin()
|
||||
|
||||
if method == 'GetItem':
|
||||
server = server.get_client()
|
||||
|
||||
item = server.jellyfin.get_item(data['Id'])
|
||||
self.void_responder(data, item)
|
||||
if method == 'Play':
|
||||
|
||||
elif method == 'GetAdditionalParts':
|
||||
items = server.jellyfin.get_items(data['ItemIds'])
|
||||
|
||||
item = server.jellyfin.get_additional_parts(data['Id'])
|
||||
self.void_responder(data, item)
|
||||
|
||||
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'])
|
||||
PlaylistWorker(data.get('ServerId'), items, data['PlayCommand'] == 'PlayNow',
|
||||
data.get('StartPositionTicks', 0), data.get('AudioStreamIndex'),
|
||||
data.get('SubtitleStreamIndex')).start()
|
||||
|
||||
# TODO no clue if this is called by anything
|
||||
elif method == 'PlayPlaylist':
|
||||
|
||||
server.jellyfin.post_session(server.config.data['app.session'], "Playing", {
|
||||
|
@ -240,14 +135,6 @@ class Monitor(xbmc.Monitor):
|
|||
'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'):
|
||||
self.player.report_playback(data.get('Report', True))
|
||||
|
||||
|
@ -270,14 +157,9 @@ class Monitor(xbmc.Monitor):
|
|||
elif method == 'VideoLibrary.OnUpdate':
|
||||
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):
|
||||
|
||||
server = Jellyfin(server_id)
|
||||
server = Jellyfin(server_id).get_client()
|
||||
self.post_capabilities(server)
|
||||
|
||||
if server_id is not None:
|
||||
|
|
|
@ -10,7 +10,6 @@ from datetime import timedelta
|
|||
from kodi_six import xbmc, xbmcgui, xbmcplugin, xbmcaddon
|
||||
|
||||
import database
|
||||
from downloader import TheVoid
|
||||
from helper import translate, playutils, api, window, settings, dialog
|
||||
from dialogs import resume
|
||||
from helper import LazyLogger
|
||||
|
@ -26,10 +25,11 @@ LOG = LazyLogger(__name__)
|
|||
|
||||
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 = TheVoid('GetServerAddress', {'ServerId': self.server_id}).get()
|
||||
self.api_client = api_client
|
||||
self.server = self.api_client.config.data['auth.server']
|
||||
self.stack = []
|
||||
|
||||
def get_playlist(self, item):
|
||||
|
@ -50,7 +50,7 @@ class Actions(object):
|
|||
|
||||
transcode = transcode or settings('playFromTranscode.bool')
|
||||
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())
|
||||
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.
|
||||
'''
|
||||
intros = TheVoid('GetIntros', {'ServerId': self.server_id, 'Id': item['Id']}).get()
|
||||
intros = self.api_client.get_intros(item['Id'])
|
||||
|
||||
if intros['Items']:
|
||||
enabled = True
|
||||
|
@ -131,7 +131,7 @@ class Actions(object):
|
|||
listitem = xbmcgui.ListItem()
|
||||
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())
|
||||
self.set_listitem(intro, listitem, intro=True)
|
||||
listitem.setPath(intro['PlaybackInfo']['Path'])
|
||||
|
@ -145,14 +145,13 @@ class Actions(object):
|
|||
|
||||
''' 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']:
|
||||
|
||||
listitem = xbmcgui.ListItem()
|
||||
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())
|
||||
play.set_external_subs(source, listitem)
|
||||
self.set_listitem(part, listitem)
|
||||
|
@ -183,7 +182,7 @@ class Actions(object):
|
|||
listitem = xbmcgui.ListItem()
|
||||
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())
|
||||
play.set_external_subs(source, listitem)
|
||||
|
||||
|
@ -213,7 +212,7 @@ class Actions(object):
|
|||
server_address, item['Id'], token)
|
||||
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())
|
||||
play.set_external_subs(source, listitem)
|
||||
|
||||
|
|
Loading…
Reference in a new issue