Esempio n. 1
0
def channel(channel_id: str):
    """Load stream for the required channel id"""
    log('Loading channel {}'.format(channel_id), LogLevel.INFO)

    stream = get_provider().get_stream_info(channel_id)
    if not stream:
        ok_dialog(localize(30900))
        return

    is_helper = inputstreamhelper.Helper(stream['manifest_type'],
                                         drm=stream['drm'])
    if not is_helper.check_inputstream():
        ok_dialog(localize(30901))
        return

    listitem = xbmcgui.ListItem(path=stream['path'])
    listitem.setMimeType(stream['mime_type'])
    listitem.setProperty('inputstream', 'inputstream.adaptive')
    listitem.setProperty('inputstream.adaptive.manifest_type',
                         stream['manifest_type'])
    listitem.setProperty('inputstream.adaptive.manifest_update_parameter',
                         'full')
    listitem.setProperty('inputstream.adaptive.license_type',
                         stream['license_type'])
    listitem.setProperty('inputstream.adaptive.license_key',
                         stream['license_key'])
    xbmcplugin.setResolvedUrl(plugin.handle, True, listitem=listitem)
Esempio n. 2
0
def discoverProvider(handle: int, options: dict):
    """Prompt user for Plex authentication type, perform server discovery based on their choice, register the provider

    :param handle: Handle id from input
    :type handle: int
    :param options: Options/parameters passed in with the call
    :type options: dict
    """
    dialog = xbmcgui.Dialog()

    authenticationChoices = [
        localize(32013),  # local only
        localize(32014)  # MyPlex
    ]
    authenticationChoice = dialog.select(localize(32050),
                                         authenticationChoices)

    if authenticationChoice == 0:  # local only
        provider = discoverProviderLocally(handle, options)
    elif authenticationChoice == 1:  # MyPlex
        provider = discoverProviderWithMyPlex(handle, options)
    else:
        return

    if not provider:
        return

    xbmcmediaimport.setDiscoveredProvider(handle, True, provider)
Esempio n. 3
0
def forceSync(handle: int, _options: dict):
    """Confirm if user wants to force a full sync and update the settings hash

    :param handle: Handle id from input
    :type handle: int
    :param _options: Options/parameters passed in with the call, Unused
    :type _options: dict
    """
    # ask the user whether he is sure
    force = xbmcgui.Dialog().yesno(localize(32022), localize(32065))
    if not force:
        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
    importSettings = mediaImport.prepareSettings()
    if not importSettings:
        log('cannot prepare media import settings', xbmc.LOGERROR)
        return

    # reset the synchronization hash setting to force a full synchronization
    SynchronizationSettings.ResetHash(importSettings, save=False)
Esempio n. 4
0
def linkMyPlexAccount(handle: int, _options: dict):
    """Have user sign into MyPlex account, find servers on the account, and save authenticatino details for the server

    :param handle: Handle id from input
    :type handle: int
    :param _options: Options/parameters passed in with the call, unused
    :type _options: dict
    """
    # 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

    plexAccount = linkToMyPlexAccount()
    if not plexAccount:
        return

    username = plexAccount.username
    if not username:
        log('no valid username available for the linked MyPlex account',
            xbmc.LOGWARNING)
        return

    # make sure the configured Plex Media Server is still accessible
    serverUrl = mediaProvider.getBasePath()
    matchingServer = None

    serverId = getServerId(mediaProvider.getIdentifier())

    # get all connected server resources
    serverResources = getServerResources(plexAccount)
    for server in serverResources:
        if server.clientIdentifier == serverId:
            matchingServer = server
            break

    if not matchingServer:
        log(f"no Plex Media Server matching {serverUrl} found",
            xbmc.LOGWARNING)
        xbmcgui.Dialog().ok(localize(32015), localize(32058))
        return

    xbmcgui.Dialog().ok(localize(32015), localize(32059, username))

    # change the settings
    providerSettings.setString(plex.constants.SETTINGS_PROVIDER_USERNAME,
                               username)
    providerSettings.setString(plex.constants.SETTINGS_PROVIDER_TOKEN,
                               matchingServer.accessToken)
