コード例 #1
0
    def latestUpdatesCatalog(self, params):
        '''
        Returns a catalog for the Latest Updates category.
        This route is cached memory-only to make sure it's always fresh.
        This memory cache lasts one Kodi session. Closing Kodi and reopening it
        will do a new web request.
        '''
        api = params['api']

        if api == requestHelper.API_ANIMETOON:
            _PROPERTY_LAST_UPDATES = 'tmania2.prop.toonUpdates'
        else:
            _PROPERTY_LAST_UPDATES = 'tmania2.prop.plusUpdates'

        # Memory-only cache of the latest updates IDs.
        latestIDs = cache.getCacheProperty(_PROPERTY_LAST_UPDATES, readFromDisk = False)
        if not latestIDs:
            requestHelper.setAPISource(api)
            requestHelper.delayBegin()
            jsonData = requestHelper.routeGET(params['route'])
            if jsonData:
                latestIDs = tuple(entry['id'] for entry in jsonData.get('updates', [ ]))
                cache.setCacheProperty(_PROPERTY_LAST_UPDATES, latestIDs, saveToDisk = False)
            requestHelper.delayEnd(1000)

        if latestIDs:
            # Latest Updates needs all items of the current API loaded, to compare IDs with.
            # Make a dictionary to map item IDs to the items themselves.
            allData = self._getMainRoutesData(api)
            idDict = {item[0]: item for item in chain.from_iterable(allData)}
            # Find the entries based on their IDs from the latest updates list.
            return self.catalogFromIterable(idDict[entryID] for entryID in latestIDs if entryID in idDict)
        else:
            return self.getEmptyCatalog()
コード例 #2
0
    def genericCatalog(self, params):
        '''
        Returns a catalog for either the '/GetNew(...)' or '/GetPopular(...)' routes.
        These routes are cached memory-only, so they last one Kodi session. Closing and
        reopening Kodi will make a new web request for fresh data.
        '''
        api = params['api']
        route = params['route']

        # Get the corresponding '/GetAll(...)' route with the data that will be needed.
        allRoute = route.replace('New', 'All') if '/GetNew' in route else route.replace('Popular', 'All')

        genericIDs = cache.getCacheProperty(api+route, readFromDisk = False)
        if not genericIDs:
            requestHelper.setAPISource(api)
            requestHelper.delayBegin()
            jsonData = requestHelper.routeGET(route)
            if jsonData:
                genericIDs = tuple(entry['id'] for entry in jsonData)
                cache.setCacheProperty(api+route, genericIDs, saveToDisk = False)
            requestHelper.delayEnd(500)

        if genericIDs:
            # Load all the main routes of the API (they are disk-cached), to compare IDs with.
            allData = self._getMainRoutesData(api, allRoute)
            idDict = {item[0]: item for item in chain.from_iterable(allData)}
            return self.catalogFromIterable(idDict[entryID] for entryID in genericIDs if entryID in idDict)
        else:
            return self.getEmptyCatalog()
コード例 #3
0
def viewSearchGenre(params):
    '''
    Directory for a sub menu that lists the available genres to filter items by.
    The genre list is cached memory-only to last a Kodi session. This is done because
    if you hit "Back" Kodi will reload this directory and do a redundant web
    request...
    '''
    xbmcplugin.setContent(int(sys.argv[1]),
                          'tvshows')  # Optional, influences the skin layout.

    api = params['api']
    route = params['route']

    if api == requestHelper.API_ANIMETOON:
        _PROPERTY_GENRE_NAMES = 'tmania2.prop.toonGenres'
    else:
        _PROPERTY_GENRE_NAMES = 'tmania2.prop.plusGenres'

    genreList = cache.getCacheProperty(_PROPERTY_GENRE_NAMES,
                                       readFromDisk=True)
    if not genreList:
        # The data from the '/GetGenres/' route is a dict with a list of genre names like "Action",
        # "Comedy" etc., but it also has some weird texts in the list probably from data entry errors.
        requestHelper.setAPISource(api)
        requestHelper.delayBegin()
        genreList = requestHelper.routeGET(route).get('genres', [])
        requestHelper.delayEnd()
        # Store the current API genres in a disk-persistent property with 1 week of lifetime.
        cache.setCacheProperty(_PROPERTY_GENRE_NAMES,
                               genreList,
                               saveToDisk=True,
                               lifetime=cache.LIFETIME_ONE_WEEK)

    listItems = ((buildURL({
        'view': 'CATALOG_MENU',
        'api': api,
        'route': route,
        'genreName': genreName
    }), xbmcgui.ListItem(genreName), True) for genreName in genreList)
    xbmcplugin.addDirectoryItems(int(sys.argv[1]), tuple(listItems))
    xbmcplugin.endOfDirectory(int(sys.argv[1]), cacheToDisc=False)

    useCatalogLayout, layoutType = ADDON_SETTINGS['layoutCatalog']
    if useCatalogLayout:
        xbmc.executebuiltin('Container.SetViewMode(' + layoutType + ')')
