jellyfin-kodi/resources/lib/PlaybackUtils.py

359 lines
13 KiB
Python
Raw Normal View History

# -*- coding: utf-8 -*-
#################################################################################################
2015-03-13 21:24:59 +00:00
import datetime
import json as json
import sys
import xbmc
import xbmcaddon
import xbmcplugin
import xbmcgui
from API import API
2015-03-13 21:24:59 +00:00
from DownloadUtils import DownloadUtils
from PlayUtils import PlayUtils
from ClientInformation import ClientInformation
import Utils as utils
2015-03-13 21:24:59 +00:00
#################################################################################################
2015-03-13 21:24:59 +00:00
class PlaybackUtils():
clientInfo = ClientInformation()
doUtils = DownloadUtils()
api = API()
addon = xbmcaddon.Addon()
language = addon.getLocalizedString
addonName = clientInfo.getAddonName()
2015-03-13 21:24:59 +00:00
username = utils.window('currUser')
userid = utils.window('userId%s' % username)
server = utils.window('server%s' % username)
2015-04-13 18:56:36 +00:00
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
server = self.server
2015-03-13 21:24:59 +00:00
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
2015-06-06 16:19:53 +00:00
# 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 = []
2015-06-06 16:19:53 +00:00
# Check for intros
if utils.settings('disableCinema') == "false" and not seekTime:
2015-06-06 16:19:53 +00:00
# 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)
2015-06-06 16:19:53 +00:00
# Add original item
playstack.append(playurl)
self.setProperties(playurl, result)
2015-06-06 16:19:53 +00:00
mainArt = API().getArtwork(result, "Primary")
listItem.setThumbnailImage(mainArt)
listItem.setIconImage(mainArt)
2015-06-05 14:46:17 +00:00
# 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)
2015-05-07 06:19:14 +00:00
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)
2015-03-13 21:24:59 +00:00
# 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)
2015-03-13 21:24:59 +00:00
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"):
2015-03-13 21:24:59 +00:00
list.setProperty(name, path)
else:
list.setArt({name:path})
2015-03-13 21:24:59 +00:00
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 = {
2015-03-13 21:24:59 +00:00
'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
2015-03-14 18:17:16 +00:00
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"))
2015-03-14 18:17:16 +00:00
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"))
2015-03-14 18:17:16 +00:00
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
2015-04-13 18:56:36 +00:00
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)
2015-04-13 18:56:36 +00:00
# Seek to position
if startPositionTicks:
seekTime = startPositionTicks / 10000000.0
self.seekToPosition(seekTime)
def AddToPlaylist(self, itemIds):
2015-04-13 18:56:36 +00:00
self.logMsg("PlayBackUtils", "== ENTER: PLAYAllItems ==")
doUtils = self.doUtils
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
2015-09-16 14:52:54 +00:00
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']
server = self.server
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):
2015-04-13 18:56:36 +00:00
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)'''