Esempio n. 5
0
def changeUrl(handle: int, _options: dict):
    """Change the URL of PMS

    :param handle: Handle id from input
    :type handle: int
    :param _options: Options/parameters passed in with the call, unused
    :type _options: dict
    """
    # 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:
        log("cannot prepare provider settings", xbmc.LOGERROR)
        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(localize(32068), 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()
    except:
        pass

    dialog = xbmcgui.Dialog()
    title = mediaProvider.getFriendlyName()
    if success:
        dialog.ok(title, localize(32018))
    else:
        # ask the user whether to change the URL anyway
        changeUrlAnyway = dialog.yesno(title, localize(32069))
        if not changeUrlAnyway:
            # revert the settings to the previous / old URL
            ProviderSettings.SetUrl(providerSettings, urlCurrent)
Esempio n. 6
0
def testConnection(handle: int, _options: dict):
    """Test connection to the user provided PMS and display results to user

    :param handle: Handle id from input
    :type handle: int
    :param _options: Options/parameters passed in with the call, unused
    :type _options: dict
    """
    # retrieve the media provider
    mediaProvider = xbmcmediaimport.getProvider(handle)
    if not mediaProvider:
        log('cannot retrieve media provider', xbmc.LOGERROR)
        return

    success = False
    try:
        success = Server(mediaProvider).Authenticate()
    except:
        pass

    title = mediaProvider.getFriendlyName()
    line = 32019
    if success:
        line = 32018

    xbmcgui.Dialog().ok(title, localize(line))
Esempio n. 7
0
    def __init__(self, *args, label, **kwargs):
        if not label:
            raise ValueError('invalid label')

        super(WindowXMLDialog, self).__init__(*args, **kwargs)

        if not isinstance(label, str):
            label = localize(label)
        self.setProperty('skiplabel', label)

        self._isOpen = False
        self._skipConfirmed = False
Esempio n. 8
0
def discoverProviderLocally(handle: int,
                            _options: dict) -> xbmcmediaimport.MediaProvider:
    """Set up a Plex server provider from user-provided URL

    :param handle: Handle id from input
    :type handle: int
    :param _options: Options/parameters passed in with the call, unused
    :type _options: dict
    :return: Fully setup and populated mediaProvider object for the PMS
    :rtype: xbmcmediaimport.MediaProvider
    """
    dialog = xbmcgui.Dialog()

    baseUrl = dialog.input(localize(32051))
    if not baseUrl:
        return None

    plexServer = PlexServer(baseUrl, timeout=plex.constants.REQUEST_TIMEOUT)
    if not plexServer:
        return None

    providerId = Server.BuildProviderId(plexServer.machineIdentifier)
    providerIconUrl = Server.BuildIconUrl(baseUrl)

    provider = xbmcmediaimport.MediaProvider(
        identifier=providerId,
        basePath=baseUrl,
        friendlyName=plexServer.friendlyName,
        iconUrl=providerIconUrl,
        mediaTypes=plex.constants.SUPPORTED_MEDIA_TYPES,
        handle=handle)

    # store local authentication in settings
    providerSettings = provider.prepareSettings()
    if not providerSettings:
        return None

    providerSettings.setInt(
        plex.constants.SETTINGS_PROVIDER_AUTHENTICATION,
        plex.constants.SETTINGS_PROVIDER_AUTHENTICATION_OPTION_LOCAL)
    providerSettings.save()

    return provider
Esempio n. 9
0
def linkToMyPlexAccount() -> MyPlexAccount:
    """Log into MyPlex with user-provided settings and get authentication token

    :return: Returns authenticated MyPlexAccount object
    :rtype: :class:`MyPlexAccount`
    """
    dialog = xbmcgui.Dialog()

    pinLogin = MyPlexPinLogin()
    if not pinLogin.pin:
        dialog.ok(localize(32015), localize(32052))
        log('failed to get PIN to link MyPlex account', xbmc.LOGWARNING)
        return None

    # show the user the pin
    dialog.ok(
        localize(32015),
        localize(32053) +
        normalizeString(f" [COLOR FFE5A00D]{pinLogin.pin}[/COLOR]"))

    # check the status of the authentication
    while not pinLogin.finished:
        if pinLogin.checkLogin():
            break

    if pinLogin.expired:
        dialog.ok(localize(32015), localize(32054))
        log("linking the MyPlex account has expiried", xbmc.LOGWARNING)
        return None

    if not pinLogin.token:
        log("no valid token received from the linked MyPlex account",
            xbmc.LOGWARNING)
        return None

    # login to MyPlex
    try:
        plexAccount = MyPlexAccount(token=pinLogin.token,
                                    timeout=plex.constants.REQUEST_TIMEOUT)
    except Exception as e:
        log(f"failed to connect to the linked MyPlex account: {e}",
            xbmc.LOGWARNING)
        return None
    if not plexAccount:
        log("failed to connect to the linked MyPlex account", xbmc.LOGWARNING)
        return None

    return plexAccount
Esempio n. 10
0
def index():
    """Addon index"""
    ok_dialog(localize(30902))
Esempio n. 11
0
def play(item, itemId, mediaProvider):
    if item.isFolder():
        contextLog(f"cannot play folder item {listItem2str(item, itemId)}", xbmc.LOGERROR, entry='play')
        return

    # create a Plex server instance
    server = Server(mediaProvider)
    if not server.Authenticate():
        contextLog(
            f"failed to connect to Plex Media Server for {mediaProvider2str(mediaProvider)}",
            xbmc.LOGWARNING, entry='sync')
        return

    plexItemClass = Api.getPlexMediaClassFromListItem(item)

    # cannot play folders
    if plexItemClass in (collection.Collection, video.Show, video.Season):
        contextLog(f"cannot play folder item {listItem2str(item, itemId)}", xbmc.LOGERROR, entry='play')
        return

    # get the Plex item with all its details
    plexItem = Api.getPlexItemDetails(server.PlexServer(), itemId, plexItemClass=plexItemClass)
    if not plexItem:
        contextLog(
            f"failed to determine Plex item for {listItem2str(item, itemId)} from {mediaProvider2str(mediaProvider)}",
            xbmc.LOGWARNING, entry='refresh')
        return

    # cannot play folders
    if not Api.canPlay(plexItem):
        contextLog(f"cannot play item {listItem2str(item, itemId)}", xbmc.LOGERROR, entry='play')
        return

    playChoices = []
    playChoicesUrl = []

    # determine whether Direct Play is allowed
    mediaProviderSettings = mediaProvider.getSettings()
    allowDirectPlay = mediaProviderSettings.getBool(SETTINGS_PROVIDER_PLAYBACK_ALLOW_DIRECT_PLAY)

    # check if the item supports Direct Play
    if allowDirectPlay:
        directPlayUrl = Api.getDirectPlayUrlFromPlexItem(plexItem)
        if directPlayUrl:
            playChoices.append(localize(32103))
            playChoicesUrl.append(directPlayUrl)

    # check if the item supports streaming
    directStreamUrl = Api.getStreamUrlFromPlexItem(plexItem, server.PlexServer())
    if directStreamUrl:
        playChoices.append(localize(32104))
        playChoicesUrl.append(directStreamUrl)

    # check if the item has multiple versions
    multipleVersions = []
    if len(plexItem.media) > 1:
        for mediaStream in plexItem.media:
            url = None
            if allowDirectPlay:
                directPlayUrl = Api.getDirectPlayUrlFromMedia(mediaStream)
                if directPlayUrl:
                    url = directPlayUrl

            if not url:
                url = Api.getStreamUrlFromMedia(mediaStream, server.PlexServer())

            # get the display title of the first videostream
            for mediaPart in mediaStream.parts:
                # get all video streams
                videoStreams = (stream for stream in mediaPart.streams if isinstance(stream, media.VideoStream))
                # extract the first non-empty display resolution
                displayResolution = next(
                    (
                        stream.displayTitle or stream.extendedDisplayTitle
                        for stream in videoStreams
                        if stream.displayTitle or stream.extendedDisplayTitle
                    ),
                    None)
                if displayResolution:
                    break

            # fall back to the basic video resolution of the stream
            if not displayResolution:
                displayResolution = mediaStream.videoResolution

            multipleVersions.append((url, mediaStream.bitrate, displayResolution))

    if len(multipleVersions) > 1:
        playChoices.append(localize(32105))
        playChoicesUrl.append(PLAY_MULTIPLE_VERSIONS_KEY)

    # if there are no options something went wrong
    if not playChoices:
        contextLog(
            f"cannot play {listItem2str(item, itemId)} from {mediaProvider2str(mediaProvider)}",
            xbmc.LOGERROR, entry='play')
        return

    # ask the user how to play
    playChoice = Dialog().contextmenu(playChoices)
    if playChoice < 0 or playChoice >= len(playChoices):
        return

    playUrl = playChoicesUrl[playChoice]

    # check if the user chose to choose which version to play
    if playUrl == PLAY_MULTIPLE_VERSIONS_KEY:
        playChoices.clear()
        playChoicesUrl.clear()

        # sort the available versions by bitrate (second field)
        multipleVersions.sort(key=lambda version: version[1], reverse=True)

        for version in multipleVersions:
            playChoices.append(
                localize(32106, bitrate=bitrate2str(version[1]), resolution=version[2]))
            playChoicesUrl.append(version[0])

        # ask the user which version to play
        playChoice = Dialog().contextmenu(playChoices)
        if playChoice < 0 or playChoice >= len(playChoices):
            return

        playUrl = playChoicesUrl[playChoice]

    # play the item
    contextLog(
        (
            f'playing {listItem2str(item, itemId)} using "{playChoices[playChoice]}" ({playUrl}) '
            f'from {mediaProvider2str(mediaProvider)}'
        ),
        entry='play')
    # overwrite the dynamic path of the ListItem
    item.setDynamicPath(playUrl)
    xbmc.Player().play(playUrl, item)
Esempio n. 12
0
def exec_import(handle, options):
    # parse all necessary options
    media_types = media_types_from_options(options)
    if not media_types:
        log('cannot execute "import" without media types', xbmc.LOGERROR)
        return

    # retrieve the media import
    media_import = xbmcmediaimport.getImport(handle)
    if not media_import:
        log("cannot retrieve media import", xbmc.LOGERROR)
        return

    # prepare and get the media import settings
    import_settings = media_import.prepareSettings()
    if not import_settings:
        log("cannot prepare media import settings", xbmc.LOGERROR)
        return

    # retrieve the media provider
    media_provider = media_import.getProvider()
    if not media_provider:
        log("cannot retrieve media provider", xbmc.LOGERROR)
        return

    log(f"importing {media_types} items from { provider2str(media_provider)}..."
        )

    # TODO(stub): prepare collecting ListItems

    # loop over all media types to be imported
    progress = 0
    progress_total = len(media_types)
    for media_type in media_types:
        # check if we need to cancel importing items
        if xbmcmediaimport.shouldCancel(handle, progress, progress_total):
            return
        progress += 1

        log(f"importing {media_type} items from {provider2str(media_provider)}..."
            )

        # report the progress status
        xbmcmediaimport.setProgressStatus(handle,
                                          localize(32001).format(media_type))

        items = []

        # TODO(stub): collect ListItems to import
        #             adjust and use lib.kodi.Api.to_file_item()

        if items:
            # pass the imported items back to Kodi
            log(f"{len(items)} {media_type} items imported from {provider2str(media_provider)}"
                )

            # TODO(stub): for partial imports use the following constants as an optional fourth argument:
            #     xbmcmediaimport.MediaImportChangesetTypeNone: let Kodi decide
            #     xbmcmediaimport.MediaImportChangesetTypeAdded: the item is new and should be added
            #     xbmcmediaimport.MediaImportChangesetTypeChanged: the item has been imported before and has changed
            #     xbmcmediaimport.MediaImportChangesetTypeRemoved: the item has to be removed
            xbmcmediaimport.addImportItems(handle, items, media_type)

    # TODO(stub): tell Kodi whether the provided items is a full or partial import
    partial_import = False

    # finish the import
    xbmcmediaimport.finishImport(handle, partial_import)
Esempio n. 13
0
def execImport(handle: int, options: dict):
    """Perform library update/import of all configured items from a configured PMS into Kodi

    :param handle: Handle id from input
    :type handle: int
    :param options: Options/parameters passed in with the call, required mediatypes or mediatypes[]
    :type options: dict
    """
    if 'path' not 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

    # prepare and get the media provider settings
    providerSettings = mediaProvider.prepareSettings()
    if not providerSettings:
        log("cannot prepare provider settings", xbmc.LOGERROR)
        return

    # create a Plex Media Server instance
    server = Server(mediaProvider)
    plexServer = server.PlexServer()
    plexLibrary = plexServer.library

    # get all (matching) library sections
    selectedLibrarySections = getLibrarySectionsFromSettings(importSettings)
    librarySections = getMatchingLibrarySections(plexServer, mediaTypes,
                                                 selectedLibrarySections)
    if not librarySections:
        log(f"cannot retrieve {mediaTypes} items without any library section",
            xbmc.LOGERROR)
        return

    # Decide if doing fast sync or not, if so set filter string to include updatedAt
    fastSync = True
    lastSync = mediaImport.getLastSynced()

    # Check if import settings have changed, or if this is the first time we are importing this library type
    if not lastSync:
        fastSync = False
        SynchronizationSettings.CalculateHash(
            importSettings=importSettings,
            providerSettings=providerSettings,
            save=True)
        log("first time syncronizing library, forcing a full syncronization",
            xbmc.LOGINFO)
    elif SynchronizationSettings.HaveChanged(importSettings=importSettings,
                                             providerSettings=providerSettings,
                                             save=True):
        fastSync = False
        log(
            "library import settings have changed, forcing a full syncronization",
            xbmc.LOGINFO)

    if SynchronizationSettings.HaveChanged(importSettings=importSettings,
                                           providerSettings=providerSettings,
                                           save=True):
        fastSync = False
        log(
            "library import settings have changed, forcing a full syncronization",
            xbmc.LOGINFO)

    if fastSync:
        log(f"performing fast syncronization of items viewed or updated since {str(lastSync)}"
            )
        lastSyncEpoch = parser.parse(lastSync).strftime('%s')
        updatedFilter = {'updatedAt>': lastSyncEpoch}
        watchedFilter = {'lastViewedAt>': lastSyncEpoch}

    # loop over all media types to be imported
    progressTotal = len(mediaTypes)
    for progress, mediaType in enumerate(mediaTypes):
        if xbmcmediaimport.shouldCancel(handle, progress, progressTotal):
            return

        mappedMediaType = Api.getPlexMediaType(mediaType)
        if not mappedMediaType:
            log(f"cannot import unsupported media type '{mediaType}'",
                xbmc.LOGERROR)
            continue

        plexLibType = mappedMediaType['libtype']
        localizedMediaType = localize(mappedMediaType['label']).decode()

        xbmcmediaimport.setProgressStatus(handle,
                                          localize(32001, localizedMediaType))

        log(
            f"importing {mediaType} items from {mediaProvider2str(mediaProvider)}",
            xbmc.LOGINFO)

        # handle library sections
        itemsToImport = []
        sectionsProgressTotal = len(librarySections)
        for sectionsProgress, librarySection in enumerate(librarySections):
            if xbmcmediaimport.shouldCancel(handle, sectionsProgress,
                                            sectionsProgressTotal):
                return

            # get the library section from the Plex Media Server
            section = plexLibrary.sectionByID(librarySection['key'])
            if not section:
                log(
                    f"cannot import {mediaType} items from unknown library section {librarySection}",
                    xbmc.LOGWARNING)
                continue

            # get all matching items from the library section and turn them into ListItems
            sectionProgress = 0
            sectionProgressTotal = ITEM_REQUEST_LIMIT

            while sectionProgress < sectionProgressTotal:
                if xbmcmediaimport.shouldCancel(handle, sectionProgress,
                                                sectionProgressTotal):
                    return

                maxResults = min(ITEM_REQUEST_LIMIT,
                                 sectionProgressTotal - sectionProgress)

                try:
                    if fastSync:
                        updatedPlexItems = section.search(
                            libtype=plexLibType,
                            container_start=sectionProgress,
                            container_size=maxResults,
                            maxresults=maxResults,
                            **updatedFilter)
                        log(f"discovered {len(updatedPlexItems)} updated items from {mediaProvider2str(mediaProvider)}"
                            )
                        watchedPlexItems = section.search(
                            libtype=plexLibType,
                            container_start=sectionProgress,
                            container_size=maxResults,
                            maxresults=maxResults,
                            **watchedFilter)
                        log(f"discovered {len(watchedPlexItems)} new watched items from {mediaProvider2str(mediaProvider)}"
                            )

                        plexItems = updatedPlexItems
                        plexItems.extend([
                            item for item in watchedPlexItems if item.key
                            not in [item.key for item in plexItems]
                        ])

                    else:
                        plexItems = section.search(
                            libtype=plexLibType,
                            container_start=sectionProgress,
                            container_size=maxResults,
                            maxresults=maxResults,
                        )
                except plexapi.exceptions.BadRequest as e:
                    log(
                        f"failed to fetch {mediaType} items from {mediaProvider2str(mediaProvider)}: {e}",
                        xbmc.LOGINFO)
                    return

                # Update sectionProgressTotal now that search has run and totalSize has been updated
                sectionProgressTotal = section.totalSize

                plexItemsProgressTotal = len(plexItems)
                for plexItemsProgress, plexItem in enumerate(plexItems):
                    if xbmcmediaimport.shouldCancel(handle, plexItemsProgress,
                                                    plexItemsProgressTotal):
                        return

                    sectionProgress += 1

                    try:
                        item = Api.toFileItem(plexServer, plexItem, mediaType,
                                              plexLibType)
                        if not item:
                            continue

                        itemsToImport.append(item)
                    except plexapi.exceptions.BadRequest as e:
                        # Api.convertDateTimeToDbDateTime may return (404) not_found for orphaned items in the library
                        log((
                            f"failed to retrieve item {plexItem.title} with key {plexItem.key} "
                            f"from {mediaProvider2str(mediaProvider)}: {e}"),
                            xbmc.LOGWARNING)
                        continue

        if itemsToImport:
            log(
                f"{len(itemsToImport)} {mediaType} items imported from {mediaProvider2str(mediaProvider)}",
                xbmc.LOGINFO)
            xbmcmediaimport.addImportItems(handle, itemsToImport, mediaType)

    xbmcmediaimport.finishImport(handle, fastSync)
Esempio n. 14
0
def discoverProviderWithMyPlex(
        handle: int, _options: dict) -> xbmcmediaimport.MediaProvider:
    """
    Prompts user to sign into their Plex account using the MyPlex pin link
    Finds a list of all servers connected to the account and prompts user to pick one
        Prompt for a local discovery if no servers found within their Plex account
    Setup and store the Plex server as a MediaProvider and store MyPlex auth information

    :param handle: Handle id from input
    :type handle: int
    :param _options: Options/parameters passed in with the call, unused
    :type _options: dict
    :return: Fully configured and authenticated Plex media provider
    :rtype: :class:`xbmcmediaimport.MediaProvider`
    """
    plexAccount = linkToMyPlexAccount()
    if not plexAccount:
        return None

    username = plexAccount.username
    if not username:
        log("no valid username available for the linked MyPlex account",
            xbmc.LOGWARNING)
        return None

    dialog = xbmcgui.Dialog()

    # get all connected server resources
    serverResources = getServerResources(plexAccount)
    if not serverResources:
        log(f"no servers available for MyPlex account {username}",
            xbmc.LOGWARNING)
        return None

    if len(serverResources) == 1:
        server = serverResources[0]
    else:
        # ask the user which server to use
        servers = [resource.name for resource in serverResources]
        serversChoice = dialog.select(localize(32055), servers)
        if serversChoice < 0 or serversChoice >= len(servers):
            return None

        server = serverResources[serversChoice]

    if not server:
        return None

    if not server.connections:
        # try to connect to the server
        plexServer = server.connect(timeout=plex.constants.REQUEST_TIMEOUT)
        if not plexServer:
            log(f"failed to connect to the Plex Media Server '{server.name}'",
                xbmc.LOGWARNING)
            return None

        baseUrl = plexServer.url('', includeToken=False)
    else:
        isLocal = False
        localConnections = [
            connection for connection in server.connections if connection.local
        ]
        remoteConnections = [
            connection for connection in server.connections
            if not connection.local and not connection.relay
        ]
        remoteRelayConnections = [
            connection for connection in server.connections
            if not connection.local and connection.relay
        ]

        if localConnections:
            # ask the user whether to use a local or remote connection
            isLocal = dialog.yesno(localize(32056),
                                   localize(32057, server.name))

        urls = []
        if isLocal:
            urls.append((localConnections[0].httpuri, False))
        else:
            urls.extend([(conn.uri, False) for conn in remoteConnections])
            urls.extend([(conn.uri, True) for conn in remoteRelayConnections])
            urls.extend([(conn.uri, False) for conn in localConnections])

        baseUrl = None
        connectViaRelay = True
        # find a working connection / base URL
        for (url, isRelay) in urls:
            try:
                # don't try to connect via relay if the user has already declined it before
                if isRelay and not connectViaRelay:
                    log(
                        f"ignoring relay connection to the Plex Media Server '{server.name}' at {url}",
                        xbmc.LOGDEBUG)
                    continue

                # try to connect to the server
                _ = PlexServer(baseurl=url,
                               token=server.accessToken,
                               timeout=plex.constants.REQUEST_TIMEOUT)

                # if this is a relay ask the user if using it is ok
                if isRelay:
                    connectViaRelay = dialog.yesno(
                        localize(32056), localize(32061, server.name))
                    if not connectViaRelay:
                        log(
                            f"ignoring relay connection to the Plex Media Server '{server.name}' at {url}",
                            xbmc.LOGDEBUG)
                        continue

                baseUrl = url
                break
            except:
                log(f"failed to connect to '{server.name}' at {url}",
                    xbmc.LOGDEBUG)
                continue

        if not baseUrl:
            dialog.ok(localize(32056), localize(32060, server.name))
            log(
                f"failed to connect to the Plex Media Server '{server.name}' for MyPlex account {username}",
                xbmc.LOGWARNING)
            return None

    if not baseUrl:
        log(
            f"failed to find the URL to access the Plex Media Server '{server.name}' for MyPlex account {username}",
            xbmc.LOGWARNING)
        return None

    log(
        f"successfully connected to Plex Media Server '{server.name}' for MyPlex account {username} at {baseUrl}",
        xbmc.LOGINFO)

    providerId = plex.server.Server.BuildProviderId(server.clientIdentifier)
    providerIconUrl = plex.server.Server.BuildIconUrl(baseUrl)
    provider = xbmcmediaimport.MediaProvider(
        providerId,
        baseUrl,
        server.name,
        providerIconUrl,
        plex.constants.SUPPORTED_MEDIA_TYPES,
        handle=handle)

    # store MyPlex account details and token in settings
    providerSettings = provider.prepareSettings()
    if not providerSettings:
        return None

    providerSettings.setInt(
        plex.constants.SETTINGS_PROVIDER_AUTHENTICATION,
        plex.constants.SETTINGS_PROVIDER_AUTHENTICATION_OPTION_MYPLEX)
    providerSettings.setString(plex.constants.SETTINGS_PROVIDER_USERNAME,
                               username)
    providerSettings.setString(plex.constants.SETTINGS_PROVIDER_TOKEN,
                               server.accessToken)
    providerSettings.save()

    return provider
Esempio n. 15
0
import xbmc  # pylint: disable=import-error
import xbmcmediaimport  # pylint: disable=import-error

from plexapi.server import PlexServer
from plex.api import Api
from plex.constants import (
    PLEX_PROTOCOL, PLEX_PLAYER_PLAYING, PLEX_PLAYER_PAUSED,
    PLEX_PLAYER_STOPPED, SETTINGS_PROVIDER_PLAYBACK_ENABLE_EXTERNAL_SUBTITLES)
from plex.server import Server

from lib.utils import log, mediaProvider2str, toMilliseconds, localize

REPORTING_INTERVAL = 5  # seconds

SUBTITLE_UNKNOWN = localize(32064).decode()


class Player(xbmc.Player):
    """Class with customization of the xmbc Player class to handle working with Plex"""
    def __init__(self):
        """Initializes the player"""
        super(Player, self).__init__()

        self._providers = {}
        self._lock = threading.Lock()

        self._state = {'playbacktime': 0, 'state': None, 'lastreport': 0}
        self._duration = None

        self._file = None
Esempio n. 16
0
def execImport(handle: int, options: dict):
    """Perform library update/import of all configured items from a configured PMS into Kodi

    :param handle: Handle id from input
    :type handle: int
    :param options: Options/parameters passed in with the call, required mediatypes or mediatypes[]
    :type options: dict
    """
    # 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

    # prepare and get the media provider settings
    providerSettings = mediaProvider.prepareSettings()
    if not providerSettings:
        log("cannot prepare provider settings", xbmc.LOGERROR)
        return

    # get direct play settings from provider
    allowDirectPlay = providerSettings.getBool(SETTINGS_PROVIDER_PLAYBACK_ALLOW_DIRECT_PLAY)

    # create a Plex Media Server instance
    server = Server(mediaProvider)
    plexServer = server.PlexServer()
    plexLibrary = plexServer.library

    # get all (matching) library sections
    selectedLibrarySections = getLibrarySectionsFromSettings(importSettings)
    librarySections = getMatchingLibrarySections(plexServer, mediaTypes, selectedLibrarySections)
    if not librarySections:
        log(f"cannot retrieve {mediaTypes} items without any library section", xbmc.LOGERROR)
        return

    numDownloadThreads = ImportSettings.GetNumberOfDownloadThreads(importSettings)
    numRetriesOnTimeout = ImportSettings.GetNumberOfRetriesOnTimeout(importSettings)
    numSecondsBetweenRetries = ImportSettings.GetNumberOfSecondsBetweenRetries(importSettings)

    # Decide if doing fast sync or not, if so set filter string to include updatedAt
    fastSync = True
    lastSync = mediaImport.getLastSynced()

    # Check if import settings have changed, or if this is the first time we are importing this library type
    if not lastSync:
        fastSync = False
        SynchronizationSettings.CalculateHash(
            importSettings=importSettings,
            providerSettings=providerSettings,
            save=True
        )
        log("first time syncronizing library, forcing a full syncronization", xbmc.LOGINFO)
    elif SynchronizationSettings.HaveChanged(
            importSettings=importSettings,
            providerSettings=providerSettings,
            save=True
    ):
        fastSync = False
        log("library import settings have changed, forcing a full syncronization", xbmc.LOGINFO)

    if SynchronizationSettings.HaveChanged(importSettings=importSettings, providerSettings=providerSettings, save=True):
        fastSync = False
        log("library import settings have changed, forcing a full syncronization", xbmc.LOGINFO)

    if fastSync:
        log(f"performing fast syncronization of items viewed or updated since {str(lastSync)}")

    # loop over all media types to be imported
    progressTotal = len(mediaTypes)
    for progress, mediaType in enumerate(mediaTypes):
        if xbmcmediaimport.shouldCancel(handle, progress, progressTotal):
            return

        mappedMediaType = Api.getPlexMediaType(mediaType)
        if not mappedMediaType:
            log(f"cannot import unsupported media type '{mediaType}'", xbmc.LOGERROR)
            continue

        plexLibType = mappedMediaType['libtype']
        localizedMediaType = localize(mappedMediaType['label']).decode()

        # prepare and start the converter threads
        converterThreads = []
        for _ in range(0, numDownloadThreads):
            converterThreads.append(ToFileItemConverterThread(mediaProvider, mediaImport, plexServer,
                media_type=mediaType, plex_lib_type=plexLibType, allow_direct_play=allowDirectPlay))
        log((
            f"starting {len(converterThreads)} threads to import {mediaType} items "
            f"from {mediaProvider2str(mediaProvider)}..."),
            xbmc.LOGDEBUG)
        for converterThread in converterThreads:
            converterThread.start()

        # prepare function to stop all converter threads
        def stopConverterThreads(converterThreads):
            log((
                f"stopping {len(converterThreads)} threads importing {mediaType} items "
                f"from {mediaProvider2str(mediaProvider)}..."),
                xbmc.LOGDEBUG)
            for converterThread in converterThreads:
                converterThread.stop()

        xbmcmediaimport.setProgressStatus(handle, localize(32001, localizedMediaType))

        log(f"importing {mediaType} items from {mediaProvider2str(mediaProvider)}", xbmc.LOGINFO)

        # handle library sections
        sectionsProgressTotal = len(librarySections)
        for sectionsProgress, librarySection in enumerate(librarySections):
            if xbmcmediaimport.shouldCancel(handle, sectionsProgress, sectionsProgressTotal):
                stopConverterThreads(converterThreads)
                return

            # get the library section from the Plex Media Server
            section = plexLibrary.sectionByID(librarySection['key'])
            if not section:
                log(f"cannot import {mediaType} items from unknown library section {librarySection}", xbmc.LOGWARNING)
                continue

            # get all matching items from the library section and turn them into ListItems
            sectionRetrievalProgress = 0
            sectionConversionProgress = 0
            sectionProgressTotal = ITEM_REQUEST_LIMIT

            while sectionRetrievalProgress < sectionProgressTotal:
                if xbmcmediaimport.shouldCancel(handle, sectionConversionProgress, sectionProgressTotal):
                    stopConverterThreads(converterThreads)
                    return

                maxResults = min(ITEM_REQUEST_LIMIT, sectionProgressTotal - sectionRetrievalProgress)

                retries = numRetriesOnTimeout
                while retries > 0:
                    try:
                        if fastSync:
                            lastSyncDatetime = parser.parse(lastSync).astimezone(timezone.utc)
                            prefix = ''
                            if mediaType in (xbmcmediaimport.MediaTypeTvShow, xbmcmediaimport.MediaTypeEpisode):
                                prefix = Api.getPlexMediaType(mediaType)['libtype'] + '.'
                            elif mediaType == xbmcmediaimport.MediaTypeSeason:
                                prefix = Api.getPlexMediaType(xbmcmediaimport.MediaTypeEpisode)['libtype'] + '.'

                            updatedFilter = {prefix + 'updatedAt>>': lastSyncDatetime}
                            watchedFilter = {prefix + 'lastViewedAt>>': lastSyncDatetime}

                            updatedPlexItems = section.search(
                                libtype=plexLibType,
                                container_start=sectionRetrievalProgress,
                                container_size=maxResults,
                                maxresults=maxResults,
                                filters=updatedFilter
                            )
                            log(f"discovered {len(updatedPlexItems)} updated {mediaType} items from {mediaProvider2str(mediaProvider)}")
                            watchedPlexItems = section.search(
                                libtype=plexLibType,
                                container_start=sectionRetrievalProgress,
                                container_size=maxResults,
                                maxresults=maxResults,
                                filters=watchedFilter
                            )
                            log(f"discovered {len(watchedPlexItems)} newly watched {mediaType} items from {mediaProvider2str(mediaProvider)}")

                            plexItems = updatedPlexItems
                            plexItems.extend(
                                [item for item in watchedPlexItems if item.key not in [item.key for item in plexItems]]
                            )

                        else:
                            plexItems = section.search(
                                libtype=plexLibType,
                                container_start=sectionRetrievalProgress,
                                container_size=maxResults,
                                maxresults=maxResults,
                            )

                        # get out of the retry loop
                        break
                    except Exception as e:
                        log(f"failed to fetch {mediaType} items from {mediaProvider2str(mediaProvider)}: {e}", xbmc.LOGWARNING)

                        # retry after timeout
                        retries -= 1

                        # check if there are any more retries left
                        # if not abort the import process
                        if retries == 0:
                            log(
                                (
                                    f"fetching {mediaType} items from {mediaProvider2str(mediaProvider)} failed "
                                    f"after {numRetriesOnTimeout} retries"
                                ),
                                xbmc.LOGWARNING)
                            stopConverterThreads(converterThreads)
                            return
                        else:
                            # otherwise wait before trying again
                            log(
                                (
                                    f"retrying to fetch {mediaType} items from {mediaProvider2str(mediaProvider)} in "
                                    f"{numSecondsBetweenRetries} seconds"
                                )
                            )
                            time.sleep(float(numSecondsBetweenRetries))

                # Update sectionProgressTotal now that search has run and totalSize has been updated
                # TODO(Montellese): fix access of private LibrarySection._totalViewSize
                sectionProgressTotal = section._totalViewSize

                # nothing to do if no items have been retrieved from Plex
                if not plexItems:
                    continue

                # automatically determine how to distribute the retrieved items across the available converter threads
                plexItemsPerConverter, remainingPlexItems = divmod(len(plexItems), len(converterThreads))
                plexItemsPerConverters = [plexItemsPerConverter] * len(converterThreads)
                plexItemsPerConverters[0:remainingPlexItems] = [plexItemsPerConverter + 1] * remainingPlexItems
                converterThreadIndex = 0

                splitItems = []
                for plexItem in plexItems:
                    if xbmcmediaimport.shouldCancel(handle, sectionConversionProgress, sectionProgressTotal):
                        stopConverterThreads(converterThreads)
                        return

                    sectionRetrievalProgress += 1

                    # copllect the plex items for the next converter thread
                    splitItems.append(plexItem)
                    plexItemsPerConverters[converterThreadIndex] -= 1

                    # move to the next converter thread if necessary
                    if not plexItemsPerConverters[converterThreadIndex]:
                        converterThreads[converterThreadIndex].add_items_to_convert(splitItems)
                        splitItems.clear()

                        converterThreadIndex += 1

                    # retrieve and combine the progress of all converter threads
                    sectionConversionProgress = \
                        sum(converterThread.get_converted_items_count() for converterThread in converterThreads)

                if splitItems:
                    log(f"forgot to process {len(splitItems)} {mediaType} items", xbmc.LOGWARNING)

        # retrieve converted items from the converter threads
        totalItemsToImport = 0
        countFinishedConverterThreads = 0
        while countFinishedConverterThreads < len(converterThreads):
            if xbmcmediaimport.shouldCancel(handle, sectionConversionProgress, sectionProgressTotal):
                stopConverterThreads(converterThreads)
                return

            sectionConversionProgress = 0
            itemsToImport = []
            for converterThread in converterThreads:
                # update the progress
                sectionConversionProgress += converterThread.get_converted_items_count()

                # ignore finished converter threads
                if converterThread.should_finish():
                    continue

                # retrieve the converted items
                convertedItems = converterThread.get_converted_items()
                itemsToImport.extend(convertedItems)

                # check if all items have been converted
                if not converterThread.get_items_to_convert_count():
                    converterThread.finish()
                    countFinishedConverterThreads += 1

            if itemsToImport:
                totalItemsToImport += len(itemsToImport)
                xbmcmediaimport.addImportItems(handle, itemsToImport, mediaType)

            time.sleep(0.1)

        if totalItemsToImport:
            log(f"{totalItemsToImport} {mediaType} items imported from {mediaProvider2str(mediaProvider)}", xbmc.LOGINFO)

    xbmcmediaimport.finishImport(handle, fastSync)