# -*- 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:
<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
[docs]class ExternalProcessProtocol(protocol.ProcessProtocol):
def __init__(self, caller):
self.caller = caller
[docs] def connectionMade(self):
print("pp connection made")
[docs] def outReceived(self, data):
print("outReceived with %d bytes!" % len(data))
self.caller.write_data(data)
[docs] def errReceived(self, data):
# print "errReceived! with %d bytes!" % len(data)
print("pp (err):", data.strip())
[docs] def inConnectionLost(self):
# print "inConnectionLost! stdin is closed! (we probably did it)"
pass
[docs] def outConnectionLost(self):
# print "outConnectionLost! The child closed their stdout!"
pass
[docs] def errConnectionLost(self):
# print "errConnectionLost! The child closed their stderr."
pass
[docs] def processEnded(self, status_object):
print("processEnded, status %d" % status_object.value.exitCode)
print("processEnded quitting")
self.caller.ended = True
self.caller.write_data('')
[docs]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
[docs] 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
[docs]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)
[docs] def write_data(self, data):
if data:
print("write %d bytes of data" % len(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
[docs] 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, {})
[docs] def pauseProducing(self):
pass
[docs] def stopProducing(self):
print("stopProducing", self.request)
self.request.unregisterProducer()
self.process.loseConnection()
self.request.finish()
self.request = None
[docs]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
[docs] def get_item(self):
print("get_item %r" % self.item)
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, 'http-get:*:%s:%s' % (
self.mimetype, self.fourth_field))
res.duration = self.duration
res.size = self.get_size()
self.item.res.append(res)
return self.item
[docs] 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
[docs] def get_path(self):
if isinstance(self.location, FilePath):
return self.location.path
else:
return self.location
[docs] def get_size(self):
if isinstance(self.location, FilePath):
try:
return self.location.getsize()
except OSError:
return self.size
else:
return self.size
[docs]class ResourceItem(Item):
[docs] def get_name(self):
if self.name is None:
self.name = 'item'
return self.name
[docs] def get_path(self):
return self.location
[docs] def get_size(self):
return self.size
[docs]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
[docs] 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
[docs] 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:]
[docs] def get_child_count(self):
return len(self.children)
[docs] def get_path(self):
return self.url
[docs] def get_item(self):
return self.item
[docs] def get_name(self):
return self.name
[docs] def get_id(self):
return self.id
[docs]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()
[docs] def get_upnp_class(self, name):
try:
return DIDLLite.upnp_classes[name]
except KeyError:
self.warning("upnp_class %r not found, trying fallback", name)
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 %r found ?!?", name)
return None
[docs] def get_next_id(self):
self.next_id += 1
return self.next_id
[docs] def get_by_id(self, id):
print("GET_BY_ID %r" % 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