def linkEmbyConnect(handle, _): # retrieve the media provider mediaProvider = xbmcmediaimport.getProvider(handle) if not mediaProvider: log('cannot retrieve media provider', xbmc.LOGERROR) return # get the media provider settings providerSettings = mediaProvider.prepareSettings() if not providerSettings: return # make sure we have a valid device ID deviceId = providerSettings.getString(emby.constants.SETTING_PROVIDER_DEVICEID) if not deviceId: deviceId = Request.GenerateDeviceId() providerSettings.setString(emby.constants.SETTING_PROVIDER_DEVICEID, deviceId) embyConnect = linkToEmbyConnect(deviceId) if not embyConnect: return # make sure the configured Emby server is still accessible serverUrl = ProviderSettings.GetUrl(providerSettings) matchingServer = None serverId = Server.GetServerId(mediaProvider.getIdentifier()) # get all connected servers servers = EmbyConnect.GetServers(embyConnect.accessToken, embyConnect.userId) if not servers: log('no servers available for Emby Connect user id {}'.format(embyConnect.userId), xbmc.LOGWARNING) return for server in servers: if server.systemId == serverId: matchingServer = server break if not matchingServer: log('no Emby server matching {} found'.format(serverUrl), xbmc.LOGWARNING) xbmcgui.Dialog().ok(localise(32038), localise(32061)) return # change the settings providerSettings.setString(emby.constants.SETTING_PROVIDER_EMBY_CONNECT_USER_ID, embyConnect.userId) providerSettings.setString(emby.constants.SETTING_PROVIDER_EMBY_CONNECT_ACCESS_KEY, matchingServer.accessKey) success = False try: success = Server(mediaProvider).Authenticate(force=True) except: pass if success: xbmcgui.Dialog().ok(localise(32038), localise(32062)) log('successfully linked to Emby Connect server {} ({}) {}'.format(matchingServer.name, serverId, serverUrl)) else: xbmcgui.Dialog().ok(localise(32038), localise(32061)) log('failed to link to Emby Connect server {} ({}) {}'.format(matchingServer.name, serverId, serverUrl), xbmc.LOGWARNING)
def discoverProviderLocally(handle, options): baseUrl = xbmcgui.Dialog().input(localise(32050), 'http://') if not baseUrl: return None log('trying to discover an Emby server at {}...'.format(baseUrl)) try: serverInfo = emby.api.server.Server.GetInfo(baseUrl) if not serverInfo: return None except: return None providerId = Server.BuildProviderId(serverInfo.id) providerIconUrl = Server.BuildIconUrl(baseUrl) provider = xbmcmediaimport.MediaProvider( providerId, baseUrl, serverInfo.name, providerIconUrl, emby.constants.SUPPORTED_MEDIA_TYPES) provider.setIconUrl(kodi.Api.downloadIcon(provider)) # store local authentication in settings providerSettings = provider.prepareSettings() if not providerSettings: return None providerSettings.setString( emby.constants.SETTING_PROVIDER_AUTHENTICATION, emby.constants.SETTING_PROVIDER_AUTHENTICATION_OPTION_LOCAL) providerSettings.save() log('Local Emby server {} successfully discovered at {}'.format( mediaProvider2str(provider), baseUrl)) return provider
def _addServer(self, server): registerServer = False # check if the server is already known if not server.id in self._servers: self._servers[server.id] = server registerServer = True else: # check if the server has already been registered or if some of its properties have changed if not self._servers[server.id].registered or self._servers[server.id].name != server.name or self._servers[server.id].address != server.address: self._servers[server.id] = server registerServer = True else: # simply update the server's last seen property self._servers[server.id].lastseen = server.lastseen # if the server doesn't need to be registered there's nothing else to do if not registerServer: return providerId = Server.BuildProviderId(server.id) providerIconUrl = Server.BuildIconUrl(server.address) mediaProvider = xbmcmediaimport.MediaProvider(providerId, server.address, server.name, providerIconUrl, emby.constants.SUPPORTED_MEDIA_TYPES) mediaProvider.setIconUrl(kodi.Api.downloadIcon(mediaProvider)) if xbmcmediaimport.addAndActivateProvider(mediaProvider): self._servers[server.id].registered = True log('Emby server "{}" ({}) successfully added and activated'.format(server.name, server.id)) else: self._servers[server.id].registered = False log('failed to add and/or activate Emby server "{}" ({})'.format(server.name, server.id))
def settingOptionsFillerViews(handle, options): # retrieve the media provider mediaProvider = xbmcmediaimport.getProvider(handle) if not mediaProvider: log('cannot retrieve media provider', xbmc.LOGERROR) return # retrieve the media import mediaImport = xbmcmediaimport.getImport(handle) if not mediaImport: log('cannot retrieve media import', xbmc.LOGERROR) return # prepare the media provider settings if not mediaProvider.prepareSettings(): log('cannot prepare media provider settings', xbmc.LOGERROR) return embyServer = Server(mediaProvider) if not embyServer.Authenticate(): log('failed to authenticate on media provider {}'.format(mediaProvider2str(mediaProvider)), xbmc.LOGERROR) return libraryViews = Library.GetViews(embyServer, mediaImport.getMediaTypes()) views = [] for libraryView in libraryViews: views.append((libraryView.name, libraryView.id)) # get the import's settings settings = mediaImport.getSettings() # pass the list of views back to Kodi settings.setStringOptions(emby.constants.SETTING_IMPORT_VIEWS_SPECIFIC, views)
def downloadIcon(mediaProvider): if not mediaProvider: raise ValueError('invalid mediaProvider') try: basePath = xbmc.translatePath(__addon__.getAddonInfo('profile')).decode('utf-8') except AttributeError: basePath = xbmc.translatePath(__addon__.getAddonInfo('profile')) # determine the icon's URL on the media provider iconUrl = Server.BuildIconUrl(mediaProvider.getBasePath()) # make sure the addon data directory exists if not xbmcvfs.exists(basePath): if not Api._makeDir(basePath): log('failed to create addon data directory at {}'.format(basePath), xbmc.LOGWARNING) return iconUrl # generate the icon's local path serverId = Server.GetServerId(mediaProvider.getIdentifier()) iconPath = os.path.join(basePath, '{}.png'.format(serverId)) # try to download the icon (since Emby's webserver doesn't support HEAD requests) try: urlretrieve(iconUrl, iconPath) except IOError as err: log('failed to download icon for {} from {}: {}'.format(mediaProvider2str(mediaProvider), iconUrl, err), xbmc.LOGWARNING) return iconUrl return iconPath
def settingOptionsFillerViews(handle, _): # retrieve the media provider mediaProvider = xbmcmediaimport.getProvider(handle) if not mediaProvider: log('cannot retrieve media provider', xbmc.LOGERROR) return # retrieve the media import mediaImport = xbmcmediaimport.getImport(handle) if not mediaImport: log('cannot retrieve media import', xbmc.LOGERROR) return # prepare the media provider settings if not mediaProvider.prepareSettings(): log('cannot prepare media provider settings', xbmc.LOGERROR) return try: embyServer = Server(mediaProvider) except: return libraryViews = getLibraryViews(embyServer, mediaImport.getMediaTypes()) views = [] for libraryView in libraryViews: views.append((libraryView.name, libraryView.id)) # get the import's settings settings = mediaImport.getSettings() # pass the list of views back to Kodi settings.setStringOptions(emby.constants.SETTING_IMPORT_VIEWS_SPECIFIC, views)
def isImportReady(handle, options): # retrieve the media import mediaImport = xbmcmediaimport.getImport(handle) if not mediaImport: log('cannot retrieve media import', xbmc.LOGERROR) return # prepare and get the media import settings importSettings = mediaImport.prepareSettings() if not importSettings: log('cannot prepare media import settings', xbmc.LOGERROR) return # retrieve the media provider mediaProvider = xbmcmediaimport.getProvider(handle) if not mediaProvider: log('cannot retrieve media provider', xbmc.LOGERROR) return # prepare the media provider settings if not mediaProvider.prepareSettings(): log('cannot prepare media provider settings', xbmc.LOGERROR) return try: embyServer = Server(mediaProvider) except: return # check if the chosen library views exist selectedViews = ImportSettings.GetLibraryViews(importSettings) matchingViews = getMatchingLibraryViews(embyServer, mediaImport.getMediaTypes(), selectedViews) xbmcmediaimport.setImportReady(handle, len(matchingViews) > 0)
def refreshMetadata(item, itemId, mediaProvider): # create an Emby server instance embyServer = Server(mediaProvider) # trigger a metadata refresh on the Emby server Library.RefreshItemMetadata(embyServer, itemId) log('[context/refresh] triggered metadata refresh for {} on {}'.format( listItem2str(item, itemId), mediaProvider2str(mediaProvider)))
def discoverProvider(handle, options): baseUrl = xbmcgui.Dialog().input(localise(32050), 'http://') if not baseUrl: return log('trying to discover an Emby server at {}...'.format(baseUrl)) try: serverInfo = emby.api.server.Server.GetInfo(baseUrl) if not serverInfo: return except: return providerId = Server.BuildProviderId(serverInfo.id) providerIconUrl = Server.BuildIconUrl(baseUrl) mediaProvider = xbmcmediaimport.MediaProvider(providerId, baseUrl, serverInfo.name, providerIconUrl, emby.constants.SUPPORTED_MEDIA_TYPES) mediaProvider.setIconUrl(kodi.Api.downloadIcon(mediaProvider)) log('Emby server {} successfully discovered at {}'.format(mediaProvider2str(mediaProvider), baseUrl)) xbmcmediaimport.setDiscoveredProvider(handle, True, mediaProvider)
def canImport(handle, options): if not 'path' in options: log('cannot execute "canimport" without path') return path = unquote(options['path'][0]) # try to get the emby server's identifier from the path id = Server.GetServerId(path) if not id: return xbmcmediaimport.setCanImport(handle, True)
def isImportReady(handle, options): # retrieve the media import mediaImport = xbmcmediaimport.getImport(handle) if not mediaImport: log('cannot retrieve media import', xbmc.LOGERROR) return # prepare and get the media import settings importSettings = mediaImport.prepareSettings() if not importSettings: log('cannot prepare media import settings', xbmc.LOGERROR) return # retrieve the media provider mediaProvider = xbmcmediaimport.getProvider(handle) if not mediaProvider: log('cannot retrieve media provider', xbmc.LOGERROR) return # prepare the media provider settings if not mediaProvider.prepareSettings(): log('cannot prepare media provider settings', xbmc.LOGERROR) return embyServer = None try: embyServer = Server(mediaProvider) except: return importReady = False # check if authentication works with the current provider settings if embyServer.Authenticate(): # check if the chosen library views exist selectedViews = getLibraryViewsFromSettings(importSettings) matchingViews = getMatchingLibraryViews(embyServer, mediaImport.getMediaTypes(), selectedViews) importReady = len(matchingViews) > 0 xbmcmediaimport.setImportReady(handle, importReady)
def testAuthentication(handle, _): # retrieve the media provider mediaProvider = xbmcmediaimport.getProvider(handle) if not mediaProvider: log('cannot retrieve media provider', xbmc.LOGERROR) return log('testing authentication with {}...'.format(mediaProvider2str(mediaProvider))) success = False try: success = Server(mediaProvider).Authenticate(force=True) except: pass line = 32018 if success: line = 32017 xbmcgui.Dialog().ok(mediaProvider.getFriendlyName(), localise(line))
def isProviderReady(handle, options): # retrieve the media provider mediaProvider = xbmcmediaimport.getProvider(handle) if not mediaProvider: log('cannot retrieve media provider', xbmc.LOGERROR) return # prepare the media provider settings if not mediaProvider.prepareSettings(): log('cannot prepare media provider settings', xbmc.LOGERROR) return # check if authentication works with the current provider settings try: providerReady = Server(mediaProvider).Authenticate(force=True) except: providerReady = False xbmcmediaimport.setProviderReady(handle, providerReady)
def changeUrl(handle, _): # retrieve the media provider mediaProvider = xbmcmediaimport.getProvider(handle) if not mediaProvider: log('cannot retrieve media provider', xbmc.LOGERROR) return # get the media provider settings providerSettings = mediaProvider.prepareSettings() if not providerSettings: return urlCurrent = ProviderSettings.GetUrl(providerSettings) if not urlCurrent: log("cannot retrieve current URL from provider settings", xbmc.LOGERROR) return # ask the user for a new URL urlNew = xbmcgui.Dialog().input(localise(32045), urlCurrent) if not urlNew: return # store the new URL in the settings ProviderSettings.SetUrl(providerSettings, urlNew) # try to connect and authenticate with the new URL success = False try: success = Server(mediaProvider).Authenticate(force=True) except: pass dialog = xbmcgui.Dialog() title = mediaProvider.getFriendlyName() if success: dialog.ok(title, localise(32017)) else: # ask the user whether to change the URL anyway changeUrlAnyway = dialog.yesno(title, localise(32066)) if not changeUrlAnyway: # revert the settings to the previous / old URL ProviderSettings.SetUrl(providerSettings, urlCurrent)
def synchronize(item, itemId, mediaProvider): # find the matching media import mediaImport = getMediaImport(mediaProvider, item) if not mediaImport: log( '[context/sync] cannot find the media import of {} from {}'.format( listItem2str(item, itemId), mediaProvider2str(mediaProvider)), xbmc.LOGERROR) return # determine whether Direct Play is allowed mediaProviderSettings = mediaProvider.getSettings() allowDirectPlay = mediaProviderSettings.getBool( emby.constants.SETTING_PROVIDER_PLAYBACK_ALLOW_DIRECT_PLAY) # create an Emby server instance embyServer = Server(mediaProvider) # synchronize the active item syncedItem = synchronizeItem(item, itemId, mediaProvider, embyServer, allowDirectPlay=allowDirectPlay) if not syncedItem: return syncedItems = [(xbmcmediaimport.MediaImportChangesetTypeChanged, syncedItem)] if xbmcmediaimport.changeImportedItems(mediaImport, syncedItems): log('[context/sync] synchronized {} from {}'.format( listItem2str(item, itemId), mediaProvider2str(mediaProvider))) else: log( '[context/sync] failed to synchronize {} from {}'.format( listItem2str(item, itemId), mediaProvider2str(mediaProvider)), xbmc.LOGWARNING)
def updateOnProvider(handle, options): # retrieve the media import mediaImport = xbmcmediaimport.getImport(handle) if not mediaImport: log('cannot retrieve media import', xbmc.LOGERROR) return # retrieve the media provider mediaProvider = mediaImport.getProvider() if not mediaProvider: log('cannot retrieve media provider', xbmc.LOGERROR) return # prepare and get the media import settings importSettings = mediaImport.prepareSettings() if not importSettings: log('cannot prepare media import settings', xbmc.LOGERROR) return item = xbmcmediaimport.getUpdatedItem(handle) if not item: log('cannot retrieve updated item', xbmc.LOGERROR) return log('updating "{}" ({}) on {}...'.format(item.getLabel(), item.getPath(), mediaProvider2str(mediaProvider))) itemVideoInfoTag = item.getVideoInfoTag() if not itemVideoInfoTag: log('updated item is not a video item', xbmc.LOGERROR) return # determine the item's identifier itemId = kodi.Api.getEmbyItemIdFromVideoInfoTag(itemVideoInfoTag) if not itemId: log('cannot determine the identifier of the updated item: "{}"'.format(itemVideoInfoTag.getPath()), xbmc.LOGERROR) return # prepare the media provider settings if not mediaProvider.prepareSettings(): log('cannot prepare media provider settings', xbmc.LOGERROR) return # create an Emby server instance embyServer = Server(mediaProvider) if not embyServer.Authenticate(): log('failed to authenticate on media provider {}'.format(mediaProvider2str(mediaProvider)), xbmc.LOGERROR) return # retrieve all details of the item itemObj = Library.GetItem(embyServer, itemId) if not itemObj: log('cannot retrieve details of updated item with id {}'.format(itemId), xbmc.LOGERROR) return if not emby.constants.PROPERTY_ITEM_USER_DATA in itemObj: log('cannot update item with id {} because it has no userdata'.format(itemId), xbmc.LOGERROR) return updateItemPlayed = False updatePlaybackPosition = False # retrieve playback states from the updated item playcount = itemVideoInfoTag.getPlayCount() watched = playcount > 0 lastPlayed = itemVideoInfoTag.getLastPlayed() # retrieve playback position from the updated item playbackPositionInSeconds = max(0.0, float(item.getProperty('resumetime'))) playbackPositionInTicks = kodi.Api.secondsToTicks(playbackPositionInSeconds) userDataObj = itemObj[emby.constants.PROPERTY_ITEM_USER_DATA] # check and update playcout if necessary if emby.constants.PROPERTY_ITEM_USER_DATA_PLAY_COUNT in userDataObj: # retrieve playcount from the original item itemPlayed = userDataObj[emby.constants.PROPERTY_ITEM_USER_DATA_PLAY_COUNT] > 0 if watched != itemPlayed: updateItemPlayed = True # check and update playback position if necessary if emby.constants.PROPERTY_ITEM_USER_DATA_PLAYBACK_POSITION_TICKS in userDataObj: # retrieve playback position from the original item itemPlaybackPositionInTicks = userDataObj[emby.constants.PROPERTY_ITEM_USER_DATA_PLAYBACK_POSITION_TICKS] if playbackPositionInTicks != itemPlaybackPositionInTicks: updatePlaybackPosition = True # nothing to do if no playback related properties have been changed if not updateItemPlayed and not updatePlaybackPosition: log('no playback related properties of "{}" ({}) have changed => nothing to update on {}'.format(item.getLabel(), item.getPath(), mediaProvider2str(mediaProvider))) return log('updating playback related properties of "{}" ({}) on {}...'.format(item.getLabel(), item.getPath(), mediaProvider2str(mediaProvider))) if not UserData.Update(embyServer, itemId, updateItemPlayed, updatePlaybackPosition, watched, playcount, lastPlayed, playbackPositionInTicks): log('updating playback related properties of "{}" ({}) on {} failed'.format(item.getLabel(), item.getPath(), mediaProvider2str(mediaProvider)), xbmc.LOGERROR) xbmcmediaimport.finishUpdateOnProvider(handle)
def execImport(handle, options): if not 'path' in options: log('cannot execute "import" without path', xbmc.LOGERROR) return # parse all necessary options mediaTypes = mediaTypesFromOptions(options) if not mediaTypes: log('cannot execute "import" without media types', xbmc.LOGERROR) return # retrieve the media import mediaImport = xbmcmediaimport.getImport(handle) if not mediaImport: log('cannot retrieve media import', xbmc.LOGERROR) return # prepare and get the media import settings importSettings = mediaImport.prepareSettings() if not importSettings: log('cannot prepare media import settings', xbmc.LOGERROR) return # retrieve the media provider mediaProvider = mediaImport.getProvider() if not mediaProvider: log('cannot retrieve media provider', xbmc.LOGERROR) return log('importing {} items from {}...'.format(mediaTypes, mediaProvider2str(mediaProvider))) # prepare the media provider settings mediaProviderSettings = mediaProvider.prepareSettings() if not mediaProviderSettings: log('cannot prepare media provider settings', xbmc.LOGERROR) return # create an Emby server instance embyServer = Server(mediaProvider) if not embyServer.Authenticate(): log('failed to authenticate on media provider {}'.format(mediaProvider2str(mediaProvider)), xbmc.LOGERROR) return # build the base URL to retrieve items baseUrl = embyServer.BuildUserUrl(emby.constants.URL_ITEMS) baseUrlOptions = { 'Recursive': 'true', 'Fields': ','.join(EMBY_ITEM_FIELDS), 'ExcludeLocationTypes': 'Virtual,Offline', 'Limit': ITEM_REQUEST_LIMIT } baseUrl = Url.addOptions(baseUrl, baseUrlOptions) # get all (matching) library views selectedViews = getLibraryViewsFromSettings(importSettings) views = getMatchingLibraryViews(embyServer, mediaTypes, selectedViews) if not views: log('cannot retrieve items without any library views', xbmc.LOGERROR) return # determine whether Direct Play is allowed allowDirectPlay = mediaProviderSettings.getBool(emby.constants.SETTING_PROVIDER_PLAYBACK_ALLOW_DIRECT_PLAY) # determine whether to import collections importCollections = importSettings.getBool(emby.constants.SETTING_IMPORT_IMPORT_COLLECTIONS) # loop over all media types to be imported progress = 0 progressTotal = len(mediaTypes) for mediaType in mediaTypes: if xbmcmediaimport.shouldCancel(handle, progress, progressTotal): return progress += 1 if mediaType == xbmcmediaimport.MediaTypeVideoCollection and not importCollections: log('importing {} items from {} is disabled'.format(mediaType, mediaProvider2str(mediaProvider)), xbmc.LOGDEBUG) continue log('importing {} items from {}...'.format(mediaType, mediaProvider2str(mediaProvider))) mappedMediaType = kodi.Api.getEmbyMediaType(mediaType) if not mappedMediaType: log('cannot import unsupported media type "{}"'.format(mediaType), xbmc.LOGERROR) continue (_, embyMediaType, localizedMediaType) = mappedMediaType xbmcmediaimport.setProgressStatus(handle, __addon__.getLocalizedString(32001).format(__addon__.getLocalizedString(localizedMediaType))) urlOptions = { 'IncludeItemTypes': embyMediaType } url = Url.addOptions(baseUrl, urlOptions) boxsetUrlOptions = { 'IncludeItemTypes': kodi.EMBY_MEDIATYPE_BOXSET } boxsetUrl = Url.addOptions(baseUrl, boxsetUrlOptions) items = [] boxsets = {} # handle library views for view in views: log('importing {} items from "{}" view from {}...'.format(mediaType, view.name, mediaProvider2str(mediaProvider))) items.extend(importItems(handle, embyServer, url, mediaType, view.id, embyMediaType=embyMediaType, viewName=view.name, allowDirectPlay=allowDirectPlay)) if importCollections and items and mediaType == xbmcmediaimport.MediaTypeMovie: # retrieve all BoxSets / collections matching the current media type boxsetObjs = importItems(handle, embyServer, boxsetUrl, mediaType, view.id, raw=True, allowDirectPlay=allowDirectPlay) for boxsetObj in boxsetObjs: if not emby.constants.PROPERTY_ITEM_ID in boxsetObj or not emby.constants.PROPERTY_ITEM_NAME in boxsetObj: continue boxsetId = boxsetObj[emby.constants.PROPERTY_ITEM_ID] boxsetName = boxsetObj[emby.constants.PROPERTY_ITEM_NAME] boxsets[boxsetId] = boxsetName # handle BoxSets / collections if importCollections and items: for (boxsetId, boxsetName) in iteritems(boxsets): # get all items belonging to the BoxSet boxsetItems = importItems(handle, embyServer, url, mediaType, boxsetId, embyMediaType=embyMediaType, viewName=boxsetName, allowDirectPlay=allowDirectPlay) for boxsetItem in boxsetItems: # find the matching retrieved item for (index, item) in enumerate(items): if boxsetItem.getPath() == item.getPath(): # set the BoxSet / collection kodi.Api.setCollection(item, boxsetName) items[index] = item log('{} {} items imported from {}'.format(len(items), mediaType, mediaProvider2str(mediaProvider))) if items: xbmcmediaimport.addImportItems(handle, items, mediaType) xbmcmediaimport.finishImport(handle)
def play(item, itemId, mediaProvider): if item.isFolder(): log( '[context/play] cannot play folder item {}'.format( listItem2str(item, itemId)), xbmc.LOGERROR) return # create an Emby server instance embyServer = Server(mediaProvider) # retrieve all details of the item itemObj = Library.GetItem(embyServer, itemId) if not itemObj: log( '[context/play] cannot retrieve the details of {} from {}'.format( listItem2str(item, itemId), mediaProvider2str(mediaProvider)), xbmc.LOGERROR) return # cannot play folders if itemObj.get(emby.constants.PROPERTY_ITEM_IS_FOLDER): log( '[context/play] cannot play folder item {}'.format( listItem2str(item, itemId)), xbmc.LOGERROR) return playChoices = [] playChoicesUrl = [] # determine whether Direct Play is allowed mediaProviderSettings = mediaProvider.getSettings() allowDirectPlay = mediaProviderSettings.getBool( emby.constants.SETTING_PROVIDER_PLAYBACK_ALLOW_DIRECT_PLAY) # check if the item supports Direct Play and / or Direct Stream canDirectPlay = None directPlayUrl = None if allowDirectPlay: (canDirectPlay, directPlayUrl) = kodi.Api.getDirectPlayUrl(itemObj) if canDirectPlay and directPlayUrl: playChoices.append(localise(32101)) playChoicesUrl.append(directPlayUrl) (canDirectStream, directStreamUrl) = kodi.Api.getDirectStreamUrl(embyServer, itemId, itemObj) if canDirectStream: playChoices.append(localise(32102)) playChoicesUrl.append(directStreamUrl) # if there are no options something went wrong if not playChoices: log( '[context/play] cannot play {} from {}'.format( listItem2str(item, itemId), mediaProvider2str(mediaProvider)), xbmc.LOGERROR) return # ask the user how to play playChoice = Dialog().contextmenu(playChoices) if playChoice < 0 or playChoice >= len(playChoices): return playUrl = playChoicesUrl[playChoice] # play the item log('[context/play] playing {} using "{}" ({}) from {}'.format( listItem2str(item, itemId), playChoices[playChoice], playUrl, mediaProvider2str(mediaProvider))) # overwrite the dynamic path of the ListItem item.setDynamicPath(playUrl) xbmc.Player().play(playUrl, item)
def _startPlayback(self): if not self._file: return if not self.isPlayingVideo(): return self._item = self.getPlayingItem() if not self._item: return # check if the item has been imported from a media provider mediaProviderId = self._item.getMediaProviderId() if not mediaProviderId: return if not mediaProviderId in self._providers: Player.log('currently playing item {} ({}) has been imported from an unknown media provider {}' \ .format(self._item.getLabel(), self._file, mediaProviderId), xbmc.LOGWARNING) return self._mediaProvider = self._providers[mediaProviderId] videoInfoTag = self.getVideoInfoTag() if not videoInfoTag: return self._itemId = kodi.Api.getEmbyItemIdFromVideoInfoTag(videoInfoTag) if not self._itemId: return settings = self._mediaProvider.prepareSettings() if not settings: Player.log('failed to load settings for {} ({}) playing from {}' \ .format(self._item.getLabel(), self._file, mediaProvider2str(self._mediaProvider)), xbmc.LOGWARNING) self._reset() return # determine the play method if Server.IsDirectStreamUrl(self._mediaProvider, self._file): self._playMethod = PLAYING_PLAY_METHOD_DIRECT_STREAM else: self._playMethod = PLAYING_PLAY_METHOD_DIRECT_PLAY # setup and authenticate with the Emby server try: self._server = Server(self._mediaProvider) except: pass if not self._server or not self._server.Authenticate(): Player.log('cannot connect to media provider {} to report playback progress of "{}" ({})' \ .format(mediaProvider2str(self._mediaProvider), self._item.getLabel(), self._file), xbmc.LOGWARNING) self._reset() return # when using DirectStream add any external subtitles if self._playMethod == PLAYING_PLAY_METHOD_DIRECT_STREAM and \ settings.getBool(SETTING_PROVIDER_PLAYBACK_ENABLE_EXTERNAL_SUBTITLES): self._addExternalSubtitles() # generate a session identifier self._playSessionId = PlaybackCheckin.GenerateSessionId() # prepare the data of the API call data = self._preparePlayingData(stopped=False) # tell the Emby server that a library item is being played PlaybackCheckin.StartPlayback(self._server, data) self._lastProgressReport = time.time() Player.log('playback start for "{}" ({}) on {} reported'.format( self._item.getLabel(), self._file, mediaProvider2str(self._mediaProvider)))
class Player(xbmc.Player): def __init__(self, progressInterval=None): super(xbmc.Player, self).__init__() self._lock = threading.Lock() self._progressInterval = progressInterval or 10 self._lastProgressReport = None self._providers = {} self._file = None self._item = None self._itemId = None self._mediaProvider = None self._server = None self._playSessionId = None self._paused = False self._playMethod = None self._lastPlaybackPosition = None def AddProvider(self, mediaProvider): if not mediaProvider: raise ValueError('invalid mediaProvider') with self._lock: self._providers[mediaProvider.getIdentifier()] = mediaProvider Player.log('{} added'.format(mediaProvider2str(mediaProvider))) def RemoveProvider(self, mediaProvider): if not mediaProvider: raise ValueError('invalid mediaProvider') with self._lock: del self._providers[mediaProvider.getIdentifier()] Player.log('{} removed'.format(mediaProvider2str(mediaProvider))) def Process(self): with self._lock: if not self._lastProgressReport: return # adhere to the configured progress interval if (time.time() - self._lastProgressReport) < self._progressInterval: return self._reportPlaybackProgress() def onPlayBackStarted(self): with self._lock: self._reset() try: self._file = self.getPlayingFile() except RuntimeError: pass def onAVStarted(self): with self._lock: self._startPlayback() def onPlayBackSeek(self, time, seekOffset): with self._lock: if self._reportPlaybackProgress(): Player.log('playback seek for "{}" ({}) on {} reported'.format( self._item.getLabel(), self._file, mediaProvider2str(self._mediaProvider))) def onPlayBackSeekChapter(self, chapter): with self._lock: if not self._item: return if self._reportPlaybackProgress(): Player.log( 'playback seek chapter for "{}" ({}) on {} reported'. format(self._item.getLabel(), self._file, mediaProvider2str(self._mediaProvider))) def onPlayBackPaused(self): with self._lock: self._paused = True if self._reportPlaybackProgress(): Player.log( 'playback paused for "{}" ({}) on {} reported'.format( self._item.getLabel(), self._file, mediaProvider2str(self._mediaProvider))) def onPlayBackResumed(self): with self._lock: self._paused = False if self._reportPlaybackProgress(): Player.log( 'playback resumed for "{}" ({}) on {} reported'.format( self._item.getLabel(), self._file, mediaProvider2str(self._mediaProvider))) def onPlayBackStopped(self): with self._lock: self._stopPlayback() def onPlayBackEnded(self): with self._lock: self._stopPlayback() def onPlayBackError(self): with self._lock: self._stopPlayback(failed=True) def _reset(self): self._file = None self._item = None self._itemId = None self._mediaProvider = None self._server = None self._playSessionId = None self._paused = False self._playMethod = None self._lastPlaybackPosition = None def _startPlayback(self): if not self._file: return if not self.isPlayingVideo(): return self._item = self.getPlayingItem() if not self._item: return # check if the item has been imported from a media provider mediaProviderId = self._item.getMediaProviderId() if not mediaProviderId: return if not mediaProviderId in self._providers: Player.log('currently playing item {} ({}) has been imported from an unknown media provider {}' \ .format(self._item.getLabel(), self._file, mediaProviderId), xbmc.LOGWARNING) return self._mediaProvider = self._providers[mediaProviderId] videoInfoTag = self.getVideoInfoTag() if not videoInfoTag: return self._itemId = kodi.Api.getEmbyItemIdFromVideoInfoTag(videoInfoTag) if not self._itemId: return settings = self._mediaProvider.prepareSettings() if not settings: Player.log('failed to load settings for {} ({}) playing from {}' \ .format(self._item.getLabel(), self._file, mediaProvider2str(self._mediaProvider)), xbmc.LOGWARNING) self._reset() return # determine the play method if Server.IsDirectStreamUrl(self._mediaProvider, self._file): self._playMethod = PLAYING_PLAY_METHOD_DIRECT_STREAM else: self._playMethod = PLAYING_PLAY_METHOD_DIRECT_PLAY # setup and authenticate with the Emby server try: self._server = Server(self._mediaProvider) except: pass if not self._server or not self._server.Authenticate(): Player.log('cannot connect to media provider {} to report playback progress of "{}" ({})' \ .format(mediaProvider2str(self._mediaProvider), self._item.getLabel(), self._file), xbmc.LOGWARNING) self._reset() return # when using DirectStream add any external subtitles if self._playMethod == PLAYING_PLAY_METHOD_DIRECT_STREAM and \ settings.getBool(SETTING_PROVIDER_PLAYBACK_ENABLE_EXTERNAL_SUBTITLES): self._addExternalSubtitles() # generate a session identifier self._playSessionId = PlaybackCheckin.GenerateSessionId() # prepare the data of the API call data = self._preparePlayingData(stopped=False) # tell the Emby server that a library item is being played PlaybackCheckin.StartPlayback(self._server, data) self._lastProgressReport = time.time() Player.log('playback start for "{}" ({}) on {} reported'.format( self._item.getLabel(), self._file, mediaProvider2str(self._mediaProvider))) def _reportPlaybackProgress(self, event=PLAYING_PROGRESS_EVENT_TIME_UPDATE): if not self.isPlaying(): self._reset() if not self._item: return False data = self._preparePlayingData(stopped=False, event=event) PlaybackCheckin.PlaybackProgress(self._server, data) self._lastProgressReport = time.time() return True def _stopPlayback(self, failed=False): if not self._item: return data = self._preparePlayingData(stopped=True, failed=failed) PlaybackCheckin.StopPlayback(self._server, data) Player.log('playback stopped for "{}" ({}) on {} reported'.format( self._item.getLabel(), self._file, mediaProvider2str(self._mediaProvider))) self._reset() def _preparePlayingData(self, stopped=False, event=None, failed=False): try: # try to get the playback position from Kodi self._lastPlaybackPosition = self.getTime() except RuntimeError: # if that fails update it based on the time passed since the last progress report if not self._paused: Player.log( 'guessing the playback position for "{}" ({})'.format( self._item.getLabel(), self._file), xbmc.LOGDEBUG) self._lastPlaybackPosition += time.time( ) - self._lastProgressReport data = { 'ItemId': self._itemId, 'PlaySessionId': self._playSessionId, 'PlaylistIndex': 0, 'PlaylistLength': 1, 'PositionTicks': kodi.Api.secondsToTicks(self._lastPlaybackPosition), } if stopped: data.update({'Failed': failed}) else: data.update({ 'QueueableMediaTypes': 'Audio,Video', 'CanSeek': True, 'PlayMethod': self._playMethod, 'IsPaused': self._paused, }) try: data.update({ 'RunTimeTicks': kodi.Api.secondsToTicks(self.getTotalTime()), }) except RuntimeError: pass if event: data.update({'EventName': event}) return data def _addExternalSubtitles(self): if not self._item: return # get the item's details to look for external subtitles itemObj = Library.GetItem(self._server, self._itemId) if not itemObj: Player.log('cannot retrieve details of "{}" ({}) from media provider {}' \ .format(self._item.getLabel(), self._file, mediaProvider2str(self._mediaProvider)), xbmc.LOGWARNING) return # extract the media source ID if not PROPERTY_ITEM_MEDIA_SOURCES in itemObj or not itemObj[ PROPERTY_ITEM_MEDIA_SOURCES]: Player.log('cannot add external subtitles for "{}" ({}) from media provider {} ' \ 'because it doesn\'t have a media source' \ .format(self._item.getLabel(), self._file, mediaProvider2str(self._mediaProvider)), xbmc.LOGDEBUG) return mediaSourceId = itemObj.get(PROPERTY_ITEM_MEDIA_SOURCES)[0].get( PROPERTY_ITEM_MEDIA_SOURCES_ID) # look for external subtitles for stream in itemObj.get(PROPERTY_ITEM_MEDIA_STREAMS): if stream.get(PROPERTY_ITEM_MEDIA_STREAM_TYPE ) != 'Subtitle' or not stream.get( PROPERTY_ITEM_MEDIA_STREAM_IS_EXTERNAL): continue # get the index of the subtitle index = stream.get(PROPERTY_ITEM_MEDIA_STREAM_INDEX) # determine the language and name name = stream.get( PROPERTY_ITEM_MEDIA_STREAM_DISPLAY_TITLE ) if PROPERTY_ITEM_MEDIA_STREAM_DISPLAY_TITLE in stream else '' language = stream.get( PROPERTY_ITEM_MEDIA_STREAM_LANGUAGE ) if PROPERTY_ITEM_MEDIA_STREAM_LANGUAGE in stream else '' # determine the stream URL if PROPERTY_ITEM_MEDIA_STREAM_DELIVERY_URL in stream and \ stream.get(PROPERTY_ITEM_MEDIA_STREAM_DELIVERY_URL).upper().startswith('/{}'.format(URL_VIDEOS)): url = self._server.BuildStreamDeliveryUrl( stream.get(PROPERTY_ITEM_MEDIA_STREAM_DELIVERY_URL)) else: url = self._server.BuildSubtitleStreamUrl( self._itemId, mediaSourceId, index, stream.get(PROPERTY_ITEM_MEDIA_STREAM_CODEC)) if not url: Player.log('cannot add external subtitle at index {} for "{}" ({}) from media provider {}' \ .format(index, self._item.getLabel(), self._file, mediaProvider2str(self._mediaProvider)), xbmc.LOGWARNING) continue self.addSubtitle(url, name, language, False) # TODO(Montellese): activate? Player.log('external subtitle "{}" [{}] at index {} added for "{}" ({}) from media provider {}' \ .format(name, language, index, self._item.getLabel(), self._file, mediaProvider2str(self._mediaProvider))) @staticmethod def log(message, level=xbmc.LOGINFO): log('[player] {}'.format(message), level)
def execImport(handle, options): if not 'path' in options: log('cannot execute "import" without path', xbmc.LOGERROR) return # parse all necessary options mediaTypes = mediaTypesFromOptions(options) if not mediaTypes: log('cannot execute "import" without media types', xbmc.LOGERROR) return # retrieve the media import mediaImport = xbmcmediaimport.getImport(handle) if not mediaImport: log('cannot retrieve media import', xbmc.LOGERROR) return # prepare and get the media import settings importSettings = mediaImport.prepareSettings() if not importSettings: log('cannot prepare media import settings', xbmc.LOGERROR) return # retrieve the media provider mediaProvider = mediaImport.getProvider() if not mediaProvider: log('cannot retrieve media provider', xbmc.LOGERROR) return log('importing {} items from {}...'.format( mediaTypes, mediaProvider2str(mediaProvider))) # prepare the media provider settings mediaProviderSettings = mediaProvider.prepareSettings() if not mediaProviderSettings: log('cannot prepare media provider settings', xbmc.LOGERROR) return # create an Emby server instance embyServer = Server(mediaProvider) # build the base URL to retrieve items baseUrl = embyServer.BuildUserUrl(emby.constants.URL_ITEMS) baseUrlOptions = { emby.constants.URL_QUERY_ITEMS_RECURSIVE: 'true', emby.constants.URL_QUERY_ITEMS_FIELDS: ','.join(EMBY_ITEM_FIELDS), emby.constants.URL_QUERY_ITEMS_EXCLUDE_LOCATION_TYPES: 'Virtual,Offline', emby.constants.URL_QUERY_ITEMS_LIMIT: ITEM_REQUEST_LIMIT } baseUrl = Url.addOptions(baseUrl, baseUrlOptions) # get all (matching) library views selectedViews = ImportSettings.GetLibraryViews(importSettings) views = getMatchingLibraryViews(embyServer, mediaTypes, selectedViews) if not views: log('cannot retrieve items without any library views', xbmc.LOGERROR) return # determine whether Direct Play is allowed allowDirectPlay = mediaProviderSettings.getBool( emby.constants.SETTING_PROVIDER_PLAYBACK_ALLOW_DIRECT_PLAY) # determine whether to import collections importCollections = importSettings.getBool( emby.constants.SETTING_IMPORT_IMPORT_COLLECTIONS) # determine the last sync time and whether we should perform a fast sync fastSync = False syncUrlOptions = {} # check if synchronization related settings have changed; if yes we have to perform a full synchronization if SynchronizationSettings.HaveChanged(mediaTypes, mediaProviderSettings, importSettings, save=True): log('forcing a full synchronization to import {} items from {} because some related settings have changed' \ .format(mediaTypes, mediaProvider2str(mediaProvider))) else: # check if we # have already performed a (full) synchronization before # should use the Kodi Companion Emby server plugin lastSync = mediaImport.getLastSynced() if lastSync and mediaProviderSettings.getBool( emby.constants. SETTING_PROVIDER_SYNCHRONIZATION_USE_KODI_COMPANION): if KodiCompanion.IsInstalled(embyServer): fastSync = True # convert the last sync datetime string to ISO 8601 lastSync = parser.parse(lastSync).astimezone(utc).isoformat( timespec='seconds') syncUrlOptions.update({ # only set MinDateLastSavedForUser because it already covers DateLastSaved, RatingLastModified # and PlaystateLastModified. Setting both MinDateLastSaved and MinDateLastSavedForUser will # cause issues, see https://emby.media/community/index.php?/topic/82258-retrieving-changeset-when-client-returns-online-mediaimport/ emby.constants.URL_QUERY_ITEMS_MIN_DATE_LAST_SAVED_FOR_USER: lastSync }) log('using fast synchronization to import {} items from {} with Kodi companion plugin' \ .format(mediaTypes, mediaProvider2str(mediaProvider)), xbmc.LOGDEBUG) # retrieving the sync queue from Kodi companion syncQueue = KodiCompanion.SyncQueue.GetItems( embyServer, lastSync) else: log('Kodi companion usage is enabled to import {} items from {} but the server plugin is not installed' \ .format(mediaTypes, mediaProvider2str(mediaProvider)), xbmc.LOGWARNING) # loop over all media types to be imported progress = 0 progressTotal = len(mediaTypes) for mediaType in mediaTypes: if xbmcmediaimport.shouldCancel(handle, progress, progressTotal): return progress += 1 if mediaType == xbmcmediaimport.MediaTypeVideoCollection and not importCollections: log( 'importing {} items from {} is disabled'.format( mediaType, mediaProvider2str(mediaProvider)), xbmc.LOGDEBUG) continue log('importing {} items from {}...'.format( mediaType, mediaProvider2str(mediaProvider))) mappedMediaType = kodi.Api.getEmbyMediaType(mediaType) if not mappedMediaType: log('cannot import unsupported media type "{}"'.format(mediaType), xbmc.LOGERROR) continue (_, embyMediaType, localizedMediaType) = mappedMediaType xbmcmediaimport.setProgressStatus( handle, __addon__.getLocalizedString(32001).format( __addon__.getLocalizedString(localizedMediaType))) urlOptions = syncUrlOptions.copy() urlOptions.update( {emby.constants.URL_QUERY_ITEMS_INCLUDE_ITEM_TYPES: embyMediaType}) url = Url.addOptions(baseUrl, urlOptions) boxsetUrlOptions = { emby.constants.URL_QUERY_ITEMS_INCLUDE_ITEM_TYPES: kodi.EMBY_MEDIATYPE_BOXSET } boxsetUrl = Url.addOptions(baseUrl, boxsetUrlOptions) items = [] boxsets = {} # handle library views for view in views: log('importing {} items from "{}" view from {}...'.format( mediaType, view.name, mediaProvider2str(mediaProvider))) items.extend( importItems(handle, embyServer, url, mediaType, view.id, embyMediaType=embyMediaType, viewName=view.name, allowDirectPlay=allowDirectPlay)) if importCollections and items and mediaType == xbmcmediaimport.MediaTypeMovie: # retrieve all BoxSets / collections matching the current media type boxsetObjs = importItems(handle, embyServer, boxsetUrl, mediaType, view.id, raw=True, allowDirectPlay=allowDirectPlay) for boxsetObj in boxsetObjs: if not emby.constants.PROPERTY_ITEM_ID in boxsetObj or not emby.constants.PROPERTY_ITEM_NAME in boxsetObj: continue boxsetId = boxsetObj[emby.constants.PROPERTY_ITEM_ID] boxsetName = boxsetObj[emby.constants.PROPERTY_ITEM_NAME] boxsets[boxsetId] = boxsetName # handle BoxSets / collections if importCollections and items: for (boxsetId, boxsetName) in iteritems(boxsets): # get all items belonging to the BoxSet boxsetItems = importItems(handle, embyServer, url, mediaType, boxsetId, embyMediaType=embyMediaType, viewName=boxsetName, allowDirectPlay=allowDirectPlay) for boxsetItem in boxsetItems: # find the matching retrieved item for index, item in enumerate(items): if boxsetItem.getPath() == item.getPath(): # set the BoxSet / collection kodi.Api.setCollection(item, boxsetName) items[index] = item # in a fast sync we need to get the removed items from Kodi companion if fastSync: if items: log('{} changed {} items imported from {}'.format( len(items), mediaType, mediaProvider2str(mediaProvider))) # handle removed items through Kodi companion's sync queue if syncQueue.itemsRemoved: # retrieve all local items matching the current media type from the current import localItems = xbmcmediaimport.getImportedItems( handle, mediaType) # match the local items against the changed items removedItems, = kodi.Api.matchImportedItemIdsToLocalItems( localItems, syncQueue.itemsRemoved) # pylint: disable=unbalanced-tuple-unpacking # erase all removed items matching the current media type from the sync queue syncQueue.itemsRemoved = [ removedItem for removedItem in syncQueue.itemsRemoved if removedItem in removedItems ] if removedItems: log('{} previously imported {} items removed from {}'. format(len(removedItems), mediaType, mediaProvider2str(mediaProvider))) xbmcmediaimport.addImportItems( handle, removedItems, mediaType, xbmcmediaimport.MediaImportChangesetTypeRemoved) else: log('{} {} items imported from {}'.format( len(items), mediaType, mediaProvider2str(mediaProvider))) # pass the imported items back to Kodi if items: xbmcmediaimport.addImportItems(handle, items, mediaType) xbmcmediaimport.finishImport(handle, fastSync)
class ProviderObserver: class Action: Start = 0 Stop = 1 def __init__(self): # default values self._actions = [] self._connected = False self._imports = [] self._mediaProvider = None self._settings = None self._server = None self._websocket = None def __del__(self): self._StopAction() def AddImport(self, mediaImport): if not mediaImport: raise ValueError('invalid mediaImport') # look for a matching import matchingImportIndices = self._FindImportIndices(mediaImport) # if a matching import has been found update it if matchingImportIndices: self._imports[matchingImportIndices[0]] = mediaImport ProviderObserver.log('media import {} from {} updated'.format( mediaImport2str(mediaImport), mediaProvider2str(self._mediaProvider))) else: # otherwise add the import to the list self._imports.append(mediaImport) ProviderObserver.log('media import {} from {} added'.format( mediaImport2str(mediaImport), mediaProvider2str(self._mediaProvider))) def RemoveImport(self, mediaImport): if not mediaImport: raise ValueError('invalid mediaImport') # look for a matching import matchingImportIndices = self._FindImportIndices(mediaImport) if not matchingImportIndices: return # remove the media import from the list del self._imports[matchingImportIndices[0]] ProviderObserver.log('media import {} from {} removed'.format( mediaImport2str(mediaImport), mediaProvider2str(self._mediaProvider))) def Start(self, mediaProvider): if not mediaProvider: raise ValueError('invalid mediaProvider') self._actions.append((ProviderObserver.Action.Start, mediaProvider)) def Stop(self): self._actions.append((ProviderObserver.Action.Stop, None)) def Process(self): # process any open actions self._ProcessActions() # process any incoming messages self._ProcessMessages() def _FindImportIndices(self, mediaImport): if not mediaImport: raise ValueError('invalid mediaImport') return [ i for i, x in enumerate(self._imports) if x.getPath() == mediaImport.getPath() and x.getMediaTypes() == mediaImport.getMediaTypes() ] def _ProcessActions(self): for (action, data) in self._actions: if action == ProviderObserver.Action.Start: self._StartAction(data) elif action == ProviderObserver.Action.Stop: self._StopAction() else: ProviderObserver.log( 'unknown action {} to process'.format(action), xbmc.LOGWARNING) self._actions = [] def _ProcessMessages(self): # nothing to do if we are not connected to an Emby server if not self._connected: return while True: try: message = self._websocket.recv() if message is None: break messageObj = json.loads(message) if not messageObj: ProviderObserver.log( 'invalid JSON message ({}) from {} received: {}'. format(len(message), mediaProvider2str(self._mediaProvider), message), xbmc.LOGWARNING) continue self._ProcessMessage(messageObj) except websocket.WebSocketTimeoutException: break except Exception as error: ProviderObserver.log( 'unknown exception when receiving data from {}: {}'.format( mediaProvider2str(self._mediaProvider), error.args[0]), xbmc.LOGWARNING) break def _ProcessMessage(self, messageObj): if not messageObj: return if not WS_MESSAGE_TYPE in messageObj: ProviderObserver.log( 'message without "{}" received from {}'.format( WS_MESSAGE_TYPE, mediaProvider2str(self._mediaProvider)), xbmc.LOGWARNING) return if not WS_DATA in messageObj: ProviderObserver.log( 'message without "{}" received from {}'.format( WS_DATA, mediaProvider2str(self._mediaProvider)), xbmc.LOGWARNING) return messageType = messageObj[WS_MESSAGE_TYPE] data = messageObj[WS_DATA] if messageType == WS_MESSAGE_TYPE_LIBRARY_CHANGED: self._ProcessMessageLibraryChanged(data) elif messageType == WS_MESSAGE_TYPE_USER_DATA_CHANGED: self._ProcessMessageUserDataChanged(data) elif messageType in (WS_MESSAGE_TYPE_SERVER_SHUTTING_DOWN, WS_MESSAGE_TYPE_SERVER_RESTARTING): self._ProcessMessageServer(messageType, data) else: ProviderObserver.log( 'ignoring "{}" message from {}'.format( messageType, mediaProvider2str(self._mediaProvider)), xbmc.LOGDEBUG) def _ProcessMessageLibraryChanged(self, data): ProviderObserver.log( 'processing library changed message from {}...'.format( mediaProvider2str(self._mediaProvider))) itemsAdded = data[WS_LIBRARY_CHANGED_ITEMS_ADDED] itemsUpdated = data[WS_LIBRARY_CHANGED_ITEMS_UPDATED] itemsRemoved = data[WS_LIBRARY_CHANGED_ITEMS_REMOVED] changedLibraryItems = [] # process all newly added items changedLibraryItems.extend( ProviderObserver._ProcessChangedItems( itemsAdded, xbmcmediaimport.MediaImportChangesetTypeAdded)) # process all updated items changedLibraryItems.extend( ProviderObserver._ProcessChangedItems( itemsUpdated, xbmcmediaimport.MediaImportChangesetTypeChanged)) # process all removed items changedLibraryItems.extend( ProviderObserver._ProcessChangedItems( itemsRemoved, xbmcmediaimport.MediaImportChangesetTypeRemoved)) # map the changed items to their media import changedItems = [] for (changesetType, itemId) in changedLibraryItems: item = None if changesetType == xbmcmediaimport.MediaImportChangesetTypeAdded or \ changesetType == xbmcmediaimport.MediaImportChangesetTypeChanged: # get all details for the added / changed item item = self._GetItemDetails(itemId) if not item: ProviderObserver.log( 'failed to get details for changed item with id "{}" from {}' .format(itemId, mediaProvider2str(self._mediaProvider)), xbmc.LOGWARNING) continue else: # find the removed item in the list of imported items importedItems = xbmcmediaimport.getImportedItemsByProvider( self._mediaProvider) matchingItems = [ importedItem for importedItem in importedItems if kodi.Api.getEmbyItemIdFromItem(importedItem) == itemId ] if not matchingItems: ProviderObserver.log( 'failed to find removed item with id "{}" from {}'. format(itemId, mediaProvider2str(self._mediaProvider)), xbmc.LOGWARNING) continue if len(matchingItems) > 1: ProviderObserver.log( 'multiple imported items for item with id "{}" from {} found => only removing the first one' .format(itemId, mediaProvider2str(self._mediaProvider)), xbmc.LOGWARNING) item = matchingItems[0] if not item: ProviderObserver.log( 'failed to process changed item with id "{}" from {}'. format(itemId, mediaProvider2str(self._mediaProvider)), xbmc.LOGWARNING) continue changedItems.append((changesetType, item, itemId)) self._ChangeItems(changedItems) @staticmethod def _ProcessChangedItems(items, changesetType): if not isinstance(items, list): return [] return [(changesetType, item) for item in items if isinstance(item, string_types) and item] def _ProcessMessageUserDataChanged(self, data): ProviderObserver.log( 'processing userdata changed message from {}...'.format( mediaProvider2str(self._mediaProvider))) userDataList = data[WS_USER_DATA_CHANGED_USER_DATA_LIST] changedItems = [] for userDataItem in userDataList: if not WS_USER_DATA_CHANGED_USER_DATA_ITEM_ID in userDataItem: continue itemId = userDataItem[WS_USER_DATA_CHANGED_USER_DATA_ITEM_ID] if not itemId or not isinstance(itemId, string_types): continue item = self._GetItemDetails(itemId) if not item: ProviderObserver.log( 'failed to get details for changed item with id "{}" from {}' .format(itemId, mediaProvider2str(self._mediaProvider)), xbmc.LOGWARNING) continue changedItems.append( (xbmcmediaimport.MediaImportChangesetTypeChanged, item, itemId)) self._ChangeItems(changedItems) def _ProcessMessageServer(self, messageType, data): if not self._settings.getBool( SETTING_PROVIDER_INTERFACE_SHOW_SERVER_MESSAGES): return if messageType == WS_MESSAGE_TYPE_SERVER_SHUTTING_DOWN: message = 32051 elif messageType == WS_MESSAGE_TYPE_SERVER_RESTARTING: message = 32052 else: return xbmcgui.Dialog().notification( 'Emby Media Importer', localise(message).format(self._mediaProvider.getFriendlyName()), self._mediaProvider.getIconUrl()) def _ChangeItems(self, changedItems): # map the changed items to their media import changedItemsMap = {} for (changesetType, item, itemId) in changedItems: if not item: continue # find a matching import for the changed item mediaImport = self._FindImportForItem(item) if not mediaImport: ProviderObserver.log( 'failed to determine media import for changed item with id "{}" from {}' .format(itemId, mediaProvider2str(self._mediaProvider)), xbmc.LOGWARNING) continue if mediaImport not in changedItemsMap: changedItemsMap[mediaImport] = [] changedItemsMap[mediaImport].append((changesetType, item)) # finally pass the changed items grouped by their media import to Kodi for (mediaImport, changedItems) in changedItemsMap.items(): if xbmcmediaimport.changeImportedItems(mediaImport, changedItems): ProviderObserver.log( 'changed {} imported items for media import {} from {}'. format(len(changedItems), mediaImport2str(mediaImport), mediaProvider2str(self._mediaProvider))) else: ProviderObserver.log( 'failed to change {} imported items for media import {} from {}' .format(len(changedItems), mediaImport2str(mediaImport), mediaProvider2str(self._mediaProvider)), xbmc.LOGWARNING) def _GetItemDetails(self, itemId): # retrieve all details of the item itemObj = Library.GetItem(self._server, itemId) if not itemObj: ProviderObserver.log( 'cannot retrieve details of updated item with id "{}" from {}'. format(itemId, mediaProvider2str(self._mediaProvider)), xbmc.LOGERROR) return None return kodi.Api.toFileItem( self._server, itemObj, allowDirectPlay=self._settings.getBool( SETTING_PROVIDER_PLAYBACK_ALLOW_DIRECT_PLAY)) def _FindImportForItem(self, item): videoInfoTag = item.getVideoInfoTag() if not videoInfoTag: return None itemMediaType = videoInfoTag.getMediaType() matchingImports = [ mediaImport for mediaImport in self._imports if itemMediaType in mediaImport.getMediaTypes() ] if not matchingImports: return None return matchingImports[0] def _StartAction(self, mediaProvider): if not mediaProvider: raise RuntimeError('invalid mediaProvider') # if we are already connected check if something important changed in the media provider if self._connected: if kodi.Api.compareMediaProviders(self._mediaProvider, mediaProvider): # update the media provider and settings anyway self._mediaProvider = mediaProvider self._settings = self._mediaProvider.prepareSettings() return True self._StopAction(restart=True) self._mediaProvider = mediaProvider self._settings = self._mediaProvider.prepareSettings() if not self._settings: raise RuntimeError('cannot prepare media provider settings') try: # create emby server instance self._server = Server(self._mediaProvider) # authenticate with the Emby server authenticated = self._server.Authenticate(force=True) except: authenticated = False if not authenticated: ProviderObserver.log( 'failed to authenticate with {}'.format( mediaProvider2str(self._mediaProvider)), xbmc.LOGERROR) self._Reset() return False # analyze the media provider's URL urlParts = urlparse(self._mediaProvider.getBasePath()) # determine the proper scheme (ws:// or wss://) and whether or not to verify the HTTPS certificate websocketScheme = 'wss' if urlParts.scheme == 'https' else 'ws' # put the urL back together url = urlunparse( urlParts._replace(scheme=websocketScheme, path='embywebsocket')) url = Url.addOptions( url, { URL_QUERY_API_KEY: self._server.AccessToken(), URL_QUERY_DEVICE_ID: self._server.DeviceId() }) # create the websocket self._websocket = websocket.WebSocket() # connect the websocket try: self._websocket.connect(url) except Exception as err: ProviderObserver.log( 'failed to connect to {} using a websocket. {}'.format( url, err), xbmc.LOGERROR) self._Reset() return False # reduce the timeout self._websocket.settimeout(1.0) ProviderObserver.log( 'successfully connected to {} to observe media imports'.format( mediaProvider2str(self._mediaProvider))) self._connected = True return True def _StopAction(self, restart=False): if not self._connected: return if not restart: ProviderObserver.log( 'stopped observing media imports from {}'.format( mediaProvider2str(self._mediaProvider))) self._websocket.close() self._Reset() def _Reset(self): self._connected = False self._server = None self._mediaProvider = None @staticmethod def log(message, level=xbmc.LOGINFO): log('[observer] {}'.format(message), level)
def _StartAction(self, mediaProvider): if not mediaProvider: raise RuntimeError('invalid mediaProvider') # if we are already connected check if something important changed in the media provider if self._connected: if kodi.Api.compareMediaProviders(self._mediaProvider, mediaProvider): # update the media provider and settings anyway self._mediaProvider = mediaProvider self._settings = self._mediaProvider.prepareSettings() return True self._StopAction(restart=True) self._mediaProvider = mediaProvider self._settings = self._mediaProvider.prepareSettings() if not self._settings: raise RuntimeError('cannot prepare media provider settings') try: # create emby server instance self._server = Server(self._mediaProvider) # authenticate with the Emby server authenticated = self._server.Authenticate(force=True) except: authenticated = False if not authenticated: ProviderObserver.log( 'failed to authenticate with {}'.format( mediaProvider2str(self._mediaProvider)), xbmc.LOGERROR) self._Reset() return False # analyze the media provider's URL urlParts = urlparse(self._mediaProvider.getBasePath()) # determine the proper scheme (ws:// or wss://) and whether or not to verify the HTTPS certificate websocketScheme = 'wss' if urlParts.scheme == 'https' else 'ws' # put the urL back together url = urlunparse( urlParts._replace(scheme=websocketScheme, path='embywebsocket')) url = Url.addOptions( url, { URL_QUERY_API_KEY: self._server.AccessToken(), URL_QUERY_DEVICE_ID: self._server.DeviceId() }) # create the websocket self._websocket = websocket.WebSocket() # connect the websocket try: self._websocket.connect(url) except Exception as err: ProviderObserver.log( 'failed to connect to {} using a websocket. {}'.format( url, err), xbmc.LOGERROR) self._Reset() return False # reduce the timeout self._websocket.settimeout(1.0) ProviderObserver.log( 'successfully connected to {} to observe media imports'.format( mediaProvider2str(self._mediaProvider))) self._connected = True return True
def discoverProviderWithEmbyConnect(handle, options): deviceId = Request.GenerateDeviceId() embyConnect = linkToEmbyConnect(deviceId) if not embyConnect: return None dialog = xbmcgui.Dialog() # get all connected servers servers = EmbyConnect.GetServers(embyConnect.accessToken, embyConnect.userId) if not servers: log( 'no servers available for Emby Connect user id {}'.format( embyConnect.userId), xbmc.LOGWARNING) return None if len(servers) == 1: server = servers[0] else: # ask the user which server to use serverChoices = [server.name for server in servers] serverChoice = dialog.select(localise(32057), serverChoices) if serverChoice < 0 or serverChoice >= len(serverChoices): return None server = server[serverChoice] if not server: return None urls = [] if server.localUrl: # ask the user whether to use a local or remote connection isLocal = dialog.yesno(localise(32058), localise(32059).format(server.name)) if isLocal: urls.append(server.localUrl) if server.remoteUrl: urls.append(server.remoteUrl) baseUrl = None # find a working connection / base URL for url in urls: try: _ = emby.api.server.Server.GetInfo(url) except: log('failed to connect to "{}" at {}'.format(server.name, url), xbmc.LOGDEBUG) continue baseUrl = url break if not baseUrl: dialog.ok(localise(32058), localise(32060).format(server.name)) log( 'failed to connect to Emby server "{}" with Emby Connect user ID {}' .format(server.name, embyConnect.userId), xbmc.LOGWARNING) return None providerId = Server.BuildProviderId(server.systemId) providerIconUrl = Server.BuildIconUrl(baseUrl) provider = xbmcmediaimport.MediaProvider( providerId, baseUrl, server.name, providerIconUrl, emby.constants.SUPPORTED_MEDIA_TYPES) provider.setIconUrl(kodi.Api.downloadIcon(provider)) # store Emby connect authentication in settings providerSettings = provider.prepareSettings() if not providerSettings: return None providerSettings.setString( emby.constants.SETTING_PROVIDER_AUTHENTICATION, emby.constants.SETTING_PROVIDER_AUTHENTICATION_OPTION_EMBY_CONNECT) providerSettings.setString( emby.constants.SETTING_PROVIDER_EMBY_CONNECT_USER_ID, embyConnect.userId) providerSettings.setString( emby.constants.SETTING_PROVIDER_EMBY_CONNECT_ACCESS_KEY, server.accessKey) providerSettings.setString(emby.constants.SETTING_PROVIDER_DEVICEID, deviceId) providerSettings.save() log('Emby Connect server {} successfully discovered at {}'.format( mediaProvider2str(provider), baseUrl)) return provider