# -*- coding: utf-8 -*- ################################################################################################# import json import logging import os import ntpath import shutil import sys import urlparse import xbmc import xbmcaddon import xbmcgui import xbmcvfs import xbmcplugin import artwork import utils import clientinfo import connectmanager import database import downloadutils import librarysync import read_embyserver as embyserver import embydb_functions as embydb import playlist import playbackutils as pbutils import playutils import api from views import Playlist, VideoNodes from utils import window, settings, dialog, language as lang, urllib_path ################################################################################################# log = logging.getLogger("EMBY."+__name__) addon = xbmcaddon.Addon(id='plugin.video.emby') XML_PATH = (addon.getAddonInfo('path'), "default", "1080i", True) ################################################################################################# def doPlayback(itemId, dbId): emby = embyserver.Read_EmbyServer() item = emby.getItem(itemId) pbutils.PlaybackUtils(item).play(itemId, dbId) ##### DO RESET AUTH ##### def resetAuth(): # User tried login and failed too many times resp = xbmcgui.Dialog().yesno( heading=lang(30132), line1=lang(33050)) if resp: log.info("Reset login attempts.") window('emby_serverStatus', value="Auth") else: xbmc.executebuiltin('Addon.OpenSettings(plugin.video.emby)') def addDirectoryItem(label, path, folder=True): li = xbmcgui.ListItem(label, path=path) li.setThumbnailImage("special://home/addons/plugin.video.emby/icon.png") li.setArt({"fanart":"special://home/addons/plugin.video.emby/fanart.jpg"}) li.setArt({"landscape":"special://home/addons/plugin.video.emby/fanart.jpg"}) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=path, listitem=li, isFolder=folder) def doMainListing(): xbmcplugin.setContent(int(sys.argv[1]), 'files') # Get emby nodes from the window props embyprops = window('Emby.nodes.total') if embyprops: totalnodes = int(embyprops) for i in range(totalnodes): path = window('Emby.nodes.%s.index' % i) if not path: path = window('Emby.nodes.%s.content' % i) label = window('Emby.nodes.%s.title' % i) node = 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: if xbmc.getCondVisibility("Window.IsActive(Pictures)") and node in ("photos", "homevideos"): addDirectoryItem(label, path) elif xbmc.getCondVisibility("Window.IsActive(Videos)") and node != "photos": addDirectoryItem(label, path) elif not xbmc.getCondVisibility("Window.IsActive(Videos) | Window.IsActive(Pictures) | Window.IsActive(Music)"): addDirectoryItem(label, path) # experimental live tv nodes ''' if not xbmc.getCondVisibility("Window.IsActive(Pictures)"): addDirectoryItem(lang(33051), "plugin://plugin.video.emby/?mode=browsecontent&type=tvchannels&folderid=root") addDirectoryItem(lang(33052), "plugin://plugin.video.emby/?mode=browsecontent&type=recordings&folderid=root") ''' ''' TODO: Create plugin listing for servers servers = window('emby_servers.json') if servers: for server in servers: log.info(window('emby_server%s.name' % server)) addDirectoryItem(window('emby_server%s.name' % server), "plugin://plugin.video.emby/?mode=%s" % server) ''' #addDirectoryItem("Manual login dialog", "plugin://plugin.video.emby/?mode=manuallogin") #addDirectoryItem("Connect login dialog", "plugin://plugin.video.emby/?mode=connectlogin") #addDirectoryItem("Manual server dialog", "plugin://plugin.video.emby/?mode=manualserver") #addDirectoryItem("Connect servers dialog", "plugin://plugin.video.emby/?mode=connectservers") #addDirectoryItem("Connect users dialog", "plugin://plugin.video.emby/?mode=connectusers") addDirectoryItem(lang(30517), "plugin://plugin.video.emby/?mode=passwords") addDirectoryItem(lang(33053), "plugin://plugin.video.emby/?mode=settings") addDirectoryItem(lang(33054), "plugin://plugin.video.emby/?mode=adduser") addDirectoryItem(lang(33055), "plugin://plugin.video.emby/?mode=refreshplaylist") addDirectoryItem(lang(33098), "plugin://plugin.video.emby/?mode=refreshboxsets") addDirectoryItem(lang(33056), "plugin://plugin.video.emby/?mode=manualsync") addDirectoryItem(lang(33057), "plugin://plugin.video.emby/?mode=repair") addDirectoryItem(lang(33058), "plugin://plugin.video.emby/?mode=reset") addDirectoryItem(lang(33059), "plugin://plugin.video.emby/?mode=texturecache") addDirectoryItem(lang(33060), "plugin://plugin.video.emby/?mode=thememedia") if settings('backupPath'): addDirectoryItem(lang(33092), "plugin://plugin.video.emby/?mode=backup") xbmcplugin.endOfDirectory(int(sys.argv[1])) def test_manual_login(): from dialogs import LoginManual dialog = LoginManual("script-emby-connect-login-manual.xml", *XML_PATH) dialog.set_server("Test server") dialog.set_user("Test user") dialog.doModal() def test_connect_login(): from dialogs import LoginConnect dialog = LoginConnect("script-emby-connect-login.xml", *XML_PATH) dialog.doModal() def test_manual_server(): from dialogs import ServerManual dialog = ServerManual("script-emby-connect-server-manual.xml", *XML_PATH) dialog.doModal() def test_connect_servers(): from dialogs import ServerConnect dialog = ServerConnect("script-emby-connect-server.xml", *XML_PATH) test_servers = [ { u'LastConnectionMode': 2, u'Name': u'Server Name', u'AccessToken': u'Token', u'RemoteAddress': u'http://remote.address:8096', u'UserId': u'd4000909883845059aadef13b7110375', u'ManualAddress': u'http://manual.address:8096', u'DateLastAccessed': '2018-01-01T02:36:58Z', u'LocalAddress': u'http://local.address:8096', u'Id': u'b1ef1940b1964e2188f00b73611d53fd', u'Users': [ { u'IsSignedInOffline': True, u'Id': u'd4000909883845059aadef13b7110375' } ] } ] kwargs = { 'username': "Test user", 'user_image': None, 'servers': test_servers, 'emby_connect': True } dialog.set_args(**kwargs) dialog.doModal() def test_connect_users(): from dialogs import UsersConnect test_users = [{ u'Name': u'Guest', u'HasConfiguredEasyPassword': False, u'LastActivityDate': u'2018-01-01T04:10:15.4195197Z', u'HasPassword': False, u'LastLoginDate': u'2017-12-28T02:53:01.5770625Z', u'Policy': { u'EnabledDevices': [ ], u'EnableMediaPlayback': True, u'EnableRemoteControlOfOtherUsers': False, u'RemoteClientBitrateLimit': 0, u'BlockUnratedItems': [ ], u'EnableAllDevices': True, u'InvalidLoginAttemptCount': 0, u'EnableUserPreferenceAccess': True, u'EnableLiveTvManagement': False, u'EnableLiveTvAccess': False, u'IsAdministrator': False, u'EnableContentDeletion': False, u'EnabledChannels': [ ], u'IsDisabled': False, u'EnableSyncTranscoding': False, u'EnableAudioPlaybackTranscoding': True, u'EnableSharedDeviceControl': False, u'AccessSchedules': [ ], u'IsHidden': False, u'EnableContentDeletionFromFolders': [ ], u'EnableContentDownloading': False, u'EnableVideoPlaybackTranscoding': True, u'EnabledFolders': [ u'0c41907140d802bb58430fed7e2cd79e', u'a329cda1727467c08a8f1493195d32d3', u'f137a2dd21bbc1b99aa5c0f6bf02a805', u'4514ec850e5ad0c47b58444e17b6346c' ], u'EnableAllChannels': False, u'BlockedTags': [ ], u'EnableAllFolders': False, u'EnablePublicSharing': False, u'EnablePlaybackRemuxing': True }, u'ServerId': u'Test', u'Configuration': { u'SubtitleMode': u'Default', u'HidePlayedInLatest': True, u'GroupedFolders': [ ], u'DisplayCollectionsView': False, u'OrderedViews': [ ], u'SubtitleLanguagePreference': u'', u'AudioLanguagePreference': u'', u'LatestItemsExcludes': [ ], u'EnableLocalPassword': False, u'RememberAudioSelections': True, u'RememberSubtitleSelections': True, u'DisplayMissingEpisodes': False, u'PlayDefaultAudioTrack': True, u'EnableNextEpisodeAutoPlay': True }, u'Id': u'a9d56d37cb6b47a3bfd3453c55138ff1', u'HasConfiguredPassword': False }] dialog = UsersConnect("script-emby-connect-users.xml", *XML_PATH) dialog.set_server("Test server") dialog.set_users(test_users) dialog.doModal() def emby_connect(): # Login user to emby connect connect = connectmanager.ConnectManager() try: connectUser = connect.login_connect() except RuntimeError: return else: user = connectUser['User'] token = connectUser['AccessToken'] username = user['Name'] dialog(type_="notification", heading="{emby}", message="%s %s" % (lang(33000), username.decode('utf-8')), icon=user.get('ImageUrl') or "{emby}", time=2000, sound=False) settings('connectUsername', value=username) def emby_backup(): # Create a backup at specified location path = settings('backupPath') # filename default_value = "Kodi%s.%s" % (xbmc.getInfoLabel('System.BuildVersion')[:2], xbmc.getInfoLabel('System.Date(dd-mm-yy)')) folder_name = dialog(type_="input", heading=lang(33089), defaultt=default_value) if not folder_name: return backup = os.path.join(path, folder_name) log.info("Backup: %s", backup) # Create directory if xbmcvfs.exists(backup+"\\"): log.info("Existing directory!") if not dialog(type_="yesno", heading="{emby}", line1=lang(33090)): return emby_backup() shutil.rmtree(backup) # Addon_data addon_data = xbmc.translatePath("special://profile/addon_data/plugin.video.emby").decode('utf-8') try: shutil.copytree(src=addon_data, dst=os.path.join(backup, "addon_data", "plugin.video.emby")) except shutil.Error as error: log.error(error) # Database files database_folder = os.path.join(backup, "Database") if not xbmcvfs.mkdir(database_folder): try: os.makedirs(database_folder) except OSError as error: log.error(error) dialog(type_="ok", heading="{emby}", line1="Failed to create backup") return # Emby database emby_path = database.emby_database() xbmcvfs.copy(emby_path, os.path.join(database_folder, ntpath.basename(emby_path))) # Videos database video_path = database.video_database() xbmcvfs.copy(video_path, os.path.join(database_folder, ntpath.basename(video_path))) # Music database if settings('enableMusic') == "true": music_path = database.music_database() xbmcvfs.copy(music_path, os.path.join(database_folder, ntpath.basename(music_path))) dialog(type_="ok", heading="{emby}", line1="%s: %s" % (lang(33091), backup)) ##### Generate a new deviceId def resetDeviceId(): dialog = xbmcgui.Dialog() deviceId_old = window('emby_deviceId') try: window('emby_deviceId', clear=True) deviceId = clientinfo.ClientInfo().get_device_id(reset=True) except Exception as e: log.error("Failed to generate a new device Id: %s" % e) dialog.ok( heading=lang(29999), line1=lang(33032)) else: log.info("Successfully removed old deviceId: %s New deviceId: %s" % (deviceId_old, deviceId)) dialog.ok( heading=lang(29999), line1=lang(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 itemId = 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: log.info("Unknown type, unable to proceed.") return with database.DatabaseConn('emby') as cursor: emby_db = embydb.Embydb_Functions(cursor) item = emby_db.getItem_byKodiId(dbId, itemType) try: itemId = item[0] except TypeError: log.error("Unknown itemId, unable to proceed.") return if settings('skipContextMenu') != "true": resp = xbmcgui.Dialog().yesno( heading=lang(29999), line1=lang(33041)) if not resp: log.info("User skipped deletion for: %s." % itemId) return embyserver.Read_EmbyServer().deleteItem(itemId) ##### ADD ADDITIONAL USERS ##### def addUser(): if window('emby_online') != "true": log.info("server is offline") return doUtils = downloadutils.DownloadUtils() art = artwork.Artwork() clientInfo = clientinfo.ClientInfo() deviceId = clientInfo.get_device_id() deviceName = clientInfo.get_device_name() userid = window('emby_currUser') dialog = xbmcgui.Dialog() # Get session url = "{server}/emby/Sessions?DeviceId=%s&format=json" % deviceId try: result = doUtils.downloadUrl(url) 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(lang(33061), [lang(33062), lang(33063)]) # 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(lang(33064), 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={}, action_type="DELETE") dialog.notification( heading=lang(29999), message="%s %s" % (lang(33066), selected), icon="special://home/addons/plugin.video.emby/icon.png", time=1000) # clear picture position = window('EmbyAdditionalUserPosition.%s' % selected_userId) 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 log.info("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={}, action_type="POST") dialog.notification( heading=lang(29999), message="%s %s" % (lang(33067), selected), icon="special://home/addons/plugin.video.emby/icon.png", time=1000) except Exception as error: log.error("Failed to add user to session: " + str(error)) dialog.notification( heading=lang(29999), message=lang(33068), icon=xbmcgui.NOTIFICATION_ERROR) # Add additional user images # always clear the individual items first totalNodes = 10 for i in range(totalNodes): if not window('EmbyAdditionalUserImage.%s' % i): break window('EmbyAdditionalUserImage.%s' % i, clear=True) url = "{server}/emby/Sessions?DeviceId=%s&format=json" % deviceId try: result = doUtils.downloadUrl(url) additionalUsers = result[0]['AdditionalUsers'] except Exception as error: log.error(error) additionalUsers = [] count = 0 for additionaluser in additionalUsers: userid = additionaluser['UserId'] url = "{server}/emby/Users/%s?format=json" % userid result = doUtils.downloadUrl(url) window('EmbyAdditionalUserImage.%s' % count, value=art.get_user_artwork(result['Id'], 'Primary')) window('EmbyAdditionalUserPosition.%s' % userid, value=str(count)) count +=1 ##### THEME MUSIC/VIDEOS ##### def getThemeMedia(): doUtils = downloadutils.DownloadUtils() dialog = xbmcgui.Dialog() dummy_listitem = xbmcgui.ListItem() playback = None # Choose playback method resp = dialog.select(lang(33072), [lang(30165), lang(33071)]) if resp == 0: playback = "DirectPlay" elif resp == 1: playback = "DirectStream" else: return library = xbmc.translatePath( "special://profile/addon_data/plugin.video.emby/library/").decode('utf-8') # Create library directory if not xbmcvfs.exists(library): xbmcvfs.mkdir(library) # Set custom path for user if xbmc.getCondVisibility('System.HasAddon(script.tvtunes)'): tvtunes = xbmcaddon.Addon(id="script.tvtunes") tvtunes.setSetting('custom_path_enable', "true") tvtunes.setSetting('custom_path', library) log.info("TV Tunes custom path is enabled and set.") 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=lang(29999), line1=lang(33073)) xbmc.executebuiltin('Addon.OpenSettings(script.tvtunes)') return # Get every user view Id with database.DatabaseConn('emby') as cursor: emby_db = embydb.Embydb_Functions(cursor) 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.emby/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, dummy_listitem) if playback == "DirectPlay": playurl = api.API(theme).get_file_path() else: playurl = putils.get_direct_url(theme['MediaSources'][0]) 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']: if playback == "DirectPlay": playurl = api.API(theme).get_file_path() else: playurl = playutils.PlayUtils(theme, dummy_listitem).get_direct_url(theme['MediaSources'][0]) 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.emby/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']: if playback == "DirectPlay": playurl = api.API(theme).get_file_path() else: playurl = playutils.PlayUtils(theme, dummy_listitem).get_direct_url(theme['MediaSources'][0]) pathstowrite += ('%s' % playurl.encode('utf-8')) nfo_file.write( '%s' % pathstowrite ) # Close nfo file nfo_file.close() dialog.notification( heading=lang(29999), message=lang(33069), icon="special://home/addons/plugin.video.emby/icon.png", time=1000, sound=False) ##### REFRESH EMBY PLAYLISTS ##### def refreshPlaylist(): if window('emby_online') != "true": log.info("server is offline") return lib = librarysync.LibrarySync() dialog = xbmcgui.Dialog() try: # First remove playlists Playlist().delete_playlists() # Remove video nodes VideoNodes().deleteNodes() # Refresh views lib.refreshViews() dialog.notification( heading=lang(29999), message=lang(33069), icon="special://home/addons/plugin.video.emby/icon.png", time=1000, sound=False) except Exception as e: log.exception("Refresh playlists/nodes failed: %s" % e) dialog.notification( heading=lang(29999), message=lang(33070), icon=xbmcgui.NOTIFICATION_ERROR, time=1000, sound=False) #### SHOW SUBFOLDERS FOR NODE ##### def GetSubFolders(nodeindex): nodetypes = ["",".recent",".recentepisodes",".inprogress",".inprogressepisodes",".unwatched",".nextepisodes",".sets",".genres",".random",".recommended"] for node in nodetypes: title = window('Emby.nodes.%s%s.title' %(nodeindex,node)) if title: path = window('Emby.nodes.%s%s.content' %(nodeindex,node)) addDirectoryItem(title, path) xbmcplugin.endOfDirectory(int(sys.argv[1])) ##### BROWSE EMBY NODES DIRECTLY ##### def BrowseContent(viewname, browse_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_type = folderid folderid = "" else: filter_type = "" xbmcplugin.setPluginCategory(int(sys.argv[1]), viewname) #get views for root level if not folderid: views = emby.getViews(browse_type) for view in views: if view.get("name") == viewname.decode('utf-8'): folderid = view.get("id") break if viewname is not None: log.info("viewname: %s - type: %s - folderid: %s - filter: %s" %(viewname.decode('utf-8'), browse_type.decode('utf-8'), folderid.decode('utf-8'), filter_type.decode('utf-8'))) #set the correct params for the content type #only proceed if we have a folderid if folderid: if browse_type == "homevideos": #xbmcplugin.setContent(int(sys.argv[1]), 'files') itemtype = "Video,Folder,PhotoAlbum,Photo" else: itemtype = "" #get the actual listing if browse_type == "recordings": listing = emby.getTvRecordings(folderid) elif browse_type == "tvchannels": listing = emby.getTvChannels() elif filter_type == "recent": listing = emby.getFilteredSection(folderid, itemtype=itemtype.split(",")[0], sortby="DateCreated", recursive=True, limit=25, sortorder="Descending") elif filter_type == "random": listing = emby.getFilteredSection(folderid, itemtype=itemtype.split(",")[0], sortby="Random", recursive=True, limit=150, sortorder="Descending") elif filter_type == "recommended": listing = emby.getFilteredSection(folderid, itemtype=itemtype.split(",")[0], sortby="SortName", recursive=True, limit=25, sortorder="Ascending", filter_type="IsFavorite") elif folderid == "favepisodes": #xbmcplugin.setContent(int(sys.argv[1]), 'episodes') listing = emby.getFilteredSection(None, itemtype="Episode", sortby="SortName", recursive=True, limit=25, sortorder="Ascending", filter_type="IsFavorite") elif filter_type == "sets": listing = emby.getFilteredSection(folderid, itemtype=itemtype.split(",")[1], sortby="SortName", recursive=True, limit=25, sortorder="Ascending", filter_type="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 params = { 'id': viewname.encode('utf-8'), 'mode': "browsecontent", 'type': browse_type, 'folderid': item['Id'] } path = urllib_path("plugin://plugin.video.emby/", params) xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=path, listitem=li, isFolder=True) else: #playable item, set plugin path and mediastreams xbmcplugin.setContent(int(sys.argv[1]), 'episodes' if folderid else 'files') xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=li.getProperty("path"), listitem=li) if filter_type == "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.get_all_artwork(item) if item["Type"] == "Photo": #listitem setup for pictures... img_path = allart.get('Primary') li.setProperty("path",img_path) try: picture = doUtils.downloadUrl("{server}/Items/%s/Images" %itemid) except Exception as error: lof.info("Error getting images from server: " + str(error)) picture = None if picture is not None: 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.get_overview()) li.setIconImage('DefaultPicture.png') elif item['Type'] == "PhotoAlbum": img_path = allart.get('Primary') li.setProperty("path", img_path) li.setThumbnailImage(img_path) li.setIconImage('DefaultPicture.png') backdrop = allart.get('Backdrop') if backdrop: li.setArt({ 'fanart': backdrop[0] }) 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.get_genres() overlay = 0 userdata = API.get_userdata() 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 = 0 # Populate the extradata list and artwork extradata = { 'id': itemid, 'rating': rating, 'year': item.get('ProductionYear'), 'genre': genre, 'playcount': str(playcount), 'title': title, 'plot': API.get_overview(), '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.get_media_streams() 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 try: result = doUtils.downloadUrl(url) except Exception as error: log.info("Error getting channel: " + str(error)) result = None if result is not None and result.get("Items"): for item in result.get("Items"): itemid = item['Id'] itemtype = item['Type'] li = createListItemFromEmbyItem(item,art,doUtils) isFolder = item.get('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'] label2 = "" li = xbmcgui.ListItem(title) li.setProperty('IsPlayable', "true") metadata = { 'Title': title, 'duration': str(item['runtime']/60), 'Plot': item['plot'], 'Playcount': item['playcount'] } if "showtitle" in item: metadata['TVshowTitle'] = item['showtitle'] label2 = item['showtitle'] if "episodeid" in item: # Listitem of episode metadata['mediatype'] = "episode" metadata['dbid'] = item['episodeid'] # TODO: Review once Krypton is RC - probably no longer needed if there's dbid if "episode" in item: episode = item['episode'] metadata['Episode'] = episode if "season" in item: season = item['season'] metadata['Season'] = season if season and episode: episodeno = "s%.2de%.2d" % (season, episode) li.setProperty('episodeno', episodeno) label2 = "%s - %s" % (label2, episodeno) if label2 else episodeno if "firstaired" in item: metadata['Premiered'] = item['firstaired'] 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.setLabel2(label2) 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 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, filters=""): count = 0 filters = filters.split(',') if filters else [] # 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}, 'properties': ["title", "sorttitle"] } } 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: allshowsIds = set(item['tvshowid'] for item in items) query = { 'jsonrpc': "2.0", 'id': 1, 'method': "VideoLibrary.GetEpisodes", 'params': { 'sort': {'order': "descending", 'method': "dateadded"}, 'properties': [ "title", "playcount", "season", "episode", "showtitle", "plot", "file", "rating", "resume", "tvshowid", "art", "streamdetails", "firstaired", "runtime", "cast", "writer", "dateadded", "lastplayed" ], "limits": {"end": limit*5} } } if 'playcount' not in filters: query['params']['filter'] = {'operator': "lessthan", 'field': "playcount", 'value': "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: 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 log.info("Requesting extrafanart for Id: %s" % embyId) # 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.get_all_artwork(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: log.debug("Found cached backdrop.") # 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: log.error("Error getting extrafanart: %s" % e) # Always do endofdirectory to prevent errors in the logs xbmcplugin.endOfDirectory(int(sys.argv[1]))