def checkLogin(self): if self.finished: return not self.expired url = Url.append(constants.URL_EMBY_CONNECT_BASE, constants.URL_EMBY_CONNECT_PIN) url = Url.addOptions(url, { constants.URL_QUERY_DEVICE_ID: self.deviceId, constants.URL_QUERY_PIN: self.pin, }) resultObj = Request.GetAsJson(url) if not resultObj or \ constants.PROPERTY_EMBY_CONNECT_PIN_IS_CONFIRMED not in resultObj or \ constants.PROPERTY_EMBY_CONNECT_PIN_IS_EXPIRED not in resultObj: log('failed to check status of PIN {} at {}: {}'.format(self.pin, url, resultObj), xbmc.LOGWARNING) self.finished = True self.expired = True return False self.finished = resultObj.get(constants.PROPERTY_EMBY_CONNECT_PIN_IS_CONFIRMED) self.expired = resultObj.get(constants.PROPERTY_EMBY_CONNECT_PIN_IS_EXPIRED) if self.expired: self.finished = True return self.finished
def BuildDirectStreamUrl(self, mediaType, itemId, container): if not itemId: raise ValueError('Invalid itemId') embyMediaType = None if mediaType == 'Video': embyMediaType = constants.URL_PLAYBACK_MEDIA_TYPE_VIDEO elif mediaType == 'Audio': embyMediaType = constants.URL_PLAYBACK_MEDIA_TYPE_AUDIO else: raise ValueError('Invalid mediaType "{}"'.format(mediaType)) url = self.BuildUrl(embyMediaType) url = Url.append(url, itemId, constants.URL_PLAYBACK_STREAM) if container: containers = container.split(',') # TODO(Montellese): for now pick the first container but maybe we # need some sanity checking / priorization url = '{}.{}'.format(url, containers[0]) url = Url.addOptions( url, { constants.URL_PLAYBACK_OPTION_STATIC: constants.URL_PLAYBACK_OPTION_STATIC_TRUE, constants.URL_QUERY_API_KEY: self.AccessToken() }) return url
def importItems(handle, embyServer, url, mediaType, viewId, embyMediaType=None, viewName=None, raw=False, allowDirectPlay=True): items = [] viewUrl = url viewUrl = Url.addOptions(viewUrl, { 'ParentId': viewId }) # retrieve all items matching the current media type totalCount = 0 startIndex = 0 while True: if xbmcmediaimport.shouldCancel(handle, startIndex, max(totalCount, 1)): return # put together a paged URL pagedUrlOptions = { 'StartIndex': startIndex } pagedUrl = Url.addOptions(viewUrl, pagedUrlOptions) resultObj = embyServer.ApiGet(pagedUrl) if not resultObj or not emby.constants.PROPERTY_ITEM_ITEMS in resultObj or not emby.constants.PROPERTY_ITEM_TOTAL_RECORD_COUNT in resultObj: log('invalid response for items of media type "{}" from {}'.format(mediaType, pagedUrl), xbmc.LOGERROR) return # retrieve the total number of items totalCount = int(resultObj[emby.constants.PROPERTY_ITEM_TOTAL_RECORD_COUNT]) # parse all items itemsObj = resultObj[emby.constants.PROPERTY_ITEM_ITEMS] for itemObj in itemsObj: startIndex = startIndex + 1 if xbmcmediaimport.shouldCancel(handle, startIndex, totalCount): return if raw: items.append(itemObj) else: item = kodi.Api.toFileItem(embyServer, itemObj, mediaType, embyMediaType, viewName, allowDirectPlay=allowDirectPlay) if not item: continue items.append(item) # check if we have retrieved all available items if startIndex >= totalCount: break return items
def UpdateResumePoint(embyServer, itemId, positionInTicks): if not embyServer: raise ValueError('invalid embyServer') if not itemId: raise ValueError('invalid itemId') url = embyServer.BuildUserPlayingItemUrl(itemId) url = Url.addOptions(url, {'PositionTicks': positionInTicks}) embyServer.ApiDelete(url) return True
def BuildImageUrl(self, itemId, imageType, imageTag=''): if not itemId: raise ValueError('Invalid itemId') if not imageType: raise ValueError('Invalid imageType') url = self.BuildItemUrl(itemId) url = Url.append(url, constants.URL_IMAGES, imageType) if imageTag: url = Url.addOptions(url, {constants.URL_QUERY_TAG: imageTag}) return url
def BuildConnectExchangeUrl(baseUrl, userId): if not baseUrl: raise ValueError('Invalid baseUrl') if not userId: raise ValueError('Invalid userId') url = Url.append(Server._buildBaseUrl(baseUrl), constants.URL_CONNECT, constants.URL_CONNECT_EXCHANGE) url = Url.addOptions(url, { constants.URL_QUERY_CONNECT_EXCHANGE_FORMAT: constants.URL_QUERY_CONNECT_EXCHANGE_FORMAT_JSON, constants.URL_QUERY_CONNECT_EXCHANGE_USER_ID: userId, }) return url
def GetServers(accessToken, userId): if not accessToken: raise ValueError('invalid accessToken') if not userId: raise ValueError('invalid userId') url = Url.append(constants.URL_EMBY_CONNECT_BASE, constants.URL_EMBY_CONNECT_SERVERS) url = Url.addOptions(url, { constants.URL_QUERY_USER_ID: userId, }) headers = EmbyConnect._getApplicationHeader() headers.update({ constants.EMBY_CONNECT_USER_TOKEN_HEADER: accessToken, }) resultObj = Request.GetAsJson(url, headers=headers) if not resultObj: log('invalid response from {}: {}'.format(url, resultObj)) return None servers = [] for server in resultObj: id = server.get(constants.PROPERTY_EMBY_CONNECT_SERVER_ID, None) systemId = server.get( constants.PROPERTY_EMBY_CONNECT_SERVER_SYSTEM_ID, None) accessKey = server.get( constants.PROPERTY_EMBY_CONNECT_SERVER_ACCESS_KEY, None) name = server.get(constants.PROPERTY_EMBY_CONNECT_SERVER_NAME, None) remoteUrl = server.get( constants.PROPERTY_EMBY_CONNECT_SERVER_REMOTE_URL, None) localUrl = server.get( constants.PROPERTY_EMBY_CONNECT_SERVER_LOCAL_URL, None) if None in (id, accessKey, name, remoteUrl, localUrl): log('invalid Emby server received from {}: {}'.format( url, server)) continue servers.append( EmbyConnect.Server(id=id, systemId=systemId, accessKey=accessKey, name=name, remoteUrl=remoteUrl, localUrl=localUrl)) return servers
def BuildSubtitleStreamUrl(self, itemId, sourceId, index, codec): if not itemId: raise ValueError('invalid itemId') if not sourceId: raise ValueError('invalid sourceId') if not index: raise ValueError('invalid index') if not codec: raise ValueError('invalid codec') # <url>/Videos/<itemId>/<sourceId>/Subtitles/<index>/Stream.<codec>?api_key=<token> url = Url.append(self._url, constants.URL_VIDEOS, itemId, sourceId, constants.URL_VIDEOS_SUBTITLES, str(index), constants.URL_VIDEOS_SUBTITLES_STREAM) url = '{}.{}'.format(url, codec) return Url.addOptions(url, {constants.URL_QUERY_API_KEY: self._authenticator.AccessToken()})
def getRawItemsChunked(embyServer, url, mediaType, viewId, startIndex, count): viewUrl = url viewUrl = Url.addOptions(viewUrl, {emby.constants.URL_QUERY_ITEMS_PARENT_ID: viewId}) # put together a paged URL pagedUrlOptions = { emby.constants.URL_QUERY_ITEMS_LIMIT: count, emby.constants.URL_QUERY_ITEMS_START_INDEX: startIndex } pagedUrl = Url.addOptions(viewUrl, pagedUrlOptions) # retrieve all items matching the current media type resultObj = embyServer.ApiGet(pagedUrl) if not resultObj or emby.constants.PROPERTY_ITEM_ITEMS not in resultObj or \ emby.constants.PROPERTY_ITEM_TOTAL_RECORD_COUNT not in resultObj: raise RuntimeError('invalid response for items of media type "{}" from {}'.format(mediaType, pagedUrl)) # retrieve the total number of items totalCount = int(resultObj[emby.constants.PROPERTY_ITEM_TOTAL_RECORD_COUNT]) # parse all items itemsObj = resultObj[emby.constants.PROPERTY_ITEM_ITEMS] return (totalCount, itemsObj)
def GetItems(embyServer, date, filters=None): if not embyServer: raise ValueError('invalid embyServer') if not date: raise ValueError('invalid date') # determine the endpoint based on whether we are talking to an Emby or Jellyfin server endpoint = KodiCompanion.SyncQueue.ENDPOINT_EMBY serverInfo = Server.GetInfo(embyServer.Url()) if serverInfo and serverInfo.isJellyfinServer(): endpoint = KodiCompanion.SyncQueue.ENDPOINT_JELLYFIN # build the URL to retrieve the items from the sync queue url = embyServer.BuildUrl(endpoint) url = Url.append(url, embyServer.UserId(), KodiCompanion.SyncQueue.ENDPOINT_GET_ITEMS) url = Url.addOptions( url, { KodiCompanion.SyncQueue.QUERY_GET_ITEMS_LAST_UPDATE: date, KodiCompanion.SyncQueue.QUERY_GET_ITEMS_FILTER: filters }) itemsObj = embyServer.ApiGet(url) if not itemsObj: return [] itemsAdded = [] itemsUpdated = [] itemsRemoved = [] userDataChanged = [] if KodiCompanion.SyncQueue.PROPERTY_GET_ITEMS_ADDED in itemsObj: itemsAdded = itemsObj[ KodiCompanion.SyncQueue.PROPERTY_GET_ITEMS_ADDED] if KodiCompanion.SyncQueue.PROPERTY_GET_ITEMS_UPDATED in itemsObj: itemsUpdated = itemsObj[ KodiCompanion.SyncQueue.PROPERTY_GET_ITEMS_UPDATED] if KodiCompanion.SyncQueue.PROPERTY_GET_ITEMS_REMOVED in itemsObj: itemsRemoved = itemsObj[ KodiCompanion.SyncQueue.PROPERTY_GET_ITEMS_REMOVED] if KodiCompanion.SyncQueue.PROPERTY_GET_ITEMS_USER_DATA_CHANGED in itemsObj: userDataChanged = itemsObj[ KodiCompanion.SyncQueue. PROPERTY_GET_ITEMS_USER_DATA_CHANGED] return KodiCompanion.SyncQueue(itemsAdded=itemsAdded, itemsUpdated=itemsUpdated, itemsRemoved=itemsRemoved, userDataChanged=userDataChanged)
def MarkAsWatched(embyServer, itemId, lastPlayed): if not embyServer: raise ValueError('invalid embyServer') if not itemId: raise ValueError('invalid itemId') lastPlayedDate = UserData.PreprocessLastPlayed(lastPlayed) url = embyServer.BuildUserPlayedItemUrl(itemId) url = Url.addOptions( url, {'DatePlayed': lastPlayedDate.strftime('%Y%m%d%H%M%S')}) if not embyServer.ApiPost(url): return False return True
def BuildDirectStreamUrl(self, mediaType, itemId): if not itemId: raise ValueError('Invalid itemId') embyMediaType = None if mediaType == 'Video': embyMediaType = constants.URL_PLAYBACK_MEDIA_TYPE_VIDEO elif mediaType == 'Audio': embyMediaType = constants.URL_PLAYBACK_MEDIA_TYPE_AUDIO else: raise ValueError('Invalid mediaType "{}"'.format(mediaType)) url = self.BuildUrl(embyMediaType) url = Url.append(url, itemId, constants.URL_PLAYBACK_STREAM) url = Url.addOptions(url, { constants.URL_PLAYBACK_OPTION_STATIC: constants.URL_PLAYBACK_OPTION_STATIC_TRUE, constants.URL_QUERY_API_KEY: self.AccessToken() }) return url
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 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 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)