coherence.backends.miroguide_storage — Cohen3 0.8.0 documentation

# Source code for coherence.backends.miroguide_storage

    # Licensed under the MIT license
    # http://opensource.org/licenses/mit-license.php

    # Coherence backend presenting the content of
    # the MIRO Guide catalog for on-line videos
    #
    # The APi is described on page:
    # https://develop.participatoryculture.org/trac/democracy/wiki/MiroGuideApi

    # Copyright 2009, Jean-Michel Sizun
    # Copyright 2009 Frank Scholz <coherence@beebits.net>

    import os
    import urllib.error
    import urllib.parse
    import urllib.request

    from coherence.backend import BackendItem, Container, \
        LazyContainer, \
        AbstractBackendStore
    from coherence.backends.youtube_storage import TestVideoProxy
    from coherence.upnp.core import DIDLLite
    from coherence.upnp.core import utils

    class VideoItem(BackendItem):

        def __init__(self, name, description, url, thumbnail_url, store):
            BackendItem.__init__(self)
            self.name = name
            self.duration = None
            self.size = None
            self.mimetype = 'video'
            self.url = None
            self.video_url = url
            self.thumbnail_url = thumbnail_url
            self.description = description
            self.date = None
            self.item = None

            self.location = TestVideoProxy(self.video_url, hash(self.video_url),
                                           store.proxy_mode,
                                           store.cache_directory,
                                           store.cache_maxsize, store.buffer_size
                                           )

        def get_item(self):
            if self.item is None:
                upnp_id = self.get_id()
                upnp_parent_id = self.parent.get_id()
                self.item = DIDLLite.VideoItem(upnp_id, upnp_parent_id, self.name)
                self.item.description = self.description
                self.item.date = self.date
                if self.thumbnail_url is not None:
                    self.item.icon = self.thumbnail_url
                    self.item.albumArtURI = self.thumbnail_url
                res = DIDLLite.Resource(
                    self.url, 'http-get:*:%s:*' % self.mimetype)
                res.duration = self.duration
                res.size = self.size
                self.item.res.append(res)
            return self.item

        def get_path(self):
            return self.url

        def get_id(self):
            return self.storage_id

    class MiroGuideStore(AbstractBackendStore):
        logCategory = 'miroguide_store'

        implements = ['MediaServer']

        description = (
            'Miro Guide',
            'connects to the MIRO Guide service and '
            'exposes the podcasts catalogued by the service. ',
            None)

        options = [
            {'option': 'name', 'text': 'Server Name:', 'type': 'string',
             'default': 'my media',
             'help': 'the name under this MediaServer '
                     'shall show up with on other UPnP clients'},
            {'option': 'version', 'text': 'UPnP Version:', 'type': 'int',
             'default': 2, 'enum': (2, 1),
             'help': 'the highest UPnP version this MediaServer shall support',
             'level': 'advance'},
            {'option': 'uuid', 'text': 'UUID Identifier:', 'type': 'string',
             'help': 'the unique (UPnP) identifier for this MediaServer, '
                     'usually automatically set',
             'level': 'advance'},
            {'option': 'language', 'text': 'Language:', 'type': 'string',
             'default': 'English'},
            {'option': 'refresh', 'text': 'Refresh period',
             'type': 'string'},
            {'option': 'proxy_mode', 'text': 'Proxy mode:', 'type': 'string',
             'enum': ('redirect', 'proxy', 'cache', 'buffered')},
            {'option': 'buffer_size', 'text': 'Buffering size:',
             'type': 'int'},
            {'option': 'cache_directory', 'text': 'Cache directory:',
             'type': 'dir', 'group': 'Cache'},
            {'option': 'cache_maxsize', 'text': 'Cache max size:',
             'type': 'int', 'group': 'Cache'},
        ]

        def __init__(self, server, **kwargs):
            AbstractBackendStore.__init__(self, server, **kwargs)

            self.name = kwargs.get('name', 'MiroGuide')

            self.language = kwargs.get('language', 'English')

            self.refresh = int(kwargs.get('refresh', 60)) * 60

            self.proxy_mode = kwargs.get('proxy_mode', 'redirect')
            self.cache_directory = kwargs.get('cache_directory',
                                              '/tmp/coherence-cache')
            try:
                if self.proxy_mode != 'redirect':
                    os.mkdir(self.cache_directory)
            except Exception as e:
                self.error(f'MiroGuideStore.__init__: {e!r}')
            self.cache_maxsize = kwargs.get('cache_maxsize', 100000000)
            self.buffer_size = kwargs.get('buffer_size', 750000)

            rootItem = Container(None, self.name)
            self.set_root_item(rootItem)

            categoriesItem = Container(rootItem, 'All by Categories')
            rootItem.add_child(categoriesItem)
            languagesItem = Container(rootItem, 'All by Languages')
            rootItem.add_child(languagesItem)

            self.appendLanguage('Recent Videos', self.language, rootItem,
                                sort='-age', count=15)
            self.appendLanguage('Top Rated', self.language, rootItem,
                                sort='rating', count=15)
            self.appendLanguage('Most Popular', self.language, rootItem,
                                sort='-popular', count=15)

            def gotError(error):
                print(f'ERROR: {error}')

            def gotCategories(result):
                if result is None:
                    print('Unable to retrieve list of categories')
                    return
                data, header = result
                categories = eval(
                    data)  # FIXME add some checks to avoid code injection
                for category in categories:
                    name = category['name'].encode('ascii', 'strict')
                    category_url = category['url'].encode('ascii', 'strict')
                    self.appendCategory(name, name, categoriesItem)

            categories_url = 'https://www.miroguide.com/api/list_categories'
            d1 = utils.getPage(categories_url)
            d1.addCallbacks(gotCategories, gotError)

            def gotLanguages(result):
                if result is None:
                    print('Unable to retrieve list of languages')
                    return
                data, header = result
                languages = eval(
                    data)  # FIXME add some checks to avoid code injection
                for language in languages:
                    name = language['name'].encode('ascii', 'strict')
                    language_url = language['url'].encode('ascii', 'strict')
                    self.appendLanguage(name, name, languagesItem)

            languages_url = 'https://www.miroguide.com/api/list_languages'
            d2 = utils.getPage(languages_url)
            d2.addCallbacks(gotLanguages, gotError)

            self.init_completed()

        def __repr__(self):
            return self.__class__.__name__

        def appendCategory(self, name, category_id, parent):
            item = LazyContainer(parent, name, category_id, self.refresh,
                                 self.retrieveChannels, filter='category',
                                 filter_value=category_id, per_page=100)
            parent.add_child(item, external_id=category_id)

        def appendLanguage(self, name, language_id, parent, sort='name', count=0):
            item = LazyContainer(parent, name, language_id, self.refresh,
                                 self.retrieveChannels, filter='language',
                                 filter_value=language_id, per_page=100, sort=sort,
                                 count=count)
            parent.add_child(item, external_id=language_id)

        def appendChannel(self, name, channel_id, parent):
            item = LazyContainer(parent, name, channel_id, self.refresh,
                                 self.retrieveChannelItems, channel_id=channel_id)
            parent.add_child(item, external_id=channel_id)

        def upnp_init(self):
            self.current_connection_id = None

            if self.server:
                self.server.connection_manager_server.set_variable(
                    0, 'SourceProtocolInfo',
                    [f'http-get:*:{"video/"}:*'],
                    # FIXME put list of all possible video mimetypes
                    default=True)

            self.wmc_mapping = {'15': self.get_root_id()}

        def retrieveChannels(self, parent, filter, filter_value, per_page=100,
                             page=0, offset=0, count=0, sort='name'):
            filter_value = urllib.parse.quote(filter_value.encode('utf-8'))

            limit = count
            if count == 0:
                limit = per_page
            uri = \
                f'https://www.miroguide.com/api/get_channels?' \
                f'limit={limit:d}&offset={offset:d}&filter={filter}&' \
                f'filter_value={filter_value}&sort={sort}'
            # print uri
            d = utils.getPage(uri)

            def gotChannels(result):
                if result is None:
                    print(
                        f'Unable to retrieve channel for category {filter}')
                    return
                data, header = result
                channels = eval(data)
                for channel in channels:
                    publisher = channel['publisher']
                    description = channel['description']
                    url = channel['url']
                    hi_def = channel['hi_def']
                    thumbnail_url = channel['thumbnail_url']
                    postal_code = channel['postal_code']
                    id = channel['id']
                    website_url = channel['website_url']
                    name = channel['name']
                    self.appendChannel(name, id, parent)
                if (count == 0) and (len(channels) >= per_page):
                    # print('reached page limit (%d)' % len(channels))
                    parent.childrenRetrievingNeeded = True

            def gotError(error):
                print(f'ERROR: {error}')

            d.addCallbacks(gotChannels, gotError)
            return d

        def retrieveChannelItems(self, parent, channel_id):
            uri = f'https://www.miroguide.com/api/get_channel?id={channel_id}'
            d = utils.getPage(uri)

            def gotItems(result):
                if result is None:
                    print(f'Unable to retrieve items for channel {channel_id}')
                    return
                data, header = result
                channel = eval(data)
                items = []
                if 'item' in channel:
                    items = channel['item']
                for item in items:
                    # print('item:',item)
                    url = item['url']
                    description = item['description']
                    # print('description:', description)
                    name = item['name']
                    thumbnail_url = None
                    if 'thumbnail_url' in channel:
                        # print('Thumbnail:', channel['thumbnail_url'])
                        thumbnail_url = channel['thumbnail_url']
                    # size = size['size']
                    item = VideoItem(name, description, url, thumbnail_url, self)
                    item.parent = parent
                    parent.add_child(item, external_id=url)

            def gotError(error):
                print(f'ERROR: {error}')

            d.addCallbacks(gotItems, gotError)
            return d