Transcode revision, stack files, playback cleanup

This commit is contained in:
angelblue05 2015-10-01 08:08:34 -05:00
parent 0294957d14
commit 4ab6991968
5 changed files with 726 additions and 853 deletions

View file

@ -1,30 +1,20 @@
# -*- coding: utf-8 -*-
#################################################################################################
# utils class
#################################################################################################
import xbmc
import xbmcgui
import xbmcaddon
import xbmcvfs
from ClientInformation import ClientInformation
import Utils as utils
###########################################################################
#################################################################################################
class PlayUtils():
_shared_state = {}
clientInfo = ClientInformation()
addonName = clientInfo.getAddonName()
WINDOW = xbmcgui.Window(10000)
def __init__(self):
self.__dict__ = self._shared_state
def logMsg(self, msg, lvl=1):
@ -33,59 +23,51 @@ class PlayUtils():
def getPlayUrl(self, server, id, result):
WINDOW = self.WINDOW
username = WINDOW.getProperty('currUser')
server = WINDOW.getProperty('server%s' % username)
if self.isDirectPlay(result,True):
# Try direct play
playurl = self.directPlay(result)
if playurl:
self.logMsg("File is direct playing.", 1)
WINDOW.setProperty("%splaymethod" % playurl.encode('utf-8'), "DirectPlay")
utils.window("%splaymethod" % playurl.encode('utf-8'), value="DirectPlay")
elif self.isDirectStream(result):
# Try direct stream
playurl = self.directStream(result, server, id)
if playurl:
self.logMsg("File is direct streaming.", 1)
WINDOW.setProperty("%splaymethod" % playurl, "DirectStream")
utils.window("%splaymethod" % playurl, value="DirectStream")
elif self.isTranscoding(result):
# Try transcoding
playurl = self.transcoding(result, server, id)
if playurl:
self.logMsg("File is transcoding.", 1)
WINDOW.setProperty("%splaymethod" % playurl, "Transcode")
else:
# Error
return False
utils.window("%splaymethod" % playurl, value="Transcode")
else: # Error
utils.window("playurlFalse", value="true")
return
return playurl.encode('utf-8')
def isDirectPlay(self, result, dialog=False):
def isDirectPlay(self, result, dialog = False):
# Requirements for Direct play:
# FileSystem, Accessible path
playhttp = utils.settings('playFromStream')
# User forcing to play via HTTP instead of SMB
if playhttp == "true":
if utils.settings('playFromStream') == "true":
# User forcing to play via HTTP instead of SMB
self.logMsg("Can't direct play: Play from HTTP is enabled.", 1)
return False
canDirectPlay = result[u'MediaSources'][0][u'SupportsDirectPlay']
canDirectPlay = result['MediaSources'][0]['SupportsDirectPlay']
# Make sure it's supported by server
if not canDirectPlay:
self.logMsg("Can't direct play: Server does not allow or support it.", 1)
return False
if result['Path'].endswith('.strm'):
# Allow strm loading when direct playing
return True
location = result[u'LocationType']
location = result['LocationType']
# File needs to be "FileSystem"
if u'FileSystem' in location:
if 'FileSystem' in location:
# Verify if path is accessible
if self.fileExists(result):
return True
@ -107,53 +89,49 @@ class PlayUtils():
return False
def directPlay(self, result):
try:
try:
playurl = result[u'MediaSources'][0][u'Path']
except:
playurl = result[u'Path']
except:
self.logMsg("Direct play failed. Trying Direct stream.", 1)
return False
else:
if u'VideoType' in result:
# Specific format modification
if u'Dvd' in result[u'VideoType']:
playurl = "%s/VIDEO_TS/VIDEO_TS.IFO" % playurl
elif u'BluRay' in result[u'VideoType']:
playurl = "%s/BDMV/index.bdmv" % playurl
playurl = result['MediaSources'][0]['Path']
except KeyError:
playurl = result['Path']
# Network - SMB protocol
if "\\\\" in playurl:
smbuser = utils.settings('smbusername')
smbpass = utils.settings('smbpassword')
# Network share
if smbuser:
playurl = playurl.replace("\\\\", "smb://%s:%s@" % (smbuser, smbpass))
else:
playurl = playurl.replace("\\\\", "smb://")
playurl = playurl.replace("\\", "/")
if "apple.com" in playurl:
USER_AGENT = "QuickTime/7.7.4"
playurl += "?|User-Agent=%s" % USER_AGENT
if 'VideoType' in result:
# Specific format modification
if 'Dvd' in result['VideoType']:
playurl = "%s/VIDEO_TS/VIDEO_TS.IFO" % playurl
elif 'BluRay' in result['VideoType']:
playurl = "%s/BDMV/index.bdmv" % playurl
# Network - SMB protocol
if "\\\\" in playurl:
smbuser = utils.settings('smbusername')
smbpass = utils.settings('smbpassword')
# Network share
if smbuser:
playurl = playurl.replace("\\\\", "smb://%s:%s@" % (smbuser, smbpass))
else:
playurl = playurl.replace("\\\\", "smb://")
playurl = playurl.replace("\\", "/")
if "apple.com" in playurl:
USER_AGENT = "QuickTime/7.7.4"
playurl += "?|User-Agent=%s" % USER_AGENT
return playurl
return playurl
def isDirectStream(self, result):
# Requirements for Direct stream:
# FileSystem or Remote, BitRate, supported encoding
canDirectStream = result[u'MediaSources'][0][u'SupportsDirectStream']
canDirectStream = result['MediaSources'][0]['SupportsDirectStream']
# Make sure it's supported by server
if not canDirectStream:
return False
location = result[u'LocationType']
location = result['LocationType']
# File can be FileSystem or Remote, not Virtual
if u'Virtual' in location:
if 'Virtual' in location:
self.logMsg("File location is virtual. Can't proceed.", 1)
return False
@ -171,33 +149,29 @@ class PlayUtils():
playurl = self.directPlay(result)
return playurl
try:
if "ThemeVideo" in type:
playurl ="%s/mediabrowser/Videos/%s/stream?static=true" % (server, id)
if "ThemeVideo" in type:
playurl = "%s/mediabrowser/Videos/%s/stream?static=true" % (server, id)
elif "Video" in type:
playurl = "%s/mediabrowser/Videos/%s/stream?static=true" % (server, id)
elif "Audio" in type:
playurl = "%s/mediabrowser/Audio/%s/stream.mp3" % (server, id)
return playurl
elif "Video" in type:
playurl = "%s/mediabrowser/Videos/%s/stream?static=true" % (server, id)
elif "Audio" in type:
playurl = "%s/mediabrowser/Audio/%s/stream.mp3" % (server, id)
return playurl
except:
self.logMsg("Direct stream failed. Trying transcoding.", 1)
return False
def isTranscoding(self, result):
# Last resort, no requirements
# BitRate
canTranscode = result[u'MediaSources'][0][u'SupportsTranscoding']
canTranscode = result['MediaSources'][0]['SupportsTranscoding']
# Make sure it's supported by server
if not canTranscode:
return False
location = result[u'LocationType']
location = result['LocationType']
# File can be FileSystem or Remote, not Virtual
if u'Virtual' in location:
if 'Virtual' in location:
return False
return True
@ -208,31 +182,28 @@ class PlayUtils():
# Allow strm loading when transcoding
playurl = self.directPlay(result)
return playurl
try:
# Play transcoding
deviceId = self.clientInfo.getMachineId()
playurl = "%s/mediabrowser/Videos/%s/master.m3u8?mediaSourceId=%s" % (server, id, id)
playurl = "%s&VideoCodec=h264&AudioCodec=ac3&deviceId=%s&VideoBitrate=%s" % (playurl, deviceId, self.getVideoBitRate()*1000)
self.logMsg("Playurl: %s" % playurl)
return playurl
except:
self.logMsg("Transcoding failed.")
return False
# Play transcoding
deviceId = self.clientInfo.getMachineId()
playurl = "%s/mediabrowser/Videos/%s/master.m3u8?mediaSourceId=%s" % (server, id, id)
playurl = "%s&VideoCodec=h264&AudioCodec=ac3&MaxAudioChannels=6&deviceId=%s&VideoBitrate=%s" % (playurl, deviceId, self.getVideoBitRate()*1000)
# Works out if the network quality can play directly or if transcoding is needed
playurl = self.audioSubsPref(playurl, result.get('MediaSources'))
self.logMsg("Playurl: %s" % playurl, 1)
return playurl
def isNetworkQualitySufficient(self, result):
# Works out if the network quality can play directly or if transcoding is needed
settingsVideoBitRate = self.getVideoBitRate()
settingsVideoBitRate = settingsVideoBitRate * 1000
try:
mediaSources = result[u'MediaSources']
sourceBitRate = int(mediaSources[0][u'Bitrate'])
except:
self.logMsg("Bitrate value is missing.")
mediaSources = result['MediaSources']
sourceBitRate = int(mediaSources[0]['Bitrate'])
except KeyError:
self.logMsg("Bitrate value is missing.", 1)
else:
self.logMsg("The video quality selected is: %s, the video bitrate required to direct stream is: %s." % (settingsVideoBitRate, sourceBitRate), 1)
if settingsVideoBitRate < sourceBitRate:
@ -243,58 +214,40 @@ class PlayUtils():
def getVideoBitRate(self):
# get the addon video quality
videoQuality = utils.settings('videoBitRate')
bitrate = {
if (videoQuality == "0"):
return 664
elif (videoQuality == "1"):
return 996
elif (videoQuality == "2"):
return 1320
elif (videoQuality == "3"):
return 2000
elif (videoQuality == "4"):
return 3200
elif (videoQuality == "5"):
return 4700
elif (videoQuality == "6"):
return 6200
elif (videoQuality == "7"):
return 7700
elif (videoQuality == "8"):
return 9200
elif (videoQuality == "9"):
return 10700
elif (videoQuality == "10"):
return 12200
elif (videoQuality == "11"):
return 13700
elif (videoQuality == "12"):
return 15200
elif (videoQuality == "13"):
return 16700
elif (videoQuality == "14"):
return 18200
elif (videoQuality == "15"):
return 20000
elif (videoQuality == "16"):
return 40000
elif (videoQuality == "17"):
return 100000
elif (videoQuality == "18"):
return 1000000
else:
return 2147483 # max bit rate supported by server (max signed 32bit integer)
'0': 664,
'1': 996,
'2': 1320,
'3': 2000,
'4': 3200,
'5': 4700,
'6': 6200,
'7': 7700,
'8': 9200,
'9': 10700,
'10': 12200,
'11': 13700,
'12': 15200,
'13': 16700,
'14': 18200,
'15': 20000,
'16': 40000,
'17': 100000,
'18': 1000000
}
# max bit rate supported by server (max signed 32bit integer)
return bitrate.get(videoQuality, 2147483)
def fileExists(self, result):
if u'Path' not in result:
if 'Path' not in result:
# File has no path in server
return False
# Convert Emby path to a path we can verify
path = self.directPlay(result)
if not path:
return False
try:
pathexists = xbmcvfs.exists(path)
@ -312,4 +265,87 @@ class PlayUtils():
return True
else:
self.logMsg("Path is detected as follow: %s. Try direct streaming." % path, 2)
return False
return False
def audioSubsPref(self, url, mediaSources):
# For transcoding only
# Present the list of audio to select from
audioStreamsList = {}
audioStreams = []
audioStreamsChannelsList = {}
subtitleStreamsList = {}
subtitleStreams = ['No subtitles']
selectAudioIndex = ""
selectSubsIndex = ""
playurlprefs = "%s" % url
mediaStream = mediaSources[0].get('MediaStreams')
for stream in mediaStream:
# Since Emby returns all possible tracks together, have to sort them.
index = stream['Index']
type = stream['Type']
if 'Audio' in type:
codec = stream['Codec']
channelLayout = stream['ChannelLayout']
try:
track = "%s - %s - %s %s" % (index, stream['Language'], codec, channelLayout)
except:
track = "%s - %s %s" % (index, codec, channelLayout)
audioStreamsChannelsList[index] = stream['Channels']
audioStreamsList[track] = index
audioStreams.append(track)
elif 'Subtitle' in type:
try:
track = "%s - %s" % (index, stream['Language'])
except:
track = "%s - %s" % (index, stream['Codec'])
default = stream['IsDefault']
forced = stream['IsForced']
if default:
track = "%s - Default" % track
if forced:
track = "%s - Forced" % track
subtitleStreamsList[track] = index
subtitleStreams.append(track)
if len(audioStreams) > 1:
resp = xbmcgui.Dialog().select("Choose the audio stream", audioStreams)
if resp > -1:
# User selected audio
selected = audioStreams[resp]
selectAudioIndex = audioStreamsList[selected]
playurlprefs += "&AudioStreamIndex=%s" % selectAudioIndex
else: # User backed out of selection
playurlprefs += "&AudioStreamIndex=%s" % mediaSources[0]['DefaultAudioStreamIndex']
else: # There's only one audiotrack.
selectAudioIndex = audioStreamsList[audioStreams[0]]
playurlprefs += "&AudioStreamIndex=%s" % selectAudioIndex
if len(subtitleStreams) > 1:
resp = xbmcgui.Dialog().select("Choose the subtitle stream", subtitleStreams)
if resp == 0:
# User selected no subtitles
pass
elif resp > -1:
# User selected subtitles
selected = subtitleStreams[resp]
selectSubsIndex = subtitleStreamsList[selected]
playurlprefs += "&SubtitleStreamIndex=%s" % selectSubsIndex
else: # User backed out of selection
playurlprefs += "&SubtitleStreamIndex=%s" % mediaSources[0].get('DefaultSubtitleStreamIndex', "")
# Get number of channels for selected audio track
audioChannels = audioStreamsChannelsList.get(selectAudioIndex, 0)
if audioChannels > 2:
playurlprefs += "&AudioBitrate=384000"
else:
playurlprefs += "&AudioBitrate=192000"
return playurlprefs