コード例 #4
0
    def getCatalog(self, params):
        '''
        Retrieves the catalog from a persistent window property between different add-on
        states, or recreates the catalog if needed.

        The catalog is a dictionary of lists of entries, used to store data between add-on states to make xbmcgui.ListItems:
        {
            (1. Sections, as in alphabet sections for list items: #, A, B, C, D, E, F etc., each section holds a tuple of items.)
            A: (
                item, item, item, ...    (2. Items, each item is a tuple with the format seen in makeCatalogEntry().)
            )
            B: (...)
            C: (...)
        }
        '''
        def _buildCatalog():
            catalog = self.catalogFunctions.get(params['route'], self.genericCatalog)(params)
            cache.setCacheProperty(self.PROPERTY_CATALOG, catalog, saveToDisk = False)
            return catalog

        # If these properties are empty (like when coming in from a favourites menu), or if a different
        # route (website area) or a different API was stored in this property, then reload it.
        lastAPI = cache.getRawProperty(self.PROPERTY_CATALOG_API)
        lastRoute = cache.getRawProperty(self.PROPERTY_CATALOG_ROUTE)
        if (
            params['api'] != lastAPI
            or params['route'] != lastRoute
            or 'genreName' in params # Special-case for the genre search, same api\route but 'genreName' changes.
            or 'query' in params    # Special-case for the name search, same api\route but 'query' changes.
            or 'searchData' in params # Special-case for search results, we might be coming in from Kodi Favourites.
        ):
            cache.setRawProperty(self.PROPERTY_CATALOG_API, params['api'])
            cache.setRawProperty(self.PROPERTY_CATALOG_ROUTE, params['route'])
            catalog = _buildCatalog()
        else:
            catalog = cache.getCacheProperty(self.PROPERTY_CATALOG, readFromDisk = False)
            if not catalog:
                catalog = _buildCatalog()
        return catalog
コード例 #5
0
    def allRouteCatalog(self, params):
        '''
        Returns a catalog for one of the main '/GetAll(...)' routes.
        '''
        api = params['api']
        route = params['route']

        propName = self._diskFriendlyPropName(api, route)

        jsonData = cache.getCacheProperty(propName, readFromDisk = True)
        if not jsonData:
            requestHelper.setAPISource(api)
            requestHelper.delayBegin()
            jsonData = requestHelper.routeGET(route)
            if jsonData:
                cache.setCacheProperty(propName, jsonData, saveToDisk = True) # Defaults to 3 days cache lifetime.
            requestHelper.delayEnd(500)

        if jsonData:
            return self.catalogFromIterable(self.makeCatalogEntry(entry) for entry in jsonData)
        else:
            return self.getEmptyCatalog()
コード例 #6
0
    def _getMainRoutesData(self, api, customRoute=None):
        '''
        Loads all the main routes from one of the APIs to be used in the "filtering" catalog functions, like
        the latestUpdatesCatalog, genreSearchCatalog etc. that need all the items loaded at once.
        :returns: A dict of the '/GetAll(...)' routes of the API, each route key holds a list of catalog entries.
        '''
        requestHelper.setAPISource(api)
        if customRoute:
            routeAlls = (customRoute,)
        else:
            if api == requestHelper.API_ANIMETOON:
                routeAlls = ('/GetAllCartoon', '/GetAllMovies', '/GetAllDubbed') # Animetoon 'All' routes.
            else:
                routeAlls = ('/GetAllMovies', '/GetAllShows') # Animeplus 'All' routes.

        routesData = [ ]
        newProperties = [ ]
        for route in routeAlls:
            # Try to get the cached property first.
            jsonData = cache.getCacheProperty(
                self._diskFriendlyPropName(api, route), readFromDisk = True
            )
            if not jsonData:
                requestHelper.delayBegin()
                jsonData = requestHelper.routeGET(route)
                if jsonData:
                    newProperties.append(
                        (cache.diskFriendlyPropName(api + route), jsonData, cache.LIFETIME_THREE_DAYS)
                    )
                requestHelper.delayEnd(1000) # Always delay between requests so we don't abuse the source.
            routesData.append(tuple(self.makeCatalogEntry(entry) for entry in jsonData))

        if newProperties:
            cache.setCacheProperties(newProperties, saveToDisk=True)

        return routesData
