mirror of
https://github.com/jellyfin/jellyfin-kodi.git
synced 2024-11-10 12:16:12 +00:00
357 lines
No EOL
13 KiB
Python
357 lines
No EOL
13 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
#################################################################################################
|
|
|
|
import datetime
|
|
import json as json
|
|
import sys
|
|
|
|
import xbmc
|
|
import xbmcaddon
|
|
import xbmcplugin
|
|
import xbmcgui
|
|
|
|
from API import API
|
|
from DownloadUtils import DownloadUtils
|
|
from PlayUtils import PlayUtils
|
|
from ClientInformation import ClientInformation
|
|
import Utils as utils
|
|
|
|
#################################################################################################
|
|
|
|
class PlaybackUtils():
|
|
|
|
clientInfo = ClientInformation()
|
|
doUtils = DownloadUtils()
|
|
api = API()
|
|
|
|
addon = xbmcaddon.Addon()
|
|
language = addon.getLocalizedString
|
|
addonName = clientInfo.getAddonName()
|
|
|
|
def logMsg(self, msg, lvl=1):
|
|
|
|
className = self.__class__.__name__
|
|
utils.logMsg("%s %s" % (self.addonName, className), msg, int(lvl))
|
|
|
|
def PLAY(self, result, setup = "service"):
|
|
|
|
self.logMsg("PLAY Called", 1)
|
|
|
|
api = self.api
|
|
doUtils = self.doUtils
|
|
username = utils.window('currUser')
|
|
server = utils.window('server%s' % username)
|
|
|
|
listItem = xbmcgui.ListItem()
|
|
id = result['Id']
|
|
userdata = result['UserData']
|
|
|
|
# Get the playurl - direct play, direct stream or transcoding
|
|
playurl = PlayUtils().getPlayUrl(server, id, result)
|
|
|
|
if utils.window('playurlFalse') == "true":
|
|
# Playurl failed - set in PlayUtils.py
|
|
utils.window('playurlFalse', clear=True)
|
|
self.logMsg("Failed to retrieve the playback path/url or dialog was cancelled.", 1)
|
|
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listItem)
|
|
|
|
# Resume point for widget only
|
|
timeInfo = api.getTimeInfo(result)
|
|
jumpBackSec = int(utils.settings('resumeJumpBack'))
|
|
seekTime = round(float(timeInfo.get('ResumeTime')), 6)
|
|
if seekTime > jumpBackSec:
|
|
# To avoid negative bookmark
|
|
seekTime = seekTime - jumpBackSec
|
|
|
|
# Show the additional resume dialog if launched from a widget
|
|
if xbmc.getCondVisibility('Window.IsActive(home)') and seekTime:
|
|
# Dialog presentation
|
|
displayTime = str(datetime.timedelta(seconds=(int(seekTime))))
|
|
display_list = ["%s %s" % (self.language(30106), displayTime), self.language(30107)]
|
|
resume_result = xbmcgui.Dialog().select(self.language(30105), display_list)
|
|
|
|
if resume_result == 0:
|
|
# User selected to resume, append resume point to listitem
|
|
listItem.setProperty('StartOffset', str(seekTime))
|
|
|
|
elif resume_result > 0:
|
|
# User selected to start from beginning
|
|
seekTime = 0
|
|
|
|
elif resume_result < 0:
|
|
# User cancelled the dialog
|
|
self.logMsg("User cancelled resume dialog.", 1)
|
|
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listItem)
|
|
|
|
|
|
# In order, intros, original item requested and any additional parts
|
|
playstack = []
|
|
|
|
# Check for intros
|
|
if utils.settings('disableCinema') == "false" and not seekTime:
|
|
# if we have any play them when the movie/show is not being resumed
|
|
url = "{server}/mediabrowser/Users/{UserId}/Items/%s/Intros?format=json&ImageTypeLimit=1&Fields=Etag" % id
|
|
intros = doUtils.downloadUrl(url)
|
|
if intros['TotalRecordCount'] != 0:
|
|
for intro in intros['Items']:
|
|
introPlayurl = PlayUtils().getPlayUrl(server, intro['Id'], intro)
|
|
self.setProperties(introPlayurl, intro)
|
|
playstack.append(introPlayurl)
|
|
|
|
# Add original item
|
|
playstack.append(playurl)
|
|
self.setProperties(playurl, result)
|
|
|
|
mainArt = API().getArtwork(result, "Primary")
|
|
listItem.setThumbnailImage(mainArt)
|
|
listItem.setIconImage(mainArt)
|
|
|
|
# Get additional parts/playurl
|
|
if result.get('PartCount'):
|
|
url = "{server}/mediabrowser/Videos/%s/AdditionalParts" % id
|
|
parts = doUtils.downloadUrl(url)
|
|
for part in parts['Items']:
|
|
additionalPlayurl = PlayUtils().getPlayUrl(server, part['Id'], part)
|
|
self.setProperties(additionalPlayurl, part)
|
|
playstack.append(additionalPlayurl)
|
|
|
|
|
|
if len(playstack) > 1:
|
|
# Convert list in stack:// playurl
|
|
playMethod = utils.window('%splaymethod' % playurl)
|
|
playurl = "stack://%s" % " , ".join(playstack)
|
|
# Set new window properties for combined playurl
|
|
utils.window("%splaymethod" % playurl, value=playMethod)
|
|
self.setProperties(playurl, result)
|
|
|
|
self.logMsg("Returned playurl: %s" % playurl, 1)
|
|
listItem.setPath(playurl)
|
|
|
|
if utils.window("%splaymethod" % playurl) != "Transcode":
|
|
# Only for direct play and direct stream
|
|
# Append external subtitles to stream
|
|
subtitleList = self.externalSubs(id, playurl, server, result['MediaSources'])
|
|
listItem.setSubtitles(subtitleList)
|
|
|
|
|
|
# Launch the playback - only set the listitem props if we're not using the setresolvedurl approach
|
|
if setup == "service" or xbmc.getCondVisibility('Window.IsActive(home)'):
|
|
# Sent via websocketclient.py or default.py but via widgets
|
|
self.setListItemProps(server, id, listItem, result)
|
|
xbmc.Player().play(playurl, listItem)
|
|
elif setup == "default":
|
|
# Sent via default.py
|
|
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listItem)
|
|
|
|
def externalSubs(self, id, playurl, server, mediaSources):
|
|
|
|
externalsubs = []
|
|
mapping = {}
|
|
|
|
mediaStream = mediaSources[0].get('MediaStreams')
|
|
kodiindex = 0
|
|
for stream in mediaStream:
|
|
|
|
index = stream['Index']
|
|
# Since Emby returns all possible tracks together, have to pull only external subtitles.
|
|
# IsTextSubtitleStream if true, is available to download from emby.
|
|
if "Subtitle" in stream['Type'] and stream['IsExternal'] and stream['IsTextSubtitleStream']:
|
|
|
|
playmethod = utils.window("%splaymethod" % playurl)
|
|
|
|
if "DirectPlay" in playmethod:
|
|
# Direct play, get direct path
|
|
url = PlayUtils().directPlay(stream)
|
|
elif "DirectStream" in playmethod: # Direct stream
|
|
url = "%s/Videos/%s/%s/Subtitles/%s/Stream.srt" % (server, id, id, index)
|
|
|
|
# map external subtitles for mapping
|
|
mapping[kodiindex] = index
|
|
externalsubs.append(url)
|
|
kodiindex += 1
|
|
|
|
mapping = json.dumps(mapping)
|
|
utils.window('%sIndexMapping' % playurl, value=mapping)
|
|
|
|
return externalsubs
|
|
|
|
def setProperties(self, playurl, result):
|
|
# Set runtimeticks, type, refresh_id and item_id
|
|
id = result.get('Id')
|
|
type = result.get('Type', "")
|
|
|
|
utils.window("%sruntimeticks" % playurl, value=str(result.get('RunTimeTicks')))
|
|
utils.window("%stype" % playurl, value=type)
|
|
utils.window("%sitem_id" % playurl, value=id)
|
|
|
|
if type == "Episode":
|
|
utils.window("%srefresh_id" % playurl, value=result.get('SeriesId'))
|
|
else:
|
|
utils.window("%srefresh_id" % playurl, value=id)
|
|
|
|
def setArt(self, list, name, path):
|
|
|
|
if name in ("thumb", "fanart_image", "small_poster", "tiny_poster", "medium_landscape", "medium_poster", "small_fanartimage", "medium_fanartimage", "fanart_noindicators"):
|
|
list.setProperty(name, path)
|
|
else:
|
|
list.setArt({name:path})
|
|
|
|
return list
|
|
|
|
def setListItemProps(self, server, id, listItem, result):
|
|
# Set up item and item info
|
|
api = self.api
|
|
|
|
type = result.get('Type')
|
|
people = api.getPeople(result)
|
|
studios = api.getStudios(result)
|
|
|
|
metadata = {
|
|
|
|
'title': result.get('Name', "Missing name"),
|
|
'year': result.get('ProductionYear'),
|
|
'plot': api.getOverview(result),
|
|
'director': people.get('Director'),
|
|
'writer': people.get('Writer'),
|
|
'mpaa': api.getMpaa(result),
|
|
'genre': api.getGenre(result),
|
|
'studio': " / ".join(studios),
|
|
'aired': api.getPremiereDate(result),
|
|
'rating': result.get('CommunityRating'),
|
|
'votes': result.get('VoteCount')
|
|
}
|
|
|
|
if "Episode" in type:
|
|
# Only for tv shows
|
|
thumbId = result.get('SeriesId')
|
|
season = result.get('ParentIndexNumber', -1)
|
|
episode = result.get('IndexNumber', -1)
|
|
show = result.get('SeriesName', "")
|
|
|
|
metadata['TVShowTitle'] = show
|
|
metadata['season'] = season
|
|
metadata['episode'] = episode
|
|
|
|
listItem.setProperty('IsPlayable', 'true')
|
|
listItem.setProperty('IsFolder', 'false')
|
|
listItem.setInfo('video', infoLabels=metadata)
|
|
|
|
# Set artwork for listitem
|
|
self.setArt(listItem,'poster', API().getArtwork(result, "Primary"))
|
|
self.setArt(listItem,'tvshow.poster', API().getArtwork(result, "SeriesPrimary"))
|
|
self.setArt(listItem,'clearart', API().getArtwork(result, "Art"))
|
|
self.setArt(listItem,'tvshow.clearart', API().getArtwork(result, "Art"))
|
|
self.setArt(listItem,'clearlogo', API().getArtwork(result, "Logo"))
|
|
self.setArt(listItem,'tvshow.clearlogo', API().getArtwork(result, "Logo"))
|
|
self.setArt(listItem,'discart', API().getArtwork(result, "Disc"))
|
|
self.setArt(listItem,'fanart_image', API().getArtwork(result, "Backdrop"))
|
|
self.setArt(listItem,'landscape', API().getArtwork(result, "Thumb"))
|
|
|
|
def seekToPosition(self, seekTo):
|
|
# Set a loop to wait for positive confirmation of playback
|
|
count = 0
|
|
while not xbmc.Player().isPlaying():
|
|
count += 1
|
|
if count >= 10:
|
|
return
|
|
else:
|
|
xbmc.sleep(500)
|
|
|
|
# Jump to seek position
|
|
count = 0
|
|
while xbmc.Player().getTime() < (seekToTime - 5) and count < 11: # only try 10 times
|
|
count += 1
|
|
xbmc.Player().seekTime(seekTo)
|
|
xbmc.sleep(100)
|
|
|
|
def PLAYAllItems(self, items, startPositionTicks):
|
|
|
|
self.logMsg("== ENTER: PLAYAllItems ==")
|
|
self.logMsg("Items: %s" % items)
|
|
|
|
doUtils = self.doUtils
|
|
|
|
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
|
|
playlist.clear()
|
|
started = False
|
|
|
|
for itemId in items:
|
|
self.logMsg("Adding Item to playlist: %s" % itemId, 1)
|
|
url = "{server}/mediabrowser/Users/{UserId}/Items/%s?format=json" % itemId
|
|
result = doUtils.downloadUrl(url)
|
|
|
|
addition = self.addPlaylistItem(playlist, result)
|
|
if not started and addition:
|
|
started = True
|
|
self.logMsg("Starting Playback Pre", 1)
|
|
xbmc.Player().play(playlist)
|
|
|
|
if not started:
|
|
self.logMsg("Starting Playback Post", 1)
|
|
xbmc.Player().play(playlist)
|
|
|
|
# Seek to position
|
|
if startPositionTicks:
|
|
seekTime = startPositionTicks / 10000000.0
|
|
self.seekToPosition(seekTime)
|
|
|
|
def AddToPlaylist(self, itemIds):
|
|
|
|
self.logMsg("PlayBackUtils", "== ENTER: PLAYAllItems ==")
|
|
|
|
doUtils = self.doUtils
|
|
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
|
|
|
|
for itemId in itemIds:
|
|
self.logMsg("Adding Item to Playlist: %s" % itemId)
|
|
url = "{server}/mediabrowser/Users/{UserId}/Items/%s?format=json" % itemId
|
|
result = doUtils.downloadUrl(url)
|
|
|
|
self.addPlaylistItem(playlist, result)
|
|
|
|
return playlist
|
|
|
|
def addPlaylistItem(self, playlist, item):
|
|
|
|
id = item['Id']
|
|
username = utils.window('currUser')
|
|
server = utils.window('server%s' % username)
|
|
|
|
playurl = PlayUtils().getPlayUrl(server, id, item)
|
|
|
|
if utils.window('playurlFalse') == "true":
|
|
# Playurl failed - set in PlayUtils.py
|
|
utils.window('playurlFalse', clear=True)
|
|
self.logMsg("Failed to retrieve the playback path/url or dialog was cancelled.", 1)
|
|
return
|
|
|
|
self.logMsg("Playurl: %s" % playurl)
|
|
|
|
thumb = API().getArtwork(item, "Primary")
|
|
listItem = xbmcgui.ListItem(path=playurl, iconImage=thumb, thumbnailImage=thumb)
|
|
self.setListItemProps(server, id, listItem, item)
|
|
self.setProperties(playurl, item)
|
|
|
|
playlist.add(playurl, listItem)
|
|
|
|
# Not currently being used
|
|
'''def PLAYAllEpisodes(self, items):
|
|
WINDOW = xbmcgui.Window(10000)
|
|
|
|
username = WINDOW.getProperty('currUser')
|
|
userid = WINDOW.getProperty('userId%s' % username)
|
|
server = WINDOW.getProperty('server%s' % username)
|
|
|
|
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
|
|
playlist.clear()
|
|
|
|
for item in items:
|
|
|
|
item_url = "{server}/mediabrowser/Users/{UserId}/Items/%s?format=json&ImageTypeLimit=1" % item["Id"]
|
|
jsonData = self.downloadUtils.downloadUrl(item_url)
|
|
|
|
item_data = jsonData
|
|
self.addPlaylistItem(playlist, item_data, server, userid)
|
|
|
|
xbmc.Player().play(playlist)''' |