jellyfin-kodi/resources/lib/playbackutils.py
2016-10-30 04:39:54 -05:00

392 lines
No EOL
15 KiB
Python

# -*- coding: utf-8 -*-
#################################################################################################
import json
import logging
import requests
import os
import shutil
import sys
import xbmc
import xbmcgui
import xbmcplugin
import xbmcvfs
import api
import artwork
import downloadutils
import playutils as putils
import playlist
import read_embyserver as embyserver
import shutil
from utils import window, settings, language as lang
#################################################################################################
log = logging.getLogger("EMBY."+__name__)
#################################################################################################
class PlaybackUtils():
def __init__(self, item):
self.item = item
self.API = api.API(self.item)
self.doUtils = downloadutils.DownloadUtils().downloadUrl
self.userid = window('emby_currUser')
self.server = window('emby_server%s' % self.userid)
self.artwork = artwork.Artwork()
self.emby = embyserver.Read_EmbyServer()
self.pl = playlist.Playlist()
def play(self, itemid, dbid=None):
listitem = xbmcgui.ListItem()
playutils = putils.PlayUtils(self.item)
log.info("Play called.")
playurl = playutils.getPlayUrl()
if not playurl:
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem)
if dbid is None:
# Item is not in Kodi database
listitem.setPath(playurl)
self.setProperties(playurl, listitem)
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
# TODO: Review once Krypton is RC, no need for workaround.
############### ORGANIZE CURRENT PLAYLIST ################
homeScreen = xbmc.getCondVisibility('Window.IsActive(home)')
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
startPos = max(playlist.getposition(), 0) # Can return -1
sizePlaylist = playlist.size()
currentPosition = startPos
propertiesPlayback = window('emby_playbackProps') == "true"
introsPlaylist = False
dummyPlaylist = False
log.debug("Playlist start position: %s" % startPos)
log.debug("Playlist plugin position: %s" % currentPosition)
log.debug("Playlist size: %s" % sizePlaylist)
############### RESUME POINT ################
userdata = self.API.get_userdata()
seektime = self.API.adjust_resume(userdata['Resume'])
# We need to ensure we add the intro and additional parts only once.
# Otherwise we get a loop.
if not propertiesPlayback:
window('emby_playbackProps', value="true")
log.info("Setting up properties in playlist.")
if not homeScreen and not seektime and window('emby_customPlaylist') != "true":
log.debug("Adding dummy file to playlist.")
dummyPlaylist = True
playlist.add(playurl, listitem, index=startPos)
# Remove the original item from playlist
self.pl.remove_from_playlist(startPos+1)
# Readd the original item to playlist - via jsonrpc so we have full metadata
self.pl.insert_to_playlist(currentPosition+1, dbid, self.item['Type'].lower())
currentPosition += 1
############### -- CHECK FOR INTROS ################
if settings('enableCinema') == "true" and not seektime:
# if we have any play them when the movie/show is not being resumed
url = "{server}/emby/Users/{UserId}/Items/%s/Intros?format=json" % itemid
intros = self.doUtils(url)
if intros['TotalRecordCount'] != 0:
getTrailers = True
if settings('askCinema') == "true":
resp = xbmcgui.Dialog().yesno("Emby for Kodi", lang(33016))
if not resp:
# User selected to not play trailers
getTrailers = False
log.info("Skip trailers.")
if getTrailers:
for intro in intros['Items']:
# The server randomly returns intros, process them.
introListItem = xbmcgui.ListItem()
introPlayurl = putils.PlayUtils(intro).getPlayUrl()
log.info("Adding Intro: %s" % introPlayurl)
# Set listitem and properties for intros
pbutils = PlaybackUtils(intro)
pbutils.setProperties(introPlayurl, introListItem)
self.pl.insert_to_playlist(currentPosition, url=introPlayurl)
introsPlaylist = True
currentPosition += 1
############### -- ADD MAIN ITEM ONLY FOR HOMESCREEN ###############
if homeScreen and not seektime and not sizePlaylist:
# Extend our current playlist with the actual item to play
# only if there's no playlist first
log.info("Adding main item to playlist.")
self.pl.add_to_playlist(dbid, self.item['Type'].lower())
# Ensure that additional parts are played after the main item
currentPosition += 1
############### -- CHECK FOR ADDITIONAL PARTS ################
if self.item.get('PartCount'):
# Only add to the playlist after intros have played
partcount = self.item['PartCount']
url = "{server}/emby/Videos/%s/AdditionalParts?format=json" % itemid
parts = self.doUtils(url)
for part in parts['Items']:
additionalListItem = xbmcgui.ListItem()
additionalPlayurl = putils.PlayUtils(part).getPlayUrl()
log.info("Adding additional part: %s" % partcount)
# Set listitem and properties for each additional parts
pbutils = PlaybackUtils(part)
pbutils.setProperties(additionalPlayurl, additionalListItem)
pbutils.setArtwork(additionalListItem)
playlist.add(additionalPlayurl, additionalListItem, index=currentPosition)
self.pl.verify_playlist()
currentPosition += 1
if dummyPlaylist:
# Added a dummy file to the playlist,
# because the first item is going to fail automatically.
log.info("Processed as a playlist. First item is skipped.")
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem)
# We just skipped adding properties. Reset flag for next time.
elif propertiesPlayback:
log.debug("Resetting properties playback flag.")
window('emby_playbackProps', clear=True)
#self.pl.verify_playlist()
########## SETUP MAIN ITEM ##########
# For transcoding only, ask for audio/subs pref
if window('emby_%s.playmethod' % playurl) == "Transcode":
# Filter ISO since Emby does not probe anymore
if self.item.get('VideoType') == "Iso":
log.info("Skipping audio/subs prompt, ISO detected.")
else:
playurl = playutils.audioSubsPref(playurl, listitem)
window('emby_%s.playmethod' % playurl, value="Transcode")
listitem.setPath(playurl)
self.setProperties(playurl, listitem)
############### PLAYBACK ################
if homeScreen and seektime and window('emby_customPlaylist') != "true":
log.info("Play as a widget item.")
self.setListItem(listitem)
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
elif ((introsPlaylist and window('emby_customPlaylist') == "true") or
(homeScreen and not sizePlaylist)):
# Playlist was created just now, play it.
log.info("Play playlist.")
xbmc.Player().play(playlist, startpos=startPos)
else:
log.info("Play as a regular item.")
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
def setProperties(self, playurl, listitem):
# Set all properties necessary for plugin path playback
itemid = self.item['Id']
itemtype = self.item['Type']
embyitem = "emby_%s" % playurl
window('%s.runtime' % embyitem, value=str(self.item.get('RunTimeTicks')))
window('%s.type' % embyitem, value=itemtype)
window('%s.itemid' % embyitem, value=itemid)
if itemtype == "Episode":
window('%s.refreshid' % embyitem, value=self.item.get('SeriesId'))
else:
window('%s.refreshid' % embyitem, value=itemid)
# Append external subtitles to stream
playmethod = window('%s.playmethod' % embyitem)
# Only for direct stream
if playmethod in ("DirectStream") and settings('enableExternalSubs') == "true":
# Direct play automatically appends external
subtitles = self.externalSubs(playurl)
listitem.setSubtitles(subtitles)
self.setArtwork(listitem)
def externalSubs(self, playurl):
externalsubs = []
mapping = {}
itemid = self.item['Id']
try:
mediastreams = self.item['MediaSources'][0]['MediaStreams']
except (TypeError, KeyError, IndexError):
return
temp = xbmc.translatePath(
"special://profile/addon_data/plugin.video.emby/temp/").decode('utf-8')
kodiindex = 0
for stream in mediastreams:
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 (stream['Type'] == "Subtitle" and
stream['IsExternal'] and stream['IsTextSubtitleStream']):
# Direct stream
url = ("%s/Videos/%s/%s/Subtitles/%s/Stream.srt"
% (self.server, itemid, itemid, index))
if "Language" in stream:
filename = "Stream.%s.srt" % stream['Language']
try:
path = self._download_external_subs(url, temp, filename)
externalsubs.append(path)
except Exception as e:
log.error(e)
externalsubs.append(url)
else:
externalsubs.append(url)
# map external subtitles for mapping
mapping[kodiindex] = index
kodiindex += 1
mapping = json.dumps(mapping)
window('emby_%s.indexMapping' % playurl, value=mapping)
return externalsubs
def _download_external_subs(self, src, dst, filename):
if not xbmcvfs.exists(dst):
xbmcvfs.mkdir(dst)
path = os.path.join(dst, filename)
try:
response = requests.get(src, stream=True)
response.raise_for_status()
except Exception:
raise
else:
response.encoding = 'utf-8'
with open(path, 'wb') as f:
f.write(response.content)
del response
return path
def setArtwork(self, listItem):
# Set up item and item info
allartwork = self.artwork.get_all_artwork(self.item, parent_info=True)
# Set artwork for listitem
arttypes = {
'poster': "Primary",
'tvshow.poster': "Primary",
'clearart': "Art",
'tvshow.clearart': "Art",
'clearlogo': "Logo",
'tvshow.clearlogo': "Logo",
'discart': "Disc",
'fanart_image': "Backdrop",
'landscape': "Thumb"
}
for arttype in arttypes:
art = arttypes[arttype]
if art == "Backdrop":
try: # Backdrop is a list, grab the first backdrop
self.setArtProp(listItem, arttype, allartwork[art][0])
except: pass
else:
self.setArtProp(listItem, arttype, allartwork[art])
def setArtProp(self, listItem, arttype, path):
if arttype in (
'thumb', 'fanart_image', 'small_poster', 'tiny_poster',
'medium_landscape', 'medium_poster', 'small_fanartimage',
'medium_fanartimage', 'fanart_noindicators'):
listItem.setProperty(arttype, path)
else:
listItem.setArt({arttype: path})
def setListItem(self, listItem, dbid=None):
people = self.API.get_people()
studios = self.API.get_studios()
metadata = {
'title': self.item.get('Name', "Missing name"),
'year': self.item.get('ProductionYear'),
'plot': self.API.get_overview(),
'director': people.get('Director'),
'writer': people.get('Writer'),
'mpaa': self.API.get_mpaa(),
'genre': " / ".join(self.item['Genres']),
'studio': " / ".join(studios),
'aired': self.API.get_premiere_date(),
'rating': self.item.get('CommunityRating'),
'votes': self.item.get('VoteCount')
}
if "Episode" in self.item['Type']:
# Only for tv shows
# For Kodi Krypton
metadata['mediatype'] = "episode"
metadata['dbid'] = dbid
thumbId = self.item.get('SeriesId')
season = self.item.get('ParentIndexNumber', -1)
episode = self.item.get('IndexNumber', -1)
show = self.item.get('SeriesName', "")
metadata['TVShowTitle'] = show
metadata['season'] = season
metadata['episode'] = episode
if "Movie" in self.item['Type']:
# For Kodi Krypton
metadata['mediatype'] = "movie"
metadata['dbid'] = dbid
listItem.setProperty('IsPlayable', 'true')
listItem.setProperty('IsFolder', 'false')
listItem.setLabel(metadata['title'])
listItem.setInfo('video', infoLabels=metadata)