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)
Example #2
0
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
Example #3
0
    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))
Example #4
0
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)
Example #5
0
    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)
Example #7
0
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)
Example #8
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)))
Example #9
0
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)
Example #10
0
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)
Example #11
0
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)
Example #12
0
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))
Example #13
0
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)
Example #14
0
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)
Example #15
0
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)
Example #16
0
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)
Example #17
0
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)
Example #18
0
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)
Example #21
0
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)
Example #22
0
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)
Example #23
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
Example #24
0
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