coherence.backends.test_storage — Cohen3 0.8.0 documentation

# Source code for coherence.backends.test_storage

    # -*- coding: utf-8 -*-

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

    # Copyright 2008 Frank Scholz <coherence@beebits.net>

    '''
    A MediaServer backend to test Items.

    Item information can be passed on the commandline or in the config as an XML
    fragment::

        coherence --plugin=backend:TestStore,name:Test,\\
           item:<item><location>audio.mp3</location>\\
                <mimetype>audio/mpeg</mimetype></item>,\\
           item:<item><location>audio.ogg</location>\\
                <mimetype>audio/ogg</mimetype></item>

        coherence --plugin='backend:TestStore,name:Test,\\
           item:<item><type>gstreamer</type>\\
                <pipeline>v4l2src num-buffers=1 ! video/x-raw-yuv,width=640,\\
                height=480 ! ffmpegcolorspace ! jpegenc name=enc</pipeline>\\
                <mimetype>image/jpeg></mimetype></item>'

    'video/x-raw-yuv,width=640,height=480' won't work here as it is a delimiter
    for the plugin string, so if you need things like that in the pipeline,
    you need to use a config file::

        coherence --plugin='backend:TestStore,name:Test,\\
            item:<item><type>process</type>\\
                <command>man date</command>\\
                <mimetype>text/html</mimetype></item>'

    The XML fragment has these elements:

        - type:

          * file: the item is some file-system object (default)
          * url: an item pointing to an object off-site
          * gstreamer: the item is actually a GStreamer pipeline
          * process: the items content is created by an external process

        - location: the filesystem path or an url (mandatory)
        - mimetype: the mimetype of the item (mandatory)
        - extension: an optional extension to append to the url created for the
          DIDLLite resource data
        - title: the 'title' this item should have (optional)
        - upnp_class: the DIDLLite class the item shall have, object.item will
          be taken as default
        - fourth_field: value for the 4th field of the protocolInfo phalanx,
          default is '*'
        - pipeline: a GStreamer pipeline that has to end with a bin named 'enc',
          some pipelines do only work properly when we have a glib mainloop
          running, so coherence needs to be started with *-o glib:yes*
        - command: the commandline for an external script to run, its output will
          be returned as the items content

    In the config file the definition of this backend could look like this:

    .. code-block:: xml

        <plugin active='yes'>
            <backend>TestStore</backend>
            <name>Test</name>
            <item>
                <location>/tmp/audio.mp3</location>
                <mimetype>audio/mpeg</mimetype>
            </item>
            <item>
                <location>/tmp/audio.ogg</location>
                <mimetype>audio/ogg</mimetype>
             </item>
        </plugin>

    '''
    import os

    from lxml import etree
    from twisted.internet import protocol, reactor
    from twisted.python.filepath import FilePath
    from twisted.web import resource, server

    from coherence.backend import BackendItem
    from coherence.backend import BackendStore

    try:
        from coherence.transcoder import GStreamerPipeline
    except ImportError:
        pass

    from coherence.upnp.core import DIDLLite

    from coherence import log

    ROOT_CONTAINER_ID = 0

    class ExternalProcessProtocol(protocol.ProcessProtocol):

        def __init__(self, caller):
            self.caller = caller

        def connectionMade(self):
            print('pp connection made')

        def outReceived(self, data):
            print(f'outReceived with {len(data):d} bytes!')
            self.caller.write_data(data)

        def errReceived(self, data):
            # print(f'errReceived! with {len(data):d} bytes!')
            print('pp (err):', data.strip())

        def inConnectionLost(self):
            # print('inConnectionLost! stdin is closed! (we probably did it)')
            pass

        def outConnectionLost(self):
            # print('outConnectionLost! The child closed their stdout!')
            pass

        def errConnectionLost(self):
            # print('errConnectionLost! The child closed their stderr.')
            pass

        def processEnded(self, status_object):
            print(f'processEnded, status {status_object.value.exitCode:d}')
            print('processEnded quitting')
            self.caller.ended = True
            self.caller.write_data('')

    class ExternalProcessPipeline(resource.Resource, log.LogAble):
        logCategory = 'externalprocess'
        addSlash = True

        def __init__(self, pipeline, mimetype):
            log.LogAble.__init__(self)
            resource.Resource.__init__(self)
            self.uri = pipeline
            self.mimetype = mimetype

        def render(self, request):
            print('ExternalProcessPipeline render')
            if self.mimetype:
                request.setHeader('content-type', self.mimetype)

            ExternalProcessProducer(self.uri, request)
            return server.NOT_DONE_YET

    class ExternalProcessProducer(log.LogAble):
        logCategory = 'externalprocess'
        addSlash = True

        def __init__(self, pipeline, request):
            log.LogAble.__init__(self)
            self.pipeline = pipeline
            self.request = request
            self.process = None
            self.written = 0
            self.data = ''
            self.ended = False
            request.registerProducer(self, 0)

        def write_data(self, data):
            if data:
                print(f'write {len(data):d} bytes of data')
                self.written += len(data)
                # this .write will spin the reactor, calling .doWrite and then
                # .resumeProducing again, so be prepared for a re-entrant call
                self.request.write(data)
            if self.request and self.ended:
                print('closing')
                self.request.unregisterProducer()
                self.request.finish()
                self.request = None

        def resumeProducing(self):
            print('resumeProducing', self.request)
            if not self.request:
                return
            if self.process is None:
                argv = self.pipeline.split()
                executable = argv[0]
                argv[0] = os.path.basename(argv[0])
                self.process = reactor.spawnProcess(
                    ExternalProcessProtocol(self),
                    executable, argv, {})

        def pauseProducing(self):
            pass

        def stopProducing(self):
            print('stopProducing', self.request)
            self.request.unregisterProducer()
            self.process.loseConnection()
            self.request.finish()
            self.request = None

    class Item(BackendItem):

        def __init__(self, parent, id, title, location, url):
            BackendItem.__init__(self)
            self.parent = parent
            self.id = id
            self.location = location
            self.url = url
            self.name = title
            self.duration = None
            self.size = None
            self.mimetype = None
            self.fourth_field = '*'
            self.description = None
            self.date = None

            self.upnp_class = DIDLLite.Item

            self.item = None

        def get_item(self):
            print(f'get_item {self.item!r}')
            if self.item is None:
                self.item = self.upnp_class(self.id, self.parent.id,
                                            self.get_name())
                self.item.description = self.description
                self.item.date = self.date

                res = DIDLLite.Resource(
                    self.url, f'http-get:*:{self.mimetype}:{self.fourth_field}')
                res.duration = self.duration
                res.size = self.get_size()
                self.item.res.append(res)
            return self.item

        def get_name(self):
            if self.name is None:
                if isinstance(self.location, FilePath):
                    self.name = self.location.basename().decode('utf-8', 'replace')
                else:
                    self.name = 'item'
            return self.name

        def get_path(self):
            if isinstance(self.location, FilePath):
                return self.location.path
            else:
                return self.location

        def get_size(self):
            if isinstance(self.location, FilePath):
                try:
                    return self.location.getsize()
                except OSError:
                    return self.size
            else:
                return self.size

    class ResourceItem(Item):

        def get_name(self):
            if self.name is None:
                self.name = 'item'
            return self.name

        def get_path(self):
            return self.location

        def get_size(self):
            return self.size

    class Container(BackendItem):

        def __init__(self, id, store, parent_id, title):
            BackendItem.__init__(self)
            self.url = store.urlbase + str(id)
            self.parent_id = parent_id
            self.id = id
            self.name = title
            self.mimetype = 'directory'
            self.update_id = 0
            self.children = []

            self.item = DIDLLite.Container(self.id, self.parent_id, self.name)
            self.item.childCount = 0

            self.sorted = False

        def add_child(self, child):
            print('ADD CHILD %r' % child)
            # id = child.id
            # if isinstance(child.id, basestring):
            #    _,id = child.id.split('.')
            self.children.append(child)
            self.item.childCount += 1
            self.sorted = False

        def get_children(self, start=0, end=0):
            print('GET CHILDREN')
            if not self.sorted:
                def childs_key_sort(x):
                    return x.name

                sorted(self.children, key=childs_key_sort)
                self.sorted = True
            if end != 0:
                return self.children[start:end]
            return self.children[start:]

        def get_child_count(self):
            return len(self.children)

        def get_path(self):
            return self.url

        def get_item(self):
            return self.item

        def get_name(self):
            return self.name

        def get_id(self):
            return self.id

    class TestStore(BackendStore):
        implements = ['MediaServer']

        def __init__(self, server, *args, **kwargs):
            print('TestStore kwargs', kwargs)
            BackendStore.__init__(self, server, **kwargs)
            self.name = kwargs.get('name', 'TestServer')
            self.next_id = 1000
            self.update_id = 0
            self.store = {}

            self.store[ROOT_CONTAINER_ID] = \
                Container(ROOT_CONTAINER_ID, self, -1, self.name)

            items = kwargs.get('item', [])
            if not isinstance(items, list):
                items = [items]

            for item in items:
                if isinstance(item, str):
                    xml = etree.fromstring(item)
                    item = {}
                    for child in xml:
                        item[child.tag] = child.text
                type = item.get('type', 'file')
                try:
                    name = item.get('title', None)
                    if type == 'file':
                        location = FilePath(item.get('location'))
                    if type == 'url':
                        location = item.get('location')

                    mimetype = item.get('mimetype')
                    item_id = self.get_next_id()

                    extension = item.get('extension')
                    if extension is None:
                        extension = ''
                    if len(extension) and extension[0] != '.':
                        extension = '.' + extension

                    if extension is not None:
                        item_id = str(item_id) + extension

                    if type in ('file', 'url'):
                        new_item = Item(
                            self.store[ROOT_CONTAINER_ID], item_id,
                            name, location, self.urlbase + str(item_id))
                    elif type == 'gstreamer':
                        pipeline = item.get('pipeline')
                        try:
                            pipeline = GStreamerPipeline(pipeline, mimetype)
                            new_item = ResourceItem(
                                self.store[ROOT_CONTAINER_ID],
                                item_id, name, pipeline,
                                self.urlbase + str(item_id))
                        except NameError:
                            self.warning(
                                'Can\'t enable GStreamerPipeline, '
                                'probably pygst not installed')
                            continue

                    elif type == 'process':
                        pipeline = item.get('command')
                        pipeline = ExternalProcessPipeline(pipeline, mimetype)
                        new_item = ResourceItem(
                            self.store[ROOT_CONTAINER_ID],
                            item_id, name, pipeline,
                            self.urlbase + str(item_id))

                    try:
                        new_item.upnp_class = self.get_upnp_class(
                            item.get('upnp_class', 'object.item'))
                    except Exception:
                        pass
                    # item.description = u'some text what's the file about'
                    # item.date = something
                    # item.size = something
                    new_item.mimetype = mimetype
                    new_item.fourth_field = item.get('fourth_field', '*')

                    self.store[ROOT_CONTAINER_ID].add_child(new_item)
                    self.store[item_id] = new_item

                except Exception:
                    import traceback
                    self.warning(traceback.format_exc())
            # print self.store

            self.init_completed()

        def get_upnp_class(self, name):
            try:
                return DIDLLite.upnp_classes[name]
            except KeyError:
                self.warning(f'upnp_class {name} not found, trying fallback')
                parts = name.split('.')
                parts.pop()
                while len(parts) > 1:
                    try:
                        return DIDLLite.upnp_classes['.'.join(parts)]
                    except KeyError:
                        parts.pop()

            self.warning('WTF - no fallback for upnp_class {name} found ?!?')
            return None

        def get_next_id(self):
            self.next_id += 1
            return self.next_id

        def get_by_id(self, id):
            print(f'GET_BY_ID {id}')
            item = self.store.get(id, None)
            if item is None:
                if int(id) == 0:
                    item = self.store[ROOT_CONTAINER_ID]
                else:
                    item = self.store.get(int(id), None)
            return item