# -*- coding: utf-8 -*- ############################################################################### import json import os import sys import xbmc import xbmcaddon import xbmcgui import xbmcvfs import xbmcplugin import artwork import utils import clientinfo import downloadutils import read_embyserver as embyserver import embydb_functions as embydb import playbackutils as pbutils import playutils import playlist import PlexFunctions import PlexAPI ############################################################################### # For logging only addonName = clientinfo.ClientInfo().getAddonName() title = "%s %s" % (addonName, __name__) def plexCompanion(fullurl, params): params = PlexFunctions.LiteralEval(params[26:]) utils.logMsg("entrypoint - plexCompanion", "params is: %s" % params, -1) if (params['machineIdentifier'] != utils.window('plex_machineIdentifier')): utils.logMsg( title, "Command was not for us, machineIdentifier controller: %s, " "our machineIdentifier : %s" % (params['machineIdentifier'], utils.window('plex_machineIdentifier')), -1) return library, key, query = PlexFunctions.ParseContainerKey( params['containerKey']) # Construct a container key that works always (get rid of playlist args) utils.window('containerKey', '/'+library+'/'+key) if 'playQueues' in library: utils.logMsg(title, "Playing a playQueue. Query was: %s" % query, 1) # Playing a playlist that we need to fetch from PMS xml = PlexFunctions.GetPlayQueue(key) if xml is None: utils.logMsg( title, "Error getting PMS playlist for key %s" % key, -1) return else: resume = PlexFunctions.ConvertPlexToKodiTime( params.get('offset', 0)) itemids = [] for item in xml: itemids.append(item.get('ratingKey')) return playlist.Playlist().playAll(itemids, resume) else: utils.logMsg( title, "Not knowing what to do for now - no playQueue sent", -1) def reConnect(): """ Triggers login to plex.tv and re-authorization """ string = xbmcaddon.Addon().getLocalizedString utils.logMsg("entrypoint reConnect", "Connection resets requested", 0) dialog = xbmcgui.Dialog() # Resetting, please wait dialog.notification( heading=addonName, message=string(39207), icon="special://home/addons/plugin.video.plexkodiconnect/icon.png", time=2000, sound=False) # Pause library sync thread - user needs to be auth in order to sync utils.window('suspend_LibraryThread', value='true') # Wait max for 5 seconds for all lib scans to finish counter = 0 while utils.window('emby_dbScan') == 'true': if counter > 500: # Failed to reset PMS and plex.tv connects. Try to restart Kodi. dialog.ok(heading=addonName, message=string(39208)) # Resuming threads, just in case utils.window('suspend_LibraryThread', clear=True) # Abort reConnection return counter += 1 xbmc.sleep(50) # Delete plex credentials in settings utils.settings('myplexlogin', value="true") utils.settings('plexLogin', value="") utils.settings('plexToken', value=""), utils.settings('plexid', value="") utils.settings('plexHomeSize', value="1") utils.settings('plexAvatar', value="") # Reset connection details utils.settings('plex_machineIdentifier', value="") utils.settings('plex_servername', value="") utils.settings('https', value="") utils.settings('ipaddress', value="") utils.settings('port', value="") # Log out currently signed in user: utils.window('emby_serverStatus', value="401") # Above method needs to have run its course! Hence wait counter = 0 while utils.window('emby_serverStatus') == "401": if counter > 100: dialog.ok(heading=addonName, message=string(39208)) # Abort reConnection return counter += 1 xbmc.sleep(50) # Suspend the user client during procedure utils.window('suspend_Userclient', value='true') import initialsetup initialsetup.InitialSetup().setup(forcePlexTV=True) # Request lib sync to get user view data (e.g. watched/unwatched) utils.window('plex_runLibScan', value='full') # Restart user client utils.window('suspend_Userclient', clear=True) def PassPlaylist(xml, resume=None): """ resume in KodiTime - seconds. """ # Set window properties to make them available later for other threads windowArgs = [ # 'containerKey' 'playQueueID', 'playQueueVersion'] for arg in windowArgs: utils.window(arg, value=xml.attrib.get(arg)) # Get resume point resume1 = PlexFunctions.ConvertPlexToKodiTime(utils.IntFromStr( xml.attrib.get('playQueueSelectedItemOffset', 0))) resume2 = resume resume = max(resume1, resume2) pbutils.PlaybackUtils(xml).StartPlay( resume=resume, resumeId=xml.attrib.get('playQueueSelectedItemID', None)) def doPlayback(itemid, dbid): """ Called only for a SINGLE element, not playQueues Always to return with a "setResolvedUrl" """ if utils.window('plex_authenticated') != "true": utils.logMsg('doPlayback', 'Not yet authenticated for a PMS, abort ' 'starting playback', -1) string = xbmcaddon.Addon().getLocalizedString # Not yet connected to a PMS server xbmcgui.Dialog().notification( heading=addonName, message=string(39210), icon=xbmcgui.NOTIFICATION_ERROR, time=7000, sound=True) return xbmcplugin.setResolvedUrl( int(sys.argv[1]), False, xbmcgui.ListItem()) item = PlexFunctions.GetPlexMetadata(itemid) if item is None: return xbmcplugin.setResolvedUrl( int(sys.argv[1]), False, xbmcgui.ListItem()) # Everything OK return pbutils.PlaybackUtils(item).play(itemid, dbid) # utils.logMsg(title, "doPlayback called with itemid=%s, dbid=%s" # % (itemid, dbid), 1) # item = PlexFunctions.GetPlexMetadata(itemid) # API = PlexAPI.API(item[0]) # # If resume != 0, then we don't need to build a playlist for trailers etc. # # No idea how we could otherwise get resume timing out of Kodi # resume, runtime = API.getRuntime() # if resume == 0: # uuid = item.attrib.get('librarySectionUUID', None) # if uuid: # if utils.settings('askCinema') == "true": # trailers = xbmcgui.Dialog().yesno(addonName, "Play trailers?") # else: # trailers = True # if trailers: # playQueue = PlexFunctions.GetPlexPlaylist( # API.getRatingKey(), uuid, mediatype=API.getType()) # if playQueue is not None: # return PassPlaylist(playQueue) # else: # utils.logMsg(title, "Error: no valid playQueue", -1) # else: # # E.g trailers being directly played # utils.logMsg(title, "No librarySectionUUID found.", 1) # # Play only 1 item, not playQueue # pbutils.PlaybackUtils(item).StartPlay(resume=resume, # resumeId=None) ##### DO RESET AUTH ##### def resetAuth(): # User tried login and failed too many times string = xbmcaddon.Addon().getLocalizedString resp = xbmcgui.Dialog().yesno( heading="Warning", line1=string(39206)) if resp == 1: utils.logMsg("PLEX", "Reset login attempts.", 1) utils.window('emby_serverStatus', value="Auth") else: xbmc.executebuiltin('Addon.OpenSettings(plugin.video.plexkodiconnect)') def addDirectoryItem(label, path, folder=True): li = xbmcgui.ListItem(label, path=path) li.setThumbnailImage("special://home/addons/plugin.video.plexkodiconnect/icon.png") li.setArt({"fanart":"special://home/addons/plugin.video.plexkodiconnect/fanart.jpg"}) li.setArt({"landscape":"special://home/addons/plugin.video.plexkodiconnect/fanart.jpg"}) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=path, listitem=li, isFolder=folder) def doMainListing(): string = xbmcaddon.Addon().getLocalizedString xbmcplugin.setContent(int(sys.argv[1]), 'files') # Get emby nodes from the window props embyprops = utils.window('Emby.nodes.total') if embyprops: totalnodes = int(embyprops) for i in range(totalnodes): path = utils.window('Emby.nodes.%s.index' % i) if not path: path = utils.window('Emby.nodes.%s.content' % i) label = utils.window('Emby.nodes.%s.title' % i) type = utils.window('Emby.nodes.%s.type' % i) #because we do not use seperate entrypoints for each content type, we need to figure out which items to show in each listing. #for now we just only show picture nodes in the picture library video nodes in the video library and all nodes in any other window if path and xbmc.getCondVisibility("Window.IsActive(Pictures)") and type == "photos": addDirectoryItem(label, path) elif path and xbmc.getCondVisibility("Window.IsActive(VideoLibrary)") and type != "photos": addDirectoryItem(label, path) elif path and not xbmc.getCondVisibility("Window.IsActive(VideoLibrary) | Window.IsActive(Pictures) | Window.IsActive(MusicLibrary)"): addDirectoryItem(label, path) # Plex user switch addDirectoryItem(string(39200) + utils.window('plex_username'), "plugin://plugin.video.plexkodiconnect/" "?mode=switchuser") #experimental live tv nodes # addDirectoryItem("Live Tv Channels (experimental)", "plugin://plugin.video.plexkodiconnect/?mode=browsecontent&type=tvchannels&folderid=root") # addDirectoryItem("Live Tv Recordings (experimental)", "plugin://plugin.video.plexkodiconnect/?mode=browsecontent&type=recordings&folderid=root") # some extra entries for settings and stuff. TODO --> localize the labels addDirectoryItem(string(39201), "plugin://plugin.video.plexkodiconnect/?mode=settings") # addDirectoryItem("Add user to session", "plugin://plugin.video.plexkodiconnect/?mode=adduser") addDirectoryItem(string(39203), "plugin://plugin.video.plexkodiconnect/?mode=refreshplaylist") addDirectoryItem(string(39204), "plugin://plugin.video.plexkodiconnect/?mode=manualsync") xbmcplugin.endOfDirectory(int(sys.argv[1])) ##### Generate a new deviceId def resetDeviceId(): dialog = xbmcgui.Dialog() language = utils.language deviceId_old = utils.window('emby_deviceId') try: utils.window('emby_deviceId', clear=True) deviceId = clientinfo.ClientInfo().getDeviceId(reset=True) except Exception as e: utils.logMsg(addonName, "Failed to generate a new device Id: %s" % e, 1) dialog.ok( heading=addonName, line1=language(33032)) else: utils.logMsg(addonName, "Successfully removed old deviceId: %s New deviceId: %s" % (deviceId_old, deviceId), 1) dialog.ok( heading=addonName, line1=language(33033)) xbmc.executebuiltin('RestartApp') ##### Delete Item def deleteItem(): # Serves as a keymap action if xbmc.getInfoLabel('ListItem.Property(embyid)'): # If we already have the embyid embyid = xbmc.getInfoLabel('ListItem.Property(embyid)') else: dbid = xbmc.getInfoLabel('ListItem.DBID') itemtype = xbmc.getInfoLabel('ListItem.DBTYPE') if not itemtype: if xbmc.getCondVisibility('Container.Content(albums)'): itemtype = "album" elif xbmc.getCondVisibility('Container.Content(artists)'): itemtype = "artist" elif xbmc.getCondVisibility('Container.Content(songs)'): itemtype = "song" elif xbmc.getCondVisibility('Container.Content(pictures)'): itemtype = "picture" else: utils.logMsg("EMBY delete", "Unknown type, unable to proceed.", 1) return embyconn = utils.kodiSQL('emby') embycursor = embyconn.cursor() emby_db = embydb.Embydb_Functions(embycursor) item = emby_db.getItem_byKodiId(dbid, itemtype) embycursor.close() try: embyid = item[0] except TypeError: utils.logMsg("EMBY delete", "Unknown embyId, unable to proceed.", 1) return if utils.settings('skipContextMenu') != "true": resp = xbmcgui.Dialog().yesno( heading="Confirm delete", line1=("Delete file from Emby Server? This will " "also delete the file(s) from disk!")) if not resp: utils.logMsg("EMBY delete", "User skipped deletion for: %s." % embyid, 1) return doUtils = downloadutils.DownloadUtils() url = "{server}/emby/Items/%s?format=json" % embyid utils.logMsg("EMBY delete", "Deleting request: %s" % embyid, 0) doUtils.downloadUrl(url, type="DELETE") ##### ADD ADDITIONAL USERS ##### def addUser(): doUtils = downloadutils.DownloadUtils() art = artwork.Artwork() clientInfo = clientinfo.ClientInfo() deviceId = clientInfo.getDeviceId() deviceName = clientInfo.getDeviceName() userid = utils.window('currUserId') dialog = xbmcgui.Dialog() # Get session url = "{server}/emby/Sessions?DeviceId=%s&format=json" % deviceId result = doUtils.downloadUrl(url) try: sessionId = result[0]['Id'] additionalUsers = result[0]['AdditionalUsers'] # Add user to session userlist = {} users = [] url = "{server}/emby/Users?IsDisabled=false&IsHidden=false&format=json" result = doUtils.downloadUrl(url) # pull the list of users for user in result: name = user['Name'] userId = user['Id'] if userid != userId: userlist[name] = userId users.append(name) # Display dialog if there's additional users if additionalUsers: option = dialog.select("Add/Remove user from the session", ["Add user", "Remove user"]) # Users currently in the session additionalUserlist = {} additionalUsername = [] # Users currently in the session for user in additionalUsers: name = user['UserName'] userId = user['UserId'] additionalUserlist[name] = userId additionalUsername.append(name) if option == 1: # User selected Remove user resp = dialog.select("Remove user from the session", additionalUsername) if resp > -1: selected = additionalUsername[resp] selected_userId = additionalUserlist[selected] url = "{server}/emby/Sessions/%s/Users/%s" % (sessionId, selected_userId) doUtils.downloadUrl(url, postBody={}, type="DELETE") dialog.notification( heading="Success!", message="%s removed from viewing session" % selected, icon="special://home/addons/plugin.video.plexkodiconnect/icon.png", time=1000) # clear picture position = utils.window('EmbyAdditionalUserPosition.%s' % selected_userId) utils.window('EmbyAdditionalUserImage.%s' % position, clear=True) return else: return elif option == 0: # User selected Add user for adduser in additionalUsername: try: # Remove from selected already added users. It is possible they are hidden. users.remove(adduser) except: pass elif option < 0: # User cancelled return # Subtract any additional users utils.logMsg("EMBY", "Displaying list of users: %s" % users) resp = dialog.select("Add user to the session", users) # post additional user if resp > -1: selected = users[resp] selected_userId = userlist[selected] url = "{server}/emby/Sessions/%s/Users/%s" % (sessionId, selected_userId) doUtils.downloadUrl(url, postBody={}, type="POST") dialog.notification( heading="Success!", message="%s added to viewing session" % selected, icon="special://home/addons/plugin.video.plexkodiconnect/icon.png", time=1000) except: utils.logMsg("EMBY", "Failed to add user to session.") dialog.notification( heading="Error", message="Unable to add/remove user from the session.", icon=xbmcgui.NOTIFICATION_ERROR) # Add additional user images # always clear the individual items first totalNodes = 10 for i in range(totalNodes): if not utils.window('EmbyAdditionalUserImage.%s' % i): break utils.window('EmbyAdditionalUserImage.%s' % i, clear=True) url = "{server}/emby/Sessions?DeviceId=%s" % deviceId result = doUtils.downloadUrl(url) additionalUsers = result[0]['AdditionalUsers'] count = 0 for additionaluser in additionalUsers: userid = additionaluser['UserId'] url = "{server}/emby/Users/%s?format=json" % userid result = doUtils.downloadUrl(url) utils.window('EmbyAdditionalUserImage.%s' % count, value=art.getUserArtwork(result['Id'], 'Primary')) utils.window('EmbyAdditionalUserPosition.%s' % userid, value=str(count)) count +=1 def switchPlexUser(): """ Signs out currently logged in user (if applicable). Triggers sign-in of a new user """ # Guess these user avatars are a future feature. Skipping for now # Delete any userimages. Since there's always only 1 user: position = 0 # position = 0 # utils.window('EmbyAdditionalUserImage.%s' % position, clear=True) utils.logMsg("entrypoint switchPlexUser", "Plex home user switch requested", 0) # Pause library sync thread - user needs to be auth in order to sync utils.window('suspend_LibraryThread', value='true') # Wait to ensure that any sync already going on has finished counter = 0 while utils.window('emby_dbScan') == 'true': if counter > 100: # Something went wrong, aborting # Resuming threads, just in case utils.window('suspend_LibraryThread', clear=True) # Abort reConnection return counter += 1 xbmc.sleep(50) # Log out currently signed in user: utils.window('emby_serverStatus', value="401") # Request lib sync to get user view data (e.g. watched/unwatched) utils.window('plex_runLibScan', value='full') ##### THEME MUSIC/VIDEOS ##### def getThemeMedia(): doUtils = downloadutils.DownloadUtils() dialog = xbmcgui.Dialog() playback = None # Choose playback method resp = dialog.select("Playback method for your themes", ["Direct Play", "Direct Stream"]) if resp == 0: playback = "DirectPlay" elif resp == 1: playback = "DirectStream" else: return library = xbmc.translatePath( "special://profile/addon_data/plugin.video.plexkodiconnect/library/").decode('utf-8') # Create library directory if not utils.IfExists(library): xbmcvfs.mkdir(library) # Set custom path for user tvtunes_path = xbmc.translatePath( "special://profile/addon_data/script.tvtunes/").decode('utf-8') if xbmcvfs.exists(tvtunes_path): tvtunes = xbmcaddon.Addon(id="script.tvtunes") tvtunes.setSetting('custom_path_enable', "true") tvtunes.setSetting('custom_path', library) utils.logMsg("EMBY", "TV Tunes custom path is enabled and set.", 1) else: # if it does not exist this will not work so warn user # often they need to edit the settings first for it to be created. dialog.ok( heading="Warning", line1=( "The settings file does not exist in tvtunes. ", "Go to the tvtunes addon and change a setting, then come back and re-run.")) xbmc.executebuiltin('Addon.OpenSettings(script.tvtunes)') return # Get every user view Id with embydb.GetEmbyDB() as emby_db: viewids = emby_db.getViews() # Get Ids with Theme Videos itemIds = {} for view in viewids: url = "{server}/emby/Users/{UserId}/Items?HasThemeVideo=True&ParentId=%s&format=json" % view result = doUtils.downloadUrl(url) if result['TotalRecordCount'] != 0: for item in result['Items']: itemId = item['Id'] folderName = item['Name'] folderName = utils.normalize_string(folderName.encode('utf-8')) itemIds[itemId] = folderName # Get paths for theme videos for itemId in itemIds: nfo_path = xbmc.translatePath( "special://profile/addon_data/plugin.video.plexkodiconnect/library/%s/" % itemIds[itemId]) # Create folders for each content if not xbmcvfs.exists(nfo_path): xbmcvfs.mkdir(nfo_path) # Where to put the nfos nfo_path = "%s%s" % (nfo_path, "tvtunes.nfo") url = "{server}/emby/Items/%s/ThemeVideos?format=json" % itemId result = doUtils.downloadUrl(url) # Create nfo and write themes to it nfo_file = xbmcvfs.File(nfo_path, 'w') pathstowrite = "" # May be more than one theme for theme in result['Items']: putils = playutils.PlayUtils(theme) if playback == "DirectPlay": playurl = putils.directPlay() else: playurl = putils.directStream() pathstowrite += ('%s' % playurl.encode('utf-8')) # Check if the item has theme songs and add them url = "{server}/emby/Items/%s/ThemeSongs?format=json" % itemId result = doUtils.downloadUrl(url) # May be more than one theme for theme in result['Items']: putils = playutils.PlayUtils(theme) if playback == "DirectPlay": playurl = putils.directPlay() else: playurl = putils.directStream() pathstowrite += ('%s' % playurl.encode('utf-8')) nfo_file.write( '%s' % pathstowrite ) # Close nfo file nfo_file.close() # Get Ids with Theme songs musicitemIds = {} for view in viewids: url = "{server}/emby/Users/{UserId}/Items?HasThemeSong=True&ParentId=%s&format=json" % view result = doUtils.downloadUrl(url) if result['TotalRecordCount'] != 0: for item in result['Items']: itemId = item['Id'] folderName = item['Name'] folderName = utils.normalize_string(folderName.encode('utf-8')) musicitemIds[itemId] = folderName # Get paths for itemId in musicitemIds: # if the item was already processed with video themes back out if itemId in itemIds: continue nfo_path = xbmc.translatePath( "special://profile/addon_data/plugin.video.plexkodiconnect/library/%s/" % musicitemIds[itemId]) # Create folders for each content if not xbmcvfs.exists(nfo_path): xbmcvfs.mkdir(nfo_path) # Where to put the nfos nfo_path = "%s%s" % (nfo_path, "tvtunes.nfo") url = "{server}/emby/Items/%s/ThemeSongs?format=json" % itemId result = doUtils.downloadUrl(url) # Create nfo and write themes to it nfo_file = xbmcvfs.File(nfo_path, 'w') pathstowrite = "" # May be more than one theme for theme in result['Items']: putils = playutils.PlayUtils(theme) if playback == "DirectPlay": playurl = putils.directPlay() else: playurl = putils.directStream() pathstowrite += ('%s' % playurl.encode('utf-8')) nfo_file.write( '%s' % pathstowrite ) # Close nfo file nfo_file.close() dialog.notification( heading="Emby for Kodi", message="Themes added!", icon="special://home/addons/plugin.video.plexkodiconnect/icon.png", time=1000, sound=False) ##### REFRESH EMBY PLAYLISTS ##### def refreshPlaylist(): utils.logMsg(addonName, 'Requesting playlist/nodes refresh', 0) utils.window('plex_runLibScan', value="views") #### SHOW SUBFOLDERS FOR NODE ##### def GetSubFolders(nodeindex): nodetypes = ["",".recent",".recentepisodes",".inprogress",".inprogressepisodes",".unwatched",".nextepisodes",".sets",".genres",".random",".recommended"] for node in nodetypes: title = utils.window('Emby.nodes.%s%s.title' %(nodeindex,node)) if title: path = utils.window('Emby.nodes.%s%s.content' %(nodeindex,node)) type = utils.window('Emby.nodes.%s%s.type' %(nodeindex,node)) addDirectoryItem(title, path) xbmcplugin.endOfDirectory(int(sys.argv[1])) ##### BROWSE EMBY NODES DIRECTLY ##### def BrowseContent(viewname, type="", folderid=""): emby = embyserver.Read_EmbyServer() art = artwork.Artwork() doUtils = downloadutils.DownloadUtils() #folderid used as filter ? if folderid in ["recent","recentepisodes","inprogress","inprogressepisodes","unwatched","nextepisodes","sets","genres","random","recommended"]: filter = folderid folderid = "" else: filter = "" xbmcplugin.setPluginCategory(int(sys.argv[1]), viewname) #get views for root level if not folderid: views = PlexFunctions.GetPlexCollections() for view in views: if view.get("name") == viewname.decode('utf-8'): folderid = view.get("id") utils.logMsg("BrowseContent","viewname: %s - type: %s - folderid: %s - filter: %s" %(viewname.decode('utf-8'), type.decode('utf-8'), folderid.decode('utf-8'), filter.decode('utf-8'))) #set the correct params for the content type #only proceed if we have a folderid if folderid: if type.lower() == "homevideos": xbmcplugin.setContent(int(sys.argv[1]), 'episodes') itemtype = "Video,Folder,PhotoAlbum" elif type.lower() == "photos": xbmcplugin.setContent(int(sys.argv[1]), 'files') itemtype = "Photo,PhotoAlbum,Folder" else: itemtype = "" #get the actual listing if type == "recordings": listing = emby.getTvRecordings(folderid) elif type == "tvchannels": listing = emby.getTvChannels() elif filter == "recent": listing = emby.getFilteredSection(folderid, itemtype=itemtype.split(",")[0], sortby="DateCreated", recursive=True, limit=25, sortorder="Descending") elif filter == "random": listing = emby.getFilteredSection(folderid, itemtype=itemtype.split(",")[0], sortby="Random", recursive=True, limit=150, sortorder="Descending") elif filter == "recommended": listing = emby.getFilteredSection(folderid, itemtype=itemtype.split(",")[0], sortby="SortName", recursive=True, limit=25, sortorder="Ascending", filter="IsFavorite") elif filter == "sets": listing = emby.getFilteredSection(folderid, itemtype=itemtype.split(",")[1], sortby="SortName", recursive=True, limit=25, sortorder="Ascending", filter="IsFavorite") else: listing = emby.getFilteredSection(folderid, itemtype=itemtype, recursive=False) #process the listing if listing: for item in listing.get("Items"): li = createListItemFromEmbyItem(item,art,doUtils) if item.get("IsFolder") == True: #for folders we add an additional browse request, passing the folderId path = "%s?id=%s&mode=browsecontent&type=%s&folderid=%s" % (sys.argv[0].decode('utf-8'), viewname.decode('utf-8'), type.decode('utf-8'), item.get("Id").decode('utf-8')) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=path, listitem=li, isFolder=True) else: #playable item, set plugin path and mediastreams xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=li.getProperty("path"), listitem=li) if filter == "recent": xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_DATE) else: xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_VIDEO_TITLE) xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_DATE) xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_VIDEO_RATING) xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_VIDEO_RUNTIME) xbmcplugin.endOfDirectory(handle=int(sys.argv[1])) ##### CREATE LISTITEM FROM EMBY METADATA ##### def createListItemFromEmbyItem(item,art=artwork.Artwork(),doUtils=downloadutils.DownloadUtils()): API = api.API(item) itemid = item['Id'] title = item.get('Name') li = xbmcgui.ListItem(title) premieredate = item.get('PremiereDate',"") if not premieredate: premieredate = item.get('DateCreated',"") if premieredate: premieredatelst = premieredate.split('T')[0].split("-") premieredate = "%s.%s.%s" %(premieredatelst[2],premieredatelst[1],premieredatelst[0]) li.setProperty("embyid",itemid) allart = art.getAllArtwork(item) if item["Type"] == "Photo": #listitem setup for pictures... img_path = allart.get('Primary') li.setProperty("path",img_path) picture = doUtils.downloadUrl("{server}/Items/%s/Images" %itemid) if picture: picture = picture[0] if picture.get("Width") > picture.get("Height"): li.setArt( {"fanart": img_path}) #add image as fanart for use with skinhelper auto thumb/backgrund creation li.setInfo('pictures', infoLabels={ "picturepath": img_path, "date": premieredate, "size": picture.get("Size"), "exif:width": str(picture.get("Width")), "exif:height": str(picture.get("Height")), "title": title}) li.setThumbnailImage(img_path) li.setProperty("plot",API.getOverview()) li.setIconImage('DefaultPicture.png') else: #normal video items li.setProperty('IsPlayable', 'true') path = "%s?id=%s&mode=play" % (sys.argv[0], item.get("Id")) li.setProperty("path",path) genre = API.getGenres() overlay = 0 userdata = API.getUserData() runtime = item.get("RunTimeTicks",0)/ 10000000.0 seektime = userdata['Resume'] if seektime: li.setProperty("resumetime", str(seektime)) li.setProperty("totaltime", str(runtime)) played = userdata['Played'] if played: overlay = 7 else: overlay = 6 playcount = userdata['PlayCount'] if playcount is None: playcount = 0 rating = item.get('CommunityRating') if not rating: rating = userdata['UserRating'] # Populate the extradata list and artwork extradata = { 'id': itemid, 'rating': rating, 'year': item.get('ProductionYear'), 'genre': genre, 'playcount': str(playcount), 'title': title, 'plot': API.getOverview(), 'Overlay': str(overlay), 'duration': runtime } if premieredate: extradata["premieredate"] = premieredate extradata["date"] = premieredate li.setInfo('video', infoLabels=extradata) if allart.get('Primary'): li.setThumbnailImage(allart.get('Primary')) else: li.setThumbnailImage('DefaultTVShows.png') li.setIconImage('DefaultTVShows.png') if not allart.get('Background'): #add image as fanart for use with skinhelper auto thumb/backgrund creation li.setArt( {"fanart": allart.get('Primary') } ) else: pbutils.PlaybackUtils(item).setArtwork(li) mediastreams = API.getMediaStreams() videostreamFound = False if mediastreams: for key, value in mediastreams.iteritems(): if key == "video" and value: videostreamFound = True if value: li.addStreamInfo(key, value[0]) if not videostreamFound: #just set empty streamdetails to prevent errors in the logs li.addStreamInfo("video", {'duration': runtime}) return li ##### BROWSE EMBY CHANNELS ##### def BrowseChannels(itemid, folderid=None): _addon_id = int(sys.argv[1]) _addon_url = sys.argv[0] doUtils = downloadutils.DownloadUtils() art = artwork.Artwork() xbmcplugin.setContent(int(sys.argv[1]), 'files') if folderid: url = ( "{server}/emby/Channels/%s/Items?userid={UserId}&folderid=%s&format=json" % (itemid, folderid)) elif itemid == "0": # id 0 is the root channels folder url = "{server}/emby/Channels?{UserId}&format=json" else: url = "{server}/emby/Channels/%s/Items?UserId={UserId}&format=json" % itemid result = doUtils.downloadUrl(url) if result and result.get("Items"): for item in result.get("Items"): itemid = item['Id'] itemtype = item['Type'] li = createListItemFromEmbyItem(item,art,doUtils) if itemtype == "ChannelFolderItem": isFolder = True else: isFolder = False channelId = item.get('ChannelId', "") channelName = item.get('ChannelName', "") if itemtype == "Channel": path = "%s?id=%s&mode=channels" % (_addon_url, itemid) xbmcplugin.addDirectoryItem(handle=_addon_id, url=path, listitem=li, isFolder=True) elif isFolder: path = "%s?id=%s&mode=channelsfolder&folderid=%s" % (_addon_url, channelId, itemid) xbmcplugin.addDirectoryItem(handle=_addon_id, url=path, listitem=li, isFolder=True) else: path = "%s?id=%s&mode=play" % (_addon_url, itemid) li.setProperty('IsPlayable', 'true') xbmcplugin.addDirectoryItem(handle=_addon_id, url=path, listitem=li) xbmcplugin.endOfDirectory(handle=int(sys.argv[1])) ##### LISTITEM SETUP FOR VIDEONODES ##### def createListItem(item): title = item['title'] li = xbmcgui.ListItem(title) li.setProperty('IsPlayable', "true") metadata = { 'Title': title, 'duration': str(item['runtime']/60), 'Plot': item['plot'], 'Playcount': item['playcount'] } if "episode" in item: episode = item['episode'] metadata['Episode'] = episode if "season" in item: season = item['season'] metadata['Season'] = season if season and episode: li.setProperty('episodeno', "s%.2de%.2d" % (season, episode)) if "firstaired" in item: metadata['Premiered'] = item['firstaired'] if "showtitle" in item: metadata['TVshowTitle'] = item['showtitle'] if "rating" in item: metadata['Rating'] = str(round(float(item['rating']),1)) if "director" in item: metadata['Director'] = " / ".join(item['director']) if "writer" in item: metadata['Writer'] = " / ".join(item['writer']) if "cast" in item: cast = [] castandrole = [] for person in item['cast']: name = person['name'] cast.append(name) castandrole.append((name, person['role'])) metadata['Cast'] = cast metadata['CastAndRole'] = castandrole li.setInfo(type="Video", infoLabels=metadata) li.setProperty('resumetime', str(item['resume']['position'])) li.setProperty('totaltime', str(item['resume']['total'])) li.setArt(item['art']) li.setThumbnailImage(item['art'].get('thumb','')) li.setIconImage('DefaultTVShows.png') li.setProperty('dbid', str(item['episodeid'])) li.setProperty('fanart_image', item['art'].get('tvshow.fanart','')) for key, value in item['streamdetails'].iteritems(): for stream in value: li.addStreamInfo(key, stream) return li ##### GET NEXTUP EPISODES FOR TAGNAME ##### def getNextUpEpisodes(tagname, limit): count = 0 # if the addon is called with nextup parameter, # we return the nextepisodes list of the given tagname xbmcplugin.setContent(int(sys.argv[1]), 'episodes') # First we get a list of all the TV shows - filtered by tag query = { 'jsonrpc': "2.0", 'id': "libTvShows", 'method': "VideoLibrary.GetTVShows", 'params': { 'sort': {'order': "descending", 'method': "lastplayed"}, 'filter': { 'and': [ {'operator': "true", 'field': "inprogress", 'value': ""}, {'operator': "is", 'field': "tag", 'value': "%s" % tagname} ]}, 'properties': ['title', 'studio', 'mpaa', 'file', 'art'] } } result = xbmc.executeJSONRPC(json.dumps(query)) result = json.loads(result) # If we found any, find the oldest unwatched show for each one. try: items = result['result']['tvshows'] except (KeyError, TypeError): pass else: for item in items: if utils.settings('ignoreSpecialsNextEpisodes') == "true": query = { 'jsonrpc': "2.0", 'id': 1, 'method': "VideoLibrary.GetEpisodes", 'params': { 'tvshowid': item['tvshowid'], 'sort': {'method': "episode"}, 'filter': { 'and': [ {'operator': "lessthan", 'field': "playcount", 'value': "1"}, {'operator': "greaterthan", 'field': "season", 'value': "0"} ]}, 'properties': [ "title", "playcount", "season", "episode", "showtitle", "plot", "file", "rating", "resume", "tvshowid", "art", "streamdetails", "firstaired", "runtime", "writer", "dateadded", "lastplayed" ], 'limits': {"end": 1} } } else: query = { 'jsonrpc': "2.0", 'id': 1, 'method': "VideoLibrary.GetEpisodes", 'params': { 'tvshowid': item['tvshowid'], 'sort': {'method': "episode"}, 'filter': {'operator': "lessthan", 'field': "playcount", 'value': "1"}, 'properties': [ "title", "playcount", "season", "episode", "showtitle", "plot", "file", "rating", "resume", "tvshowid", "art", "streamdetails", "firstaired", "runtime", "writer", "dateadded", "lastplayed" ], 'limits': {"end": 1} } } result = xbmc.executeJSONRPC(json.dumps(query)) result = json.loads(result) try: episodes = result['result']['episodes'] except (KeyError, TypeError): pass else: for episode in episodes: li = createListItem(episode) xbmcplugin.addDirectoryItem( handle=int(sys.argv[1]), url=episode['file'], listitem=li) count += 1 if count == limit: break xbmcplugin.endOfDirectory(handle=int(sys.argv[1])) ##### GET INPROGRESS EPISODES FOR TAGNAME ##### def getInProgressEpisodes(tagname, limit): count = 0 # if the addon is called with inprogressepisodes parameter, # we return the inprogressepisodes list of the given tagname xbmcplugin.setContent(int(sys.argv[1]), 'episodes') # First we get a list of all the in-progress TV shows - filtered by tag query = { 'jsonrpc': "2.0", 'id': "libTvShows", 'method': "VideoLibrary.GetTVShows", 'params': { 'sort': {'order': "descending", 'method': "lastplayed"}, 'filter': { 'and': [ {'operator': "true", 'field': "inprogress", 'value': ""}, {'operator': "is", 'field': "tag", 'value': "%s" % tagname} ]}, 'properties': ['title', 'studio', 'mpaa', 'file', 'art'] } } result = xbmc.executeJSONRPC(json.dumps(query)) result = json.loads(result) # If we found any, find the oldest unwatched show for each one. try: items = result['result']['tvshows'] except (KeyError, TypeError): pass else: for item in items: query = { 'jsonrpc': "2.0", 'id': 1, 'method': "VideoLibrary.GetEpisodes", 'params': { 'tvshowid': item['tvshowid'], 'sort': {'method': "episode"}, 'filter': {'operator': "true", 'field': "inprogress", 'value': ""}, 'properties': [ "title", "playcount", "season", "episode", "showtitle", "plot", "file", "rating", "resume", "tvshowid", "art", "cast", "streamdetails", "firstaired", "runtime", "writer", "dateadded", "lastplayed" ] } } result = xbmc.executeJSONRPC(json.dumps(query)) result = json.loads(result) try: episodes = result['result']['episodes'] except (KeyError, TypeError): pass else: for episode in episodes: li = createListItem(episode) xbmcplugin.addDirectoryItem( handle=int(sys.argv[1]), url=episode['file'], listitem=li) count += 1 if count == limit: break xbmcplugin.endOfDirectory(handle=int(sys.argv[1])) ##### GET RECENT EPISODES FOR TAGNAME ##### # def getRecentEpisodes(tagname, limit): def getRecentEpisodes(viewid, mediatype, tagname, limit): count = 0 # if the addon is called with recentepisodes parameter, # we return the recentepisodes list of the given tagname xbmcplugin.setContent(int(sys.argv[1]), 'episodes') # First we get a list of all the TV shows - filtered by tag query = { 'jsonrpc': "2.0", 'id': "libTvShows", 'method': "VideoLibrary.GetTVShows", 'params': { 'sort': {'order': "descending", 'method': "dateadded"}, 'filter': {'operator': "is", 'field': "tag", 'value': "%s" % tagname}, } } result = xbmc.executeJSONRPC(json.dumps(query)) result = json.loads(result) # If we found any, find the oldest unwatched show for each one. try: items = result['result'][mediatype] except (KeyError, TypeError): # No items, empty folder xbmcplugin.endOfDirectory(handle=int(sys.argv[1])) return allshowsIds = set() for item in items: allshowsIds.add(item['tvshowid']) query = { 'jsonrpc': "2.0", 'id': 1, 'method': "VideoLibrary.GetEpisodes", 'params': { 'sort': {'order': "descending", 'method': "dateadded"}, 'filter': {'operator': "lessthan", 'field': "playcount", 'value': "1"}, 'properties': [ "title", "playcount", "season", "episode", "showtitle", "plot", "file", "rating", "resume", "tvshowid", "art", "streamdetails", "firstaired", "runtime", "cast", "writer", "dateadded", "lastplayed" ], "limits": {"end": limit} } } result = xbmc.executeJSONRPC(json.dumps(query)) result = json.loads(result) try: episodes = result['result']['episodes'] except (KeyError, TypeError): pass else: for episode in episodes: if episode['tvshowid'] in allshowsIds: li = createListItem(episode) xbmcplugin.addDirectoryItem( handle=int(sys.argv[1]), url=episode['file'], listitem=li) count += 1 if count == limit: break xbmcplugin.endOfDirectory(handle=int(sys.argv[1])) ##### GET VIDEO EXTRAS FOR LISTITEM ##### def getVideoFiles(embyId,embyPath): #returns the video files for the item as plugin listing, can be used for browsing the actual files or videoextras etc. emby = embyserver.Read_EmbyServer() if not embyId: if "plugin.video.emby" in embyPath: embyId = embyPath.split("/")[-2] if embyId: item = emby.getItem(embyId) putils = playutils.PlayUtils(item) if putils.isDirectPlay(): #only proceed if we can access the files directly. TODO: copy local on the fly if accessed outside filelocation = putils.directPlay() if not filelocation.endswith("/"): filelocation = filelocation.rpartition("/")[0] dirs, files = xbmcvfs.listdir(filelocation) for file in files: file = filelocation + file li = xbmcgui.ListItem(file, path=file) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=file, listitem=li) for dir in dirs: dir = filelocation + dir li = xbmcgui.ListItem(dir, path=dir) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=dir, listitem=li, isFolder=True) xbmcplugin.endOfDirectory(int(sys.argv[1])) ##### GET EXTRAFANART FOR LISTITEM ##### def getExtraFanArt(embyId,embyPath): emby = embyserver.Read_EmbyServer() art = artwork.Artwork() # Get extrafanart for listitem # will be called by skinhelper script to get the extrafanart try: # for tvshows we get the embyid just from the path if not embyId: if "plugin.video.emby" in embyPath: embyId = embyPath.split("/")[-2] if embyId: #only proceed if we actually have a emby id utils.logMsg("EMBY", "Requesting extrafanart for Id: %s" % embyId, 0) # We need to store the images locally for this to work # because of the caching system in xbmc fanartDir = xbmc.translatePath("special://thumbnails/emby/%s/" % embyId).decode('utf-8') if not xbmcvfs.exists(fanartDir): # Download the images to the cache directory xbmcvfs.mkdirs(fanartDir) item = emby.getItem(embyId) if item: backdrops = art.getAllArtwork(item)['Backdrop'] tags = item['BackdropImageTags'] count = 0 for backdrop in backdrops: # Same ordering as in artwork tag = tags[count] if os.path.supports_unicode_filenames: fanartFile = os.path.join(fanartDir, "fanart%s.jpg" % tag) else: fanartFile = os.path.join(fanartDir.encode("utf-8"), "fanart%s.jpg" % tag.encode("utf-8")) li = xbmcgui.ListItem(tag, path=fanartFile) xbmcplugin.addDirectoryItem( handle=int(sys.argv[1]), url=fanartFile, listitem=li) xbmcvfs.copy(backdrop, fanartFile) count += 1 else: utils.logMsg("EMBY", "Found cached backdrop.", 2) # Use existing cached images dirs, files = xbmcvfs.listdir(fanartDir) for file in files: fanartFile = os.path.join(fanartDir, file.decode('utf-8')) li = xbmcgui.ListItem(file, path=fanartFile) xbmcplugin.addDirectoryItem( handle=int(sys.argv[1]), url=fanartFile, listitem=li) except Exception as e: utils.logMsg("EMBY", "Error getting extrafanart: %s" % e, 0) # Always do endofdirectory to prevent errors in the logs xbmcplugin.endOfDirectory(int(sys.argv[1])) def RunLibScan(mode): if utils.window('emby_online') != "true": # Server is not online, do not run the sync string = xbmcaddon.Addon().getLocalizedString xbmcgui.Dialog().ok(heading=addonName, line1=string(39205)) else: utils.window('plex_runLibScan', value='full') def BrowsePlexContent(viewid, mediatype="", nodetype=""): """ Plex: viewid: PMS name of the library mediatype: mediatype, e.g. 'movies', 'tvshows', 'photos' nodetype: e.g. 'ondeck' """ utils.logMsg(title, "BrowsePlexContent called with viewid: %s, mediatype: %s, nodetype: %s" % (viewid, mediatype, nodetype), 1) if nodetype == 'ondeck': xml = PlexFunctions.GetPlexOnDeck( viewid, containerSize=int(utils.settings('limitindex'))) if not xml: utils.logMsg(title, "Cannot get view for section %s" % viewid, -1) return viewname = xml.attrib.get('librarySectionTitle') xbmcplugin.setPluginCategory(int(sys.argv[1]), viewname) # set the correct params for the content type if mediatype.lower() == "homevideos, tvshows": xbmcplugin.setContent(int(sys.argv[1]), 'episodes') itemtype = "Video,Folder,PhotoAlbum" elif mediatype.lower() == "photos": xbmcplugin.setContent(int(sys.argv[1]), 'files') itemtype = "Photo,PhotoAlbum,Folder" else: itemtype = "" # process the listing for item in xml: API = PlexAPI.API(item) li = API.CreateListItemFromPlexItem() if item.tag == 'Directory': # for folders we add an additional browse request, passing the # folderId li.setProperty('IsFolder', 'true') li.setProperty('IsPlayable', 'false') path = "%s?id=%s&mode=browsecontent&type=%s&folderid=%s" % (sys.argv[0].decode('utf-8'), viewname.decode('utf-8'), type.decode('utf-8'), item.get("Id").decode('utf-8')) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=path, listitem=li, isFolder=True) else: # playable item, set plugin path and mediastreams path = "%s?id=%s&mode=play" % (sys.argv[0], API.getRatingKey()) li.setProperty("path", path) API.AddStreamInfo(li) pbutils.PlaybackUtils(item).setArtwork(li) xbmcplugin.addDirectoryItem( handle=int(sys.argv[1]), url=li.getProperty("path"), listitem=li) if filter == "recent": xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_DATE) else: xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_VIDEO_TITLE) xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_DATE) xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_VIDEO_RATING) xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_VIDEO_RUNTIME) xbmcplugin.endOfDirectory(handle=int(sys.argv[1])) def getOnDeck(viewid, mediatype, tagname, limit): """ Retrieves Plex On Deck items, currently only for TV shows Input: viewid: Plex id of the library section, e.g. '1' mediatype: Kodi mediatype, e.g. 'tvshows', 'movies', 'homevideos', 'photos' tagname: Name of the Plex library, e.g. "My Movies" limit: Max. number of items to retrieve, e.g. 50 """ # if the addon is called with nextup parameter, # we return the nextepisodes list of the given tagname xbmcplugin.setContent(int(sys.argv[1]), 'episodes') # First we get a list of all the TV shows - filtered by tag query = { 'jsonrpc': "2.0", 'id': "libTvShows", 'method': "VideoLibrary.GetTVShows", 'params': { 'sort': {'order': "descending", 'method': "lastplayed"}, 'filter': { 'and': [ {'operator': "true", 'field': "inprogress", 'value': ""}, {'operator': "is", 'field': "tag", 'value': "%s" % tagname} ]} } } result = xbmc.executeJSONRPC(json.dumps(query)) result = json.loads(result) # If we found any, find the oldest unwatched show for each one. try: items = result['result'][mediatype] except (KeyError, TypeError): # Now items retrieved - empty directory xbmcplugin.endOfDirectory(handle=int(sys.argv[1])) return query = { 'jsonrpc': "2.0", 'id': 1, 'method': "VideoLibrary.GetEpisodes", 'params': { 'sort': {'method': "episode"}, 'limits': {"end": 1}, 'properties': [ "title", "playcount", "season", "episode", "showtitle", "plot", "file", "rating", "resume", "tvshowid", "art", "streamdetails", "firstaired", "runtime", "cast", "writer", "dateadded", "lastplayed" ], } } if utils.settings('ignoreSpecialsNextEpisodes') == "true": query['params']['filter'] = { 'and': [ {'operator': "lessthan", 'field': "playcount", 'value': "1"}, {'operator': "greaterthan", 'field': "season", 'value': "0"} ] } else: query['params']['filter'] = { 'or': [ {'operator': "lessthan", 'field': "playcount", 'value': "1"}, {'operator': "true", 'field': "inprogress", 'value': ""} ] } # Are there any episodes still in progress/not yet finished watching?!? # Then we should show this episode, NOT the "next up" inprogrQuery = { 'jsonrpc': "2.0", 'id': 1, 'method': "VideoLibrary.GetEpisodes", 'params': { 'sort': {'method': "episode"}, 'filter': {'operator': "true", 'field': "inprogress", 'value': ""}, } } inprogrQuery['params']['properties'] = query['params']['properties'] count = 0 for item in items: inprogrQuery['params']['tvshowid'] = item['tvshowid'] result = xbmc.executeJSONRPC(json.dumps(inprogrQuery)) result = json.loads(result) try: episodes = result['result']['episodes'] except (KeyError, TypeError): # No, there are no episodes not yet finished. Get "next up" query['params']['tvshowid'] = item['tvshowid'] result = xbmc.executeJSONRPC(json.dumps(query)) result = json.loads(result) try: episodes = result['result']['episodes'] except (KeyError, TypeError): # Also no episodes currently coming up continue for episode in episodes: # There will always be only 1 episode ('limit=1') li = createListItem(episode) # Fix some skin shortcomings title = episode.get('title', '') if utils.settings('OnDeckTvAppendSeason') == 'true': seasonid = episode.get('season') episodeid = episode.get('episode') if seasonid and episodeid: title = ('S' + str(seasonid) + 'E' + str(episodeid) + ' - ' + title) if utils.settings('OnDeckTvAppendShow') == 'true': show = episode.get('showtitle') if show: title = show + ' - ' + title li.setLabel(title) xbmcplugin.addDirectoryItem( handle=int(sys.argv[1]), url=episode['file'], listitem=li, isFolder=False) count += 1 if count >= limit: break xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))