コード例 #7
0
def viewListEpisodes(params):
    '''
    Directory for the list of episodes from a show.
    This is the last directory before playing a video.
    Pages aren't necessary in here even if the thumbnails setting is on, because
    for now all episodes use the same thumb and plot, inherited from the show.
    '''
    xbmcplugin.setContent(int(sys.argv[1]), 'episodes')

    # Sort episode list by labels. In some rare cases the episode date is set wrong,
    # so Kodi lists them out of order.
    xbmcplugin.addSortMethod(int(sys.argv[1]),
                             xbmcplugin.SORT_METHOD_LABEL_IGNORE_THE)

    api = params['api']
    jsonData = None

    # Cache (memory-only) the episode list of the show, as the user might watch more than one in sequence.
    _PROPERTY_LAST_SHOW_DETAILS = 'tmania2.prop.lastShow'
    lastShowDetails = cache.getCacheProperty(_PROPERTY_LAST_SHOW_DETAILS,
                                             readFromDisk=False)
    showKey = params['id'] + api
    if lastShowDetails and showKey in lastShowDetails:
        jsonData = lastShowDetails[
            showKey]  # Use the show ID + API name as the unique identifier.
    if not jsonData:
        requestHelper.setAPISource(api)
        requestHelper.delayBegin()
        jsonData = requestHelper.routeGET('/GetDetails/' + params['id'])
        if jsonData:
            cache.setCacheProperty(_PROPERTY_LAST_SHOW_DETAILS,
                                   {showKey: jsonData},
                                   saveToDisk=False)
        requestHelper.delayEnd()

    # Genres, thumb and plot are taken from the parent show \ movie.
    # But the date of the parent show \ movie will only be used if the individual episode doesn't have a date itself.
    showTitle = jsonData.get('name', '')
    showGenres = params['genres'].split(',')
    showThumb = params.get(
        'thumb',
        '')  # Might be empty in case ADDON_SETTINGS['showThumbs'] is off.
    showPlot = params['plot']
    showDate = params.get('date', '')

    # If the JSON data for this show has more than one episode, list all the episodes as usual.
    if len(jsonData['episode']) > 1:
        xbmcplugin.addDirectoryItems(
            int(sys.argv[1]),
            tuple(
                _makeEpisodeItems(api, jsonData['episode'], showTitle,
                                  showGenres, showThumb, showPlot, showDate)))
    else:
        # For a single episode, this is probably a movie, OVA or special (considered as a "1 episode show").
        # Try to get the direct stream links to see if it's a movie (which are always multi-part).
        episodeEntry = jsonData['episode'][0]
        episodeProviders = None

        # Cache (memory-only) this request, as it might be for a multi part video (a movie)
        # where the user would go back and forth a lot on this view to watch all the movie parts.
        _PROPERTY_LAST_EPISODE_DETAILS = 'tmania2.prop.lastEpisode'
        lastEpisodeDetails = cache.getCacheProperty(
            _PROPERTY_LAST_EPISODE_DETAILS, readFromDisk=False)
        episodeKey = episodeEntry['id'] + api
        if lastEpisodeDetails and episodeKey in lastEpisodeDetails:
            episodeProviders = lastEpisodeDetails[episodeKey]
        else:
            episodeProviders = getEpisodeProviders(api, episodeEntry['id'])
            if episodeProviders:
                cache.setCacheProperty(_PROPERTY_LAST_EPISODE_DETAILS,
                                       {episodeKey: episodeProviders},
                                       saveToDisk=False)

        if episodeProviders:
            if len(next(episodeProviders.itervalues())
                   ) > 1:  # If it's a multi-video-part media.
                xbmcplugin.addDirectoryItems(
                    int(sys.argv[1]),
                    tuple(
                        _makeEpisodePartItems(episodeEntry, episodeProviders,
                                              showTitle, showGenres, showThumb,
                                              showPlot, showDate)))
            else:
                # The item doesn't have multiple video parts. List the single item as usual and send its providers
                # as a URL parameter so they don't have to be retrieved again.
                dirItem = next(
                    _makeEpisodeItems(api, (episodeEntry, ), showTitle,
                                      showGenres, showThumb, showPlot,
                                      showDate))
                newUrl = dirItem[0] + '&' + urlencode(
                    {'providers': str(episodeProviders)})
                xbmcplugin.addDirectoryItem(int(sys.argv[1]), newUrl,
                                            dirItem[1], False)
        else:
            logStreamError(params['api'], api, episodeEntry['id'])

    xbmcplugin.endOfDirectory(int(sys.argv[1]), cacheToDisc=False)

    # Custom layout.
    useEpisodeLayout, layoutType = ADDON_SETTINGS['layoutEpisodes']
    if useEpisodeLayout:
        xbmc.executebuiltin('Container.SetViewMode(' + layoutType + ')')