coherence.backends.radiotime_storage — Cohen3 0.8.0 documentation

# Source code for coherence.backends.radiotime_storage

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

    # an internet radio media server for the Coherence UPnP Framework
    # based on the radiotime (http://radiotime.com) catalog service

    # Copyright 2007, Frank Scholz <coherence@beebits.net>
    # Copyright 2009-2010, Jean-Michel Sizun <jmDOTsizunATfreeDOTfr>
    from lxml import etree
    from twisted.python.failure import Failure

    from coherence.backend import BackendItem, Container, LazyContainer, \
        AbstractBackendStore
    from coherence.upnp.core import DIDLLite
    from coherence.upnp.core import utils
    from coherence.upnp.core.DIDLLite import Resource

    OPML_BROWSE_URL = 'http://opml.radiotime.com/Browse.ashx'

    # we only handle mp3 audio streams for now
    DEFAULT_FORMAT = 'mp3'
    DEFAULT_MIMETYPE = 'audio/mpeg'

    # TODO : extend format handling using radiotime API

    class RadiotimeAudioItem(BackendItem):
        logCategory = 'radiotime'

        def __init__(self, outline):
            BackendItem.__init__(self)
            self.preset_id = outline.get('preset_id')
            self.name = outline.get('text')
            self.mimetype = DEFAULT_MIMETYPE
            self.stream_url = outline.get('URL')
            self.image = outline.get('image')
            # self.location = PlaylistStreamProxy(self.stream_url)
            # self.url = self.stream_url

            self.item = None
            self.parent = None

        def replace_by(self, item):
            # do nothing: we suppose the replacement item is the same
            return

        def get_item(self):
            if self.item is None:
                upnp_id = self.get_id()
                upnp_parent_id = self.parent.get_id()
                self.item = DIDLLite.AudioBroadcast(upnp_id, upnp_parent_id,
                                                    self.name)
                self.item.albumArtURI = self.image
                protocols = ';'.join(
                            ('DLNA.ORG_PN=MP3',
                             'DLNA.ORG_CI=0',
                             'DLNA.ORG_OP=01',
                             'DLNA.ORG_FLAGS=01700000000000000000000000000000'))
                res = Resource(
                    self.stream_url,
                    f'http-get:*:{self.mimetype}:{protocols}')
                res.size = 0  # None
                self.item.res.append(res)
            return self.item

        def get_path(self):
            return self.stream_url

        def get_id(self):
            return self.storage_id

    class RadiotimeStore(AbstractBackendStore):
        logCategory = 'radiotime'

        implements = ['MediaServer']

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

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

            self.browse_url = self.config.get('browse_url', OPML_BROWSE_URL)
            self.partner_id = self.config.get('partner_id', 'TMe3Cn6v')
            self.username = self.config.get('username', None)
            self.locale = self.config.get('locale', 'en')
            self.serial = server.uuid if server else 'n/a'

            # construct URL for root menu
            if self.username is not None:
                identification_param = f'username={self.username}'
            else:
                identification_param = f'serial={self.serial}'
            formats_value = DEFAULT_FORMAT
            root_url = \
                f'{self.browse_url}?partnerId={self.partner_id}&' \
                f'{identification_param}&formats={formats_value}&' \
                f'locale={self.locale}'

            # set root item
            root_item = LazyContainer(None, 'root', 'root', self.refresh,
                                      self.retrieveItemsForOPML, url=root_url)
            self.set_root_item(root_item)

            self.init_completed()

        def upnp_init(self):
            self.current_connection_id = None

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

            if self.server:
                self.server.connection_manager_server.set_variable(
                    0, 'SourceProtocolInfo',
                    ['http-get:*:audio/mpeg:*',
                     'http-get:*:audio/x-scpls:*'],
                    default=True)

        def retrieveItemsForOPML(self, parent, url):

            def append_outline(parent, outline):
                type = outline.get('type')
                outline_url = outline.get('URL', None)
                if type is None:
                    # This outline is just a classification item containing
                    # other outline elements the corresponding item
                    # will a static Container
                    text = outline.get('text')
                    key = outline.get('key')
                    external_id = None
                    if external_id is None and key is not None:
                        external_id = f'{parent.external_id}_{key}'
                    if external_id is None:
                        external_id = outline_url
                    item = Container(parent, text)
                    item.external_id = external_id
                    item.store = parent.store
                    parent.add_child(item, external_id=external_id)
                    sub_outlines = outline.findall('outline')
                    for sub_outline in sub_outlines:
                        append_outline(item, sub_outline)

                elif type == 'link':
                    # the corresponding item will a self-populating Container
                    text = outline.get('text')
                    key = outline.get('key')
                    guide_id = outline.get('guide_id')
                    external_id = guide_id
                    if external_id is None and key is not None:
                        external_id = f'{parent.external_id}_{key}'
                    if external_id is None:
                        external_id = outline_url
                    item = LazyContainer(
                        parent, text, external_id, self.refresh,
                        self.retrieveItemsForOPML, url=outline_url)
                    parent.add_child(item, external_id=external_id)

                elif type == 'audio':
                    item = RadiotimeAudioItem(outline)
                    parent.add_child(item, external_id=item.preset_id)

            def got_page(result):
                self.info(
                    f'connection to Radiotime service successful for url {url}')

                outlines = result.findall('body/outline')
                for outline in outlines:
                    append_outline(parent, outline)

                return True

            def got_error(error):
                self.warning(
                    f'connection to Radiotime service failed for url {url}')
                self.debug('%r', error.getTraceback())
                parent.childrenRetrievingNeeded = True  # we retry
                return Failure(f'Unable to retrieve items for url {url}')

            def got_xml_error(error):
                self.warning(
                    f'Data received from Radiotime service is invalid: {url}')
                # self.debug('%r', error.getTraceback())
                print(error.getTraceback())
                parent.childrenRetrievingNeeded = True  # we retry
                return Failure(f'Unable to retrieve items for url {url}')

            d = utils.getPage(url, )
            d.addCallback(etree.fromstring)
            d.addErrback(got_error)
            d.addCallback(got_page)
            d.addErrback(got_xml_error)
            return d