View file

@ -1,190 +1,151 @@
# -*- coding: utf-8 -*-
#################################################################################################
import xbmc
import xbmcplugin
import xbmcgui
import xbmcaddon
import urllib
import datetime
import time
import json as json
import inspect
import sys
import xbmc
import xbmcaddon
import xbmcplugin
import xbmcgui
from API import API
from DownloadUtils import DownloadUtils
from PlayUtils import PlayUtils
from ReadKodiDB import ReadKodiDB
from ReadEmbyDB import ReadEmbyDB
from ClientInformation import ClientInformation
import Utils as utils
from API import API
import os
import xbmcvfs
addon = xbmcaddon.Addon()
addondir = xbmc.translatePath(addon.getAddonInfo('profile'))
WINDOW = xbmcgui.Window( 10000 )
#################################################################################################
class PlaybackUtils():
settings = None
clientInfo = ClientInformation()
doUtils = DownloadUtils()
api = API()
addon = xbmcaddon.Addon()
language = addon.getLocalizedString
logLevel = 0
downloadUtils = DownloadUtils()
addonName = clientInfo.getAddonName()
WINDOW.clearProperty('playurlFalse')
def __init__(self, *args):
pass
username = utils.window('currUser')
userid = utils.window('userId%s' % username)
server = utils.window('server%s' % username)
def PLAY(self, result, setup="service"):
xbmc.log("PLAY Called")
WINDOW = xbmcgui.Window(10000)
username = WINDOW.getProperty('currUser')
userid = WINDOW.getProperty('userId%s' % username)
server = WINDOW.getProperty('server%s' % username)
def logMsg(self, msg, lvl=1):
try:
id = result["Id"]
except:
return
className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, className), msg, int(lvl))
userData = result['UserData']
def PLAY(self, result, setup = "service"):
# BOOKMARK - RESUME POINT
timeInfo = API().getTimeInfo(result)
jumpBackSec = int(utils.settings("resumeJumpBack"))
self.logMsg("PLAY Called", 1)
api = self.api
doUtils = self.doUtils
server = self.server
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
itemsToPlay = []
# 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 seekTime == 0:
if utils.settings('disableCinema') == "false" and not seekTime:
# if we have any play them when the movie/show is not being resumed
# We can add the option right here
url = "{server}/mediabrowser/Users/{UserId}/Items/%s/Intros?format=json&ImageTypeLimit=1&Fields=Etag" % id
intros = self.downloadUtils.downloadUrl(url)
if intros[u'TotalRecordCount'] == 0:
pass
else:
for intro in intros[u'Items']:
introId = intro[u'Id']
itemsToPlay.append(introId)
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
itemsToPlay.append(id)
playstack.append(playurl)
self.setProperties(playurl, result)
# For split movies
if u'PartCount' in result:
partcount = result[u'PartCount']
# Get additional parts/playurl
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 = self.downloadUtils.downloadUrl(url)
for part in parts[u'Items']:
partId = part[u'Id']
itemsToPlay.append(partId)
if len(itemsToPlay) > 1:
# Let's play the playlist
playlist = self.AddToPlaylist(itemsToPlay)
if xbmc.getCondVisibility("Window.IsActive(home)"):
# Widget workaround
return xbmc.Player().play(playlist)
else:
# Can't pass a playlist to setResolvedUrl, so return False
# Wait for Kodi to process setResolved failure, then launch playlist
xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, xbmcgui.ListItem())
xbmc.sleep(10)
# Since we clear the original Kodi playlist, this is to prevent
# info dialog - can't play next item from showing up.
xbmc.executebuiltin('Dialog.Close(all,true)')
return xbmc.Player().play(playlist)
playurl = PlayUtils().getPlayUrl(server, id, result)
if playurl == False or WINDOW.getProperty('playurlFalse') == "true":
WINDOW.clearProperty('playurlFalse')
xbmc.log("Failed to retrieve the playback path/url.")
return
if WINDOW.getProperty("%splaymethod" % playurl) == "Transcode":
# Transcoding, we pull every track to set before playback starts
playurlprefs = self.audioSubsPref(playurl, result.get("MediaSources"))
if playurlprefs:
playurl = playurlprefs
else: # User cancelled dialog
return
parts = doUtils.downloadUrl(url)
for part in parts['Items']:
additionalPlayurl = PlayUtils().getPlayUrl(server, part['Id'], part)
self.setProperties(additionalPlayurl, part)
playstack.append(additionalPlayurl)
thumbPath = API().getArtwork(result, "Primary")
#if the file is a virtual strm file, we need to override the path by reading it's contents
if playurl.endswith(".strm"):
xbmc.log("virtual strm file file detected, starting playback with 3th party addon...")
StrmTemp = "special://temp/temp.strm"
xbmcvfs.copy(playurl, StrmTemp)
playurl = open(xbmc.translatePath(StrmTemp), 'r').readline()
listItem = xbmcgui.ListItem(path=playurl, iconImage=thumbPath, thumbnailImage=thumbPath)
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)
if WINDOW.getProperty("%splaymethod" % playurl) != "Transcode":
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.get('MediaSources'))
subtitleList = self.externalSubs(id, playurl, server, result['MediaSources'])
listItem.setSubtitles(subtitleList)
#pass
# Can not play virtual items
if (result.get("LocationType") == "Virtual"):
xbmcgui.Dialog().ok(self.language(30128), self.language(30129))
watchedurl = "%s/mediabrowser/Users/%s/PlayedItems/%s" % (server, userid, id)
positionurl = "%s/mediabrowser/Users/%s/PlayingItems/%s" % (server, userid, id)
deleteurl = "%s/mediabrowser/Items/%s" % (server, id)
# set the current playing info
WINDOW.setProperty(playurl+"watchedurl", watchedurl)
WINDOW.setProperty(playurl+"positionurl", positionurl)
WINDOW.setProperty(playurl+"deleteurl", "")
WINDOW.setProperty(playurl+"deleteurl", deleteurl)
#show the additional resume dialog if launched from a widget
if xbmc.getCondVisibility("Window.IsActive(home)"):
if seekTime != 0:
displayTime = str(datetime.timedelta(seconds=(int(seekTime))))
display_list = [ self.language(30106) + ' ' + displayTime, self.language(30107)]
resumeScreen = xbmcgui.Dialog()
resume_result = resumeScreen.select(self.language(30105), display_list)
if resume_result == 0:
listItem.setProperty('StartOffset', str(seekTime))
elif resume_result < 0:
# User cancelled dialog
xbmc.log("Emby player -> User cancelled resume dialog.")
xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listItem)
return
if result.get("Type")=="Episode":
WINDOW.setProperty(playurl+"refresh_id", result.get("SeriesId"))
else:
WINDOW.setProperty(playurl+"refresh_id", id)
WINDOW.setProperty(playurl+"runtimeticks", str(result.get("RunTimeTicks")))
WINDOW.setProperty(playurl+"type", result.get("Type"))
WINDOW.setProperty(playurl+"item_id", id)
#launch the playback - only set the listitem props if we're not using the setresolvedurl approach
if setup == "service":
# 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)
xbmc.Player().play(playurl, listItem)
elif setup == "default":
if xbmc.getCondVisibility("Window.IsActive(home)"):
self.setListItemProps(server, id, listItem, result)
xbmc.Player().play(playurl,listItem)
else:
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listItem)
# Sent via default.py
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listItem)
def externalSubs(self, id, playurl, server, mediaSources):
@ -214,173 +175,170 @@ class PlaybackUtils():
kodiindex += 1
mapping = json.dumps(mapping)
utils.window('%sIndexMapping' % playurl, mapping)
utils.window('%sIndexMapping' % playurl, value=mapping)
return externalsubs
def audioSubsPref(self, url, mediaSources):
def setProperties(self, playurl, result):
# Set runtimeticks, type, refresh_id and item_id
id = result.get('Id')
type = result.get('Type', "")
WINDOW = xbmcgui.Window(10000)
# Present the list of audio to select from
audioStreamsList = {}
audioStreams = []
selectAudioIndex = ""
subtitleStreamsList = {}
subtitleStreams = ['No subtitles']
selectSubsIndex = ""
playurlprefs = "%s" % url
utils.window("%sruntimeticks" % playurl, value=str(result.get('RunTimeTicks')))
utils.window("%stype" % playurl, value=type)
utils.window("%sitem_id" % playurl, value=id)
mediaStream = mediaSources[0].get('MediaStreams')
for stream in mediaStream:
index = stream['Index']
# Since Emby returns all possible tracks together, have to sort them.
if 'Audio' in stream['Type']:
try:
track = stream['Language']
audioStreamsList[track] = index
audioStreams.append(track)
except:
track = stream['Codec']
audioStreamsList[track] = index
audioStreams.append(track)
if type == "Episode":
utils.window("%srefresh_id" % playurl, value=result.get('SeriesId'))
else:
utils.window("%srefresh_id" % playurl, value=id)
elif 'Subtitle' in stream['Type']:
try:
track = stream['Language']
subtitleStreamsList[track] = index
subtitleStreams.append(track)
except:
track = stream['Codec']
subtitleStreamsList[track] = index
subtitleStreams.append(track)
if len(audioStreams) > 1:
resp = xbmcgui.Dialog().select("Choose the audio stream", audioStreams)
if resp > -1:
# User selected audio
selected = audioStreams[resp]
selected_audioIndex = audioStreamsList[selected]
playurlprefs += "&AudioStreamIndex=%s" % selected_audioIndex
selectAudioIndex = str(selected_audioIndex)
else: return False
else: # There's only one audiotrack.
audioIndex = audioStreamsList[audioStreams[0]]
playurlprefs += "&AudioStreamIndex=%s" % audioIndex
selectAudioIndex = str(audioIndex)
if len(subtitleStreams) > 1:
resp = xbmcgui.Dialog().select("Choose the subtitle stream", subtitleStreams)
if resp == 0:
# User selected no subtitles
pass
elif resp > -1:
# User selected subtitles
selected = subtitleStreams[resp]
selected_subsIndex = subtitleStreamsList[selected]
playurlprefs += "&SubtitleStreamIndex=%s" % selected_subsIndex
selectSubsIndex = str(selected_subsIndex)
else: return False
def setArt(self, list, name, path):
# Reset the method with the new playurl
WINDOW.setProperty("%splaymethod" % playurlprefs, "Transcode")
WINDOW.setProperty("%sAudioStreamIndex" % playurlprefs, selectAudioIndex)
WINDOW.setProperty("%sSubtitleStreamIndex" % playurlprefs, selectSubsIndex)
return playurlprefs
def setArt(self, list,name,path):
if name=='thumb' or name=='fanart_image' or name=='small_poster' or name=='tiny_poster' or name == "medium_landscape" or name=='medium_poster' or name=='small_fanartimage' or name=='medium_fanartimage' or name=='fanart_noindicators':
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
thumbID = id
eppNum = -1
seasonNum = -1
tvshowTitle = ""
if(result.get("Type") == "Episode"):
thumbID = result.get("SeriesId")
seasonNum = result.get("ParentIndexNumber")
eppNum = result.get("IndexNumber")
tvshowTitle = result.get("SeriesName")
# 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,'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,'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"))
listItem.setProperty('IsPlayable', 'true')
listItem.setProperty('IsFolder', 'false')
# Process Studios
studios = API().getStudios(result)
if studios == []:
studio = ""
else:
studio = studios[0]
listItem.setInfo('video', {'studio' : studio})
details = {
'title' : result.get("Name", "Missing Name"),
'plot' : result.get("Overview")
}
if(eppNum > -1):
details["episode"] = str(eppNum)
if(seasonNum > -1):
details["season"] = str(seasonNum)
if tvshowTitle != None:
details["TVShowTitle"] = tvshowTitle
listItem.setInfo( "Video", infoLabels=details )
people = API().getPeople(result)
# Process Genres
genre = API().getGenre(result)
listItem.setInfo('video', {'director' : people.get('Director')})
listItem.setInfo('video', {'writer' : people.get('Writer')})
listItem.setInfo('video', {'mpaa': result.get("OfficialRating")})
listItem.setInfo('video', {'genre': API().getGenre(result)})
self.setArt(listItem,'landscape', API().getArtwork(result, "Thumb"))
def seekToPosition(self, seekTo):
#Set a loop to wait for positive confirmation of playback
# Set a loop to wait for positive confirmation of playback
count = 0
while not xbmc.Player().isPlaying():
count = count + 1
count += 1
if count >= 10:
return
else:
xbmc.sleep(500)
#Jump to resume point
jumpBackSec = int(addon.getSetting("resumeJumpBack"))
seekToTime = seekTo - jumpBackSec
# Jump to seek position
count = 0
while xbmc.Player().getTime() < (seekToTime - 5) and count < 11: # only try 10 times
count = count + 1
#xbmc.Player().pause
#xbmc.sleep(100)
xbmc.Player().seekTime(seekToTime)
count += 1
xbmc.Player().seekTime(seekTo)
xbmc.sleep(100)
#xbmc.Player().play()
def PLAYAllItems(self, items, startPositionTicks):
utils.logMsg("PlayBackUtils", "== ENTER: PLAYAllItems ==")
utils.logMsg("PlayBackUtils", "Items : " + str(items))
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']
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):
WINDOW = xbmcgui.Window(10000)
username = WINDOW.getProperty('currUser')
@ -389,42 +347,6 @@ class PlaybackUtils():
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
playlist.clear()
started = False
for itemID in items:
utils.logMsg("PlayBackUtils", "Adding Item to Playlist : " + itemID)
item_url = "{server}/mediabrowser/Users/{UserId}/Items/%s?format=json" % itemID
jsonData = self.downloadUtils.downloadUrl(item_url)
item_data = jsonData
added = self.addPlaylistItem(playlist, item_data, server, userid)
if(added and started == False):
started = True
utils.logMsg("PlayBackUtils", "Starting Playback Pre")
xbmc.Player().play(playlist)
if(started == False):
utils.logMsg("PlayBackUtils", "Starting Playback Post")
xbmc.Player().play(playlist)
#seek to position
seekTime = 0
if(startPositionTicks != None):
seekTime = (startPositionTicks / 1000) / 10000
if seekTime > 0:
self.seekToPosition(seekTime)
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:
@ -434,80 +356,4 @@ class PlaybackUtils():
item_data = jsonData
self.addPlaylistItem(playlist, item_data, server, userid)
xbmc.Player().play(playlist)
def AddToPlaylist(self, itemIds):
utils.logMsg("PlayBackUtils", "== ENTER: PLAYAllItems ==")
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 itemID in itemIds:
utils.logMsg("PlayBackUtils", "Adding Item to Playlist : " + itemID)
item_url = "{server}/mediabrowser/Users/{UserId}/Items/%s?format=json" % itemID
jsonData = self.downloadUtils.downloadUrl(item_url)
item_data = jsonData
self.addPlaylistItem(playlist, item_data, server, userid)
return playlist
def addPlaylistItem(self, playlist, item, server, userid):
id = item.get("Id")
playurl = PlayUtils().getPlayUrl(server, id, item)
utils.logMsg("PlayBackUtils", "Play URL: " + playurl)
thumbPath = API().getArtwork(item, "Primary")
listItem = xbmcgui.ListItem(path=playurl, iconImage=thumbPath, thumbnailImage=thumbPath)
self.setListItemProps(server, id, listItem, item)
WINDOW = xbmcgui.Window(10000)
username = WINDOW.getProperty('currUser')
userid = WINDOW.getProperty('userId%s' % username)
server = WINDOW.getProperty('server%s' % username)
# Can not play virtual items
if (item.get("LocationType") == "Virtual") or (item.get("IsPlaceHolder") == True):
xbmcgui.Dialog().ok(self.language(30128), self.language(30129))
return False
else:
watchedurl = "%s/mediabrowser/Users/%s/PlayedItems/%s" % (server, userid, id)
positionurl = "%s/mediabrowser/Users/%s/PlayingItems/%s" % (server, userid, id)
deleteurl = "%s/mediabrowser/Items/%s" % (server, id)
# set the current playing info
WINDOW = xbmcgui.Window( 10000 )
WINDOW.setProperty(playurl + "watchedurl", watchedurl)
WINDOW.setProperty(playurl + "positionurl", positionurl)
WINDOW.setProperty(playurl + "deleteurl", "")
if item.get("Type") == "Episode" and addon.getSetting("offerDeleteTV")=="true":
WINDOW.setProperty(playurl + "deleteurl", deleteurl)
if item.get("Type") == "Movie" and addon.getSetting("offerDeleteMovies")=="true":
WINDOW.setProperty(playurl + "deleteurl", deleteurl)
WINDOW.setProperty(playurl + "runtimeticks", str(item.get("RunTimeTicks")))
WINDOW.setProperty(playurl+"type", item.get("Type"))
WINDOW.setProperty(playurl + "item_id", id)
if (item.get("Type") == "Episode"):
WINDOW.setProperty(playurl + "refresh_id", item.get("SeriesId"))
else:
WINDOW.setProperty(playurl + "refresh_id", id)
utils.logMsg("PlayBackUtils", "PlayList Item Url : " + str(playurl))
playlist.add(playurl, listItem)
return True
xbmc.Player().play(playlist)'''

View file

@ -1,165 +1,229 @@
import xbmcaddon
import xbmcplugin
# -*- coding: utf-8 -*-
#################################################################################################
import json as json
import xbmc
import xbmcgui
import os
import threading
import json
import inspect
import KodiMonitor
import Utils as utils
from DownloadUtils import DownloadUtils
from WebSocketClient import WebSocketThread
from PlayUtils import PlayUtils
from ClientInformation import ClientInformation
from LibrarySync import LibrarySync
from PlaybackUtils import PlaybackUtils
from ReadEmbyDB import ReadEmbyDB
from API import API
import Utils as utils
librarySync = LibrarySync()
#################################################################################################
# service class for playback monitoring
class Player( xbmc.Player ):
# Borg - multiple instances, shared state
_shared_state = {}
xbmcplayer = xbmc.Player()
doUtils = DownloadUtils()
clientInfo = ClientInformation()
ws = WebSocketThread()
librarySync = LibrarySync()
addonName = clientInfo.getAddonName()
WINDOW = xbmcgui.Window(10000)
logLevel = 0
played_information = {}
settings = None
playStats = {}
currentFile = None
stackFiles = None
stackElapsed = 0
def __init__(self, *args):
audioPref = "default"
subsPref = "default"
def __init__( self, *args ):
self.__dict__ = self._shared_state
self.logMsg("Starting playback monitor service", 1)
self.logMsg("Starting playback monitor.", 2)
def logMsg(self, msg, lvl=1):
self.className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, self.className), msg, int(lvl))
def setAudioSubsPref(self, audio, subs):
self.audioPref = audio
self.subsPref = subs
def hasData(self, data):
if(data == None or len(data) == 0 or data == "None"):
return False
def GetPlayStats(self):
return self.playStats
def currentStackItem(self, stackItems):
# Only for stacked items - stack://
xbmcplayer = self.xbmcplayer
stack = stackItems.replace("stack://", "").split(" , ")
position = xbmcplayer.getTime()
totalRuntime = 0
for item in stack:
runtime = int(utils.window("%sruntimeticks" % item)) / 10000000
# Verify the position compared to the totalRuntime for stacked items processed in loop so far.
if position < (runtime + totalRuntime):
self.stackElapsed = totalRuntime
self.currentFile = item
return item
else:
totalRuntime += runtime
def onPlayBackStarted( self ):
# Will be called when xbmc starts playing a file
xbmcplayer = self.xbmcplayer
self.stopAll()
# Get current file - if stack://, get currently playing item
currentFile = xbmcplayer.getPlayingFile()
if "stack://" in currentFile:
self.stackFiles = currentFile
currentFile = self.currentStackItem(currentFile)
else:
return True
def stopAll(self):
self.stackFiles = None
self.currentFile = currentFile
self.stackElapsed = 0
WINDOW = xbmcgui.Window(10000)
if(len(self.played_information) == 0):
return
self.logMsg("ONPLAYBACK_STARTED: %s" % currentFile, 0)
# We may need to wait for info to be set in kodi monitor
itemId = utils.window("%sitem_id" % currentFile)
tryCount = 0
while not itemId:
self.logMsg("emby Service -> played_information : " + str(self.played_information))
xbmc.sleep(500)
itemId = utils.window("%sitem_id" % currentFile)
if tryCount == 20: # try 20 times or about 10 seconds
break
else: tryCount += 1
else:
# Only proceed if an itemId was found.
runtime = utils.window("%sruntimeticks" % currentFile)
refresh_id = utils.window("%srefresh_id" % currentFile)
playMethod = utils.window("%splaymethod" % currentFile)
itemType = utils.window("%stype" % currentFile)
mapping = utils.window("%sIndexMapping" % currentFile)
seekTime = xbmc.Player().getTime()
for item_url in self.played_information:
data = self.played_information.get(item_url)
if (data is not None):
self.logMsg("emby Service -> item_url : " + item_url)
self.logMsg("emby Service -> item_data : " + str(data))
self.logMsg("Mapping for subtitles index: %s" % mapping, 2)
runtime = data.get("runtime")
currentPosition = data.get("currentPosition")
item_id = data.get("item_id")
refresh_id = data.get("refresh_id")
currentFile = data.get("currentfile")
type = data.get("Type")
playMethod = data.get('playmethod')
# Get playback volume
volume_query = '{"jsonrpc": "2.0", "method": "Application.GetProperties", "params": {"properties": ["volume","muted"]}, "id": 1}'
result = xbmc.executeJSONRPC(volume_query)
result = json.loads(result)
volume = result.get('result').get('volume')
muted = result.get('result').get('muted')
# Prevent websocket feedback
self.WINDOW.setProperty("played_itemId", item_id)
url = "{server}/mediabrowser/Sessions/Playing"
postdata = {
if(currentPosition != None and self.hasData(runtime)):
runtimeTicks = int(runtime)
self.logMsg("emby Service -> runtimeticks:" + str(runtimeTicks))
percentComplete = (currentPosition * 10000000) / runtimeTicks
markPlayedAt = float(90) / 100
'QueueableMediaTypes': "Video",
'CanSeek': True,
'ItemId': itemId,
'MediaSourceId': itemId,
'PlayMethod': playMethod,
'VolumeLevel': volume,
'PositionTicks': int(seekTime * 10000000) - int(self.stackElapsed * 10000000),
'IsMuted': muted
}
self.logMsg("emby Service -> Percent Complete:" + str(percentComplete) + " Mark Played At:" + str(markPlayedAt))
if percentComplete < markPlayedAt:
# Do not mark as watched
self.WINDOW.setProperty('played_skipWatched', 'true')
# Get the current audio track and subtitles
if playMethod == "Transcode":
# property set in PlayUtils.py
postdata['AudioStreamIndex'] = utils.window("%sAudioStreamIndex" % currentFile)
postdata['SubtitleStreamIndex'] = utils.window("%sSubtitleStreamIndex" % currentFile)
self.stopPlayback(data)
offerDelete=False
if data.get("Type") == "Episode" and utils.settings("offerDeleteTV")=="true":
offerDelete = True
elif data.get("Type") == "Movie" and utils.settings("offerDeleteMovies")=="true":
offerDelete = True
if percentComplete > .80 and offerDelete == True:
return_value = xbmcgui.Dialog().yesno("Offer Delete", "Delete\n" + data.get("currentfile").split("/")[-1] + "\non Emby Server? ")
if return_value:
# Delete Kodi entry before Emby
listItem = [item_id]
LibrarySync().removefromDB(listItem, True)
# Stop transcoding
if playMethod == "Transcode":
self.logMsg("Transcoding for %s terminated." % item_id)
deviceId = self.clientInfo.getMachineId()
url = "{server}/mediabrowser/Videos/ActiveEncodings?DeviceId=%s" % deviceId
self.doUtils.downloadUrl(url, type="DELETE")
else:
track_query = '{"jsonrpc": "2.0", "method": "Player.GetProperties", "params": {"playerid": 1,"properties": ["currentsubtitle","currentaudiostream","subtitleenabled"]} , "id": 1}'
result = xbmc.executeJSONRPC(track_query)
result = json.loads(result)
self.played_information.clear()
def stopPlayback(self, data):
self.logMsg("stopPlayback called", 2)
item_id = data.get("item_id")
currentPosition = data.get("currentPosition")
positionTicks = int(currentPosition * 10000000)
# Audio tracks
indexAudio = result.get('result', 0)
if indexAudio:
indexAudio = indexAudio.get('currentaudiostream', {}).get('index', 0)
# Subtitles tracks
indexSubs = result.get('result', 0)
if indexSubs:
indexSubs = indexSubs.get('currentsubtitle', {}).get('index', 0)
# If subtitles are enabled
subsEnabled = result.get('result', "")
if subsEnabled:
subsEnabled = subsEnabled.get('subtitleenabled', "")
# Postdata for the audio and subs tracks
audioTracks = len(xbmc.Player().getAvailableAudioStreams())
postdata['AudioStreamIndex'] = indexAudio + 1
if subsEnabled and len(xbmc.Player().getAvailableSubtitleStreams()) > 0:
if mapping:
externalIndex = json.loads(mapping)
else: # Direct paths scenario
externalIndex = ""
url = "{server}/mediabrowser/Sessions/Playing/Stopped"
postdata = {
'ItemId': item_id,
'MediaSourceId': item_id,
'PositionTicks': positionTicks
}
if externalIndex:
# If there's external subtitles added via PlaybackUtils
if externalIndex.get(str(indexSubs)):
# If the current subtitle is in the mapping
postdata['SubtitleStreamIndex'] = externalIndex[str(indexSubs)]
else:
# Internal subtitle currently selected
external = len(externalIndex)
postdata['SubtitleStreamIndex'] = indexSubs - external + audioTracks + 1
else:
# No external subtitles added via PlayUtils
postdata['SubtitleStreamIndex'] = indexSubs + audioTracks + 1
else:
postdata['SubtitleStreamIndex'] = ""
self.doUtils.downloadUrl(url, postBody=postdata, type="POST")
# Post playback to server
self.logMsg("Sending POST play started.", 1)
self.doUtils.downloadUrl(url, postBody=postdata, type="POST")
# save data map for updates and position calls
data = {
'runtime': runtime,
'item_id': itemId,
'refresh_id': refresh_id,
'currentfile': currentFile,
'AudioStreamIndex': postdata['AudioStreamIndex'],
'SubtitleStreamIndex': postdata['SubtitleStreamIndex'],
'playmethod': playMethod,
'Type': itemType,
'currentPosition': int(seekTime) - int(self.stackElapsed)
}
self.played_information[currentFile] = data
self.logMsg("ADDING_FILE: %s" % self.played_information, 1)
# log some playback stats
'''if(itemType != None):
if(self.playStats.get(itemType) != None):
count = self.playStats.get(itemType) + 1
self.playStats[itemType] = count
else:
self.playStats[itemType] = 1
if(playMethod != None):
if(self.playStats.get(playMethod) != None):
count = self.playStats.get(playMethod) + 1
self.playStats[playMethod] = count
else:
self.playStats[playMethod] = 1'''
def reportPlayback(self):
self.logMsg("reportPlayback Called", 2)
xbmcplayer = self.xbmcplayer
if not xbmcplayer.isPlaying():
self.logMsg("reportPlayback: Not playing anything so returning", 0)
return
currentFile = xbmcplayer.getPlayingFile()
# Get current file
currentFile = self.currentFile
data = self.played_information.get(currentFile)
# only report playback if emby has initiated the playback (item_id has value)
if data is not None and data.get("item_id") is not None:
if data:
# Get playback information
item_id = data.get("item_id")
itemId = data.get("item_id")
audioindex = data.get("AudioStreamIndex")
subtitleindex = data.get("SubtitleStreamIndex")
playTime = data.get("currentPosition")
@ -173,14 +237,15 @@ class Player( xbmc.Player ):
volume_query = '{"jsonrpc": "2.0", "method": "Application.GetProperties", "params": {"properties": ["volume","muted"]}, "id": 1}'
result = xbmc.executeJSONRPC(volume_query)
result = json.loads(result)
volume = result.get(u'result').get(u'volume')
muted = result.get(u'result').get(u'muted')
volume = result.get('result').get('volume')
muted = result.get('result').get('muted')
postdata = {
'QueueableMediaTypes': "Video",
'CanSeek': True,
'ItemId': item_id,
'MediaSourceId': item_id,
'ItemId': itemId,
'MediaSourceId': itemId,
'PlayMethod': playMethod,
'IsPaused': paused,
'VolumeLevel': volume,
@ -188,9 +253,14 @@ class Player( xbmc.Player ):
}
if playTime:
postdata['PositionTicks'] = int(playTime * 10000000)
postdata['PositionTicks'] = int(playTime * 10000000) - int(self.stackElapsed * 10000000)
if playMethod != "Transcode":
if playMethod == "Transcode":
data['AudioStreamIndex'] = audioindex
data['SubtitleStreamIndex'] = subtitleindex
else:
# Get current audio and subtitles track
track_query = '{"jsonrpc": "2.0", "method": "Player.GetProperties", "params": {"playerid":1,"properties": ["currentsubtitle","currentaudiostream","subtitleenabled"]} , "id": 1}'
result = xbmc.executeJSONRPC(track_query)
@ -249,208 +319,127 @@ class Player( xbmc.Player ):
postdata['SubtitleStreamIndex'] = indexSubs
data['SubtitleStreamIndex'] = indexSubs
else:
data['AudioStreamIndex'] = audioindex
data['SubtitleStreamIndex'] = subtitleindex
postdata = json.dumps(postdata)
self.logMsg("Report: %s" % postdata, 2)
self.ws.sendProgressUpdate(postdata)
def onPlayBackPaused( self ):
currentFile = xbmc.Player().getPlayingFile()
self.logMsg("PLAYBACK_PAUSED : " + currentFile,2)
if(self.played_information.get(currentFile) != None):
self.played_information[currentFile]["paused"] = "true"
currentFile = self.currentFile
self.logMsg("PLAYBACK_PAUSED: %s" % currentFile, 2)
if self.played_information.get(currentFile):
self.played_information[currentFile]['paused'] = "true"
self.reportPlayback()
def onPlayBackResumed( self ):
currentFile = xbmc.Player().getPlayingFile()
self.logMsg("PLAYBACK_RESUMED : " + currentFile,2)
if(self.played_information.get(currentFile) != None):
self.played_information[currentFile]["paused"] = "false"
currentFile = self.currentFile
self.logMsg("PLAYBACK_RESUMED: %s" % currentFile, 2)
if self.played_information.get(currentFile):
self.played_information[currentFile]['paused'] = "false"
self.reportPlayback()
def onPlayBackSeek( self, time, seekOffset ):
self.logMsg("PLAYBACK_SEEK", 2)
xbmcplayer = self.xbmcplayer
# Make position when seeking a bit more accurate
position = xbmcplayer.getTime()
currentFile = self.currentFile
if self.played_information.get(currentFile):
self.played_information[currentFile]['currentPosition'] = position
self.reportPlayback()
def onPlayBackSeek( self, time, seekOffset ):
self.logMsg("PLAYBACK_SEEK",2)
# Make position when seeking a bit more accurate
try:
playTime = xbmc.Player().getTime()
currentFile = xbmc.Player().getPlayingFile()
if(self.played_information.get(currentFile) != None):
self.played_information[currentFile]["currentPosition"] = playTime
except: pass
self.reportPlayback()
def onPlayBackStarted( self ):
# Will be called when xbmc starts playing a file
WINDOW = xbmcgui.Window(10000)
xbmcplayer = self.xbmcplayer
self.stopAll()
if xbmcplayer.isPlaying():
currentFile = ""
try:
currentFile = xbmcplayer.getPlayingFile()
except: pass
self.logMsg("onPlayBackStarted: %s" % currentFile, 0)
# we may need to wait until the info is available
item_id = WINDOW.getProperty(currentFile + "item_id")
tryCount = 0
while(item_id == None or item_id == ""):
xbmc.sleep(500)
item_id = WINDOW.getProperty(currentFile + "item_id")
tryCount += 1
if(tryCount == 20): # try 20 times or about 10 seconds
return
xbmc.sleep(500)
# grab all the info about this item from the stored windows props
# only ever use the win props here, use the data map in all other places
runtime = WINDOW.getProperty(currentFile + "runtimeticks")
refresh_id = WINDOW.getProperty(currentFile + "refresh_id")
playMethod = WINDOW.getProperty(currentFile + "playmethod")
itemType = WINDOW.getProperty(currentFile + "type")
mapping = WINDOW.getProperty("%sIndexMapping" % currentFile)
self.logMsg("Mapping for index: %s" % mapping)
# Get playback volume
volume_query = '{"jsonrpc": "2.0", "method": "Application.GetProperties", "params": {"properties": ["volume","muted"]}, "id": 1}'
result = xbmc.executeJSONRPC(volume_query)
result = json.loads(result)
volume = result.get(u'result').get(u'volume')
muted = result.get(u'result').get(u'muted')
seekTime = xbmc.Player().getTime()
url = "{server}/mediabrowser/Sessions/Playing"
postdata = {
'QueueableMediaTypes': "Video",
'CanSeek': True,
'ItemId': item_id,
'MediaSourceId': item_id,
'PlayMethod': playMethod,
'VolumeLevel': volume,
'PositionTicks': int(seekTime * 10000000),
'IsMuted': muted
}
# Get the current audio track and subtitles
if playMethod == "Transcode":
audioindex = WINDOW.getProperty(currentFile + "AudioStreamIndex")
subtitleindex = WINDOW.getProperty(currentFile + "SubtitleStreamIndex")
postdata['AudioStreamIndex'] = audioindex
postdata['SubtitleStreamIndex'] = subtitleindex
else:
track_query = '{"jsonrpc": "2.0", "method": "Player.GetProperties", "params": {"playerid": 1,"properties": ["currentsubtitle","currentaudiostream","subtitleenabled"]} , "id": 1}'
result = xbmc.executeJSONRPC(track_query)
result = json.loads(result)
# Audio tracks
indexAudio = result.get('result', 0)
if indexAudio:
indexAudio = indexAudio.get('currentaudiostream', {}).get('index', 0)
# Subtitles tracks
indexSubs = result.get('result', 0)
if indexSubs:
indexSubs = indexSubs.get('currentsubtitle', {}).get('index', 0)
# If subtitles are enabled
subsEnabled = result.get('result', "")
if subsEnabled:
subsEnabled = subsEnabled.get('subtitleenabled', "")
# Postdata for the audio and subs tracks
audioTracks = len(xbmc.Player().getAvailableAudioStreams())
postdata['AudioStreamIndex'] = indexAudio + 1
if subsEnabled and len(xbmc.Player().getAvailableSubtitleStreams()) > 0:
if mapping:
externalIndex = json.loads(mapping)
else: # Direct paths scenario
externalIndex = ""
if externalIndex:
# If there's external subtitles added via PlaybackUtils
if externalIndex.get(str(indexSubs)):
# If the current subtitle is in the mapping
postdata['SubtitleStreamIndex'] = externalIndex[str(indexSubs)]
else:
# Internal subtitle currently selected
external = len(externalIndex)
postdata['SubtitleStreamIndex'] = indexSubs - external + audioTracks + 1
else:
# No external subtitles added via PlayUtils
postdata['SubtitleStreamIndex'] = indexSubs + audioTracks + 1
else:
postdata['SubtitleStreamIndex'] = ""
# Post playback to server
self.logMsg("Sending POST play started.", 1)
self.doUtils.downloadUrl(url, postBody=postdata, type="POST")
# save data map for updates and position calls
data = {
'runtime': runtime,
'item_id': item_id,
'refresh_id': refresh_id,
'currentfile': currentFile,
'AudioStreamIndex': postdata['AudioStreamIndex'],
'SubtitleStreamIndex': postdata['SubtitleStreamIndex'],
'playmethod': playMethod,
'Type': itemType,
'currentPosition': int(seekTime)
}
self.played_information[currentFile] = data
self.logMsg("ADDING_FILE: %s" % self.played_information, 1)
# log some playback stats
if(itemType != None):
if(self.playStats.get(itemType) != None):
count = self.playStats.get(itemType) + 1
self.playStats[itemType] = count
else:
self.playStats[itemType] = 1
if(playMethod != None):
if(self.playStats.get(playMethod) != None):
count = self.playStats.get(playMethod) + 1
self.playStats[playMethod] = count
else:
self.playStats[playMethod] = 1
# reset in progress position
#self.reportPlayback()
def GetPlayStats(self):
return self.playStats
def onPlayBackEnded( self ):
# Will be called when xbmc stops playing a file
self.logMsg("onPlayBackEnded", 0)
#workaround when strm files are launched through the addon - mark watched when finished playing
#TODO --> mark watched when 95% is played of the file
WINDOW = xbmcgui.Window( 10000 )
if WINDOW.getProperty("virtualstrm") != "":
try:
id = WINDOW.getProperty("virtualstrm")
type = WINDOW.getProperty("virtualstrmtype")
watchedurl = "{server}/mediabrowser/Users/{UserId}/PlayedItems/%s" % id
self.doUtils.downloadUrl(watchedurl, postBody="", type="POST")
librarySync.updatePlayCount(id)
except: pass
WINDOW.clearProperty("virtualstrm")
self.stopAll()
def onPlayBackStopped( self ):
# Will be called when user stops xbmc playing a file
self.logMsg("onPlayBackStopped", 0)
self.stopAll()
self.logMsg("ONPLAYBACK_STOPPED", 2)
self.stopAll()
def onPlayBackEnded( self ):
# Will be called when xbmc stops playing a file
self.logMsg("ONPLAYBACK_ENDED", 2)
self.stopAll()
def stopAll(self):
if not self.played_information:
return
self.logMsg("Played_information: %s" % str(self.played_information), 1)
# Process each items
for item in self.played_information:
data = self.played_information.get(item)
if data:
self.logMsg("Item path: %s" % item, 1)
self.logMsg("Item data: %s" % str(data), 1)
runtime = data.get('runtime')
currentPosition = data.get('currentPosition')
itemId = data.get('item_id')
refresh_id = data.get('refresh_id')
currentFile = data.get('currentfile')
type = data.get('Type')
playMethod = data.get('playmethod')
if currentPosition and runtime:
self.logMsg("RuntimeTicks: %s" % runtime, 1)
percentComplete = (currentPosition * 10000000) / int(runtime)
markPlayedAt = float(utils.settings('markPlayed')) / 100
self.logMsg("Percent complete: %s Mark played at: %s" % (percentComplete, markPlayedAt))
if percentComplete < markPlayedAt:
# Do not mark as watched for Kodi Monitor
utils.window('played_skipWatched', value="true")
self.stopPlayback(data)
offerDelete = False
if type == "Episode" and utils.settings('offerDeleteTV') == "true":
offerDelete = True
elif type == "Movie" and utils.settings('offerDeleteMovies') == "true":
offerDelete = True
if percentComplete >= markPlayedAt and offerDelete:
# Item could be stacked, so only offer to delete the main item.
if not self.stackFiles or itemId == utils.window('%sitem_id' % self.stackFiles):
return_value = xbmcgui.Dialog().yesno("Offer Delete", "Delete %s" % data.get('currentfile').split("/")[-1], "on Emby Server?")
if return_value:
# Delete Kodi entry before Emby
listItem = [itemId]
LibrarySync().removefromDB(listItem, True)
# Stop transcoding
if playMethod == "Transcode":
self.logMsg("Transcoding for %s terminated." % itemId, 1)
deviceId = self.clientInfo.getMachineId()
url = "{server}/mediabrowser/Videos/ActiveEncodings?DeviceId=%s" % deviceId
self.doUtils.downloadUrl(url, type="DELETE")
self.played_information.clear()
def stopPlayback(self, data):
self.logMsg("stopPlayback called", 2)
itemId = data.get('item_id')
currentPosition = data.get('currentPosition')
positionTicks = int(currentPosition * 10000000)
url = "{server}/mediabrowser/Sessions/Playing/Stopped"
postdata = {
'ItemId': itemId,
'MediaSourceId': itemId,
'PositionTicks': positionTicks
}
self.doUtils.downloadUrl(url, postBody=postdata, type="POST")

View file

@ -35,8 +35,10 @@
<setting id="smbusername" type="text" label="30007" default="" visible="true" enable="true" />
<setting id="smbpassword" type="text" label="30008" default="" option="hidden" visible="true" enable="true" />
<setting type="sep" />
<setting id="disableCinema" type="bool" label="Disable Emby cinema mode" default="false" visible="true" enable="true" />
<setting id="markPlayed" label="Mark watched at" type="slider" default="90" range="60,5,100" option="percent" visible="true" enable="true" />
<setting id="offerDelete" type="bool" label="30114" visible="true" enable="true" default="false" />
<setting id="offerDeleteTV" type="bool" label=" 30115" visible="eq(-1,true)" enable="true" default="false" />
<setting id="offerDeleteTV" type="bool" label="30115" visible="eq(-1,true)" enable="true" default="false" />
<setting id="offerDeleteMovies" type="bool" label="30116" visible="eq(-2,true)" enable="true" default="false" />
<setting id="resumeJumpBack" type="slider" label="On Resume Jump Back Seconds" default="10" range="0,1,120" option="int" visible="true" enable="true" />
<setting id="playFromStream" type="bool" label="30002" visible="true" enable="true" default="false" />

View file

@ -140,7 +140,7 @@ class Service():
# Update and report progress
playTime = xbmc.Player().getTime()
totalTime = xbmc.Player().getTotalTime()
currentFile = xbmc.Player().getPlayingFile()
currentFile = player.currentFile
# Update positionticks
if player.played_information.get(currentFile) is not None: